mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 08:46:22 +02:00
feat: old chat to new-chat with persistance
This commit is contained in:
parent
0c3574d049
commit
b5e20e7515
17 changed files with 490 additions and 385 deletions
|
|
@ -1,14 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { deleteChatMutationAtom } from "@/atoms/chats/chat-mutation.atoms";
|
||||
import { chatsAtom } from "@/atoms/chats/chat-query.atoms";
|
||||
import { globalChatsQueryParamsAtom } from "@/atoms/chats/ui.atoms";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { hasUnsavedEditorChangesAtom, pendingEditorNavigationAtom } from "@/atoms/editor/ui.atoms";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import { AppSidebar } from "@/components/sidebar/app-sidebar";
|
||||
|
|
@ -23,6 +20,7 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import { notesApiService } from "@/lib/apis/notes-api.service";
|
||||
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
|
||||
import { deleteThread, fetchThreads } from "@/lib/chat/thread-persistence";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
|
||||
interface AppSidebarProviderProps {
|
||||
|
|
@ -52,18 +50,24 @@ export function AppSidebarProvider({
|
|||
const t = useTranslations("dashboard");
|
||||
const tCommon = useTranslations("common");
|
||||
const router = useRouter();
|
||||
const setChatsQueryParams = useSetAtom(globalChatsQueryParamsAtom);
|
||||
const { data: chats, error: chatError, isLoading: isLoadingChats } = useAtomValue(chatsAtom);
|
||||
const [{ isPending: isDeletingChat, mutateAsync: deleteChat, error: deleteError }] =
|
||||
useAtom(deleteChatMutationAtom);
|
||||
const queryClient = useQueryClient();
|
||||
const [isDeletingThread, setIsDeletingThread] = useState(false);
|
||||
|
||||
// Editor state for handling unsaved changes
|
||||
const hasUnsavedEditorChanges = useAtomValue(hasUnsavedEditorChangesAtom);
|
||||
const setPendingNavigation = useSetAtom(pendingEditorNavigationAtom);
|
||||
|
||||
useEffect(() => {
|
||||
setChatsQueryParams((prev) => ({ ...prev, search_space_id: searchSpaceId, skip: 0, limit: 4 }));
|
||||
}, [searchSpaceId, setChatsQueryParams]);
|
||||
// Fetch new chat threads
|
||||
const {
|
||||
data: threadsData,
|
||||
error: threadError,
|
||||
isLoading: isLoadingThreads,
|
||||
refetch: refetchThreads,
|
||||
} = useQuery({
|
||||
queryKey: ["threads", searchSpaceId],
|
||||
queryFn: () => fetchThreads(Number(searchSpaceId), 4),
|
||||
enabled: !!searchSpaceId,
|
||||
});
|
||||
|
||||
const {
|
||||
data: searchSpace,
|
||||
|
|
@ -95,7 +99,7 @@ export function AppSidebarProvider({
|
|||
});
|
||||
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [chatToDelete, setChatToDelete] = useState<{ id: number; name: string } | null>(null);
|
||||
const [threadToDelete, setThreadToDelete] = useState<{ id: number; name: string } | null>(null);
|
||||
const [showDeleteNoteDialog, setShowDeleteNoteDialog] = useState(false);
|
||||
const [noteToDelete, setNoteToDelete] = useState<{
|
||||
id: number;
|
||||
|
|
@ -103,62 +107,56 @@ export function AppSidebarProvider({
|
|||
search_space_id: number;
|
||||
} | null>(null);
|
||||
const [isDeletingNote, setIsDeletingNote] = useState(false);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
// Set isClient to true when component mounts on the client
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
// Retry function
|
||||
const retryFetch = useCallback(() => {
|
||||
fetchSearchSpace();
|
||||
}, [fetchSearchSpace]);
|
||||
|
||||
// Transform API response to the format expected by AppSidebar
|
||||
// Transform threads to the format expected by AppSidebar
|
||||
const recentChats = useMemo(() => {
|
||||
if (!chats) return [];
|
||||
if (!threadsData?.threads) return [];
|
||||
|
||||
// Sort chats by created_at (most recent first)
|
||||
const sortedChats = [...chats].sort((a, b) => {
|
||||
const dateA = new Date(a.created_at).getTime();
|
||||
const dateB = new Date(b.created_at).getTime();
|
||||
return dateB - dateA; // Descending order (most recent first)
|
||||
});
|
||||
|
||||
return sortedChats.map((chat) => ({
|
||||
name: chat.title || `Chat ${chat.id}`,
|
||||
url: `/dashboard/${chat.search_space_id}/researcher/${chat.id}`,
|
||||
// Threads are already sorted by updated_at desc from the API
|
||||
return threadsData.threads.map((thread) => ({
|
||||
name: thread.title || `Chat ${thread.id}`,
|
||||
url: `/dashboard/${searchSpaceId}/new-chat/${thread.id}`,
|
||||
icon: "MessageCircleMore",
|
||||
id: chat.id,
|
||||
search_space_id: chat.search_space_id,
|
||||
id: thread.id,
|
||||
search_space_id: Number(searchSpaceId),
|
||||
actions: [
|
||||
{
|
||||
name: "Delete",
|
||||
icon: "Trash2",
|
||||
onClick: () => {
|
||||
setChatToDelete({ id: chat.id, name: chat.title || `Chat ${chat.id}` });
|
||||
setThreadToDelete({
|
||||
id: thread.id,
|
||||
name: thread.title || `Chat ${thread.id}`,
|
||||
});
|
||||
setShowDeleteDialog(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
}, [chats]);
|
||||
}, [threadsData, searchSpaceId]);
|
||||
|
||||
// Handle delete chat with better error handling
|
||||
const handleDeleteChat = useCallback(async () => {
|
||||
if (!chatToDelete) return;
|
||||
// Handle delete thread
|
||||
const handleDeleteThread = useCallback(async () => {
|
||||
if (!threadToDelete) return;
|
||||
|
||||
setIsDeletingThread(true);
|
||||
try {
|
||||
await deleteChat({ id: chatToDelete.id });
|
||||
await deleteThread(threadToDelete.id);
|
||||
// Invalidate threads query to refresh the list
|
||||
queryClient.invalidateQueries({ queryKey: ["threads", searchSpaceId] });
|
||||
} catch (error) {
|
||||
console.error("Error deleting chat:", error);
|
||||
// You could show a toast notification here
|
||||
console.error("Error deleting thread:", error);
|
||||
} finally {
|
||||
setIsDeletingThread(false);
|
||||
setShowDeleteDialog(false);
|
||||
setChatToDelete(null);
|
||||
setThreadToDelete(null);
|
||||
}
|
||||
}, [chatToDelete, deleteChat]);
|
||||
}, [threadToDelete, queryClient, searchSpaceId]);
|
||||
|
||||
// Handle delete note with confirmation
|
||||
const handleDeleteNote = useCallback(async () => {
|
||||
|
|
@ -182,7 +180,7 @@ export function AppSidebarProvider({
|
|||
|
||||
// Memoized fallback chats
|
||||
const fallbackChats = useMemo(() => {
|
||||
if (chatError) {
|
||||
if (threadError) {
|
||||
return [
|
||||
{
|
||||
name: t("error_loading_chats"),
|
||||
|
|
@ -194,7 +192,7 @@ export function AppSidebarProvider({
|
|||
{
|
||||
name: tCommon("retry"),
|
||||
icon: "RefreshCw",
|
||||
onClick: retryFetch,
|
||||
onClick: () => refetchThreads(),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -202,7 +200,7 @@ export function AppSidebarProvider({
|
|||
}
|
||||
|
||||
return [];
|
||||
}, [chatError, isLoadingChats, recentChats.length, searchSpaceId, retryFetch, t, tCommon]);
|
||||
}, [threadError, searchSpaceId, refetchThreads, t, tCommon]);
|
||||
|
||||
// Use fallback chats if there's an error or no chats
|
||||
const displayChats = recentChats.length > 0 ? recentChats : fallbackChats;
|
||||
|
|
@ -262,7 +260,7 @@ export function AppSidebarProvider({
|
|||
// Memoized updated navSecondary
|
||||
const updatedNavSecondary = useMemo(() => {
|
||||
const updated = [...navSecondary];
|
||||
if (updated.length > 0 && isClient) {
|
||||
if (updated.length > 0) {
|
||||
updated[0] = {
|
||||
...updated[0],
|
||||
title:
|
||||
|
|
@ -275,15 +273,7 @@ export function AppSidebarProvider({
|
|||
};
|
||||
}
|
||||
return updated;
|
||||
}, [
|
||||
navSecondary,
|
||||
isClient,
|
||||
searchSpace?.name,
|
||||
isLoadingSearchSpace,
|
||||
searchSpaceError,
|
||||
t,
|
||||
tCommon,
|
||||
]);
|
||||
}, [navSecondary, searchSpace?.name, isLoadingSearchSpace, searchSpaceError, t, tCommon]);
|
||||
|
||||
// Prepare page usage data
|
||||
const pageUsage = user
|
||||
|
|
@ -293,21 +283,6 @@ export function AppSidebarProvider({
|
|||
}
|
||||
: undefined;
|
||||
|
||||
// Show loading state if not client-side
|
||||
if (!isClient) {
|
||||
return (
|
||||
<AppSidebar
|
||||
searchSpaceId={searchSpaceId}
|
||||
navSecondary={navSecondary}
|
||||
navMain={navMain}
|
||||
RecentChats={[]}
|
||||
RecentNotes={[]}
|
||||
onAddNote={handleAddNote}
|
||||
pageUsage={pageUsage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppSidebar
|
||||
|
|
@ -329,25 +304,25 @@ export function AppSidebarProvider({
|
|||
<span>{t("delete_chat")}</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("delete_chat_confirm")} <span className="font-medium">{chatToDelete?.name}</span>?{" "}
|
||||
{t("action_cannot_undone")}
|
||||
{t("delete_chat_confirm")} <span className="font-medium">{threadToDelete?.name}</span>
|
||||
? {t("action_cannot_undone")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2 sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={isDeletingChat}
|
||||
disabled={isDeletingThread}
|
||||
>
|
||||
{tCommon("cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteChat}
|
||||
disabled={isDeletingChat}
|
||||
onClick={handleDeleteThread}
|
||||
disabled={isDeletingThread}
|
||||
className="gap-2"
|
||||
>
|
||||
{isDeletingChat ? (
|
||||
{isDeletingThread ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
{t("deleting")}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue