"use client"; import { useMemo } from "react"; type ToolInvocation = { tool: string; startTime: number; endTime: number; latency: number; status: "success" | "error"; resultCount?: number; }; type ToolTimelineProps = { toolTraces?: Array>; reasoningTrace?: Array>; }; export function ToolTimeline({ toolTraces = [], reasoningTrace = [] }: ToolTimelineProps) { const timelineData = useMemo(() => { if (!toolTraces || toolTraces.length === 0) { return []; } // Extract tool invocations from traces const invocations: ToolInvocation[] = toolTraces.map((trace, idx) => { const tool = trace.tool || trace.tool_name || "unknown"; const startTime = trace.start_time || trace.timestamp || idx * 100; // Fallback timing const latency = trace.latency_ms || trace.latency || 0; const endTime = startTime + latency; const status = trace.status === "error" || trace.error ? "error" : "success"; const resultCount = trace.result_count || trace.hits || trace.results?.length || 0; return { tool, startTime, endTime, latency, status, resultCount, }; }); // Sort by start time invocations.sort((a, b) => a.startTime - b.startTime); // Calculate timeline bounds const minTime = Math.min(...invocations.map((i) => i.startTime), 0); const maxTime = Math.max(...invocations.map((i) => i.endTime), minTime + 1000); // Normalize times to 0-100% for visualization const timeRange = maxTime - minTime || 1000; return invocations.map((inv) => ({ ...inv, startPercent: ((inv.startTime - minTime) / timeRange) * 100, widthPercent: (inv.latency / timeRange) * 100, })); }, [toolTraces]); if (timelineData.length === 0) { return (

No tool invocations yet. Tools will appear here as they are executed.

); } const totalLatency = timelineData.reduce((sum, inv) => sum + inv.latency, 0); const maxLatency = Math.max(...timelineData.map((inv) => inv.latency), 0); return (

Tool Invocation Timeline

{timelineData.length} tools ·{" "} {totalLatency}ms total
{/* Timeline visualization */}
{/* Time axis */}
0ms {maxLatency}ms
{/* Tool bars */}
{timelineData.map((inv, idx) => { const height = 24; const top = idx * (height + 8); const color = inv.status === "error" ? "bg-red-500/60 border-red-400" : "bg-cyan-500/60 border-cyan-400"; return (
{/* Tool label */}
{inv.tool}
{/* Timeline bar */}
{/* Latency label */} {inv.widthPercent > 5 && ( {inv.latency}ms )} {/* Result count badge */} {inv.resultCount > 0 && inv.widthPercent > 8 && ( {inv.resultCount > 9 ? "9+" : inv.resultCount} )} {/* Error indicator */} {inv.status === "error" && ( ⚠️ )}
); })}
{/* Tool list */}
{timelineData.map((inv, idx) => (
{inv.tool} {inv.resultCount > 0 && ( {inv.resultCount} result{inv.resultCount !== 1 ? "s" : ""} )}
{inv.latency}ms {inv.status === "error" && ( Error )}
))}
{/* Summary stats */}

Total Tools

{timelineData.length}

Total Time

{totalLatency}ms

Avg Latency

{Math.round(totalLatency / timelineData.length)}ms

); }