{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "elements-context-display",
  "type": "registry:component",
  "title": "Context Display Core",
  "description": "The props-driven context usage display: ring, bar, and text presets with the usage popover, no runtime required.",
  "registryDependencies": [
    "tooltip"
  ],
  "files": [
    {
      "type": "registry:component",
      "path": "components/assistant-ui/elements/context-display.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/context-display.radix.tsx",
      "content": "\"use client\";\n\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  createContext,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  type FC,\n  type ReactNode,\n} from \"react\";\n\nexport type TokenUsage = {\n  totalTokens?: number | undefined;\n  inputTokens?: number | undefined;\n  cachedInputTokens?: number | undefined;\n  outputTokens?: number | undefined;\n  reasoningTokens?: number | undefined;\n};\n\nconst formatTokenCount = (tokens: number): string => {\n  if (tokens >= 1_000_000)\n    return `${(tokens / 1_000_000).toFixed(1).replace(/\\.0$/, \"\")}M`;\n  if (tokens >= 1_000)\n    return `${(tokens / 1_000).toFixed(1).replace(/\\.0$/, \"\")}k`;\n  return `${tokens}`;\n};\n\nconst getUsagePercent = (\n  totalTokens: number | undefined,\n  modelContextWindow: number,\n): number => {\n  if (!totalTokens) return 0;\n  return Math.min((totalTokens / modelContextWindow) * 100, 100);\n};\n\ntype UsageSeverity = \"normal\" | \"warning\" | \"critical\";\n\nconst getUsageSeverity = (percent: number): UsageSeverity => {\n  if (percent > 85) return \"critical\";\n  if (percent >= 65) return \"warning\";\n  return \"normal\";\n};\n\nconst getStrokeColor = (percent: number): string => {\n  const severity = getUsageSeverity(percent);\n  if (severity === \"critical\") return \"stroke-red-500\";\n  if (severity === \"warning\") return \"stroke-amber-500\";\n  return \"stroke-foreground\";\n};\n\nconst getBarColor = (percent: number): string => {\n  const severity = getUsageSeverity(percent);\n  if (severity === \"critical\") return \"bg-red-500\";\n  if (severity === \"warning\") return \"bg-amber-500\";\n  return \"bg-foreground\";\n};\n\nconst getPercentColor = (percent: number): string => {\n  const severity = getUsageSeverity(percent);\n  if (severity === \"critical\") return \"text-red-500\";\n  if (severity === \"warning\") return \"text-amber-500\";\n  return \"text-muted-foreground\";\n};\ntype ContextDisplayContextValue = {\n  usage: TokenUsage | undefined;\n  totalTokens: number;\n  percent: number;\n  modelContextWindow: number;\n};\n\nconst ContextDisplayContext = createContext<ContextDisplayContextValue | null>(\n  null,\n);\n\nfunction useContextDisplay(): ContextDisplayContextValue {\n  const ctx = useContext(ContextDisplayContext);\n  if (!ctx) {\n    throw new Error(\"ContextDisplay.* must be used within ContextDisplay.Root\");\n  }\n  return ctx;\n}\nexport type PresetProps = {\n  modelContextWindow: number;\n  className?: string;\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n  usage?: TokenUsage | undefined;\n  resetKey?: string | undefined;\n};\n\nexport type ContextDisplayRootProps = {\n  modelContextWindow: number;\n  children: ReactNode;\n  usage?: TokenUsage | undefined;\n  resetKey?: string | undefined;\n};\n\nfunction ContextDisplayRoot({\n  modelContextWindow,\n  children,\n  usage,\n  resetKey,\n}: ContextDisplayRootProps) {\n  const rawTokens = usage?.totalTokens ?? 0;\n  const [tokenState, setTokenState] = useState({\n    resetKey,\n    totalTokens: rawTokens > 0 ? rawTokens : 0,\n    usage,\n  });\n\n  useEffect(() => {\n    setTokenState((prev) => {\n      if (prev.resetKey !== resetKey) {\n        return {\n          resetKey,\n          totalTokens: rawTokens > 0 ? rawTokens : 0,\n          usage,\n        };\n      }\n      if (rawTokens > 0 && rawTokens !== prev.totalTokens) {\n        return { ...prev, totalTokens: rawTokens, usage };\n      }\n      if (usage !== prev.usage) {\n        return { ...prev, usage };\n      }\n      return prev;\n    });\n  }, [resetKey, rawTokens, usage]);\n\n  const current =\n    tokenState.resetKey === resetKey\n      ? tokenState\n      : { totalTokens: rawTokens > 0 ? rawTokens : 0, usage };\n  const totalTokens = current.totalTokens;\n  const percent = getUsagePercent(totalTokens, modelContextWindow);\n  const hasUsage = current.usage !== undefined || totalTokens > 0;\n\n  const contextValue = useMemo(\n    () => ({\n      usage: current.usage,\n      totalTokens,\n      percent,\n      modelContextWindow,\n    }),\n    [current.usage, totalTokens, percent, modelContextWindow],\n  );\n\n  if (!hasUsage) return null;\n\n  return (\n    <ContextDisplayContext.Provider value={contextValue}>\n      <TooltipProvider>\n        <Tooltip>{children}</Tooltip>\n      </TooltipProvider>\n    </ContextDisplayContext.Provider>\n  );\n}\nfunction ContextDisplayTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"button\">) {\n  return (\n    <TooltipTrigger asChild>\n      <button\n        type=\"button\"\n        data-slot=\"context-display-trigger\"\n        className={cn(\n          \"inline-flex items-center rounded-md transition-colors\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </button>\n    </TooltipTrigger>\n  );\n}\n\ntype ContextSegment = {\n  label: string;\n  tokens: number;\n};\n\n// Whether a provider counts cached tokens inside inputTokens, or reasoning\n// inside outputTokens, differs by provider: OpenAI reports cached_tokens as a\n// subset of prompt_tokens, while Anthropic documents input_tokens as excluding\n// cache_read_input_tokens. Nothing in the usage contract says which is in hand,\n// so these are reported as the counts they are and none of them is given a\n// share of the bar, which stays the one reading that always holds: the\n// provider's own total against the window.\nconst getContextSegments = (\n  usage: TokenUsage | undefined,\n): ContextSegment[] => {\n  if (!usage) return [];\n  return [\n    { label: \"Input\", tokens: usage.inputTokens ?? 0 },\n    { label: \"Cached input\", tokens: usage.cachedInputTokens ?? 0 },\n    { label: \"Output\", tokens: usage.outputTokens ?? 0 },\n    { label: \"Reasoning\", tokens: usage.reasoningTokens ?? 0 },\n  ].filter((segment) => segment.tokens > 0);\n};\n\nfunction ContextDisplayContent({\n  side = \"top\",\n  className,\n}: {\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\" | undefined;\n  className?: string;\n}) {\n  const { usage, totalTokens, percent, modelContextWindow } =\n    useContextDisplay();\n  const segments = getContextSegments(usage);\n\n  return (\n    <TooltipContent\n      side={side}\n      sideOffset={8}\n      data-slot=\"context-display-popover\"\n      className={cn(\n        \"bg-popover text-popover-foreground block w-56 border p-3 text-left [&_[data-slot=tooltip-arrow]]:hidden\",\n        className,\n      )}\n    >\n      <div className=\"text-xs\">\n        <div className=\"flex items-baseline justify-between gap-6 whitespace-nowrap\">\n          <span className={getPercentColor(percent)}>\n            {Math.round(percent)}% full\n          </span>\n          <span className=\"font-mono tabular-nums\">\n            {formatTokenCount(Math.min(totalTokens, modelContextWindow))} /{\" \"}\n            {formatTokenCount(modelContextWindow)}\n          </span>\n        </div>\n        <div className=\"bg-muted mt-2.5 h-1 overflow-hidden rounded-full\">\n          <div\n            className={cn(\n              \"h-full w-(--usage-width) rounded-full transition-[width] duration-300\",\n              totalTokens > 0 && \"min-w-1\",\n              getBarColor(percent),\n            )}\n            style={{ \"--usage-width\": `${percent}%` } as React.CSSProperties}\n          />\n        </div>\n        {segments.length > 0 && (\n          <div className=\"mt-3 grid gap-1.5\">\n            {segments.map((segment) => (\n              <div\n                key={segment.label}\n                className=\"flex items-baseline justify-between gap-6\"\n              >\n                <span className=\"text-muted-foreground\">{segment.label}</span>\n                <span className=\"font-mono tabular-nums\">\n                  {formatTokenCount(segment.tokens)}\n                </span>\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n    </TooltipContent>\n  );\n}\n\nconst RING_SIZE = 18;\nconst RING_STROKE = 2.5;\nconst RING_RADIUS = (RING_SIZE - RING_STROKE) / 2;\nconst RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;\n\nfunction RingVisual() {\n  const { percent } = useContextDisplay();\n\n  return (\n    <svg\n      aria-hidden=\"true\"\n      width={RING_SIZE}\n      height={RING_SIZE}\n      viewBox={`0 0 ${RING_SIZE} ${RING_SIZE}`}\n      className=\"-rotate-90\"\n    >\n      <circle\n        cx={RING_SIZE / 2}\n        cy={RING_SIZE / 2}\n        r={RING_RADIUS}\n        fill=\"none\"\n        strokeWidth={RING_STROKE}\n        className=\"stroke-muted\"\n      />\n      <circle\n        cx={RING_SIZE / 2}\n        cy={RING_SIZE / 2}\n        r={RING_RADIUS}\n        fill=\"none\"\n        strokeWidth={RING_STROKE}\n        strokeLinecap=\"round\"\n        strokeDasharray={RING_CIRCUMFERENCE}\n        strokeDashoffset={\n          RING_CIRCUMFERENCE - (percent / 100) * RING_CIRCUMFERENCE\n        }\n        className={cn(\n          \"transition-[stroke-dashoffset,stroke] duration-300\",\n          getStrokeColor(percent),\n        )}\n      />\n    </svg>\n  );\n}\n\nfunction RingPercentLabel() {\n  const { percent } = useContextDisplay();\n  return <span className=\"font-mono tabular-nums\">{Math.round(percent)}%</span>;\n}\nconst ContextDisplayRing: FC<PresetProps> = ({\n  modelContextWindow,\n  className,\n  side,\n  usage,\n  resetKey,\n}) => (\n  <ContextDisplayRoot\n    modelContextWindow={modelContextWindow}\n    usage={usage}\n    resetKey={resetKey}\n  >\n    <ContextDisplayTrigger\n      className={cn(\n        \"text-muted-foreground hover:text-foreground gap-1.5 px-1.5 py-1 text-xs\",\n        className,\n      )}\n      aria-label=\"Context usage\"\n    >\n      <RingVisual />\n      <RingPercentLabel />\n    </ContextDisplayTrigger>\n    <ContextDisplayContent side={side} />\n  </ContextDisplayRoot>\n);\n\nfunction BarVisual() {\n  const { percent, totalTokens } = useContextDisplay();\n\n  return (\n    <div className=\"flex items-center gap-2\">\n      <div className=\"bg-muted h-1.5 w-16 overflow-hidden rounded-full\">\n        <div\n          className={cn(\n            \"h-full rounded-full transition-all duration-300\",\n            getBarColor(percent),\n          )}\n          style={{ width: `${percent}%` }}\n        />\n      </div>\n      <span className=\"text-muted-foreground text-[10px] tabular-nums\">\n        {formatTokenCount(totalTokens)} ({Math.round(percent)}%)\n      </span>\n    </div>\n  );\n}\n\nconst ContextDisplayBar: FC<PresetProps> = ({\n  modelContextWindow,\n  className,\n  side,\n  usage,\n  resetKey,\n}) => (\n  <ContextDisplayRoot\n    modelContextWindow={modelContextWindow}\n    usage={usage}\n    resetKey={resetKey}\n  >\n    <ContextDisplayTrigger\n      className={cn(\"px-2 py-1\", className)}\n      aria-label=\"Context usage\"\n    >\n      <BarVisual />\n    </ContextDisplayTrigger>\n    <ContextDisplayContent side={side} />\n  </ContextDisplayRoot>\n);\n\nfunction TextVisual() {\n  const { totalTokens, modelContextWindow } = useContextDisplay();\n\n  return (\n    <>\n      {formatTokenCount(totalTokens)} / {formatTokenCount(modelContextWindow)}\n    </>\n  );\n}\n\nconst ContextDisplayText: FC<PresetProps> = ({\n  modelContextWindow,\n  className,\n  side,\n  usage,\n  resetKey,\n}) => (\n  <ContextDisplayRoot\n    modelContextWindow={modelContextWindow}\n    usage={usage}\n    resetKey={resetKey}\n  >\n    <ContextDisplayTrigger\n      aria-label=\"Context usage\"\n      className={cn(\n        \"text-muted-foreground hover:bg-accent hover:text-accent-foreground px-2 py-1 font-mono text-xs tabular-nums\",\n        className,\n      )}\n    >\n      <TextVisual />\n    </ContextDisplayTrigger>\n    <ContextDisplayContent side={side} />\n  </ContextDisplayRoot>\n);\n\nconst ContextDisplay = {} as {\n  Root: typeof ContextDisplayRoot;\n  Trigger: typeof ContextDisplayTrigger;\n  Content: typeof ContextDisplayContent;\n  Ring: typeof ContextDisplayRing;\n  Bar: typeof ContextDisplayBar;\n  Text: typeof ContextDisplayText;\n};\n\nContextDisplay.Root = ContextDisplayRoot;\nContextDisplay.Trigger = ContextDisplayTrigger;\nContextDisplay.Content = ContextDisplayContent;\nContextDisplay.Ring = ContextDisplayRing;\nContextDisplay.Bar = ContextDisplayBar;\nContextDisplay.Text = ContextDisplayText;\n\nexport {\n  ContextDisplay,\n  ContextDisplayRoot,\n  ContextDisplayTrigger,\n  ContextDisplayContent,\n  ContextDisplayRing,\n  ContextDisplayBar,\n  ContextDisplayText,\n};\n"
    }
  ]
}