Skip to content

Commit 2f9a4ea

Browse files
authored
Merge pull request #329 from joriskalz/main
Enhanced Search: Frequency Ranking and In-Message Querying #324
2 parents 4a8a2b9 + ee7dae8 commit 2f9a4ea

File tree

2 files changed

+92
-3
lines changed

2 files changed

+92
-3
lines changed

src/apps/chat/components/applayout/ChatDrawerItems.tsx

+81-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import * as React from 'react';
22
import { 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';
55
import AddIcon from '@mui/icons-material/Add';
66
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
77
import FileDownloadIcon from '@mui/icons-material/FileDownload';
88
import FileUploadIcon from '@mui/icons-material/FileUpload';
99
import FolderOpenOutlinedIcon from '@mui/icons-material/FolderOpenOutlined';
1010
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
11+
import SearchIcon from '@mui/icons-material/Search';
1112

1213
import { DFolder, useFoldersToggle, useFolderStore } from '~/common/state/store-folders';
1314
import { PageDrawerHeader } from '~/common/layout/optima/components/PageDrawerHeader';
@@ -19,6 +20,7 @@ import { useUXLabsStore } from '~/common/state/store-ux-labs';
1920

2021
import { ChatFolderList } from './folder/ChatFolderList';
2122
import { 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

71108
export 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}

src/apps/chat/components/applayout/ChatNavigationItem.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface ChatNavigationItemData {
2525
messageCount: number;
2626
assistantTyping: boolean;
2727
systemPurposeId: SystemPurposeId;
28+
searchFrequency?: number;
2829
}
2930

3031
function ChatNavigationItem(props: {
@@ -44,7 +45,7 @@ function ChatNavigationItem(props: {
4445
const doubleClickToEdit = useUIPreferencesStore(state => state.doubleClickToEdit);
4546

4647
// derived state
47-
const { conversationId, isActive, title, messageCount, assistantTyping, systemPurposeId } = props.item;
48+
const { conversationId, isActive, title, messageCount, assistantTyping, systemPurposeId, searchFrequency } = props.item;
4849
const isNew = messageCount === 0;
4950

5051
// auto-close the arming menu when clicking away
@@ -130,6 +131,15 @@ function ChatNavigationItem(props: {
130131
)}
131132
</ListItemDecorator>}
132133

134+
{/* Display search frequency if it exists and is greater than 0 */}
135+
{searchFrequency && searchFrequency > 0 && (
136+
<Box sx={{ ml: 1 }}>
137+
<Typography sx={{ color: 'text.secondary' }}>
138+
({searchFrequency})
139+
</Typography>
140+
</Box>
141+
)}
142+
133143
{/* Text */}
134144
{!isEditingTitle ? (
135145

0 commit comments

Comments
 (0)