(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

@@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.22066 0.46934C5.36129 0.328889 5.55191 0.25 5.75066 0.25C5.94941 0.25 6.14003 0.328889 6.28066 0.46934L10.5307 4.71934C10.6711 4.85997 10.75 5.05059 10.75 5.24934C10.75 5.44809 10.6711 5.63871 10.5307 5.77934L6.28066 10.0293C6.13851 10.162 5.95041 10.2342 5.75602 10.2309C5.56163 10.2275 5.37614 10.1488 5.23866 10.0113C5.10119 9.87386 5.02247 9.68837 5.01911 9.49398C5.01576 9.29959 5.08802 9.11149 5.22066 8.96934L8.19066 5.99934L0.75066 5.99934C0.551747 5.99934 0.360983 5.92032 0.22033 5.77967C0.0796773 5.63902 0.000659715 5.44825 0.000659724 5.24934C0.000659733 5.05043 0.0796774 4.85966 0.22033 4.71901C0.360983 4.57836 0.551747 4.49934 0.75066 4.49934L8.19066 4.49934L5.22066 1.52934C5.08021 1.38871 5.00132 1.19809 5.00132 0.99934C5.00132 0.800589 5.08021 0.609965 5.22066 0.46934Z" fill="#7D54D1"/>
</svg>

After

Width:  |  Height:  |  Size: 924 B

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>
);
}