'use client'; import { useState, memo } from 'react'; import { IconCheck, IconX, IconTerminal, IconChevronDown, IconChevronRight } from '@tabler/icons-react'; import { GridSpinner } from '@/components/grid-spinner'; import { cn } from '@/lib/utils'; import type { Message } from '@/lib/types/http'; import { Badge } from '@kandev/ui/badge'; interface ScriptExecutionMetadata { script_type: 'setup' | 'cleanup'; command: string; status: 'starting' | 'running' | 'exited' | 'failed'; exit_code?: number; started_at?: string; completed_at?: string; process_id?: string; error?: string; } export const ScriptExecutionMessage = memo(function ScriptExecutionMessage({ comment }: { comment: Message }) { const [isExpanded, setIsExpanded] = useState(false); const metadata = comment.metadata as ScriptExecutionMetadata | undefined; // Show fallback if metadata is missing or incomplete if (!metadata || !metadata.script_type) { const hasExpandableContent = !!comment.content; return (
{ if (hasExpandableContent) setIsExpanded(!isExpanded); }} >
{hasExpandableContent && ( isExpanded ? : )}
Script Execution (metadata unavailable)
{isExpanded && comment.content && (
                  {comment.content}
                
)}
); } const { script_type, command, status, exit_code, error } = metadata; const isSetup = script_type === 'setup'; const isRunning = status === 'starting' || status === 'running'; const isSuccess = status === 'exited' && exit_code === 0; const StatusIcon = isSuccess ? IconCheck : IconX; const statusColor = isSuccess ? 'text-green-500' : 'text-red-500'; const hasExpandableContent = !!(comment.content || error || exit_code !== undefined); return (
{ if (hasExpandableContent) setIsExpanded(!isExpanded); }} > {/* Icon with hover-to-show chevron */}
{hasExpandableContent && ( isExpanded ? : )}
{/* Content */}
{isSetup ? 'Setup' : 'Cleanup'} {command} {isRunning && } {!isSuccess && !isRunning && ( )}
{/* Expanded Details */} {isExpanded && hasExpandableContent && (
{/* Output */} {comment.content && comment.content.trim() !== '' && (
Output
                    {comment.content}
                  
)} {/* Error message */} {error && (
Error
{error}
)} {/* Footer with exit code */} {!isRunning && exit_code !== undefined && (
Exit code: {exit_code} {metadata.started_at && metadata.completed_at && ( Duration: {calculateDuration(metadata.started_at, metadata.completed_at)} )}
)}
)}
); }); // 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`; } else if (durationMs < 60000) { return `${(durationMs / 1000).toFixed(1)}s`; } else { const minutes = Math.floor(durationMs / 60000); const seconds = Math.floor((durationMs % 60000) / 1000); return `${minutes}m ${seconds}s`; } } catch { return 'N/A'; } }