import { useState } from "react";
import { IconFilePlus, IconFileMinus, IconFileX, IconArrowBackUp, IconCheck, IconGitCompare } from "@tabler/icons-react";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useChatStore } from "@/stores";
import { bridge } from "@/services";
import { cn } from "@/lib/utils";
import { FileChange } from "shared/types";
import { toast } from "./ui/sonner";
const STATUS_CONFIG = {
Added: { icon: IconFilePlus, color: "text-green-600 dark:text-green-400" },
Deleted: { icon: IconFileX, color: "text-red-600 dark:text-red-400" },
Modified: { icon: IconFileMinus, color: "text-yellow-600 dark:text-yellow-400" },
} as const;
function getTotalStats(changes: FileChange[]) {
return changes.reduce(
(a, c) => ({
additions: a.additions + c.additions,
deletions: a.deletions + c.deletions,
}),
{ additions: 0, deletions: 0 },
);
}
interface FileItemProps {
file: FileChange;
onRevert: () => void;
onKeep: () => void;
onViewDiff: () => void;
disabled: boolean;
isStreaming?: boolean;
}
function FileItem({ file, onRevert, onKeep, onViewDiff, disabled, isStreaming }: FileItemProps) {
const { icon: Icon, color } = STATUS_CONFIG[file.status];
const name = file.path.split("/").pop() || file.path;
const dir = file.path.includes("/") ? file.path.slice(0, file.path.lastIndexOf("/")) : "";
return (
{name}
{dir && {dir}}
View Changes
{!isStreaming && (
<>
Undo Changes
Keep Changes
>
)}
+{file.additions}
-{file.deletions}
);
}
interface FileChangesPanelProps {
changes: FileChange[];
}
export function FileChangesPanel({ changes }: FileChangesPanelProps) {
const { isStreaming } = useChatStore();
const [loading, setLoading] = useState(false);
const handleRevert = async (filePath?: string) => {
setLoading(true);
try {
await bridge.revertFiles(filePath);
} catch (error) {
toast.error(`Unable to undo changes: ${error instanceof Error ? error.message : String(error)}`);
} finally {
setLoading(false);
}
};
const handleKeep = async (filePath?: string) => {
setLoading(true);
try {
await bridge.keepChanges(filePath);
} catch (error) {
toast.error(`Unable to keep changes: ${error instanceof Error ? error.message : String(error)}`);
} finally {
setLoading(false);
}
};
const stats = getTotalStats(changes);
if (!changes.length) {
return No file changes
;
}
return (
{/* Header with actions */}
{changes.length} file{changes.length !== 1 ? "s" : ""}
+{stats.additions}
-{stats.deletions}
{!isStreaming && (
)}
{/* File list */}
{changes.map((file) => (
{
void handleRevert(file.path);
}}
onKeep={() => {
void handleKeep(file.path);
}}
onViewDiff={() => {
void bridge.openFileDiff(file.path);
}}
disabled={loading}
isStreaming={isStreaming}
/>
))}
);
}