From b6eeaab1bbed7b24182af62f7231a184ce7ed779 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Thu, 8 May 2025 22:23:00 -0700 Subject: [PATCH] migrate current branch to query pattern (#116) --- src/components/chat/ChatHeader.tsx | 77 +++++++++++----------------- src/hooks/useCurrentBranch.ts | 30 +++++++++++ src/ipc/handlers/version_handlers.ts | 20 ++------ src/ipc/ipc_client.ts | 3 +- src/ipc/ipc_types.ts | 12 +---- 5 files changed, 65 insertions(+), 77 deletions(-) create mode 100644 src/hooks/useCurrentBranch.ts diff --git a/src/components/chat/ChatHeader.tsx b/src/components/chat/ChatHeader.tsx index fee8494..11578c6 100644 --- a/src/components/chat/ChatHeader.tsx +++ b/src/components/chat/ChatHeader.tsx @@ -22,8 +22,8 @@ import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { useChats } from "@/hooks/useChats"; import { showError } from "@/lib/toast"; import { useEffect, useState } from "react"; -import { BranchResult } from "@/ipc/ipc_types"; import { useStreamChat } from "@/hooks/useStreamChat"; +import { useCurrentBranch } from "@/hooks/useCurrentBranch"; interface ChatHeaderProps { isPreviewOpen: boolean; @@ -37,55 +37,35 @@ export function ChatHeader({ onVersionClick, }: ChatHeaderProps) { const appId = useAtomValue(selectedAppIdAtom); - const { versions, loading } = useVersions(appId); + const { versions, loading: versionsLoading } = useVersions(appId); const { navigate } = useRouter(); const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom); const { refreshChats } = useChats(appId); - const [branchInfo, setBranchInfo] = useState(null); const [checkingOutMain, setCheckingOutMain] = useState(false); const { isStreaming } = useStreamChat(); - // Fetch the current branch when appId changes + const { + branchInfo, + isLoading: branchInfoLoading, + refetchBranchInfo, + } = useCurrentBranch(appId); + useEffect(() => { - if (!appId) return; - - const fetchBranch = async () => { - try { - const result = await IpcClient.getInstance().getCurrentBranch(appId); - if (result.success) { - setBranchInfo(result); - } else { - showError("Failed to get current branch: " + result.errorMessage); - } - } catch (error) { - showError(`Failed to get current branch: ${error}`); - } - }; - - fetchBranch(); - // The use of selectedChatId and isStreaming is a hack to ensure that - // the branch info is relatively up to date. - }, [appId, selectedChatId, isStreaming]); + if (appId) { + refetchBranchInfo(); + } + }, [appId, selectedChatId, isStreaming, refetchBranchInfo]); const handleCheckoutMainBranch = async () => { if (!appId) return; try { setCheckingOutMain(true); - // Find the latest commit on main branch - // For simplicity, we'll just checkout to "main" directly await IpcClient.getInstance().checkoutVersion({ appId, versionId: "main", }); - - // Refresh branch info - const result = await IpcClient.getInstance().getCurrentBranch(appId); - if (result.success) { - setBranchInfo(result); - } else { - showError(result.errorMessage); - } + await refetchBranchInfo(); } catch (error) { showError(`Failed to checkout main branch: ${error}`); } finally { @@ -94,36 +74,29 @@ export function ChatHeader({ }; const handleNewChat = async () => { - // Only create a new chat if an app is selected if (appId) { try { - // Create a new chat with an empty title for now const chatId = await IpcClient.getInstance().createChat(appId); - - // Navigate to the new chat setSelectedChatId(chatId); navigate({ to: "/chat", search: { id: chatId }, }); - - // Refresh the chat list await refreshChats(); } catch (error) { - // DO A TOAST showError(`Failed to create new chat: ${(error as any).toString()}`); } } else { - // If no app is selected, navigate to home page navigate({ to: "/" }); } }; - // TODO: KEEP UP TO DATE WITH app_handlers.ts + + // REMINDER: KEEP UP TO DATE WITH app_handlers.ts const versionPostfix = versions.length === 10_000 ? `+` : ""; - // Check if we're not on the main branch - const isNotMainBranch = - branchInfo?.success && branchInfo.data.branch !== "main"; + const isNotMainBranch = branchInfo && branchInfo.branch !== "main"; + + const currentBranchName = branchInfo?.branch; return (
@@ -132,7 +105,7 @@ export function ChatHeader({
- {branchInfo?.data.branch === "" && ( + {currentBranchName === "" && ( <> @@ -153,13 +126,19 @@ export function ChatHeader({ )} + {currentBranchName && currentBranchName !== "" && ( + + You are on branch: {currentBranchName}. + + )} + {branchInfoLoading && Checking branch...}
@@ -182,7 +161,9 @@ export function ChatHeader({ className="hidden @6xs:flex cursor-pointer items-center gap-1 text-sm px-2 py-1 rounded-md" > - {loading ? "..." : `Version ${versions.length}${versionPostfix}`} + {versionsLoading + ? "..." + : `Version ${versions.length}${versionPostfix}`}
diff --git a/src/hooks/useCurrentBranch.ts b/src/hooks/useCurrentBranch.ts new file mode 100644 index 0000000..41f230a --- /dev/null +++ b/src/hooks/useCurrentBranch.ts @@ -0,0 +1,30 @@ +import { IpcClient } from "@/ipc/ipc_client"; +import { useQuery } from "@tanstack/react-query"; +import type { BranchResult } from "@/ipc/ipc_types"; + +export function useCurrentBranch(appId: number | null) { + const { + data: branchInfo, + isLoading, + refetch: refetchBranchInfo, + } = useQuery({ + queryKey: ["currentBranch", appId], + queryFn: async (): Promise => { + if (appId === null) { + // This case should ideally be handled by the `enabled` option + // but as a safeguard, and to ensure queryFn always has a valid appId if called. + throw new Error("appId is null, cannot fetch current branch."); + } + const ipcClient = IpcClient.getInstance(); + return ipcClient.getCurrentBranch(appId); + }, + enabled: appId !== null, + meta: { showErrorToast: false }, + }); + + return { + branchInfo, + isLoading, + refetchBranchInfo, + }; +} diff --git a/src/ipc/handlers/version_handlers.ts b/src/ipc/handlers/version_handlers.ts index b48112a..d07821b 100644 --- a/src/ipc/handlers/version_handlers.ts +++ b/src/ipc/handlers/version_handlers.ts @@ -59,20 +59,14 @@ export function registerVersionHandlers() { }); if (!app) { - return { - success: false, - errorMessage: "App not found", - }; + throw new Error("App not found"); } const appPath = getDyadAppPath(app.path); // Return appropriate result if the app is not a git repo if (!fs.existsSync(path.join(appPath, ".git"))) { - return { - success: false, - errorMessage: "Not a git repository", - }; + throw new Error("Not a git repository"); } try { @@ -83,17 +77,11 @@ export function registerVersionHandlers() { }); return { - success: true, - data: { - branch: currentBranch || "", - }, + branch: currentBranch || "", }; } catch (error: any) { logger.error(`Error getting current branch for app ${appId}:`, error); - return { - success: false, - errorMessage: `Failed to get current branch: ${error.message}`, - }; + throw new Error(`Failed to get current branch: ${error.message}`); } }, ); diff --git a/src/ipc/ipc_client.ts b/src/ipc/ipc_client.ts index e7f8a4b..c51899b 100644 --- a/src/ipc/ipc_client.ts +++ b/src/ipc/ipc_client.ts @@ -493,10 +493,9 @@ export class IpcClient { // Get the current branch of an app public async getCurrentBranch(appId: number): Promise { - const result = await this.ipcRenderer.invoke("get-current-branch", { + return this.ipcRenderer.invoke("get-current-branch", { appId, }); - return result; } // Get user settings diff --git a/src/ipc/ipc_types.ts b/src/ipc/ipc_types.ts index 62e554a..e7d3368 100644 --- a/src/ipc/ipc_types.ts +++ b/src/ipc/ipc_types.ts @@ -76,17 +76,7 @@ export interface Version { timestamp: number; } -export type Result = - | { - success: true; - data: T; - } - | { - success: false; - errorMessage: string; - }; - -export type BranchResult = Result<{ branch: string }>; +export type BranchResult = { branch: string }; export interface SandboxConfig { files: Record;