refactor: enhance chat functionality with query invalidation and navigation improvements

- Integrated useQueryClient from React Query to manage query invalidation for threads, ensuring real-time updates in the sidebar when a new chat is created or a thread is deleted.
- Updated the AllChatsSidebar to close the sidebar and navigate to the new chat page if the currently open chat is deleted.
- Improved error handling and user experience during thread deletion by ensuring the sidebar closes smoothly before redirection.
This commit is contained in:
Anish Sarkar 2025-12-26 03:11:49 +05:30
parent d9df63f57e
commit 287fc236ad
3 changed files with 36 additions and 4 deletions

View file

@ -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<number | null>(null);
const [messages, setMessages] = useState<ThreadMessageLike[]>([]);
@ -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,
]
);

View file

@ -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 () => {

View file

@ -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<number | null>(null);
const [archivingThreadId, setArchivingThreadId] = useState<number | null>(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