From 7f84160a62e520b1723b50c0189c3e2b882c07ed Mon Sep 17 00:00:00 2001 From: Joris Kalz Date: Sat, 6 Jan 2024 11:55:26 +0100 Subject: [PATCH 1/3] Enable Search --- .../components/applayout/ChatDrawerItems.tsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/apps/chat/components/applayout/ChatDrawerItems.tsx b/src/apps/chat/components/applayout/ChatDrawerItems.tsx index 13dee82e81..3d06e9b7b3 100644 --- a/src/apps/chat/components/applayout/ChatDrawerItems.tsx +++ b/src/apps/chat/components/applayout/ChatDrawerItems.tsx @@ -1,12 +1,13 @@ import * as React from 'react'; import { shallow } from 'zustand/shallow'; -import { Box, IconButton, ListDivider, ListItemDecorator, MenuItem, Tooltip } from '@mui/joy'; +import { Box, IconButton, ListDivider, ListItemDecorator, MenuItem, Tooltip, Input} from '@mui/joy'; import AddIcon from '@mui/icons-material/Add'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import FileUploadIcon from '@mui/icons-material/FileUpload'; import FolderOpenOutlinedIcon from '@mui/icons-material/FolderOpenOutlined'; import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined'; +import SearchIcon from '@mui/icons-material/Search'; import { DFolder, useFoldersToggle, useFolderStore } from '~/common/state/store-folders'; import { OpenAIIcon } from '~/common/components/icons/OpenAIIcon'; @@ -19,6 +20,7 @@ import { useUXLabsStore } from '~/common/state/store-ux-labs'; import { ChatFolderList } from './folder/ChatFolderList'; import { ChatDrawerItemMemo, ChatNavigationItemData } from './ChatNavigationItem'; +import Search from '@mui/icons-material/Search'; // type ListGrouping = 'off' | 'persona'; @@ -82,6 +84,8 @@ function ChatDrawerItems(props: { }) { // local state + const [searchQuery, setSearchQuery] = React.useState(''); + // const [grouping] = React.useState('off'); const { onConversationDelete, onConversationNew, onConversationActivate } = props; @@ -115,6 +119,23 @@ function ChatDrawerItems(props: { !singleChat && conversationId && onConversationDelete(conversationId, true); }, [onConversationDelete, singleChat]); + // Handle search input changes + const handleSearchChange = (event: React.ChangeEvent) => { + setSearchQuery(event.target.value); + }; + + // Filter chatNavItems based on the search query + const filteredChatNavItems = React.useMemo(() => { + if (!searchQuery) return chatNavItems; + return chatNavItems.filter(item => { + // Check if the conversation title includes the search query + const titleMatch = item.title.toLowerCase().includes(searchQuery.toLowerCase()); + // Get the conversation by ID and check if any message text includes the search query + const conversation = useChatStore.getState().conversations.find(c => c.id === item.conversationId); + const messageMatch = conversation?.messages.some(message => message.text.toLowerCase().includes(searchQuery.toLowerCase())); + return titleMatch || messageMatch; + }); + }, [chatNavItems, searchQuery]); // grouping /*let sortedIds = conversationIDs; @@ -165,6 +186,16 @@ function ChatDrawerItems(props: { {useFolders && } + {/* Search Input Field */} + } + placeholder="Search chats..." + variant="outlined" + value={searchQuery} + onChange={handleSearchChange} + sx={{ m: 2 }} + /> + */} {/**/} - {chatNavItems.map(item => + {filteredChatNavItems.map(item => Date: Sat, 6 Jan 2024 12:05:31 +0100 Subject: [PATCH 2/3] Debounced Input field 300ms --- .../components/applayout/ChatDrawerItems.tsx | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/apps/chat/components/applayout/ChatDrawerItems.tsx b/src/apps/chat/components/applayout/ChatDrawerItems.tsx index 3d06e9b7b3..b2bc5b81a0 100644 --- a/src/apps/chat/components/applayout/ChatDrawerItems.tsx +++ b/src/apps/chat/components/applayout/ChatDrawerItems.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { shallow } from 'zustand/shallow'; -import { Box, IconButton, ListDivider, ListItemDecorator, MenuItem, Tooltip, Input} from '@mui/joy'; +import { Box, IconButton, ListDivider, ListItemDecorator, MenuItem, Tooltip, Input, InputProps } from '@mui/joy'; import AddIcon from '@mui/icons-material/Add'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import FileUploadIcon from '@mui/icons-material/FileUpload'; @@ -68,6 +68,41 @@ export const useChatNavigationItems = (activeConversationId: DConversationId | n return { chatNavItems, folders }; }; +type DebounceProps = { + handleDebounce: (value: string) => void; + debounceTimeout: number; +}; + +function DebounceInput(props: InputProps & DebounceProps) { + const { handleDebounce, debounceTimeout, ...rest } = props; + const [inputValue, setInputValue] = React.useState(''); // Local state for the input value + const timerRef = React.useRef(); + + const handleChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setInputValue(value); // Update local state immediately + + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + timerRef.current = window.setTimeout(() => { + handleDebounce(value); // Only call handleDebounce after the timeout + }, debounceTimeout); + }; + + // Clean up the timer when the component unmounts + React.useEffect(() => { + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + }; + }, []); + + return ; +} + export const ChatDrawerContentMemo = React.memo(ChatDrawerItems); @@ -119,9 +154,9 @@ function ChatDrawerItems(props: { !singleChat && conversationId && onConversationDelete(conversationId, true); }, [onConversationDelete, singleChat]); - // Handle search input changes - const handleSearchChange = (event: React.ChangeEvent) => { - setSearchQuery(event.target.value); + // Handle debounced search input changes + const handleDebounce = (value: string) => { + setSearchQuery(value); }; // Filter chatNavItems based on the search query @@ -187,12 +222,13 @@ function ChatDrawerItems(props: { {useFolders && } {/* Search Input Field */} - } placeholder="Search chats..." variant="outlined" value={searchQuery} - onChange={handleSearchChange} + handleDebounce={handleDebounce} + debounceTimeout={300} sx={{ m: 2 }} /> From 6fe94e344a6386976f13163216a5b9ffd8be28e8 Mon Sep 17 00:00:00 2001 From: Joris Kalz Date: Sat, 6 Jan 2024 12:20:42 +0100 Subject: [PATCH 3/3] Show number of results --- .../components/applayout/ChatDrawerItems.tsx | 30 +++++++++++++------ .../applayout/ChatNavigationItem.tsx | 12 +++++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/apps/chat/components/applayout/ChatDrawerItems.tsx b/src/apps/chat/components/applayout/ChatDrawerItems.tsx index b2bc5b81a0..40d78df5f8 100644 --- a/src/apps/chat/components/applayout/ChatDrawerItems.tsx +++ b/src/apps/chat/components/applayout/ChatDrawerItems.tsx @@ -159,19 +159,31 @@ function ChatDrawerItems(props: { setSearchQuery(value); }; - // Filter chatNavItems based on the search query + // Filter chatNavItems based on the search query and rank them by search frequency const filteredChatNavItems = React.useMemo(() => { if (!searchQuery) return chatNavItems; - return chatNavItems.filter(item => { - // Check if the conversation title includes the search query - const titleMatch = item.title.toLowerCase().includes(searchQuery.toLowerCase()); - // Get the conversation by ID and check if any message text includes the search query - const conversation = useChatStore.getState().conversations.find(c => c.id === item.conversationId); - const messageMatch = conversation?.messages.some(message => message.text.toLowerCase().includes(searchQuery.toLowerCase())); - return titleMatch || messageMatch; - }); + return chatNavItems + .map(item => { + // Get the conversation by ID + const conversation = useChatStore.getState().conversations.find(c => c.id === item.conversationId); + // Calculate the frequency of the search term in the title and messages + const titleFrequency = (item.title.toLowerCase().match(new RegExp(searchQuery.toLowerCase(), 'g')) || []).length; + const messageFrequency = conversation?.messages.reduce((count, message) => { + return count + (message.text.toLowerCase().match(new RegExp(searchQuery.toLowerCase(), 'g')) || []).length; + }, 0) || 0; + // Return the item with the searchFrequency property + return { + ...item, + searchFrequency: titleFrequency + messageFrequency, + }; + }) + // Exclude items with a searchFrequency of 0 + .filter(item => item.searchFrequency > 0) + // Sort the items by searchFrequency in descending order + .sort((a, b) => b.searchFrequency! - a.searchFrequency!); }, [chatNavItems, searchQuery]); + // grouping /*let sortedIds = conversationIDs; if (grouping === 'persona') { diff --git a/src/apps/chat/components/applayout/ChatNavigationItem.tsx b/src/apps/chat/components/applayout/ChatNavigationItem.tsx index ddc700fcbf..a06cbe1052 100644 --- a/src/apps/chat/components/applayout/ChatNavigationItem.tsx +++ b/src/apps/chat/components/applayout/ChatNavigationItem.tsx @@ -24,6 +24,7 @@ export interface ChatNavigationItemData { messageCount: number; assistantTyping: boolean; systemPurposeId: SystemPurposeId; + searchFrequency?: number; } function ChatNavigationItem(props: { @@ -43,7 +44,7 @@ function ChatNavigationItem(props: { const doubleClickToEdit = useUIPreferencesStore(state => state.doubleClickToEdit); // derived state - const { conversationId, isActive, title, messageCount, assistantTyping, systemPurposeId } = props.item; + const { conversationId, isActive, title, messageCount, assistantTyping, systemPurposeId, searchFrequency } = props.item; const isNew = messageCount === 0; // auto-close the arming menu when clicking away @@ -129,6 +130,15 @@ function ChatNavigationItem(props: { )} } + {/* Display search frequency if it exists and is greater than 0 */} + {searchFrequency && searchFrequency > 0 && ( + + + ({searchFrequency}) + + + )} + {/* Text */} {!isEditingTitle ? (