"use client"; import { AnimatePresence, motion } from "framer-motion"; import { Activity, AlertTriangle, Gauge, History, Repeat2, RotateCcw, ShieldCheck, TimerReset, Workflow } from "lucide-react"; import { StepControls } from "@/components/visualizations/shared/step-controls"; import { useSteppedVisualization } from "@/hooks/useSteppedVisualization"; import { cn } from "@/lib/utils"; const STEPS = [ { title: "Normal Call Still Comes First", desc: "The runtime starts with a regular LLM call and only enters recovery when a specific failure appears.", mode: "normal", }, { title: "max_tokens Means Output Was Cut Off", desc: "First recovery is to retry with a larger budget before adding any synthetic continuation message.", mode: "max-tokens", }, { title: "prompt_too_long Means Context Must Shrink", desc: "The runtime performs reactive compact once, then retries the same task with a smaller message list.", mode: "prompt-too-long", }, { title: "429 Means Wait, Then Retry", desc: "Rate limits use exponential backoff with jitter so retries do not stampede the provider.", mode: "rate-limit", }, { title: "Repeated 529 Can Switch Models", desc: "Provider overload increments RecoveryState and can move to a fallback model after repeated failures.", mode: "overloaded", }, { title: "Recovered Calls Return to the Loop", desc: "Each recovery path is bounded, inspectable, and eventually returns to the normal tool loop or exits cleanly.", mode: "summary", }, ] as const; const CASES = [ { id: "max-tokens", label: "max_tokens", symptom: "model stopped mid-answer", action: "8K -> 64K, retry same request", state: "token escalated once", tone: "amber", }, { id: "prompt-too-long", label: "prompt_too_long", symptom: "context too large", action: "reactive_compact(messages), retry once", state: "compact retry used", tone: "orange", }, { id: "rate-limit", label: "429", symptom: "rate limited", action: "backoff + jitter, max 10 retries", state: "retry attempt counted", tone: "blue", }, { id: "overloaded", label: "529", symptom: "provider overloaded", action: "backoff; 3 consecutive -> fallback model", state: "consecutive_529 tracked", tone: "red", }, ] as const; type StepMode = (typeof STEPS)[number]["mode"]; type CaseId = (typeof CASES)[number]["id"]; type Tone = "amber" | "orange" | "blue" | "red" | "emerald" | "zinc"; function toneClass(tone: Tone, active = true) { if (!active) return "border-zinc-200 bg-white text-zinc-700 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200"; if (tone === "amber") return "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-200"; if (tone === "orange") return "border-orange-200 bg-orange-50 text-orange-800 dark:border-orange-900 dark:bg-orange-950/40 dark:text-orange-200"; if (tone === "blue") return "border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-900 dark:bg-blue-950/40 dark:text-blue-200"; if (tone === "red") return "border-red-200 bg-red-50 text-red-800 dark:border-red-900 dark:bg-red-950/40 dark:text-red-200"; if (tone === "emerald") return "border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-200"; return "border-zinc-200 bg-zinc-50 text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200"; } function activeCase(mode: StepMode): CaseId | null { if (mode === "max-tokens") return "max-tokens"; if (mode === "prompt-too-long") return "prompt-too-long"; if (mode === "rate-limit") return "rate-limit"; if (mode === "overloaded") return "overloaded"; return null; } function Surface({ title, icon, active, children, }: { title: string; icon: React.ReactNode; active: boolean; children: React.ReactNode; }) { return (
{value}