import { AlertCircle, CheckCircle2, CircleDashed } from "lucide-react"; import { useTranslation } from "react-i18next"; import { FileReferenceChip } from "@/components/FileReferenceChip"; import type { UIFileEdit } from "@/lib/types"; import { cn } from "@/lib/utils"; import { ActivityStep } from "./ActivityStep"; import { DiffPair } from "./DiffPair"; export interface FileEditSummary { key: string; path: string; absolute_path?: string; added: number; deleted: number; approximate: boolean; binary: boolean; status: UIFileEdit["status"]; operation?: UIFileEdit["operation"]; pending: boolean; error?: string; } export function FileEditGroup({ edits }: { edits: FileEditSummary[] }) { if (edits.length === 0) return null; return ( ); } function FileEditRow({ edit }: { edit: FileEditSummary }) { const { t } = useTranslation(); const editing = edit.status === "editing"; const failed = edit.status === "error"; const hasCountedDiff = !failed && !edit.binary && hasVisibleDiffStats(edit); const failureDetail = failed ? formatFileEditError(edit.error) || t("message.fileEditFailedFallback", { defaultValue: "File change was not applied." }) : ""; const statusIcon = failed ? ( ) : editing ? ( ) : ( ); return ( {statusIcon} )} active={editing} tone={failed ? "error" : editing ? "active" : "success"} className="text-xs" contentClassName="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3" title={failureDetail || edit.absolute_path || edit.path} label={edit.pending && !edit.path ? t("message.fileEditPreparing", { defaultValue: "Preparing file edit…" }) : ( )} detail={failed ? ( {failureDetail} ) : null} aside={hasCountedDiff ? : null} /> ); } export function hasVisibleDiffStats(edit: Pick): boolean { return edit.added > 0 || edit.deleted > 0; } function formatFileEditError(error?: string): string { const firstLine = (error || "").replace(/\s+/g, " ").trim(); if (!firstLine) return ""; const cleaned = firstLine .replace(/^Error applying patch:\s*/i, "") .replace(/^Error writing file:\s*/i, "") .replace(/^Error editing file:\s*/i, "") .replace(/^Error:\s*/i, ""); return cleaned .replace(/^old_text not found in (.+)$/i, "Target text was not found in $1.") .replace(/^old_text appears multiple times in (.+)$/i, "Target text matched multiple places in $1.") .replace(/^file to (?:update|delete) does not exist: (.+)$/i, "File does not exist: $1.") .replace(/^path to (?:update|delete) is not a file: (.+)$/i, "Path is not a file: $1.") .slice(0, 180); }