"use client"; import { memo } from "react"; import { IconCheck, IconX, IconTerminal } from "@tabler/icons-react"; import { GridSpinner } from "@/components/grid-spinner"; import type { Message } from "@/lib/types/http"; import { stripAnsi } from "@/lib/utils/ansi"; import { Badge } from "@kandev/ui/badge"; import { ExpandableRow } from "./expandable-row"; import { useExpandState } from "./use-expand-state"; import { useTranslation } from "react-i18next"; import { isSuccessfulScriptExecutionMetadata } from "@/hooks/processed-message-filtering"; interface ScriptExecutionMetadata { script_type: "setup" | "cleanup" | "agent_boot"; agent_name?: string; command: string; status: "starting" | "running" | "exited" | "failed"; exit_code?: number; is_resuming?: boolean; started_at?: string; completed_at?: string; process_id?: string; error?: string; } // Returns the catalog key for the whole " agent " line rather than // a verb to concatenate: the word order is not English-universal. function getAgentBootMessageKey( isResuming: boolean | undefined, isSuccess: boolean, isRunning: boolean, ): string { if (isResuming) { if (isSuccess) return "task:agentBootResumed"; if (isRunning) return "task:agentBootResuming"; return "task:agentBootFailedToResume"; } if (isSuccess) return "task:agentBootStarted"; if (isRunning) return "task:agentBootStarting"; return "task:agentBootFailedToStart"; } function AgentBootHeader({ metadata, isSuccess, isRunning, }: { metadata: ScriptExecutionMetadata; isSuccess: boolean; isRunning: boolean; }) { const { t } = useTranslation(); const agentName = metadata.agent_name || t("task:agentFallbackName"); const messageKey = getAgentBootMessageKey(metadata.is_resuming, isSuccess, isRunning); return (
{t(messageKey, { agentName })} {isRunning && }
); } function ScriptHeader({ command, isSetup, isRunning, isSuccess, }: { command: string; isSetup: boolean; isRunning: boolean; isSuccess: boolean; }) { const { t } = useTranslation(); return (
{isSetup ? t("task:setup") : t("task:cleanup")} {command} {isRunning && } {isSuccess && !isRunning && } {!isSuccess && !isRunning && }
); } // Helper function to calculate duration function calculateDuration(startedAt: string, completedAt: string): string { try { const start = new Date(startedAt).getTime(); const end = new Date(completedAt).getTime(); const durationMs = end - start; if (durationMs < 1000) return `${durationMs}ms`; if (durationMs < 60000) return `${(durationMs / 1000).toFixed(1)}s`; const minutes = Math.floor(durationMs / 60000); const seconds = Math.floor((durationMs % 60000) / 1000); return `${minutes}m ${seconds}s`; } catch { return "N/A"; } } type ScriptFooterProps = { isAgentBoot: boolean; isRunning: boolean; metadata: ScriptExecutionMetadata; exitCode: number | undefined; }; function ScriptFooter({ isAgentBoot, isRunning, metadata, exitCode }: ScriptFooterProps) { const { t } = useTranslation(); if (isRunning) return null; if (isAgentBoot && metadata.started_at && metadata.completed_at) { return (
{t("task:durationValue", { duration: calculateDuration(metadata.started_at, metadata.completed_at), })}
); } if (!isAgentBoot && exitCode !== undefined) { return (
{t("task:exitCodeValue", { exitCode })} {metadata.started_at && metadata.completed_at && ( {t("task:durationValue", { duration: calculateDuration(metadata.started_at, metadata.completed_at), })} )}
); } return null; } type ScriptBodyProps = { isAgentBoot: boolean; command: string; content: string; error: string | undefined; isRunning: boolean; metadata: ScriptExecutionMetadata; exitCode: number | undefined; }; function ScriptExpandedContent({ isAgentBoot, command, content, error, isRunning, metadata, exitCode, }: ScriptBodyProps) { const { t } = useTranslation(); return (
{isAgentBoot && command && (
{t("task:command")}
            {command}
          
)} {content && content.trim() !== "" && (
{t("task:output")}
            {stripAnsi(content)}
          
)} {error && (
{t("task:error")}
{error}
)}
); } function parseScriptMetadata(comment: Message) { const metadata = comment.metadata as ScriptExecutionMetadata | undefined; const status = metadata?.status; const scriptType = metadata?.script_type; const isRunning = status === "starting" || status === "running"; const isSuccess = isSuccessfulScriptExecutionMetadata(metadata); return { metadata, status, scriptType, isRunning, isSuccess }; } export const ScriptExecutionMessage = memo(function ScriptExecutionMessage({ comment, }: { comment: Message; }) { const { t } = useTranslation(); const { metadata, status, scriptType, isRunning, isSuccess } = parseScriptMetadata(comment); const autoExpanded = isRunning; const { isExpanded, handleToggle } = useExpandState(status, autoExpanded); // Fallback for missing metadata if (!metadata || !scriptType) { const hasExpandableContent = !!comment.content; return ( } header={
{t("task:scriptExecutionMetadataUnavailable")}
} hasExpandableContent={hasExpandableContent} isExpanded={isExpanded} onToggle={handleToggle} > {comment.content && (
              {stripAnsi(comment.content)}
            
)}
); } const { command, exit_code, error } = metadata; const isAgentBoot = scriptType === "agent_boot"; const isSetup = scriptType === "setup"; const hasDuration = !!(metadata.started_at && metadata.completed_at); const hasExpandableContent = !!( command || comment.content || error || exit_code !== undefined || hasDuration ); return ( } header={ isAgentBoot ? ( ) : ( ) } hasExpandableContent={hasExpandableContent} isExpanded={isExpanded} onToggle={handleToggle} > ); });