"use client"; import { useState } from "react"; import { Terminal, FileCode, Loader2, CheckCircle, XCircle, ChevronDown, ChevronUp, MessageSquare, FileText, } from "lucide-react"; // Common props for all action types type BaseActionProps = { status: "loading" | "generating" | "done"; success?: boolean; reasoningText?: string; summaryText?: string; }; // Shell command specific props type ShellActionProps = BaseActionProps & { actionType: "shell"; command: string[]; workdir?: string; output?: string; errorCode?: number; }; // Apply patch specific props type PatchActionProps = BaseActionProps & { actionType: "apply-patch"; file: string; diff?: string; errorMessage?: string; fixedDiff?: string; }; // Union type for all possible action props export type ActionStepProps = | (BaseActionProps & { status: "loading" }) | ShellActionProps | PatchActionProps; export function ActionStep(props: ActionStepProps) { const [expanded, setExpanded] = useState(false); const [showReasoning, setShowReasoning] = useState(false); const [showSummary, setShowSummary] = useState(false); const getStatusIcon = () => { switch (props.status) { case "loading": return (
); case "generating": return ( ); case "done": return props.success ? ( ) : ( ); } }; const getStatusText = () => { if (props.status === "loading") { return "Preparing action..."; } if (props.status === "generating") { return props.actionType === "shell" ? "Executing..." : "Applying patch..."; } if (props.status === "done") { if (props.actionType === "shell") { return props.success ? "Command completed" : "Command failed"; } else if (props.actionType === "apply-patch") { return props.success ? "Patch applied" : "Patch failed"; } } return ""; }; // Determine if we should show the content toggle button const shouldShowToggle = () => { if (props.status !== "done") return false; if (props.actionType === "shell") { return !!props.output; } else if (props.actionType === "apply-patch") { return !!props.diff; } return false; }; // Render the header icon based on action type const renderHeaderIcon = () => { if (props.status === "loading" || !("actionType" in props)) { // In loading state, we don't know the type yet, use a generic icon return ; } return props.actionType === "shell" ? ( ) : ( ); }; // Render the header content based on action type const renderHeaderContent = () => { if (props.status === "loading" || !("actionType" in props)) { return ( Preparing action... ); } if (props.actionType === "shell") { return (
{props.workdir && (
{props.workdir}
)} {props.command.join(" ")}
); } else { return ( {props.file} ); } }; // Render the content based on action type const renderContent = () => { if (props.status !== "done" || !("actionType" in props)) return null; if (!expanded) return null; if (props.actionType === "shell" && props.output) { return (
            {props.output}
          
{props.errorCode !== undefined && !props.success && (
Exit code: {props.errorCode}
)}
); } else if (props.actionType === "apply-patch" && props.diff) { return (

          {!props.success && props.errorMessage && (
            
{props.errorMessage}
)} {!props.success && props.fixedDiff && (
Fixed diff:
            
)}
); } return null; }; return (
{props.reasoningText && (
{showReasoning && (

{props.reasoningText}

)}
)}
{renderHeaderIcon()} {renderHeaderContent()}
{getStatusText()} {getStatusIcon()} {shouldShowToggle() && ( )}
{renderContent()} {props.summaryText && props.status === "done" && (
{showSummary && (

{props.summaryText}

)}
)}
); } function formatDiff(diff: string) { return diff .split("\n") .map((line) => { if (line.startsWith("+")) { return `${line}`; } else if (line.startsWith("-")) { return `${line}`; } else if (line.startsWith("@")) { return `${line}`; } return line; }) .join("\n"); }