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 { atom } from "jotai";
import type { ChatSummary } from "@/lib/schemas";
// Per-chat atoms implemented with maps keyed by chatId
export const chatMessagesByIdAtom = atom<Map<number, Message[]>>(new Map());
......@@ -13,10 +12,6 @@ export const isStreamingByIdAtom = atom<Map<number, boolean>>(new Map());
export const chatInputValueAtom = 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)
export const chatStreamCountByIdAtom = atom<Map<number, number>>(new Map());
export const recentStreamChatIdsAtom = atom<Set<number>>(new Set<number>());
......
......@@ -36,7 +36,7 @@ export function ChatList({ show }: { show?: boolean }) {
const [selectedAppId] = useAtom(selectedAppIdAtom);
const [, setIsDropdownOpen] = useAtom(dropdownOpenAtom);
const { chats, loading, refreshChats } = useChats(selectedAppId);
const { chats, loading, invalidateChats } = useChats(selectedAppId);
const routerState = useRouterState();
const isChatRoute = routerState.location.pathname === "/chat";
......@@ -95,7 +95,7 @@ export function ChatList({ show }: { show?: boolean }) {
});
// Refresh the chat list
await refreshChats();
await invalidateChats();
} catch (error) {
// DO A TOAST
showError(`Failed to create new chat: ${(error as any).toString()}`);
......@@ -118,7 +118,7 @@ export function ChatList({ show }: { show?: boolean }) {
}
// Refresh the chat list
await refreshChats();
await invalidateChats();
} catch (error) {
showError(`Failed to delete chat: ${(error as any).toString()}`);
}
......@@ -278,7 +278,7 @@ export function ChatList({ show }: { show?: boolean }) {
currentTitle={renameChatTitle}
isOpen={isRenameDialogOpen}
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 {
......@@ -11,7 +11,6 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getAllChats } from "@/lib/chat";
import type { ChatSummary } from "@/lib/schemas";
import { useAtomValue } from "jotai";
import {
......@@ -20,6 +19,7 @@ import {
} from "@/atoms/chatAtoms";
import { useLoadApps } from "@/hooks/useLoadApps";
import { useSelectChat } from "@/hooks/useSelectChat";
import { useChats } from "@/hooks/useChats";
export function ChatActivityButton() {
const [open, setOpen] = useState(false);
......@@ -61,33 +61,18 @@ export function ChatActivityButton() {
}
function ChatActivityList({ onSelect }: { onSelect?: () => void }) {
const [chats, setChats] = useState<ChatSummary[]>([]);
const [loading, setLoading] = useState(true);
const isStreamingById = useAtomValue(isStreamingByIdAtom);
const recentStreamChatIds = useAtomValue(recentStreamChatIdsAtom);
const apps = useLoadApps();
const { selectChat } = useSelectChat();
useEffect(() => {
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 { chats: allChats, loading } = useChats(null);
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) {
return (
......
......@@ -46,7 +46,7 @@ export function ChatHeader({
const { versions, loading: versionsLoading } = useVersions(appId);
const { navigate } = useRouter();
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom);
const { refreshChats } = useChats(appId);
const { invalidateChats } = useChats(appId);
const { isStreaming } = useStreamChat();
const isAnyCheckoutVersionInProgress = useAtomValue(
isAnyCheckoutVersionInProgressAtom,
......@@ -89,7 +89,7 @@ export function ChatHeader({
to: "/chat",
search: { id: chatId },
});
await refreshChats();
await invalidateChats();
} catch (error) {
showError(`Failed to create new chat: ${(error as any).toString()}`);
}
......
import { useAtom } from "jotai";
import { useEffect } from "react";
import { chatsAtom, chatsLoadingAtom } from "@/atoms/chatAtoms";
import { getAllChats } from "@/lib/chat";
import { IpcClient } from "@/ipc/ipc_client";
import type { ChatSummary } from "@/lib/schemas";
import { useQuery, useQueryClient } from "@tanstack/react-query";
export function useChats(appId: number | null) {
const [chats, setChats] = useAtom(chatsAtom);
const [loading, setLoading] = useAtom(chatsLoadingAtom);
export const CHATS_QUERY_KEY = "chats";
useEffect(() => {
const fetchChats = async () => {
try {
setLoading(true);
const chatList = await getAllChats(appId || undefined);
setChats(chatList);
} catch (error) {
console.error("Failed to load chats:", error);
} finally {
setLoading(false);
}
};
export function useChats(appId: number | null) {
const queryClient = useQueryClient();
fetchChats();
}, [appId, setChats, setLoading]);
const { data, isLoading } = useQuery<ChatSummary[]>({
queryKey: [CHATS_QUERY_KEY, appId],
queryFn: async () => {
return IpcClient.getInstance().getChats(appId ?? undefined);
},
});
const refreshChats = async () => {
try {
setLoading(true);
const chatList = await getAllChats(appId || undefined);
setChats(chatList);
return chatList;
} catch (error) {
console.error("Failed to refresh chats:", error);
return [] as ChatSummary[];
} finally {
setLoading(false);
}
const invalidateChats = () => {
// Invalidate all chat queries (any appId) since mutations affect both
// app-specific lists and the global list (appId=null)
queryClient.invalidateQueries({ queryKey: [CHATS_QUERY_KEY] });
};
return { chats, loading, refreshChats };
return {
chats: data ?? [],
loading: isLoading,
invalidateChats,
};
}
......@@ -13,6 +13,5 @@ export const useCheckName = (appName: string) => {
refetchOnMount: false,
refetchOnReconnect: false,
retry: false,
staleTime: 300000, // 5 minutes
});
};
......@@ -43,7 +43,7 @@ export function useStreamChat({
const setErrorById = useSetAtom(chatErrorByIdAtom);
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [selectedAppId] = useAtom(selectedAppIdAtom);
const { refreshChats } = useChats(selectedAppId);
const { invalidateChats } = useChats(selectedAppId);
const { refreshApp } = useLoadApp(selectedAppId);
const setStreamCountById = useSetAtom(chatStreamCountByIdAtom);
......@@ -152,7 +152,7 @@ export function useStreamChat({
next.set(chatId, false);
return next;
});
refreshChats();
invalidateChats();
refreshApp();
refreshVersions();
invalidateTokenCount();
......@@ -172,7 +172,7 @@ export function useStreamChat({
next.set(chatId, false);
return next;
});
refreshChats();
invalidateChats();
refreshApp();
refreshVersions();
invalidateTokenCount();
......
......@@ -29,7 +29,7 @@ export function useVersions(appId: number | null) {
return ipcClient.listVersions({ appId });
},
enabled: appId !== null,
initialData: [],
placeholderData: [],
meta: { showErrorToast: true },
});
......
import { IpcClient } from "../ipc/ipc_client";
import type { ChatSummary } from "./schemas";
import type { CreateAppParams, CreateAppResult } from "../ipc/ipc_types";
/**
......@@ -17,17 +16,3 @@ export async function createApp(
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" {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
retry: false,
},
mutations: {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论