diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 5d00232f6..fb84849e7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -9,7 +9,6 @@ import { import { useQueryClient } from "@tanstack/react-query"; import { useAtomValue, useSetAtom } from "jotai"; import { useParams, useSearchParams } from "next/navigation"; -import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { z } from "zod"; @@ -39,7 +38,7 @@ import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast"; import { LinkPreviewToolUI } from "@/components/tool-ui/link-preview"; import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage"; import { RecallMemoryToolUI, SaveMemoryToolUI } from "@/components/tool-ui/user-memory"; -import { Spinner } from "@/components/ui/spinner"; +import { Skeleton } from "@/components/ui/skeleton"; import { useChatSessionStateSync } from "@/hooks/use-chat-session-state"; import { useMessagesElectric } from "@/hooks/use-messages-electric"; // import { WriteTodosToolUI } from "@/components/tool-ui/write-todos"; @@ -53,12 +52,10 @@ import { } from "@/lib/chat/podcast-state"; import { appendMessage, - type ChatVisibility, createThread, getRegenerateUrl, getThreadFull, getThreadMessages, - type MessageRecord, type ThreadRecord, } from "@/lib/chat/thread-persistence"; import { @@ -67,6 +64,7 @@ import { trackChatMessageSent, trackChatResponseReceived, } from "@/lib/posthog/events"; +import { documentsApiService } from "@/lib/apis/documents-api.service"; /** * Extract thinking steps from message content @@ -137,7 +135,6 @@ interface ThinkingStepData { } export default function NewChatPage() { - const t = useTranslations("dashboard"); const params = useParams(); const queryClient = useQueryClient(); const [isInitializing, setIsInitializing] = useState(true); @@ -329,6 +326,33 @@ export default function NewChatPage() { initializeThread(); }, [initializeThread]); + // Prefetch document titles for @ mention picker + // Runs when user lands on page so data is ready when they type @ + useEffect(() => { + if (!searchSpaceId) return; + + const prefetchParams = { + search_space_id: searchSpaceId, + page: 0, + page_size: 20, + }; + + queryClient.prefetchQuery({ + queryKey: ["document-titles", prefetchParams], + queryFn: () => documentsApiService.searchDocumentTitles({ queryParams: prefetchParams }), + staleTime: 60 * 1000, + }); + + queryClient.prefetchQuery({ + queryKey: ["surfsense-docs-mention", "", false], + queryFn: () => + documentsApiService.getSurfsenseDocs({ + queryParams: { page: 0, page_size: 20 }, + }), + staleTime: 3 * 60 * 1000, + }); + }, [searchSpaceId, queryClient]); + // Handle scroll to comment from URL query params (e.g., from inbox item click) const searchParams = useSearchParams(); const targetCommentIdParam = searchParams.get("commentId"); @@ -366,20 +390,7 @@ export default function NewChatPage() { } setIsRunning(false); }, []); - - // Handle visibility change from ChatShareButton - const handleVisibilityChange = useCallback( - (newVisibility: ChatVisibility) => { - setCurrentThread((prev) => (prev ? { ...prev, visibility: newVisibility } : null)); - // Refetch all thread queries so sidebar reflects the change immediately - // Use predicate to match any query that starts with "threads" - queryClient.refetchQueries({ - predicate: (query) => Array.isArray(query.queryKey) && query.queryKey[0] === "threads", - }); - }, - [queryClient] - ); - + // Handle new message from user const onNew = useCallback( async (message: AppendMessage) => { @@ -1347,7 +1358,7 @@ export default function NewChatPage() { // Handle reloading/refreshing the last AI response const onReload = useCallback( - async (parentId: string | null) => { + async () => { // parentId is the ID of the message to reload from (the user message) // We call regenerate without a query to use the same query await handleRegenerate(null); @@ -1372,9 +1383,39 @@ export default function NewChatPage() { // Show loading state only when loading an existing thread if (isInitializing) { return ( -
- -
{t("loading_chat")}
+
+
+ {/* User message */} +
+ +
+ + {/* Assistant message */} +
+ + + +
+ + {/* User message */} +
+ +
+ + {/* Assistant message */} +
+ + + +
+
+ + {/* Input bar */} +
+
+ +
+
); } diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 5c4c9bc61..7a0d0f009 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -1,6 +1,6 @@ "use client"; -import { keepPreviousData, useQuery, useQueryClient } from "@tanstack/react-query"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { forwardRef, useCallback, @@ -14,6 +14,7 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { Document, SearchDocumentTitlesResponse } from "@/contracts/types/document.types"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { cn } from "@/lib/utils"; +import { Skeleton } from "@/components/ui/skeleton"; export interface DocumentMentionPickerRef { selectHighlighted: () => void; @@ -77,8 +78,6 @@ export const DocumentMentionPicker = forwardRef< }, ref ) { - const queryClient = useQueryClient(); - // Debounced search value to minimize API calls and prevent race conditions const search = externalSearch; const debouncedSearch = useDebounced(search, DEBOUNCE_MS); @@ -106,32 +105,6 @@ export const DocumentMentionPicker = forwardRef< const shouldSearch = debouncedSearch.trim().length > 0; const isSingleCharSearch = debouncedSearch.trim().length === 1; - // Prefetch initial data on mount for instant display when picker opens - useEffect(() => { - if (!searchSpaceId) return; - - const prefetchParams = { - search_space_id: searchSpaceId, - page: 0, - page_size: PAGE_SIZE, - }; - - queryClient.prefetchQuery({ - queryKey: ["document-titles", prefetchParams], - queryFn: () => documentsApiService.searchDocumentTitles({ queryParams: prefetchParams }), - staleTime: 60 * 1000, - }); - - queryClient.prefetchQuery({ - queryKey: ["surfsense-docs-mention", "", false], - queryFn: () => - documentsApiService.getSurfsenseDocs({ - queryParams: { page: 0, page_size: PAGE_SIZE }, - }), - staleTime: 3 * 60 * 1000, - }); - }, [searchSpaceId, queryClient]); - // Reset pagination state when search query or search space changes. // Documents are not cleared to maintain visual continuity during fetches. // biome-ignore lint/correctness/useExhaustiveDependencies: Intentional reset on search/space change @@ -439,8 +412,26 @@ export const DocumentMentionPicker = forwardRef< onScroll={handleScroll} > {actualLoading ? ( -
-
+
+
+ +
+ {["a", "b", "c", "d", "e"].map((id, i) => ( +
= 3 && "hidden sm:flex" + )} + > + + + + + + +
+ ))}
) : actualDocuments.length > 0 ? (