{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stream-debugger",
  "title": "AI Stream Debugger",
  "description": "Real-time streaming chunk visualization for AI responses with auto-scroll, pause/resume, chunk type indicators, and timestamps for debugging SSE streams.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-stream-debugger/components/elements/ai-stream-debugger.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  Activity,\n  AlertCircle,\n  CheckCircle,\n  Flag,\n  MessageSquare,\n  Pause,\n  Play,\n  Trash2,\n  Wrench,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype ChunkType = \"text\" | \"tool_call\" | \"tool_result\" | \"error\" | \"finish\";\n\ninterface StreamChunk {\n  id: string;\n  type: ChunkType;\n  content: string;\n  timestamp: Date;\n}\n\ninterface AiStreamDebuggerContextValue {\n  chunks: StreamChunk[];\n  isStreaming: boolean;\n  isPaused: boolean;\n  autoScroll: boolean;\n  setIsPaused: (paused: boolean) => void;\n  clearChunks?: () => void;\n}\n\nconst AiStreamDebuggerContext =\n  React.createContext<AiStreamDebuggerContextValue | null>(null);\n\nfunction useStreamDebuggerContext() {\n  const context = React.useContext(AiStreamDebuggerContext);\n  if (!context) {\n    throw new Error(\n      \"AiStreamDebugger components must be used within <AiStreamDebugger>\",\n    );\n  }\n  return context;\n}\n\ninterface AiStreamDebuggerProps {\n  chunks: StreamChunk[];\n  isStreaming?: boolean;\n  autoScroll?: boolean;\n  maxChunks?: number;\n  onClear?: () => void;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiStreamDebugger({\n  chunks,\n  isStreaming = false,\n  autoScroll = true,\n  maxChunks = 100,\n  onClear,\n  children,\n  className,\n}: AiStreamDebuggerProps) {\n  const [isPaused, setIsPaused] = React.useState(false);\n\n  const displayedChunks = React.useMemo(() => {\n    const sliced = chunks.slice(-maxChunks);\n    return sliced;\n  }, [chunks, maxChunks]);\n\n  const contextValue = React.useMemo(\n    () => ({\n      chunks: displayedChunks,\n      isStreaming,\n      isPaused,\n      autoScroll,\n      setIsPaused,\n      clearChunks: onClear,\n    }),\n    [displayedChunks, isStreaming, isPaused, autoScroll, onClear],\n  );\n\n  return (\n    <AiStreamDebuggerContext.Provider value={contextValue}>\n      <div\n        data-slot=\"ai-stream-debugger\"\n        className={cn(\n          \"rounded-lg border border-border bg-card text-card-foreground overflow-hidden flex flex-col\",\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </AiStreamDebuggerContext.Provider>\n  );\n}\n\ninterface AiStreamDebuggerHeaderProps {\n  title?: string;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiStreamDebuggerHeader({\n  title = \"Stream Debugger\",\n  children,\n  className,\n}: AiStreamDebuggerHeaderProps) {\n  const { chunks, isStreaming, isPaused, setIsPaused, clearChunks } =\n    useStreamDebuggerContext();\n\n  return (\n    <div\n      data-slot=\"ai-stream-debugger-header\"\n      className={cn(\n        \"flex items-center gap-3 px-4 py-3 border-b border-border bg-muted/30\",\n        className,\n      )}\n    >\n      <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-muted\">\n        <Activity\n          className={cn(\n            \"size-4\",\n            isStreaming && !isPaused\n              ? \"text-green-500 animate-pulse\"\n              : \"text-muted-foreground\",\n          )}\n        />\n      </div>\n      <div className=\"flex flex-1 items-center gap-2\">\n        <span className=\"font-medium text-sm\">{title}</span>\n        {isStreaming && (\n          <span\n            className={cn(\n              \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium\",\n              isPaused\n                ? \"bg-yellow-100 text-yellow-700 dark:bg-yellow-950 dark:text-yellow-300\"\n                : \"bg-green-100 text-green-700 dark:bg-green-950 dark:text-green-300\",\n            )}\n          >\n            <span className=\"relative flex size-2\">\n              {!isPaused && (\n                <span className=\"absolute inline-flex size-full animate-ping rounded-full bg-green-400 opacity-75\" />\n              )}\n              <span\n                className={cn(\n                  \"relative inline-flex size-2 rounded-full\",\n                  isPaused ? \"bg-yellow-500\" : \"bg-green-500\",\n                )}\n              />\n            </span>\n            {isPaused ? \"Paused\" : \"Live\"}\n          </span>\n        )}\n        <span className=\"text-xs text-muted-foreground\">\n          {chunks.length} chunks\n        </span>\n      </div>\n      <div className=\"flex items-center gap-1\">\n        {isStreaming && (\n          <button\n            type=\"button\"\n            onClick={() => setIsPaused(!isPaused)}\n            className=\"inline-flex items-center justify-center size-8 rounded-md transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n            title={isPaused ? \"Resume\" : \"Pause\"}\n          >\n            {isPaused ? (\n              <Play className=\"size-4 text-muted-foreground\" />\n            ) : (\n              <Pause className=\"size-4 text-muted-foreground\" />\n            )}\n          </button>\n        )}\n        {clearChunks && (\n          <button\n            type=\"button\"\n            onClick={clearChunks}\n            className=\"inline-flex items-center justify-center size-8 rounded-md transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n            title=\"Clear\"\n          >\n            <Trash2 className=\"size-4 text-muted-foreground\" />\n          </button>\n        )}\n        {children}\n      </div>\n    </div>\n  );\n}\n\ninterface AiStreamDebuggerContentProps {\n  className?: string;\n}\n\nfunction AiStreamDebuggerContent({ className }: AiStreamDebuggerContentProps) {\n  const { chunks, isPaused, autoScroll } = useStreamDebuggerContext();\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const shouldAutoScroll = autoScroll && !isPaused;\n\n  React.useEffect(() => {\n    if (shouldAutoScroll && containerRef.current) {\n      containerRef.current.scrollTop = containerRef.current.scrollHeight;\n    }\n  }, [shouldAutoScroll]);\n\n  if (chunks.length === 0) {\n    return (\n      <div\n        data-slot=\"ai-stream-debugger-content\"\n        className={cn(\n          \"flex-1 flex items-center justify-center p-8 text-muted-foreground\",\n          className,\n        )}\n      >\n        <div className=\"text-center\">\n          <Activity className=\"mx-auto size-8 opacity-50\" />\n          <p className=\"mt-2 text-sm\">Waiting for stream data...</p>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"ai-stream-debugger-content\"\n      className={cn(\"flex-1 overflow-y-auto min-h-0 max-h-80\", className)}\n    >\n      <div className=\"divide-y divide-border\">\n        {chunks.map((chunk) => (\n          <AiStreamDebuggerChunk key={chunk.id} chunk={chunk} />\n        ))}\n      </div>\n    </div>\n  );\n}\n\ninterface AiStreamDebuggerChunkProps {\n  chunk: StreamChunk;\n  className?: string;\n}\n\nfunction AiStreamDebuggerChunk({\n  chunk,\n  className,\n}: AiStreamDebuggerChunkProps) {\n  const typeConfig = React.useMemo(() => {\n    const configs: Record<\n      ChunkType,\n      { icon: React.ReactNode; label: string; className: string }\n    > = {\n      text: {\n        icon: <MessageSquare className=\"size-3.5\" />,\n        label: \"text\",\n        className:\n          \"bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300\",\n      },\n      tool_call: {\n        icon: <Wrench className=\"size-3.5\" />,\n        label: \"tool_call\",\n        className:\n          \"bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300\",\n      },\n      tool_result: {\n        icon: <CheckCircle className=\"size-3.5\" />,\n        label: \"tool_result\",\n        className:\n          \"bg-green-100 text-green-700 dark:bg-green-950 dark:text-green-300\",\n      },\n      error: {\n        icon: <AlertCircle className=\"size-3.5\" />,\n        label: \"error\",\n        className: \"bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300\",\n      },\n      finish: {\n        icon: <Flag className=\"size-3.5\" />,\n        label: \"finish\",\n        className: \"bg-muted text-muted-foreground\",\n      },\n    };\n    return configs[chunk.type];\n  }, [chunk.type]);\n\n  const formattedTime = React.useMemo(() => {\n    return chunk.timestamp.toLocaleTimeString(undefined, {\n      hour: \"2-digit\",\n      minute: \"2-digit\",\n      second: \"2-digit\",\n      fractionalSecondDigits: 3,\n    });\n  }, [chunk.timestamp]);\n\n  return (\n    <div\n      data-slot=\"ai-stream-debugger-chunk\"\n      data-type={chunk.type}\n      className={cn(\n        \"flex items-start gap-3 px-4 py-2 text-sm\",\n        chunk.type === \"error\" && \"bg-red-50/50 dark:bg-red-950/20\",\n        className,\n      )}\n    >\n      <span className=\"shrink-0 text-xs font-mono text-muted-foreground pt-0.5\">\n        {formattedTime}\n      </span>\n      <span\n        className={cn(\n          \"inline-flex items-center gap-1 shrink-0 rounded px-1.5 py-0.5 text-xs font-medium\",\n          typeConfig.className,\n        )}\n      >\n        {typeConfig.icon}\n        {typeConfig.label}\n      </span>\n      <span className=\"flex-1 font-mono text-xs text-foreground whitespace-pre-wrap break-all\">\n        {chunk.content}\n      </span>\n    </div>\n  );\n}\n\nexport {\n  AiStreamDebugger,\n  AiStreamDebuggerHeader,\n  AiStreamDebuggerContent,\n  AiStreamDebuggerChunk,\n};\nexport type { AiStreamDebuggerProps, StreamChunk, ChunkType };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Compound component for debugging AI streaming responses. Chunk types: text, tool_call, tool_result, error, finish. Features auto-scroll with pause/resume, clear button, live/paused status indicator, configurable max chunks. Each chunk shows millisecond-precision timestamps.",
  "categories": [
    "ai"
  ],
  "type": "registry:ui"
}