"use client"; import { useCallback, useMemo, useState } from "react"; import { ErrorBox, Button } from "@/ui"; import type { GitAction, GitState } from "@/features/agent/contracts"; import { safeJson } from "@/features/agent/safe-json"; import { useMountSubscription } from "@/hooks/use-mount-subscription"; import { parseUnifiedDiff, type DiffFile, type DiffViewMode, } from "@/features/agent/ui/git-diff-panel-model"; import { GitPanelHeader, GitWorkflowBar, PrSection, loadPr, mergePr, type MergeMethod, type PrPayload, } from "@/features/agent/ui/git-diff-panel-workflow"; import { DiffFileList } from "@/features/agent/ui/git-diff-panel-diff-view"; export function GitDiffPanel({ cwd }: { cwd: string | null }) { const [payload, setPayload] = useState<(Partial & { error?: string }) | null>(null); const [prPayload, setPrPayload] = useState(null); const [loading, setLoading] = useState(false); const [merging, setMerging] = useState(false); const [mergeError, setMergeError] = useState(null); const [commitMessage, setCommitMessage] = useState(""); const [viewMode, setViewMode] = useState("unified"); const load = useCallback(async () => { if (!cwd) { setPayload(null); setPrPayload(null); return; } setLoading(true); setMergeError(null); const [git, pr] = await Promise.allSettled([loadGitState(cwd), loadPr(cwd)]); setPayload( git.status === "fulfilled" ? git.value : { error: git.reason instanceof Error ? git.reason.message : "Failed to load git state" }, ); setPrPayload(pr.status === "fulfilled" ? pr.value : null); setLoading(false); }, [cwd]); const run = useCallback( async (action: GitAction) => { if (!cwd) return; setLoading(true); try { setPayload(await runGitAction(cwd, action)); if (action.action === "commit") setCommitMessage(""); } catch (error) { setPayload((current) => ({ ...(current ?? {}), error: error instanceof Error ? error.message : "Git action failed", })); } finally { setLoading(false); } }, [cwd], ); const merge = useCallback( async (method: MergeMethod) => { if (!cwd || !prPayload?.pr) return; setMerging(true); setMergeError(null); try { await mergePr(cwd, prPayload.pr.number, method); await load(); } catch (error) { setMergeError(error instanceof Error ? error.message : "Merge failed"); } finally { setMerging(false); } }, [cwd, prPayload?.pr, load], ); useMountSubscription(() => { void load(); }, [load]); const files = useMemo(() => parseUnifiedDiff(payload?.diff ?? ""), [payload?.diff]); return (
run({ action: "init" })} loading={loading} payload={payload} />
); } function GitDiffPanelBody({ cwd, files, viewMode, onViewMode, initGit, loading, payload, }: { cwd: string | null; files: DiffFile[]; viewMode: DiffViewMode; onViewMode: (mode: DiffViewMode) => void; initGit: () => Promise; loading: boolean; payload: (Partial & { error?: string }) | null; }) { if (!cwd) return (
Choose a project directory to view git changes.
); if (payload?.error) return {payload.error}; if (payload?.isRepo === false) return ; if (files.length === 0) return ; return ; } function InitializeGitPanel({ initGit, loading, }: { initGit: () => Promise; loading: boolean; }) { return (
This directory is not a git repository.
); } function EmptyDiffPanel({ loading, status }: { loading: boolean; status: string[] }) { return (
{loading ? "Loading diff…" : "No unstaged tracked-file changes."} {status.length > 0 ? (
          {status.join("\n")}
        
) : null}
); } async function loadGitState(cwd: string): Promise { const response = await fetch(`/api/agent/git?cwd=${encodeURIComponent(cwd)}`, { cache: "no-store", }); const payload = await safeJson(response); if (!response.ok) throw new Error(payload.error || "Failed to load git state"); return payload; } async function runGitAction(cwd: string, action: GitAction): Promise { const response = await fetch(`/api/agent/git?cwd=${encodeURIComponent(cwd)}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(action), }); const payload = await safeJson(response); if (!response.ok) throw new Error(payload.error || "Git action failed"); return payload; }