11import * as React from 'react' ;
22import { shallow } from 'zustand/shallow' ;
33
4- import { Box , IconButton , ListDivider , ListItemDecorator , MenuItem , Tooltip } from '@mui/joy' ;
4+ import { Box , IconButton , ListDivider , ListItemDecorator , MenuItem , Tooltip , Input , InputProps } from '@mui/joy' ;
55import AddIcon from '@mui/icons-material/Add' ;
66import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' ;
77import FileDownloadIcon from '@mui/icons-material/FileDownload' ;
88import FileUploadIcon from '@mui/icons-material/FileUpload' ;
99import FolderOpenOutlinedIcon from '@mui/icons-material/FolderOpenOutlined' ;
1010import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined' ;
11+ import SearchIcon from '@mui/icons-material/Search' ;
1112
1213import { DFolder , useFoldersToggle , useFolderStore } from '~/common/state/store-folders' ;
1314import { PageDrawerHeader } from '~/common/layout/optima/components/PageDrawerHeader' ;
@@ -19,6 +20,7 @@ import { useUXLabsStore } from '~/common/state/store-ux-labs';
1920
2021import { ChatFolderList } from './folder/ChatFolderList' ;
2122import { ChatDrawerItemMemo , ChatNavigationItemData } from './ChatNavigationItem' ;
23+ import Search from '@mui/icons-material/Search' ;
2224
2325// type ListGrouping = 'off' | 'persona';
2426
@@ -67,6 +69,41 @@ export const useChatNavigationItems = (activeConversationId: DConversationId | n
6769 return { chatNavItems, folders } ;
6870} ;
6971
72+ type DebounceProps = {
73+ handleDebounce : ( value : string ) => void ;
74+ debounceTimeout : number ;
75+ } ;
76+
77+ function DebounceInput ( props : InputProps & DebounceProps ) {
78+ const { handleDebounce, debounceTimeout, ...rest } = props ;
79+ const [ inputValue , setInputValue ] = React . useState ( '' ) ; // Local state for the input value
80+ const timerRef = React . useRef < number > ( ) ;
81+
82+ const handleChange = ( event : React . ChangeEvent < HTMLInputElement > ) => {
83+ const value = event . target . value ;
84+ setInputValue ( value ) ; // Update local state immediately
85+
86+ if ( timerRef . current ) {
87+ clearTimeout ( timerRef . current ) ;
88+ }
89+
90+ timerRef . current = window . setTimeout ( ( ) => {
91+ handleDebounce ( value ) ; // Only call handleDebounce after the timeout
92+ } , debounceTimeout ) ;
93+ } ;
94+
95+ // Clean up the timer when the component unmounts
96+ React . useEffect ( ( ) => {
97+ return ( ) => {
98+ if ( timerRef . current ) {
99+ clearTimeout ( timerRef . current ) ;
100+ }
101+ } ;
102+ } , [ ] ) ;
103+
104+ return < Input { ...rest } value = { inputValue } onChange = { handleChange } /> ;
105+ }
106+
70107
71108export const ChatDrawerContentMemo = React . memo ( ChatDrawerItems ) ;
72109
@@ -84,6 +121,8 @@ function ChatDrawerItems(props: {
84121} ) {
85122
86123 // local state
124+ const [ searchQuery , setSearchQuery ] = React . useState ( '' ) ;
125+
87126 // const [grouping] = React.useState<ListGrouping>('off');
88127 const { onConversationDelete, onConversationNew, onConversationActivate } = props ;
89128
@@ -117,6 +156,35 @@ function ChatDrawerItems(props: {
117156 ! singleChat && conversationId && onConversationDelete ( conversationId , true ) ;
118157 } , [ onConversationDelete , singleChat ] ) ;
119158
159+ // Handle debounced search input changes
160+ const handleDebounce = ( value : string ) => {
161+ setSearchQuery ( value ) ;
162+ } ;
163+
164+ // Filter chatNavItems based on the search query and rank them by search frequency
165+ const filteredChatNavItems = React . useMemo ( ( ) => {
166+ if ( ! searchQuery ) return chatNavItems ;
167+ return chatNavItems
168+ . map ( item => {
169+ // Get the conversation by ID
170+ const conversation = useChatStore . getState ( ) . conversations . find ( c => c . id === item . conversationId ) ;
171+ // Calculate the frequency of the search term in the title and messages
172+ const titleFrequency = ( item . title . toLowerCase ( ) . match ( new RegExp ( searchQuery . toLowerCase ( ) , 'g' ) ) || [ ] ) . length ;
173+ const messageFrequency = conversation ?. messages . reduce ( ( count , message ) => {
174+ return count + ( message . text . toLowerCase ( ) . match ( new RegExp ( searchQuery . toLowerCase ( ) , 'g' ) ) || [ ] ) . length ;
175+ } , 0 ) || 0 ;
176+ // Return the item with the searchFrequency property
177+ return {
178+ ...item ,
179+ searchFrequency : titleFrequency + messageFrequency ,
180+ } ;
181+ } )
182+ // Exclude items with a searchFrequency of 0
183+ . filter ( item => item . searchFrequency > 0 )
184+ // Sort the items by searchFrequency in descending order
185+ . sort ( ( a , b ) => b . searchFrequency ! - a . searchFrequency ! ) ;
186+ } , [ chatNavItems , searchQuery ] ) ;
187+
120188
121189 // grouping
122190 /*let sortedIds = conversationIDs;
@@ -167,6 +235,17 @@ function ChatDrawerItems(props: {
167235
168236 { useFolders && < ListDivider sx = { { mb : 0 } } /> }
169237
238+ { /* Search Input Field */ }
239+ < DebounceInput
240+ startDecorator = { < SearchIcon /> }
241+ placeholder = "Search chats..."
242+ variant = "outlined"
243+ value = { searchQuery }
244+ handleDebounce = { handleDebounce }
245+ debounceTimeout = { 300 }
246+ sx = { { m : 2 } }
247+ />
248+
170249 < MenuItem disabled = { props . disableNewButton } onClick = { handleButtonNew } sx = { PageDrawerTallItemSx } >
171250 < ListItemDecorator > < AddIcon /> </ ListItemDecorator >
172251 < Box sx = { {
@@ -201,7 +280,7 @@ function ChatDrawerItems(props: {
201280 { /* </ToggleButtonGroup>*/ }
202281 { /*</ListItem>*/ }
203282
204- { chatNavItems . map ( item =>
283+ { filteredChatNavItems . map ( item =>
205284 < ChatDrawerItemMemo
206285 key = { 'nav-' + item . conversationId }
207286 item = { item }
0 commit comments