(feat:sources) add pop-up to switch sources

This commit is contained in:
ManishMadan2882
2025-03-20 09:42:08 +05:30
parent 02c8bd06f5
commit 7149461d8e
2 changed files with 216 additions and 4 deletions

View File

@@ -1,10 +1,16 @@
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDarkTheme } from '../hooks';
import { useSelector } from 'react-redux';
import PaperPlane from '../assets/paper_plane.svg';
import SourceIcon from '../assets/source.svg';
import SpinnerDark from '../assets/spinner-dark.svg';
import Spinner from '../assets/spinner.svg';
import SourcesPopup from './SourcesPopup';
import { selectSelectedDocs } from '../preferences/preferenceSlice';
import { ActiveState } from '../models/misc';
import Upload from '../upload/Upload';
import ClipIcon from '../assets/clip.svg';
interface MessageInputProps {
value: string;
@@ -22,6 +28,12 @@ export default function MessageInput({
const { t } = useTranslation();
const [isDarkTheme] = useDarkTheme();
const inputRef = useRef<HTMLTextAreaElement>(null);
const sourceButtonRef = useRef<HTMLButtonElement>(null);
const [isSourcesPopupOpen, setIsSourcesPopupOpen] = useState(false);
const [uploadModalState, setUploadModalState] =
useState<ActiveState>('INACTIVE');
const selectedDocs = useSelector(selectSelectedDocs);
const handleInput = () => {
if (inputRef.current) {
@@ -50,6 +62,10 @@ export default function MessageInput({
}
};
const handlePostDocumentSelect = (doc: any) => {
console.log('Selected document:', doc);
};
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">
@@ -71,14 +87,30 @@ export default function MessageInput({
/>
</div>
<div className="flex flex-wrap items-center gap-2 px-4 py-2 border-t border-[#EEEEEE] dark:border-[#333333]">
<div className="flex flex-wrap items-center gap-2 px-4 py-2">
<div className="flex-grow flex flex-wrap gap-2">
<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">
<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"
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">
Sources
{selectedDocs
? selectedDocs.name
: t('conversation.sources.title')}
</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')}
>
<img src={ClipIcon} alt="Attach" className="w-4 h-4 mr-1.5" />
<span className="text-[14px] text-[#5D5D5D] dark:text-bright-gray font-medium">
Attach
</span>
</button>
{/* Additional badges can be added here in the future */}
</div>
@@ -104,6 +136,24 @@ export default function MessageInput({
</button>
</div>
</div>
<SourcesPopup
isOpen={isSourcesPopupOpen}
onClose={() => setIsSourcesPopupOpen(false)}
anchorRef={sourceButtonRef}
handlePostDocumentSelect={handlePostDocumentSelect}
setUploadModalState={setUploadModalState}
/>
{uploadModalState === 'ACTIVE' && (
<Upload
receivedFile={[]}
setModalState={setUploadModalState}
isOnboarding={false}
renderTab={null}
close={() => setUploadModalState('INACTIVE')}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,162 @@
import React, { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Doc } from '../models/misc';
import SourceIcon from '../assets/source.svg';
import CheckIcon from '../assets/checkmark.svg';
import {
selectSourceDocs,
selectSelectedDocs,
setSelectedDocs,
} from '../preferences/preferenceSlice';
import { ActiveState } from '../models/misc';
type SourcesPopupProps = {
isOpen: boolean;
onClose: () => void;
anchorRef: React.RefObject<HTMLButtonElement>;
handlePostDocumentSelect: (doc: Doc | null) => void;
setUploadModalState: React.Dispatch<React.SetStateAction<ActiveState>>;
};
export default function SourcesPopup({
isOpen,
onClose,
anchorRef,
handlePostDocumentSelect,
setUploadModalState,
}: SourcesPopupProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const popupRef = useRef<HTMLDivElement>(null);
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
'huggingface_sentence-transformers/all-mpnet-base-v2';
const options = useSelector(selectSourceDocs);
const selectedDocs = useSelector(selectSelectedDocs);
const getPopupPosition = () => {
if (!anchorRef.current) return {};
const rect = anchorRef.current.getBoundingClientRect();
return {
position: 'fixed' as const,
top: `${rect.top - 8}px`,
left: `${rect.left}px`,
minWidth: `${rect.width}px`,
transform: 'translateY(-100%)',
};
};
const handleEmptyDocumentSelect = () => {
dispatch(setSelectedDocs(null));
handlePostDocumentSelect(null);
onClose();
};
const handleClickOutside = (event: MouseEvent) => {
if (
popupRef.current &&
!popupRef.current.contains(event.target as Node) &&
!anchorRef.current?.contains(event.target as Node)
) {
onClose();
}
};
useEffect(() => {
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
if (!isOpen) return null;
const handleUploadClick = () => {
setUploadModalState('ACTIVE');
onClose();
};
return (
<div
ref={popupRef}
style={getPopupPosition()}
className="bg-lotion dark:bg-charleston-green-2 rounded-xl shadow-md w-full max-w-md flex flex-col z-50 absolute"
>
<div className="px-6 py-4">
<h2 className="text-lg font-bold text-[#141414] dark:text-bright-gray">
{t('conversation.sources.text')}
</h2>
</div>
<div className="overflow-y-auto h-[488px] mx-4 border border-[#D9D9D9] dark:border-purple-taupe rounded-md">
{options ? (
<>
{options.map((option: any, index: number) => {
if (option.model === embeddingsName) {
return (
<div
key={index}
className="flex cursor-pointer items-center p-3 hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors border-b border-[#D9D9D9] border-opacity-80"
onClick={() => {
dispatch(setSelectedDocs(option));
handlePostDocumentSelect(option);
onClose();
}}
>
<img
src={SourceIcon}
alt="Source"
className="w-5 h-5 mr-3"
/>
<span className="text-[#5D5D5D] dark:text-bright-gray font-medium flex-grow overflow-hidden overflow-ellipsis whitespace-nowrap">
{option.name}
</span>
{selectedDocs && selectedDocs.id === option.id && (
<img
src={CheckIcon}
alt="Selected"
className="h-5 w-5 mr-2"
/>
)}
</div>
);
}
return null;
})}
<div
className="flex cursor-pointer items-center p-3 hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors border-b border-[#D9D9D9] border-opacity-80"
onClick={handleEmptyDocumentSelect}
>
<img src={SourceIcon} alt="Source" className="w-5 h-5 mr-3" />
<span className="text-[#5D5D5D] dark:text-bright-gray font-medium flex-grow">
{t('none')}
</span>
{selectedDocs === null && (
<img src={CheckIcon} alt="Selected" className="h-5 w-5 mr-2" />
)}
</div>
</>
) : (
<div className="p-4 text-center text-gray-500 dark:text-gray-400">
{t('noSourcesAvailable')}
</div>
)}
</div>
<div className="px-6 py-3 flex justify-start">
<button
onClick={handleUploadClick}
className="py-2 px-4 rounded-full border border-[#7D54D1] text-[#7D54D1] hover:bg-[#7D54D1] hover:text-white transition-colors duration-200 text-[14px] font-medium w-auto"
>
Upload new
</button>
</div>
</div>
);
}