"use client"; import { useState } from "react"; import { GitBranchIcon, ReloadIcon } from "@/ui/icons"; import { Input, Button } from "@/ui"; import type { GitAction, GitRef, GitState } from "@/features/agent/contracts"; import { safeJson } from "@/features/agent/safe-json"; import { gitDiffHeaderTitle } from "@/features/agent/ui/git-diff-panel-model"; export function GitPanelHeader({ cwd, loading, payload, onReload, }: { cwd: string | null; loading: boolean; payload: Partial | null; onReload: () => Promise; }) { return (
{gitDiffHeaderTitle(payload, cwd)}
); } export function GitWorkflowBar({ payload, loading, commitMessage, onCommitMessage, onRun, }: { payload: (Partial & { error?: string }) | null; loading: boolean; commitMessage: string; onCommitMessage: (value: string) => void; onRun: (action: GitAction) => Promise; }) { if (!payload?.isRepo) return null; const dirty = (payload.status?.length ?? 0) > 0; return (
+{payload.additions ?? 0}{" "} -{payload.deletions ?? 0}{" "} {payload.status?.length ?? 0} files
{dirty ? (
onCommitMessage(event.target.value)} placeholder="commit message" className="h-7 min-w-0 flex-1 rounded-md border border-(--border)/80 bg-(--color-input) px-2 text-(--fg) outline-none focus:border-(--border-hover)" />
) : null}
); } function RefSelect({ refs, branch, loading, onRun, }: { refs: GitRef[]; branch?: string | null; loading: boolean; onRun: (action: GitAction) => Promise; }) { return ( ); } type ChecksSummary = { pending: number; passing: number; failing: number; total: number }; export type PullRequest = { number: number; title: string; url: string; state: string; isDraft: boolean; headRefName: string; baseRefName: string; additions: number; deletions: number; mergeable: string; checksSummary: ChecksSummary; }; export type PrPayload = { pr?: PullRequest; error?: string }; export type MergeMethod = "merge" | "squash" | "rebase"; export async function loadPr(cwd: string): Promise { const response = await fetch(`/api/agent/pr?cwd=${encodeURIComponent(cwd)}`, { cache: "no-store", }); const payload = await safeJson(response); if (!response.ok) throw new Error(payload.error || "Failed to load pull request"); return payload; } export async function mergePr(cwd: string, number: number, method: MergeMethod): Promise { const response = await fetch("/api/agent/pr/merge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ cwd, number, method }), }); const payload = await safeJson<{ ok?: boolean; error?: string }>(response); if (!response.ok || payload.error) throw new Error(payload.error || "Merge failed"); } export function PrSection({ pr, merging, mergeError, onMerge, }: { pr: PullRequest | null; merging: boolean; mergeError: string | null; onMerge: (method: MergeMethod) => Promise; }) { const [method, setMethod] = useState("merge"); if (!pr) return null; const mergeDisabled = merging || pr.state !== "OPEN" || pr.mergeable === "CONFLICTING"; return (
{pr.title} #{pr.number} {prStatusLabel(pr)}
{pr.headRefName || "—"} → {pr.baseRefName || "—"} +{pr.additions}{" "} −{pr.deletions}
{pr.mergeable === "CONFLICTING" ? (

This branch has conflicts that must be resolved.

) : null} {mergeError ?

{mergeError}

: null}
); } function ChecksLabel({ summary }: { summary: ChecksSummary }) { if (summary.total === 0) return null; const parts: string[] = []; if (summary.passing > 0) parts.push(`${summary.passing} passing`); if (summary.failing > 0) parts.push(`${summary.failing} failing`); if (summary.pending > 0) parts.push(`${summary.pending} pending`); return ( 0 ? "text-(--color-diff-removed)" : undefined}> {parts.join(", ")} ); } function prStatusLabel(pr: PullRequest): string { if (pr.state !== "OPEN") { return pr.state.charAt(0) + pr.state.slice(1).toLowerCase(); } return pr.isDraft ? "Draft" : "Ready for review"; }