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 f288bfa59..722973067 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 @@ -6,6 +6,7 @@ import { type ThreadMessageLike, useExternalStoreRuntime, } from "@assistant-ui/react"; +import { useQueryClient } from "@tanstack/react-query"; import { useAtomValue, useSetAtom } from "jotai"; import { useParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -140,6 +141,7 @@ interface ThinkingStepData { export default function NewChatPage() { const params = useParams(); + const queryClient = useQueryClient(); const [isInitializing, setIsInitializing] = useState(true); const [threadId, setThreadId] = useState(null); const [messages, setMessages] = useState([]); @@ -300,11 +302,13 @@ export default function NewChatPage() { // Lazy thread creation: create thread on first message if it doesn't exist let currentThreadId = threadId; + let isNewThread = false; if (!currentThreadId) { try { const newThread = await createThread(searchSpaceId, "New Chat"); currentThreadId = newThread.id; setThreadId(currentThreadId); + isNewThread = true; // Update URL silently using browser API (not router.replace) to avoid // interrupting the ongoing fetch/streaming with React navigation window.history.replaceState( @@ -362,7 +366,15 @@ export default function NewChatPage() { appendMessage(currentThreadId, { role: "user", content: persistContent, - }).catch((err) => console.error("Failed to persist user message:", err)); + }) + .then(() => { + // For new threads, the backend updates the title from the first user message + // Invalidate threads query so sidebar shows the updated title in real-time + if (isNewThread) { + queryClient.invalidateQueries({ queryKey: ["threads", String(searchSpaceId)] }); + } + }) + .catch((err) => console.error("Failed to persist user message:", err)); // Start streaming response setIsRunning(true); @@ -692,6 +704,7 @@ export default function NewChatPage() { setMentionedDocumentIds, setMentionedDocuments, setMessageDocumentsMap, + queryClient, ] ); diff --git a/surfsense_web/components/sidebar/AppSidebarProvider.tsx b/surfsense_web/components/sidebar/AppSidebarProvider.tsx index 5e7f08c4d..0ee0bb230 100644 --- a/surfsense_web/components/sidebar/AppSidebarProvider.tsx +++ b/surfsense_web/components/sidebar/AppSidebarProvider.tsx @@ -149,6 +149,8 @@ export function AppSidebarProvider({ await deleteThread(threadToDelete.id); // Invalidate threads query to refresh the list queryClient.invalidateQueries({ queryKey: ["threads", searchSpaceId] }); + // Navigate to new-chat after successful deletion + router.push(`/dashboard/${searchSpaceId}/new-chat`); } catch (error) { console.error("Error deleting thread:", error); } finally { @@ -156,7 +158,7 @@ export function AppSidebarProvider({ setShowDeleteDialog(false); setThreadToDelete(null); } - }, [threadToDelete, queryClient, searchSpaceId]); + }, [threadToDelete, queryClient, searchSpaceId, router]); // Handle delete note with confirmation const handleDeleteNote = useCallback(async () => { diff --git a/surfsense_web/components/sidebar/all-chats-sidebar.tsx b/surfsense_web/components/sidebar/all-chats-sidebar.tsx index 9076715a3..a820a90c8 100644 --- a/surfsense_web/components/sidebar/all-chats-sidebar.tsx +++ b/surfsense_web/components/sidebar/all-chats-sidebar.tsx @@ -13,7 +13,7 @@ import { X, } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; -import { useRouter } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { createPortal } from "react-dom"; @@ -47,7 +47,15 @@ interface AllChatsSidebarProps { export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsSidebarProps) { const t = useTranslations("sidebar"); const router = useRouter(); + const params = useParams(); const queryClient = useQueryClient(); + + // Get the current chat ID from URL to check if user is deleting the currently open chat + const currentChatId = Array.isArray(params.chat_id) + ? Number(params.chat_id[0]) + : params.chat_id + ? Number(params.chat_id) + : null; const [deletingThreadId, setDeletingThreadId] = useState(null); const [archivingThreadId, setArchivingThreadId] = useState(null); const [searchQuery, setSearchQuery] = useState(""); @@ -126,6 +134,15 @@ export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsS queryClient.invalidateQueries({ queryKey: ["all-threads", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["search-threads", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["threads", searchSpaceId] }); + + // If the deleted chat is currently open, close sidebar first then redirect + if (currentChatId === threadId) { + onOpenChange(false); + // Wait for sidebar close animation to complete before navigating + setTimeout(() => { + router.push(`/dashboard/${searchSpaceId}/new-chat`); + }, 250); + } } catch (error) { console.error("Error deleting thread:", error); toast.error(t("error_deleting_chat") || "Failed to delete chat"); @@ -133,7 +150,7 @@ export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsS setDeletingThreadId(null); } }, - [queryClient, searchSpaceId, t] + [queryClient, searchSpaceId, t, currentChatId, router, onOpenChange] ); // Handle thread archive/unarchive