import { Play, CheckCircle2, XCircle, AlertTriangle, Zap, Activity, StopCircle, GitBranch, type LucideIcon, } from "lucide-react"; import { ScrollArea } from "@/components/ui/scroll-area"; import type { StreamEvent, StreamEventType } from "@/types/execution"; const eventConfig: Record = { run_start: { icon: Play, color: "text-blue-500" }, run_end: { icon: CheckCircle2, color: "text-green-500" }, agent_start: { icon: Play, color: "text-blue-400" }, agent_output: { icon: CheckCircle2, color: "text-green-400" }, agent_error: { icon: XCircle, color: "text-red-500" }, budget_warning: { icon: AlertTriangle, color: "text-yellow-500" }, budget_exceeded: { icon: AlertTriangle, color: "text-red-500" }, topology_changed: { icon: GitBranch, color: "text-purple-500" }, early_stop: { icon: StopCircle, color: "text-amber-500" }, prune: { icon: Activity, color: "text-orange-500" }, parallel_start: { icon: Zap, color: "text-indigo-500" }, parallel_end: { icon: Zap, color: "text-indigo-400" }, }; function formatTime(timestamp: string): string { try { const d = new Date(timestamp); return d.toLocaleTimeString([], { hour12: false } as Intl.DateTimeFormatOptions); } catch { return ""; } } function eventSummary(event: StreamEvent): string { const ev = event as Record; switch (event.event_type) { case "run_start": return `Run started: ${event.num_agents ?? 0} agents`; case "run_end": return `Run ${event.success ? "completed" : "failed"} in ${(event.total_time ?? 0).toFixed(1)}s (${event.total_tokens ?? 0} tokens)`; case "agent_start": return `${event.agent_name || event.agent_id} started`; case "agent_output": { const preview = (event.content || "").slice(0, 80); return `${event.agent_name || event.agent_id}: ${preview}${(event.content?.length ?? 0) > 80 ? "..." : ""}`; } case "agent_error": return `${event.agent_id} error: ${event.error_message}`; case "topology_changed": { const parts: string[] = ["Topology modified"]; if (ev.added_edges) parts.push(`+${ev.added_edges} edges`); if (ev.removed_edges) parts.push(`-${ev.removed_edges} edges`); if (ev.skipped_agents?.length) parts.push(`skipped: ${ev.skipped_agents.join(", ")}`); if (ev.forced_agents?.length) parts.push(`forced: ${ev.forced_agents.join(", ")}`); return parts.join(" | "); } case "early_stop": return `Early stopped: ${ev.reason || event.content || "condition met"}`; case "prune": return `Pruned: ${ev.agent_id || ev.agents?.join(", ") || "agents"}`; case "parallel_start": return `Parallel: ${event.agent_ids?.join(", ")}`; case "error": return `Error: ${event.error || "Unknown"}`; case "cancelled": return "Execution cancelled"; default: return event.event_type; } } interface ExecutionTimelineProps { events: StreamEvent[]; } export function ExecutionTimeline({ events }: ExecutionTimelineProps) { const filtered = events.filter( (e) => e.event_type !== "token" && e.event_type !== "memory_read" && e.event_type !== "memory_write" ); return (
{filtered.length === 0 && (

No events yet. Execute a workflow to see events here.

)} {filtered.map((event, i) => { const cfg = eventConfig[event.event_type] || { icon: Activity, color: "text-muted-foreground" }; const Icon = cfg.icon; return (
{formatTime(event.timestamp)} {eventSummary(event)}
); })}
); }