{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "token-viewer",
  "title": "AI Token Viewer",
  "description": "Token usage visualization with input/output/total counts, cost estimation for popular models, and interactive token breakdown with hover-to-inspect boundaries.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-token-viewer/components/elements/ai-token-viewer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { ArrowDown, ArrowUp, DollarSign, Hash } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype TokenType = \"text\" | \"special\" | \"control\";\n\ninterface Token {\n  text: string;\n  id?: number;\n  type?: TokenType;\n}\n\ninterface ModelPricing {\n  inputCostPer1k: number;\n  outputCostPer1k: number;\n}\n\nconst MODEL_PRICING: Record<string, ModelPricing> = {\n  \"gpt-4\": { inputCostPer1k: 0.03, outputCostPer1k: 0.06 },\n  \"gpt-4-turbo\": { inputCostPer1k: 0.01, outputCostPer1k: 0.03 },\n  \"gpt-4o\": { inputCostPer1k: 0.005, outputCostPer1k: 0.015 },\n  \"gpt-4o-mini\": { inputCostPer1k: 0.00015, outputCostPer1k: 0.0006 },\n  \"gpt-3.5-turbo\": { inputCostPer1k: 0.0005, outputCostPer1k: 0.0015 },\n  \"claude-3-opus\": { inputCostPer1k: 0.015, outputCostPer1k: 0.075 },\n  \"claude-3-sonnet\": { inputCostPer1k: 0.003, outputCostPer1k: 0.015 },\n  \"claude-3-haiku\": { inputCostPer1k: 0.00025, outputCostPer1k: 0.00125 },\n  \"claude-3.5-sonnet\": { inputCostPer1k: 0.003, outputCostPer1k: 0.015 },\n  \"claude-opus-4\": { inputCostPer1k: 0.015, outputCostPer1k: 0.075 },\n  \"claude-sonnet-4\": { inputCostPer1k: 0.003, outputCostPer1k: 0.015 },\n};\n\ninterface AiTokenViewerContextValue {\n  tokens?: Token[];\n  inputTokens?: number;\n  outputTokens?: number;\n  model?: string;\n  showCost: boolean;\n}\n\nconst AiTokenViewerContext =\n  React.createContext<AiTokenViewerContextValue | null>(null);\n\nfunction useTokenViewerContext() {\n  const context = React.useContext(AiTokenViewerContext);\n  if (!context) {\n    throw new Error(\n      \"AiTokenViewer components must be used within <AiTokenViewer>\",\n    );\n  }\n  return context;\n}\n\ninterface AiTokenViewerProps {\n  tokens?: Token[];\n  inputTokens?: number;\n  outputTokens?: number;\n  model?: string;\n  showCost?: boolean;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiTokenViewer({\n  tokens,\n  inputTokens,\n  outputTokens,\n  model,\n  showCost = false,\n  children,\n  className,\n}: AiTokenViewerProps) {\n  const contextValue = React.useMemo(\n    () => ({ tokens, inputTokens, outputTokens, model, showCost }),\n    [tokens, inputTokens, outputTokens, model, showCost],\n  );\n\n  return (\n    <AiTokenViewerContext.Provider value={contextValue}>\n      <div\n        data-slot=\"ai-token-viewer\"\n        className={cn(\n          \"rounded-lg border border-border bg-card text-card-foreground overflow-hidden\",\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </AiTokenViewerContext.Provider>\n  );\n}\n\ninterface AiTokenViewerHeaderProps {\n  title?: string;\n  className?: string;\n}\n\nfunction AiTokenViewerHeader({\n  title = \"Token Usage\",\n  className,\n}: AiTokenViewerHeaderProps) {\n  const { inputTokens = 0, outputTokens = 0, model } = useTokenViewerContext();\n  const totalTokens = inputTokens + outputTokens;\n\n  return (\n    <div\n      data-slot=\"ai-token-viewer-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        <Hash className=\"size-4 text-muted-foreground\" />\n      </div>\n      <div className=\"flex flex-1 items-center gap-2\">\n        <span className=\"font-medium text-sm\">{title}</span>\n        <span className=\"inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground font-mono\">\n          {totalTokens.toLocaleString()}\n        </span>\n        {model && (\n          <span className=\"text-xs text-muted-foreground\">{model}</span>\n        )}\n      </div>\n    </div>\n  );\n}\n\ninterface AiTokenViewerStatsProps {\n  className?: string;\n}\n\nfunction AiTokenViewerStats({ className }: AiTokenViewerStatsProps) {\n  const {\n    inputTokens = 0,\n    outputTokens = 0,\n    model,\n    showCost,\n  } = useTokenViewerContext();\n  const totalTokens = inputTokens + outputTokens;\n\n  const cost = React.useMemo(() => {\n    if (!showCost || !model) return null;\n    const pricing = MODEL_PRICING[model];\n    if (!pricing) return null;\n\n    const inputCost = (inputTokens / 1000) * pricing.inputCostPer1k;\n    const outputCost = (outputTokens / 1000) * pricing.outputCostPer1k;\n    return {\n      input: inputCost,\n      output: outputCost,\n      total: inputCost + outputCost,\n    };\n  }, [showCost, model, inputTokens, outputTokens]);\n\n  const formatCost = React.useCallback((value: number) => {\n    if (value < 0.01) return `$${value.toFixed(4)}`;\n    return `$${value.toFixed(2)}`;\n  }, []);\n\n  return (\n    <div data-slot=\"ai-token-viewer-stats\" className={cn(\"p-4\", className)}>\n      <div className=\"grid grid-cols-3 gap-4\">\n        <div className=\"space-y-1\">\n          <div className=\"flex items-center gap-1 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n            <ArrowUp className=\"size-3\" />\n            Input\n          </div>\n          <p className=\"text-lg font-semibold font-mono\">\n            {inputTokens.toLocaleString()}\n          </p>\n          {cost && (\n            <p className=\"text-xs text-muted-foreground\">\n              {formatCost(cost.input)}\n            </p>\n          )}\n        </div>\n        <div className=\"space-y-1\">\n          <div className=\"flex items-center gap-1 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n            <ArrowDown className=\"size-3\" />\n            Output\n          </div>\n          <p className=\"text-lg font-semibold font-mono\">\n            {outputTokens.toLocaleString()}\n          </p>\n          {cost && (\n            <p className=\"text-xs text-muted-foreground\">\n              {formatCost(cost.output)}\n            </p>\n          )}\n        </div>\n        <div className=\"space-y-1\">\n          <div className=\"flex items-center gap-1 text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n            <Hash className=\"size-3\" />\n            Total\n          </div>\n          <p className=\"text-lg font-semibold font-mono\">\n            {totalTokens.toLocaleString()}\n          </p>\n          {cost && (\n            <p className=\"text-xs text-green-600 dark:text-green-400 font-medium\">\n              {formatCost(cost.total)}\n            </p>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\ninterface AiTokenViewerCostProps {\n  className?: string;\n}\n\nfunction AiTokenViewerCost({ className }: AiTokenViewerCostProps) {\n  const { inputTokens = 0, outputTokens = 0, model } = useTokenViewerContext();\n\n  const cost = React.useMemo(() => {\n    if (!model) return null;\n    const pricing = MODEL_PRICING[model];\n    if (!pricing) return null;\n\n    const inputCost = (inputTokens / 1000) * pricing.inputCostPer1k;\n    const outputCost = (outputTokens / 1000) * pricing.outputCostPer1k;\n    return {\n      input: inputCost,\n      output: outputCost,\n      total: inputCost + outputCost,\n    };\n  }, [model, inputTokens, outputTokens]);\n\n  if (!cost) {\n    return (\n      <div\n        data-slot=\"ai-token-viewer-cost\"\n        className={cn(\n          \"px-4 py-3 border-t border-border text-sm text-muted-foreground\",\n          className,\n        )}\n      >\n        Cost estimation unavailable for this model\n      </div>\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"ai-token-viewer-cost\"\n      className={cn(\n        \"flex items-center gap-3 px-4 py-3 border-t border-border bg-muted/20\",\n        className,\n      )}\n    >\n      <DollarSign className=\"size-4 text-green-500\" />\n      <span className=\"text-sm text-muted-foreground\">Estimated cost:</span>\n      <span className=\"text-sm font-semibold text-green-600 dark:text-green-400\">\n        ${cost.total.toFixed(4)}\n      </span>\n    </div>\n  );\n}\n\ninterface AiTokenViewerBreakdownProps {\n  className?: string;\n}\n\nfunction AiTokenViewerBreakdown({ className }: AiTokenViewerBreakdownProps) {\n  const { tokens } = useTokenViewerContext();\n\n  if (!tokens || tokens.length === 0) {\n    return (\n      <div\n        data-slot=\"ai-token-viewer-breakdown\"\n        className={cn(\n          \"p-4 border-t border-border text-sm text-muted-foreground text-center\",\n          className,\n        )}\n      >\n        No token breakdown available\n      </div>\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"ai-token-viewer-breakdown\"\n      className={cn(\"p-4 border-t border-border\", className)}\n    >\n      <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2 block\">\n        Token Breakdown\n      </span>\n      <div className=\"flex flex-wrap gap-0.5 font-mono text-xs\">\n        {tokens.map((token, index) => (\n          <AiTokenViewerToken key={index} token={token} index={index} />\n        ))}\n      </div>\n    </div>\n  );\n}\n\ninterface AiTokenViewerTokenProps {\n  token: Token;\n  index: number;\n  className?: string;\n}\n\nfunction AiTokenViewerToken({\n  token,\n  index,\n  className,\n}: AiTokenViewerTokenProps) {\n  const [isHovered, setIsHovered] = React.useState(false);\n\n  const typeConfig = React.useMemo(() => {\n    const type = token.type ?? \"text\";\n    const configs: Record<TokenType, { bg: string; border: string }> = {\n      text: {\n        bg: \"bg-blue-50 dark:bg-blue-950/50\",\n        border: \"border-blue-200 dark:border-blue-800\",\n      },\n      special: {\n        bg: \"bg-purple-50 dark:bg-purple-950/50\",\n        border: \"border-purple-200 dark:border-purple-800\",\n      },\n      control: {\n        bg: \"bg-orange-50 dark:bg-orange-950/50\",\n        border: \"border-orange-200 dark:border-orange-800\",\n      },\n    };\n    return configs[type];\n  }, [token.type]);\n\n  const displayText = React.useMemo(() => {\n    return token.text\n      .replace(/\\n/g, \"\\\\n\")\n      .replace(/\\t/g, \"\\\\t\")\n      .replace(/\\r/g, \"\\\\r\");\n  }, [token.text]);\n\n  return (\n    <span\n      data-slot=\"ai-token-viewer-token\"\n      data-type={token.type ?? \"text\"}\n      className={cn(\n        \"relative inline-flex items-center px-1 py-0.5 rounded border transition-colors cursor-default\",\n        typeConfig.bg,\n        typeConfig.border,\n        isHovered && \"ring-2 ring-primary ring-offset-1\",\n        className,\n      )}\n      onMouseEnter={() => setIsHovered(true)}\n      onMouseLeave={() => setIsHovered(false)}\n    >\n      <span className=\"whitespace-pre\">{displayText}</span>\n      {isHovered && (token.id !== undefined || token.type) && (\n        <span className=\"absolute -top-6 left-1/2 -translate-x-1/2 px-1.5 py-0.5 rounded bg-foreground text-background text-[10px] whitespace-nowrap z-10\">\n          {token.id !== undefined && `#${token.id}`}\n          {token.id !== undefined && token.type && \" - \"}\n          {token.type}\n        </span>\n      )}\n    </span>\n  );\n}\n\nexport {\n  AiTokenViewer,\n  AiTokenViewerHeader,\n  AiTokenViewerStats,\n  AiTokenViewerCost,\n  AiTokenViewerBreakdown,\n  AiTokenViewerToken,\n};\nexport type { AiTokenViewerProps, Token, TokenType };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Compound component for visualizing token usage. Features input/output/total token counts, cost estimation for GPT-4/4o/3.5-turbo and Claude 3/3.5 models, visual token breakdown with color-coded types (text, special, control), hover tooltips showing token IDs and types.",
  "categories": [
    "ai"
  ],
  "type": "registry:ui"
}