(faet:input) tools pop-up

This commit is contained in:
ManishMadan2882
2025-03-24 17:16:24 +05:30
parent 76b9bc0d56
commit 2940a60b3c
3 changed files with 230 additions and 6 deletions

View File

@@ -4,9 +4,11 @@ import { useDarkTheme } from '../hooks';
import { useSelector } from 'react-redux';
import PaperPlane from '../assets/paper_plane.svg';
import SourceIcon from '../assets/source.svg';
import ToolIcon from '../assets/tool.svg';
import SpinnerDark from '../assets/spinner-dark.svg';
import Spinner from '../assets/spinner.svg';
import SourcesPopup from './SourcesPopup';
import ToolsPopup from './ToolsPopup';
import { selectSelectedDocs } from '../preferences/preferenceSlice';
import { ActiveState } from '../models/misc';
import Upload from '../upload/Upload';
@@ -29,7 +31,9 @@ export default function MessageInput({
const [isDarkTheme] = useDarkTheme();
const inputRef = useRef<HTMLTextAreaElement>(null);
const sourceButtonRef = useRef<HTMLButtonElement>(null);
const toolButtonRef = useRef<HTMLButtonElement>(null);
const [isSourcesPopupOpen, setIsSourcesPopupOpen] = useState(false);
const [isToolsPopupOpen, setIsToolsPopupOpen] = useState(false);
const [uploadModalState, setUploadModalState] =
useState<ActiveState>('INACTIVE');
@@ -68,7 +72,7 @@ export default function MessageInput({
return (
<div className="flex flex-col w-full mx-2">
<div className="flex flex-col w-full rounded-[23px] border dark:border-grey border-dark-gray bg-lotion dark:bg-charleston-green-3 relative">
<div className="flex flex-col w-full rounded-[23px] border dark:border-grey border-dark-gray bg-lotion dark:bg-transparent relative">
<div className="w-full">
<label htmlFor="message-input" className="sr-only">
{t('inputPlaceholder')}
@@ -80,7 +84,7 @@ export default function MessageInput({
onChange={onChange}
tabIndex={1}
placeholder={t('inputPlaceholder')}
className="inputbox-style w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-t-[23px] bg-lotion dark:bg-charleston-green-3 py-5 text-base leading-tight opacity-100 focus:outline-none dark:text-bright-gray dark:placeholder-bright-gray dark:placeholder-opacity-50 px-6 no-scrollbar"
className="inputbox-style w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-t-[23px] bg-lotion dark:bg-transparent py-5 text-base leading-tight opacity-100 focus:outline-none dark:text-bright-gray dark:placeholder-bright-gray dark:placeholder-opacity-50 px-6 no-scrollbar"
onInput={handleInput}
onKeyDown={handleKeyDown}
aria-label={t('inputPlaceholder')}
@@ -91,17 +95,28 @@ export default function MessageInput({
<div className="flex-grow flex flex-wrap gap-2">
<button
ref={sourceButtonRef}
className="flex items-center px-3 py-1.5 rounded-[32px] border border-[#AAAAAA] dark:border-purple-taupe hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors"
className="flex items-center px-3 py-1.5 rounded-[32px] border border-[#AAAAAA] dark:border-purple-taupe hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors max-w-[200px]"
onClick={() => setIsSourcesPopupOpen(!isSourcesPopupOpen)}
>
<img src={SourceIcon} alt="Sources" className="w-4 h-4 mr-1.5" />
<span className="text-[14px] text-[#5D5D5D] dark:text-bright-gray font-medium">
<img src={SourceIcon} alt="Sources" className="w-4 h-4 mr-1.5 flex-shrink-0" />
<span className="text-[14px] text-[#5D5D5D] dark:text-bright-gray font-medium truncate overflow-hidden">
{selectedDocs
? selectedDocs.name
: t('conversation.sources.title')}
</span>
</button>
<button
ref={toolButtonRef}
className="flex items-center px-3 py-1.5 rounded-[32px] border border-[#AAAAAA] dark:border-purple-taupe hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors max-w-[200px]"
onClick={() => setIsToolsPopupOpen(!isToolsPopupOpen)}
>
<img src={ToolIcon} alt="Tools" className="w-4 h-4 mr-1.5 flex-shrink-0" />
<span className="text-[14px] text-[#5D5D5D] dark:text-bright-gray font-medium truncate overflow-hidden">
{t('settings.tools.label')}
</span>
</button>
{/*<button
className="flex items-center px-3 py-1.5 rounded-[32px] border border-[#AAAAAA] dark:border-purple-taupe hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors"
onClick={() => setUploadModalState('ACTIVE')}
>
@@ -109,7 +124,7 @@ export default function MessageInput({
<span className="text-[14px] text-[#5D5D5D] dark:text-bright-gray font-medium">
Attach
</span>
</button>
</button>*/}
{/* Additional badges can be added here in the future */}
</div>
@@ -145,6 +160,12 @@ export default function MessageInput({
setUploadModalState={setUploadModalState}
/>
<ToolsPopup
isOpen={isToolsPopupOpen}
onClose={() => setIsToolsPopupOpen(false)}
anchorRef={toolButtonRef}
/>
{uploadModalState === 'ACTIVE' && (
<Upload
receivedFile={[]}

View File

@@ -0,0 +1,200 @@
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { selectToken } from '../preferences/preferenceSlice';
import userService from '../api/services/userService';
import { UserToolType } from '../settings/types';
import Input from './Input';
import RedirectIcon from '../assets/redirect.svg';
import NoFilesIcon from '../assets/no-files.svg';
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import CheckmarkIcon from '../assets/checkmark.svg';
import { useDarkTheme } from '../hooks';
interface ToolsPopupProps {
isOpen: boolean;
onClose: () => void;
anchorRef: React.RefObject<HTMLButtonElement>;
}
export default function ToolsPopup({
isOpen,
onClose,
anchorRef,
}: ToolsPopupProps) {
const { t } = useTranslation();
const token = useSelector(selectToken);
const [userTools, setUserTools] = React.useState<UserToolType[]>([]);
const [loading, setLoading] = React.useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [isDarkTheme] = useDarkTheme();
const popupRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
popupRef.current &&
!popupRef.current.contains(event.target as Node) &&
anchorRef.current &&
!anchorRef.current.contains(event.target as Node)
) {
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [onClose, anchorRef]);
useEffect(() => {
if (isOpen) {
getUserTools();
}
}, [isOpen, token]);
const getUserTools = () => {
setLoading(true);
userService
.getUserTools(token)
.then((res) => {
return res.json();
})
.then((data) => {
setUserTools(data.tools);
setLoading(false);
})
.catch((error) => {
console.error('Error fetching tools:', error);
setLoading(false);
});
};
const updateToolStatus = (toolId: string, newStatus: boolean) => {
userService
.updateToolStatus({ id: toolId, status: newStatus }, token)
.then(() => {
setUserTools((prevTools) =>
prevTools.map((tool) =>
tool.id === toolId ? { ...tool, status: newStatus } : tool,
),
);
})
.catch((error) => {
console.error('Failed to update tool status:', error);
});
};
if (!isOpen) return null;
return (
<div
ref={popupRef}
className="absolute z-10 w-[462px] rounded-lg border border-light-silver dark:border-dim-gray bg-lotion dark:bg-charleston-green-2 shadow-lg"
style={{
bottom: anchorRef.current
? window.innerHeight -
anchorRef.current.getBoundingClientRect().top +
10
: 0,
left: anchorRef.current
? anchorRef.current.getBoundingClientRect().left
: 0,
position: 'fixed',
zIndex: 9999,
}}
>
<div className="p-4">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
{t('settings.tools.label')}
</h3>
<Input
id="tool-search"
name="tool-search"
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('settings.tools.searchPlaceholder')}
labelBgClassName="bg-lotion dark:bg-charleston-green-2"
borderVariant="thin"
className="mb-4"
/>
{loading ? (
<div className="flex justify-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900 dark:border-white"></div>
</div>
) : (
<div className="border border-[#D9D9D9] dark:border-dim-gray rounded-md overflow-hidden">
<div className="h-[440px] overflow-y-auto">
{userTools.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full py-8">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No tools found"
className="h-24 w-24 mx-auto mb-4"
/>
<p className="text-gray-500 dark:text-gray-400 text-center">
{t('settings.tools.noToolsFound')}
</p>
</div>
) : (
userTools
.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
)
.map((tool) => (
<div
key={tool.id}
onClick={() => updateToolStatus(tool.id, !tool.status)}
className="flex items-center justify-between p-3 border-b border-[#D9D9D9] dark:border-dim-gray hover:bg-gray-100 dark:hover:bg-charleston-green-3"
>
<div className="flex items-center">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${tool.displayName} icon`}
className="h-6 w-6 mr-4"
/>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{tool.displayName}
</p>
</div>
</div>
<div className="flex items-center">
<img
src={tool.status ? CheckmarkIcon : ''}
alt="Tool enabled"
width={14}
height={14}
className={`${!tool.status && 'hidden'}`}
/>
</div>
</div>
))
)}
</div>
</div>
)}
<div className="mt-4 flex justify-start">
<a
href="/settings/tools"
className="text-base text-purple-30 font-medium hover:text-violets-are-blue flex items-center"
>
{t('settings.tools.manageTools')}
<img
src={RedirectIcon}
alt="Go to tools"
className="ml-2 h-[11px] w-[11px]"
/>
</a>
</div>
</div>
</div>
);
}