"use client"; import { useEffect, useState } from "react"; type ReasoningStep = { step: string; status: "pending" | "running" | "completed" | "error"; message?: string; details?: Record; timestamp?: number; }; type ReasoningVisualizerProps = { reasoningTrace?: Array>; isActive?: boolean; onComplete?: () => void; }; const STEP_ICONS: Record = { request_received: "📥", admin_rules_check: "🛡️", intent_detection: "🧠", rag_prefetch: "📚", tool_scoring: "📊", tool_selection: "🎯", tool_execution: "⚙️", llm_response: "💬", result_merger: "🔀", parallel_execution: "⚡", error: "❌", }; const STEP_LABELS: Record = { request_received: "Request Received", admin_rules_check: "Checking Admin Rules", intent_detection: "Detecting Intent", rag_prefetch: "Pre-fetching RAG Results", tool_scoring: "Scoring Tools", tool_selection: "Selecting Tools", tool_execution: "Executing Tools", llm_response: "Generating Response", result_merger: "Merging Results", parallel_execution: "Parallel Execution", error: "Error", }; export function ReasoningVisualizer({ reasoningTrace = [], isActive = false, onComplete, }: ReasoningVisualizerProps) { const [steps, setSteps] = useState([]); const [currentStepIndex, setCurrentStepIndex] = useState(0); useEffect(() => { if (!reasoningTrace || reasoningTrace.length === 0) { setSteps([]); setCurrentStepIndex(0); return; } // Convert reasoning trace to visual steps const visualSteps: ReasoningStep[] = reasoningTrace.map((trace, idx) => { const stepName = trace.step || "unknown"; const icon = STEP_ICONS[stepName] || "⚙️"; const label = STEP_LABELS[stepName] || stepName.replace(/_/g, " "); // Build message from trace data let message = label; const details: Record = {}; if (stepName === "admin_rules_check") { const matchCount = trace.match_count || 0; message = matchCount > 0 ? `Found ${matchCount} rule violation(s)` : "No violations found"; details.matches = trace.matches || []; } else if (stepName === "intent_detection") { message = `Intent: ${trace.intent || "unknown"}`; details.intent = trace.intent; } else if (stepName === "rag_prefetch") { const hitCount = trace.hit_count || 0; message = hitCount > 0 ? `Found ${hitCount} relevant document(s)` : "No documents found"; details.hit_count = hitCount; details.latency_ms = trace.latency_ms; } else if (stepName === "tool_selection") { const decision = trace.decision; if (decision) { message = `Selected: ${decision.tool || "llm"} (${decision.action})`; details.decision = decision; } } else if (stepName === "tool_execution") { const tool = trace.tool || "unknown"; const hitCount = trace.hit_count || 0; message = `${tool.toUpperCase()}: ${hitCount} result(s)`; details.tool = tool; details.hit_count = hitCount; } else if (stepName === "result_merger") { const mergedItems = trace.merged_items || 0; message = `Merged ${mergedItems} result(s)`; details.merged_items = mergedItems; details.sources = trace.sources || []; } else if (stepName === "llm_response") { message = "Generating final response"; details.latency_ms = trace.latency_ms; details.estimated_tokens = trace.estimated_tokens; } return { step: stepName, status: idx < currentStepIndex ? "completed" : idx === currentStepIndex ? "running" : "pending", message, details, timestamp: Date.now(), }; }); setSteps(visualSteps); // Animate through steps if active if (isActive && visualSteps.length > 0) { const interval = setInterval(() => { setCurrentStepIndex((prev) => { if (prev < visualSteps.length - 1) { return prev + 1; } else { clearInterval(interval); if (onComplete) onComplete(); return prev; } }); }, 800); // 800ms per step return () => clearInterval(interval); } else if (!isActive && visualSteps.length > 0) { // Show all steps as completed if not active setCurrentStepIndex(visualSteps.length); } }, [reasoningTrace, isActive, currentStepIndex, onComplete]); if (steps.length === 0) { return (

No reasoning trace available. Send a message to see the agent's reasoning path.

); } return (

Real-Time Reasoning Path

{steps.length} steps
{steps.map((step, idx) => { const isCompleted = step.status === "completed"; const isRunning = step.status === "running"; const isPending = step.status === "pending"; return (
{/* Step number and icon */}
{isRunning ? ( ) : isCompleted ? ( "✓" ) : ( idx + 1 )}
{/* Step content */}
{STEP_ICONS[step.step] || "⚙️"}

{STEP_LABELS[step.step] || step.step.replace(/_/g, " ")}

{isRunning && ( Running... )}

{step.message}

{/* Step details */} {step.details && Object.keys(step.details).length > 0 && isCompleted && (
{step.details.latency_ms && ( ⏱️ {step.details.latency_ms}ms )} {step.details.hit_count !== undefined && ( 📊 {step.details.hit_count} hits )} {step.details.estimated_tokens && ( 🔢 ~{step.details.estimated_tokens} tokens )} {step.details.score && ( ⭐ Score: {step.details.score.toFixed(2)} )}
)}
{/* Connecting line */} {idx < steps.length - 1 && (
)}
); })}
); }