import React, { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { ArrowLeft, Copy, ChevronDown, Clock, Hash, DollarSign, Bot } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { Popover } from "@/components/ui/popover"; import { api, type AgentRunWithMetrics } from "@/lib/api"; import { cn } from "@/lib/utils"; import { formatISOTimestamp } from "@/lib/date-utils"; import { StreamMessage } from "./StreamMessage"; import { AGENT_ICONS } from "./CCAgents"; import type { ClaudeStreamMessage } from "./AgentExecution"; import { ErrorBoundary } from "./ErrorBoundary"; interface AgentRunViewProps { /** * The run ID to view */ runId: number; /** * Callback to go back */ onBack: () => void; /** * Optional className for styling */ className?: string; } /** * AgentRunView component for viewing past agent execution details * * @example * setView('list')} /> */ export const AgentRunView: React.FC = ({ runId, onBack, className, }) => { const [run, setRun] = useState(null); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [copyPopoverOpen, setCopyPopoverOpen] = useState(false); useEffect(() => { loadRun(); }, [runId]); const loadRun = async () => { try { setLoading(true); setError(null); const runData = await api.getAgentRunWithRealTimeMetrics(runId); setRun(runData); // Parse JSONL output into messages if (runData.output) { const parsedMessages: ClaudeStreamMessage[] = []; const lines = runData.output.split('\n').filter(line => line.trim()); for (const line of lines) { try { const msg = JSON.parse(line) as ClaudeStreamMessage; parsedMessages.push(msg); } catch (err) { console.error("Failed to parse line:", line, err); } } setMessages(parsedMessages); } } catch (err) { console.error("Failed to load run:", err); setError("Failed to load execution details"); } finally { setLoading(false); } }; const handleCopyAsJsonl = async () => { if (!run?.output) return; await navigator.clipboard.writeText(run.output); setCopyPopoverOpen(false); }; const handleCopyAsMarkdown = async () => { if (!run) return; let markdown = `# Agent Execution: ${run.agent_name}\n\n`; markdown += `**Task:** ${run.task}\n`; markdown += `**Model:** ${run.model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}\n`; markdown += `**Date:** ${formatISOTimestamp(run.created_at)}\n`; if (run.metrics?.duration_ms) markdown += `**Duration:** ${(run.metrics.duration_ms / 1000).toFixed(2)}s\n`; if (run.metrics?.total_tokens) markdown += `**Total Tokens:** ${run.metrics.total_tokens}\n`; if (run.metrics?.cost_usd) markdown += `**Cost:** $${run.metrics.cost_usd.toFixed(4)} USD\n`; markdown += `\n---\n\n`; for (const msg of messages) { if (msg.type === "system" && msg.subtype === "init") { markdown += `## System Initialization\n\n`; markdown += `- Session ID: \`${msg.session_id || 'N/A'}\`\n`; markdown += `- Model: \`${msg.model || 'default'}\`\n`; if (msg.cwd) markdown += `- Working Directory: \`${msg.cwd}\`\n`; if (msg.tools?.length) markdown += `- Tools: ${msg.tools.join(', ')}\n`; markdown += `\n`; } else if (msg.type === "assistant" && msg.message) { markdown += `## Assistant\n\n`; for (const content of msg.message.content || []) { if (content.type === "text") { markdown += `${content.text}\n\n`; } else if (content.type === "tool_use") { markdown += `### Tool: ${content.name}\n\n`; markdown += `\`\`\`json\n${JSON.stringify(content.input, null, 2)}\n\`\`\`\n\n`; } } if (msg.message.usage) { markdown += `*Tokens: ${msg.message.usage.input_tokens} in, ${msg.message.usage.output_tokens} out*\n\n`; } } else if (msg.type === "user" && msg.message) { markdown += `## User\n\n`; for (const content of msg.message.content || []) { if (content.type === "text") { markdown += `${content.text}\n\n`; } else if (content.type === "tool_result") { markdown += `### Tool Result\n\n`; markdown += `\`\`\`\n${content.content}\n\`\`\`\n\n`; } } } else if (msg.type === "result") { markdown += `## Execution Result\n\n`; if (msg.result) { markdown += `${msg.result}\n\n`; } if (msg.error) { markdown += `**Error:** ${msg.error}\n\n`; } } } await navigator.clipboard.writeText(markdown); setCopyPopoverOpen(false); }; const renderIcon = (iconName: string) => { const Icon = AGENT_ICONS[iconName as keyof typeof AGENT_ICONS] || Bot; return ; }; if (loading) { return (
); } if (error || !run) { return (

{error || "Run not found"}

); } return (
{/* Header */}
{renderIcon(run.agent_icon)}

{run.agent_name}

Execution History

Copy Output } content={
} open={copyPopoverOpen} onOpenChange={setCopyPopoverOpen} align="end" />
{/* Run Details */}

Task:

{run.task}

{run.model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}
{formatISOTimestamp(run.created_at)}
{run.metrics?.duration_ms && (
{(run.metrics.duration_ms / 1000).toFixed(2)}s
)} {run.metrics?.total_tokens && (
{run.metrics.total_tokens} tokens
)} {run.metrics?.cost_usd && (
${run.metrics.cost_usd.toFixed(4)}
)}
{/* Output Display */}
{messages.map((message, index) => ( ))}
); };