From b4712738fe847df59268bbc9376e494e9f74df8e Mon Sep 17 00:00:00 2001 From: Sujal Shah Date: Wed, 27 Nov 2024 18:30:09 +0530 Subject: [PATCH] feat: search chats --- app/components/sidebar/Menu.client.tsx | 29 ++++++++++++-- app/lib/hooks/useSearchFilter.ts | 52 ++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 app/lib/hooks/useSearchFilter.ts diff --git a/app/components/sidebar/Menu.client.tsx b/app/components/sidebar/Menu.client.tsx index d753d242e..ca99d6f4c 100644 --- a/app/components/sidebar/Menu.client.tsx +++ b/app/components/sidebar/Menu.client.tsx @@ -8,6 +8,7 @@ import { cubicEasingFn } from '~/utils/easings'; import { logger } from '~/utils/logger'; import { HistoryItem } from './HistoryItem'; import { binDates } from './date-binning'; +import { useSearchFilter } from '~/lib/hooks/useSearchFilter'; const menuVariants = { closed: { @@ -39,6 +40,11 @@ export function Menu() { const [open, setOpen] = useState(false); const [dialogContent, setDialogContent] = useState(null); + const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({ + items: list, + searchFields: ['description'], + }); + const loadEntries = useCallback(() => { if (db) { getAll(db) @@ -115,11 +121,11 @@ export function Menu() { initial="closed" animate={open ? 'open' : 'closed'} variants={menuVariants} - className="flex flex-col side-menu fixed top-0 w-[350px] h-full bg-bolt-elements-background-depth-2 border-r rounded-r-3xl border-bolt-elements-borderColor z-sidebar shadow-xl shadow-bolt-elements-sidebar-dropdownShadow text-sm" + className="flex selection-accent flex-col side-menu fixed top-0 w-[350px] h-full bg-bolt-elements-background-depth-2 border-r rounded-r-3xl border-bolt-elements-borderColor z-sidebar shadow-xl shadow-bolt-elements-sidebar-dropdownShadow text-sm" >
{/* Placeholder */}
-
+
+
+
+ +
+
Your Chats
- {list.length === 0 &&
No previous conversations
} + {filteredList.length === 0 && ( +
+ {list.length === 0 ? 'No previous conversations' : 'No matches found'} +
+ )} - {binDates(list).map(({ category, items }) => ( + {binDates(filteredList).map(({ category, items }) => (
{category} diff --git a/app/lib/hooks/useSearchFilter.ts b/app/lib/hooks/useSearchFilter.ts new file mode 100644 index 000000000..ec576cbeb --- /dev/null +++ b/app/lib/hooks/useSearchFilter.ts @@ -0,0 +1,52 @@ +import { useState, useMemo, useCallback } from 'react'; +import { debounce } from '~/utils/debounce'; +import type { ChatHistoryItem } from '~/lib/persistence'; + +interface UseSearchFilterOptions { + items: ChatHistoryItem[]; + searchFields?: (keyof ChatHistoryItem)[]; + debounceMs?: number; +} + +export function useSearchFilter({ + items = [], + searchFields = ['description'], + debounceMs = 300, +}: UseSearchFilterOptions) { + const [searchQuery, setSearchQuery] = useState(''); + + const debouncedSetSearch = useCallback(debounce(setSearchQuery, debounceMs), []); + + const handleSearchChange = useCallback( + (event: React.ChangeEvent) => { + debouncedSetSearch(event.target.value); + }, + [debouncedSetSearch], + ); + + const filteredItems = useMemo(() => { + if (!searchQuery.trim()) { + return items; + } + + const query = searchQuery.toLowerCase(); + + return items.filter((item) => + searchFields.some((field) => { + const value = item[field]; + + if (typeof value === 'string') { + return value.toLowerCase().includes(query); + } + + return false; + }), + ); + }, [items, searchQuery, searchFields]); + + return { + searchQuery, + filteredItems, + handleSearchChange, + }; +}