From 068699b0c62b2107a8fdeb12aad111b671f30de6 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sun, 18 Jan 2026 19:47:52 +0530 Subject: [PATCH] refactor: enhance DocumentMentionPicker with client-side filtering for single character searches - Implemented client-side filtering for single character searches to improve responsiveness and user experience. - Adjusted loading state management to differentiate between single character and multi-character searches. - Removed unnecessary search hint display for single character searches, streamlining the user interface. --- .../new-chat/document-mention-picker.tsx | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 7e39c6100..af3eeabb6 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -68,7 +68,14 @@ export const DocumentMentionPicker = forwardRef< DocumentMentionPickerRef, DocumentMentionPickerProps >(function DocumentMentionPicker( - { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "", containerStyle }, + { + searchSpaceId, + onSelectionChange, + onDone, + initialSelectedDocuments = [], + externalSearch = "", + containerStyle, + }, ref ) { const queryClient = useQueryClient(); @@ -90,9 +97,11 @@ export const DocumentMentionPicker = forwardRef< const [hasMore, setHasMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); - // Check if search is long enough + // Check if search is long enough for server-side search const isSearchValid = debouncedSearch.trim().length >= MIN_SEARCH_LENGTH; const shouldSearch = debouncedSearch.trim().length > 0; + // Single character search uses client-side filtering (no API call, instant) + const isSingleCharSearch = debouncedSearch.trim().length === 1; // Prefetch first page when picker mounts - results appear instantly useEffect(() => { @@ -266,18 +275,29 @@ export const DocumentMentionPicker = forwardRef< [hasMore, isLoadingMore, loadNextPage] ); - const actualDocuments = accumulatedDocuments; - const actualLoading = (isTitleSearchLoading || isSurfsenseDocsLoading) && currentPage === 0; - const isFetchingResults = isTitleSearchFetching || isSurfsenseDocsFetching; + // Client-side filtered results for single character search (instant, no API call) + // This filters the cached/accumulated documents instead of hitting the server + const clientFilteredDocs = useMemo(() => { + if (!isSingleCharSearch) return null; + const searchLower = debouncedSearch.trim().toLowerCase(); + return accumulatedDocuments.filter((doc) => doc.title.toLowerCase().includes(searchLower)); + }, [isSingleCharSearch, debouncedSearch, accumulatedDocuments]); - // Show hint when search is too short - const showSearchHint = shouldSearch && !isSearchValid; + // Use client-side filtering for single char, server results for 2+ chars + const actualDocuments = isSingleCharSearch ? (clientFilteredDocs ?? []) : accumulatedDocuments; + const actualLoading = + (isTitleSearchLoading || isSurfsenseDocsLoading) && currentPage === 0 && !isSingleCharSearch; + const isFetchingResults = + (isTitleSearchFetching || isSurfsenseDocsFetching) && !isSingleCharSearch; // Hide popup when user is searching and no documents match (only after fetch completes) // We return null instead of calling onDone() so that mention mode stays active // This allows the popup to reappear when user deletes characters and results come back const hasNoSearchResults = - isSearchValid && !actualLoading && !isFetchingResults && actualDocuments.length === 0; + (isSearchValid || isSingleCharSearch) && + !actualLoading && + !isFetchingResults && + actualDocuments.length === 0; // Split documents into SurfSense docs and user docs for grouped rendering const surfsenseDocsList = useMemo( @@ -443,14 +463,7 @@ export const DocumentMentionPicker = forwardRef< className="max-h-[180px] sm:max-h-[280px] overflow-y-auto" onScroll={handleScroll} > - {showSearchHint ? ( -
- Type {MIN_SEARCH_LENGTH - debouncedSearch.trim().length} more character - {MIN_SEARCH_LENGTH - debouncedSearch.trim().length > 1 ? "s" : ""} to search -
-