Unverified 提交 74665598 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

refactor usechats and set global stale time (#2020)

<!-- CURSOR_SUMMARY --> > [!NOTE] > Modernizes chat data flow and caching. > > - Replaces Jotai chat list state and `getAllChats` with `useChats` using React Query (`CHATS_QUERY_KEY`), exposing `chats`, `loading`, and `invalidateChats` > - Updates `ChatList`, `ChatHeader`, `useStreamChat`, and `ChatActivity` to consume `useChats` and call `invalidateChats` after create/delete/stream events > - Simplifies `ChatActivityList` to build recent rows from `recentStreamChatIds` and `useChats(null)` > - Sets global `queries.staleTime=60_000` in `renderer.tsx`; removes per-hook `staleTime` in `useCheckName` > - Switches `useVersions` to `placeholderData` > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8532c5919b25f77f3f38899a53bae80eb7e99c27. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Refactored chat fetching to use React Query and set a global 60s stale time for consistent caching. Simplifies chat state management and updates ChatActivity to use the new hook. - **Refactors** - Rewrote useChats using @tanstack/react-query with query key [CHATS_QUERY_KEY, appId]; exposes chats and loading, adds invalidateChats, and removes refreshChats. - Updated ChatList, ChatHeader, and useStreamChat to call invalidateChats after mutations. - Removed chatsAtom and chatsLoadingAtom; deleted getAllChats from lib/chat. - Updated ChatActivityList to use useChats and derive recent rows from recentStreamChatIds. - Set global queries.staleTime to 60_000 in queryClient; removed per-hook staleTime in useCheckName. - useVersions now uses placeholderData instead of initialData. <sup>Written for commit 8532c5919b25f77f3f38899a53bae80eb7e99c27. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
上级 22712b42
import type { FileAttachment, Message } from "@/ipc/ipc_types"; import type { FileAttachment, Message } from "@/ipc/ipc_types";
import { atom } from "jotai"; import { atom } from "jotai";
import type { ChatSummary } from "@/lib/schemas";
// Per-chat atoms implemented with maps keyed by chatId // Per-chat atoms implemented with maps keyed by chatId
export const chatMessagesByIdAtom = atom<Map<number, Message[]>>(new Map()); export const chatMessagesByIdAtom = atom<Map<number, Message[]>>(new Map());
...@@ -13,10 +12,6 @@ export const isStreamingByIdAtom = atom<Map<number, boolean>>(new Map()); ...@@ -13,10 +12,6 @@ export const isStreamingByIdAtom = atom<Map<number, boolean>>(new Map());
export const chatInputValueAtom = atom<string>(""); export const chatInputValueAtom = atom<string>("");
export const homeChatInputValueAtom = atom<string>(""); export const homeChatInputValueAtom = atom<string>("");
// Atoms for chat list management
export const chatsAtom = atom<ChatSummary[]>([]);
export const chatsLoadingAtom = atom<boolean>(false);
// Used for scrolling to the bottom of the chat messages (per chat) // Used for scrolling to the bottom of the chat messages (per chat)
export const chatStreamCountByIdAtom = atom<Map<number, number>>(new Map()); export const chatStreamCountByIdAtom = atom<Map<number, number>>(new Map());
export const recentStreamChatIdsAtom = atom<Set<number>>(new Set<number>()); export const recentStreamChatIdsAtom = atom<Set<number>>(new Set<number>());
......
...@@ -36,7 +36,7 @@ export function ChatList({ show }: { show?: boolean }) { ...@@ -36,7 +36,7 @@ export function ChatList({ show }: { show?: boolean }) {
const [selectedAppId] = useAtom(selectedAppIdAtom); const [selectedAppId] = useAtom(selectedAppIdAtom);
const [, setIsDropdownOpen] = useAtom(dropdownOpenAtom); const [, setIsDropdownOpen] = useAtom(dropdownOpenAtom);
const { chats, loading, refreshChats } = useChats(selectedAppId); const { chats, loading, invalidateChats } = useChats(selectedAppId);
const routerState = useRouterState(); const routerState = useRouterState();
const isChatRoute = routerState.location.pathname === "/chat"; const isChatRoute = routerState.location.pathname === "/chat";
...@@ -95,7 +95,7 @@ export function ChatList({ show }: { show?: boolean }) { ...@@ -95,7 +95,7 @@ export function ChatList({ show }: { show?: boolean }) {
}); });
// Refresh the chat list // Refresh the chat list
await refreshChats(); await invalidateChats();
} catch (error) { } catch (error) {
// DO A TOAST // DO A TOAST
showError(`Failed to create new chat: ${(error as any).toString()}`); showError(`Failed to create new chat: ${(error as any).toString()}`);
...@@ -118,7 +118,7 @@ export function ChatList({ show }: { show?: boolean }) { ...@@ -118,7 +118,7 @@ export function ChatList({ show }: { show?: boolean }) {
} }
// Refresh the chat list // Refresh the chat list
await refreshChats(); await invalidateChats();
} catch (error) { } catch (error) {
showError(`Failed to delete chat: ${(error as any).toString()}`); showError(`Failed to delete chat: ${(error as any).toString()}`);
} }
...@@ -278,7 +278,7 @@ export function ChatList({ show }: { show?: boolean }) { ...@@ -278,7 +278,7 @@ export function ChatList({ show }: { show?: boolean }) {
currentTitle={renameChatTitle} currentTitle={renameChatTitle}
isOpen={isRenameDialogOpen} isOpen={isRenameDialogOpen}
onOpenChange={handleRenameDialogClose} onOpenChange={handleRenameDialogClose}
onRename={refreshChats} onRename={invalidateChats}
/> />
)} )}
......
import { useEffect, useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { Bell, Loader2, CheckCircle2 } from "lucide-react"; import { Bell, Loader2, CheckCircle2 } from "lucide-react";
import { import {
...@@ -11,7 +11,6 @@ import { ...@@ -11,7 +11,6 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { getAllChats } from "@/lib/chat";
import type { ChatSummary } from "@/lib/schemas"; import type { ChatSummary } from "@/lib/schemas";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { import {
...@@ -20,6 +19,7 @@ import { ...@@ -20,6 +19,7 @@ import {
} from "@/atoms/chatAtoms"; } from "@/atoms/chatAtoms";
import { useLoadApps } from "@/hooks/useLoadApps"; import { useLoadApps } from "@/hooks/useLoadApps";
import { useSelectChat } from "@/hooks/useSelectChat"; import { useSelectChat } from "@/hooks/useSelectChat";
import { useChats } from "@/hooks/useChats";
export function ChatActivityButton() { export function ChatActivityButton() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
...@@ -61,33 +61,18 @@ export function ChatActivityButton() { ...@@ -61,33 +61,18 @@ export function ChatActivityButton() {
} }
function ChatActivityList({ onSelect }: { onSelect?: () => void }) { function ChatActivityList({ onSelect }: { onSelect?: () => void }) {
const [chats, setChats] = useState<ChatSummary[]>([]);
const [loading, setLoading] = useState(true);
const isStreamingById = useAtomValue(isStreamingByIdAtom); const isStreamingById = useAtomValue(isStreamingByIdAtom);
const recentStreamChatIds = useAtomValue(recentStreamChatIdsAtom); const recentStreamChatIds = useAtomValue(recentStreamChatIdsAtom);
const apps = useLoadApps(); const apps = useLoadApps();
const { selectChat } = useSelectChat(); const { selectChat } = useSelectChat();
useEffect(() => { const { chats: allChats, loading } = useChats(null);
let mounted = true;
(async () => {
try {
const all = await getAllChats();
if (!mounted) return;
const recent = Array.from(recentStreamChatIds)
.map((id) => all.find((c) => c.id === id))
.filter((c) => c !== undefined);
// Sort recent first
setChats([...recent].reverse());
} finally {
if (mounted) setLoading(false);
}
})();
return () => {
mounted = false;
};
}, [recentStreamChatIds]);
const rows = useMemo(() => chats.slice(0, 30), [chats]); const rows = useMemo(() => {
const recent = Array.from(recentStreamChatIds)
.map((id) => allChats.find((c: ChatSummary) => c.id === id))
.filter((c): c is ChatSummary => c !== undefined);
return [...recent].reverse().slice(0, 30);
}, [recentStreamChatIds, allChats]);
if (loading) { if (loading) {
return ( return (
......
...@@ -46,7 +46,7 @@ export function ChatHeader({ ...@@ -46,7 +46,7 @@ export function ChatHeader({
const { versions, loading: versionsLoading } = useVersions(appId); const { versions, loading: versionsLoading } = useVersions(appId);
const { navigate } = useRouter(); const { navigate } = useRouter();
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom); const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom);
const { refreshChats } = useChats(appId); const { invalidateChats } = useChats(appId);
const { isStreaming } = useStreamChat(); const { isStreaming } = useStreamChat();
const isAnyCheckoutVersionInProgress = useAtomValue( const isAnyCheckoutVersionInProgress = useAtomValue(
isAnyCheckoutVersionInProgressAtom, isAnyCheckoutVersionInProgressAtom,
...@@ -89,7 +89,7 @@ export function ChatHeader({ ...@@ -89,7 +89,7 @@ export function ChatHeader({
to: "/chat", to: "/chat",
search: { id: chatId }, search: { id: chatId },
}); });
await refreshChats(); await invalidateChats();
} catch (error) { } catch (error) {
showError(`Failed to create new chat: ${(error as any).toString()}`); showError(`Failed to create new chat: ${(error as any).toString()}`);
} }
......
import { useAtom } from "jotai"; import { IpcClient } from "@/ipc/ipc_client";
import { useEffect } from "react";
import { chatsAtom, chatsLoadingAtom } from "@/atoms/chatAtoms";
import { getAllChats } from "@/lib/chat";
import type { ChatSummary } from "@/lib/schemas"; import type { ChatSummary } from "@/lib/schemas";
import { useQuery, useQueryClient } from "@tanstack/react-query";
export function useChats(appId: number | null) { export const CHATS_QUERY_KEY = "chats";
const [chats, setChats] = useAtom(chatsAtom);
const [loading, setLoading] = useAtom(chatsLoadingAtom);
useEffect(() => { export function useChats(appId: number | null) {
const fetchChats = async () => { const queryClient = useQueryClient();
try {
setLoading(true);
const chatList = await getAllChats(appId || undefined);
setChats(chatList);
} catch (error) {
console.error("Failed to load chats:", error);
} finally {
setLoading(false);
}
};
fetchChats(); const { data, isLoading } = useQuery<ChatSummary[]>({
}, [appId, setChats, setLoading]); queryKey: [CHATS_QUERY_KEY, appId],
queryFn: async () => {
return IpcClient.getInstance().getChats(appId ?? undefined);
},
});
const refreshChats = async () => { const invalidateChats = () => {
try { // Invalidate all chat queries (any appId) since mutations affect both
setLoading(true); // app-specific lists and the global list (appId=null)
const chatList = await getAllChats(appId || undefined); queryClient.invalidateQueries({ queryKey: [CHATS_QUERY_KEY] });
setChats(chatList);
return chatList;
} catch (error) {
console.error("Failed to refresh chats:", error);
return [] as ChatSummary[];
} finally {
setLoading(false);
}
}; };
return { chats, loading, refreshChats }; return {
chats: data ?? [],
loading: isLoading,
invalidateChats,
};
} }
...@@ -13,6 +13,5 @@ export const useCheckName = (appName: string) => { ...@@ -13,6 +13,5 @@ export const useCheckName = (appName: string) => {
refetchOnMount: false, refetchOnMount: false,
refetchOnReconnect: false, refetchOnReconnect: false,
retry: false, retry: false,
staleTime: 300000, // 5 minutes
}); });
}; };
...@@ -43,7 +43,7 @@ export function useStreamChat({ ...@@ -43,7 +43,7 @@ export function useStreamChat({
const setErrorById = useSetAtom(chatErrorByIdAtom); const setErrorById = useSetAtom(chatErrorByIdAtom);
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom); const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [selectedAppId] = useAtom(selectedAppIdAtom); const [selectedAppId] = useAtom(selectedAppIdAtom);
const { refreshChats } = useChats(selectedAppId); const { invalidateChats } = useChats(selectedAppId);
const { refreshApp } = useLoadApp(selectedAppId); const { refreshApp } = useLoadApp(selectedAppId);
const setStreamCountById = useSetAtom(chatStreamCountByIdAtom); const setStreamCountById = useSetAtom(chatStreamCountByIdAtom);
...@@ -152,7 +152,7 @@ export function useStreamChat({ ...@@ -152,7 +152,7 @@ export function useStreamChat({
next.set(chatId, false); next.set(chatId, false);
return next; return next;
}); });
refreshChats(); invalidateChats();
refreshApp(); refreshApp();
refreshVersions(); refreshVersions();
invalidateTokenCount(); invalidateTokenCount();
...@@ -172,7 +172,7 @@ export function useStreamChat({ ...@@ -172,7 +172,7 @@ export function useStreamChat({
next.set(chatId, false); next.set(chatId, false);
return next; return next;
}); });
refreshChats(); invalidateChats();
refreshApp(); refreshApp();
refreshVersions(); refreshVersions();
invalidateTokenCount(); invalidateTokenCount();
......
...@@ -29,7 +29,7 @@ export function useVersions(appId: number | null) { ...@@ -29,7 +29,7 @@ export function useVersions(appId: number | null) {
return ipcClient.listVersions({ appId }); return ipcClient.listVersions({ appId });
}, },
enabled: appId !== null, enabled: appId !== null,
initialData: [], placeholderData: [],
meta: { showErrorToast: true }, meta: { showErrorToast: true },
}); });
......
import { IpcClient } from "../ipc/ipc_client"; import { IpcClient } from "../ipc/ipc_client";
import type { ChatSummary } from "./schemas";
import type { CreateAppParams, CreateAppResult } from "../ipc/ipc_types"; import type { CreateAppParams, CreateAppResult } from "../ipc/ipc_types";
/** /**
...@@ -17,17 +16,3 @@ export async function createApp( ...@@ -17,17 +16,3 @@ export async function createApp(
throw error; throw error;
} }
} }
/**
* Get all chats from the database
* @param appId Optional app ID to filter chats by app
* @returns Array of chat summaries with id, title, and createdAt
*/
export async function getAllChats(appId?: number): Promise<ChatSummary[]> {
try {
return await IpcClient.getInstance().getChats(appId);
} catch (error) {
console.error("[CHAT] Error getting all chats:", error);
throw error;
}
}
...@@ -33,6 +33,7 @@ declare module "@tanstack/react-query" { ...@@ -33,6 +33,7 @@ declare module "@tanstack/react-query" {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
staleTime: 60_000,
retry: false, retry: false,
}, },
mutations: { mutations: {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论