Chatbot

Context

A compound component system for displaying AI model context window usage, token consumption, and cost estimation.

The Context component provides a comprehensive view of AI model usage through a compound component system. It displays context window utilization, token consumption breakdown (input, output, reasoning, cache), and cost estimation in an interactive hover card interface.

"use client";import {  Context,  ContextCacheUsage,  ContextContent,  ContextContentBody,  ContextContentFooter,  ContextContentHeader,  ContextInputUsage,  ContextOutputUsage,  ContextReasoningUsage,  ContextTrigger,} from "@/components/ai-elements/elements/context";const Example = () => (  <div className="flex items-center justify-center p-8">    <Context      maxTokens={128_000}      modelId="openai:gpt-5"      usage={{        inputTokens: 32_000,        outputTokens: 8000,        totalTokens: 40_000,        cachedInputTokens: 0,        reasoningTokens: 0,      }}      usedTokens={40_000}    >      <ContextTrigger />      <ContextContent>        <ContextContentHeader />        <ContextContentBody>          <ContextInputUsage />          <ContextOutputUsage />          <ContextReasoningUsage />          <ContextCacheUsage />        </ContextContentBody>        <ContextContentFooter />      </ContextContent>    </Context>  </div>);export default Example;

Installation

npx ai-elements@latest add context
npx shadcn@latest add @ai-elements/context
"use client";import { Button } from "@repo/shadcn-ui/components/ui/button";import {  HoverCard,  HoverCardContent,  HoverCardTrigger,} from "@repo/shadcn-ui/components/ui/hover-card";import { Progress } from "@repo/shadcn-ui/components/ui/progress";import { cn } from "@repo/shadcn-ui/lib/utils";import type { LanguageModelUsage } from "ai";import { type ComponentProps, createContext, useContext } from "react";import { estimateCost, type ModelId } from "tokenlens";const PERCENT_MAX = 100;const ICON_RADIUS = 10;const ICON_VIEWBOX = 24;const ICON_CENTER = 12;const ICON_STROKE_WIDTH = 2;type ContextSchema = {  usedTokens: number;  maxTokens: number;  usage?: LanguageModelUsage;  modelId?: ModelId;};const ContextContext = createContext<ContextSchema | null>(null);const useContextValue = () => {  const context = useContext(ContextContext);  if (!context) {    throw new Error("Context components must be used within Context");  }  return context;};export type ContextProps = ComponentProps<typeof HoverCard> & ContextSchema;export const Context = ({  usedTokens,  maxTokens,  usage,  modelId,  ...props}: ContextProps) => (  <ContextContext.Provider    value={{      usedTokens,      maxTokens,      usage,      modelId,    }}  >    <HoverCard closeDelay={0} openDelay={0} {...props} />  </ContextContext.Provider>);const ContextIcon = () => {  const { usedTokens, maxTokens } = useContextValue();  const circumference = 2 * Math.PI * ICON_RADIUS;  const usedPercent = usedTokens / maxTokens;  const dashOffset = circumference * (1 - usedPercent);  return (    <svg      aria-label="Model context usage"      height="20"      role="img"      style={{ color: "currentcolor" }}      viewBox={`0 0 ${ICON_VIEWBOX} ${ICON_VIEWBOX}`}      width="20"    >      <circle        cx={ICON_CENTER}        cy={ICON_CENTER}        fill="none"        opacity="0.25"        r={ICON_RADIUS}        stroke="currentColor"        strokeWidth={ICON_STROKE_WIDTH}      />      <circle        cx={ICON_CENTER}        cy={ICON_CENTER}        fill="none"        opacity="0.7"        r={ICON_RADIUS}        stroke="currentColor"        strokeDasharray={`${circumference} ${circumference}`}        strokeDashoffset={dashOffset}        strokeLinecap="round"        strokeWidth={ICON_STROKE_WIDTH}        style={{ transformOrigin: "center", transform: "rotate(-90deg)" }}      />    </svg>  );};export type ContextTriggerProps = ComponentProps<typeof Button>;export const ContextTrigger = ({ children, ...props }: ContextTriggerProps) => {  const { usedTokens, maxTokens } = useContextValue();  const usedPercent = usedTokens / maxTokens;  const renderedPercent = new Intl.NumberFormat("en-US", {    style: "percent",    maximumFractionDigits: 1,  }).format(usedPercent);  return (    <HoverCardTrigger asChild>      {children ?? (        <Button type="button" variant="ghost" {...props}>          <span className="font-medium text-muted-foreground">            {renderedPercent}          </span>          <ContextIcon />        </Button>      )}    </HoverCardTrigger>  );};export type ContextContentProps = ComponentProps<typeof HoverCardContent>;export const ContextContent = ({  className,  ...props}: ContextContentProps) => (  <HoverCardContent    className={cn("min-w-[240px] divide-y overflow-hidden p-0", className)}    {...props}  />);export type ContextContentHeader = ComponentProps<"div">;export const ContextContentHeader = ({  children,  className,  ...props}: ContextContentHeader) => {  const { usedTokens, maxTokens } = useContextValue();  const usedPercent = usedTokens / maxTokens;  const displayPct = new Intl.NumberFormat("en-US", {    style: "percent",    maximumFractionDigits: 1,  }).format(usedPercent);  const used = new Intl.NumberFormat("en-US", {    notation: "compact",  }).format(usedTokens);  const total = new Intl.NumberFormat("en-US", {    notation: "compact",  }).format(maxTokens);  return (    <div className={cn("w-full space-y-2 p-3", className)} {...props}>      {children ?? (        <>          <div className="flex items-center justify-between gap-3 text-xs">            <p>{displayPct}</p>            <p className="font-mono text-muted-foreground">              {used} / {total}            </p>          </div>          <div className="space-y-2">            <Progress className="bg-muted" value={usedPercent * PERCENT_MAX} />          </div>        </>      )}    </div>  );};export type ContextContentBody = ComponentProps<"div">;export const ContextContentBody = ({  children,  className,  ...props}: ContextContentBody) => (  <div className={cn("w-full p-3", className)} {...props}>    {children}  </div>);export type ContextContentFooter = ComponentProps<"div">;export const ContextContentFooter = ({  children,  className,  ...props}: ContextContentFooter) => {  const { modelId, usage } = useContextValue();  const costUSD = modelId    ? estimateCost({        modelId,        usage: {          input: usage?.inputTokens ?? 0,          output: usage?.outputTokens ?? 0,        },      }).totalUSD    : undefined;  const totalCost = new Intl.NumberFormat("en-US", {    style: "currency",    currency: "USD",  }).format(costUSD ?? 0);  return (    <div      className={cn(        "flex w-full items-center justify-between gap-3 bg-secondary p-3 text-xs",        className      )}      {...props}    >      {children ?? (        <>          <span className="text-muted-foreground">Total cost</span>          <span>{totalCost}</span>        </>      )}    </div>  );};export type ContextInputUsageProps = ComponentProps<"div">;export const ContextInputUsage = ({  className,  children,  ...props}: ContextInputUsageProps) => {  const { usage, modelId } = useContextValue();  const inputTokens = usage?.inputTokens ?? 0;  if (children) {    return children;  }  if (!inputTokens) {    return null;  }  const inputCost = modelId    ? estimateCost({        modelId,        usage: { input: inputTokens, output: 0 },      }).totalUSD    : undefined;  const inputCostText = new Intl.NumberFormat("en-US", {    style: "currency",    currency: "USD",  }).format(inputCost ?? 0);  return (    <div      className={cn("flex items-center justify-between text-xs", className)}      {...props}    >      <span className="text-muted-foreground">Input</span>      <TokensWithCost costText={inputCostText} tokens={inputTokens} />    </div>  );};export type ContextOutputUsageProps = ComponentProps<"div">;export const ContextOutputUsage = ({  className,  children,  ...props}: ContextOutputUsageProps) => {  const { usage, modelId } = useContextValue();  const outputTokens = usage?.outputTokens ?? 0;  if (children) {    return children;  }  if (!outputTokens) {    return null;  }  const outputCost = modelId    ? estimateCost({        modelId,        usage: { input: 0, output: outputTokens },      }).totalUSD    : undefined;  const outputCostText = new Intl.NumberFormat("en-US", {    style: "currency",    currency: "USD",  }).format(outputCost ?? 0);  return (    <div      className={cn("flex items-center justify-between text-xs", className)}      {...props}    >      <span className="text-muted-foreground">Output</span>      <TokensWithCost costText={outputCostText} tokens={outputTokens} />    </div>  );};export type ContextReasoningUsageProps = ComponentProps<"div">;export const ContextReasoningUsage = ({  className,  children,  ...props}: ContextReasoningUsageProps) => {  const { usage, modelId } = useContextValue();  const reasoningTokens = usage?.reasoningTokens ?? 0;  if (children) {    return children;  }  if (!reasoningTokens) {    return null;  }  const reasoningCost = modelId    ? estimateCost({        modelId,        usage: { reasoningTokens },      }).totalUSD    : undefined;  const reasoningCostText = new Intl.NumberFormat("en-US", {    style: "currency",    currency: "USD",  }).format(reasoningCost ?? 0);  return (    <div      className={cn("flex items-center justify-between text-xs", className)}      {...props}    >      <span className="text-muted-foreground">Reasoning</span>      <TokensWithCost costText={reasoningCostText} tokens={reasoningTokens} />    </div>  );};export type ContextCacheUsageProps = ComponentProps<"div">;export const ContextCacheUsage = ({  className,  children,  ...props}: ContextCacheUsageProps) => {  const { usage, modelId } = useContextValue();  const cacheTokens = usage?.cachedInputTokens ?? 0;  if (children) {    return children;  }  if (!cacheTokens) {    return null;  }  const cacheCost = modelId    ? estimateCost({        modelId,        usage: { cacheReads: cacheTokens, input: 0, output: 0 },      }).totalUSD    : undefined;  const cacheCostText = new Intl.NumberFormat("en-US", {    style: "currency",    currency: "USD",  }).format(cacheCost ?? 0);  return (    <div      className={cn("flex items-center justify-between text-xs", className)}      {...props}    >      <span className="text-muted-foreground">Cache</span>      <TokensWithCost costText={cacheCostText} tokens={cacheTokens} />    </div>  );};const TokensWithCost = ({  tokens,  costText,}: {  tokens?: number;  costText?: string;}) => (  <span>    {tokens === undefined      ? "—"      : new Intl.NumberFormat("en-US", {          notation: "compact",        }).format(tokens)}    {costText ? (      <span className="ml-2 text-muted-foreground">• {costText}</span>    ) : null}  </span>);

Usage

import {
  Context,
  ContextTrigger,
  ContextContent,
  ContextContentHeader,
  ContextContentBody,
  ContextContentFooter,
  ContextInputUsage,
  ContextOutputUsage,
  ContextReasoningUsage,
  ContextCacheUsage,
} from '@/components/ai-elements/context';
<Context
  maxTokens={128000}
  usedTokens={40000}
  usage={{
    inputTokens: 32000,
    outputTokens: 8000,
    totalTokens: 40000,
    cachedInputTokens: 0,
    reasoningTokens: 0,
  }}
  modelId="openai:gpt-4"
>
  <ContextTrigger />
  <ContextContent>
    <ContextContentHeader />
    <ContextContentBody>
      <ContextInputUsage />
      <ContextOutputUsage />
      <ContextReasoningUsage />
      <ContextCacheUsage />
    </ContextContentBody>
    <ContextContentFooter />
  </ContextContent>
</Context>

Features

  • Compound Component Architecture: Flexible composition of context display elements
  • Visual Progress Indicator: Circular SVG progress ring showing context usage percentage
  • Token Breakdown: Detailed view of input, output, reasoning, and cached tokens
  • Cost Estimation: Real-time cost calculation using the tokenlens library
  • Intelligent Formatting: Automatic token count formatting (K, M, B suffixes)
  • Interactive Hover Card: Detailed information revealed on hover
  • Context Provider Pattern: Clean data flow through React Context API
  • TypeScript Support: Full type definitions for all components
  • Accessible Design: Proper ARIA labels and semantic HTML
  • Theme Integration: Uses currentColor for automatic theme adaptation

Props

<Context />

Prop

Type

<ContextTrigger />

Prop

Type

<ContextContent />

Prop

Type

<ContextContentHeader />

Prop

Type

<ContextContentBody />

Prop

Type

<ContextContentFooter />

Prop

Type

Usage Components

All usage components (ContextInputUsage, ContextOutputUsage, ContextReasoningUsage, ContextCacheUsage) share the same props:

Prop

Type

Component Architecture

The Context component uses a compound component pattern with React Context for data sharing:

  1. <Context> - Root provider component that holds all context data
  2. <ContextTrigger> - Interactive trigger element (default: button with percentage)
  3. <ContextContent> - Hover card content container
  4. <ContextContentHeader> - Header section with progress visualization
  5. <ContextContentBody> - Body section for usage breakdowns
  6. <ContextContentFooter> - Footer section for total cost
  7. Usage Components - Individual token usage displays (Input, Output, Reasoning, Cache)

Token Formatting

The component uses Intl.NumberFormat with compact notation for automatic formatting:

  • Under 1,000: Shows exact count (e.g., "842")
  • 1,000+: Shows with K suffix (e.g., "32K")
  • 1,000,000+: Shows with M suffix (e.g., "1.5M")
  • 1,000,000,000+: Shows with B suffix (e.g., "2.1B")

Cost Calculation

When a modelId is provided, the component automatically calculates costs using the tokenlens library:

  • Input tokens: Cost based on model's input pricing
  • Output tokens: Cost based on model's output pricing
  • Reasoning tokens: Special pricing for reasoning-capable models
  • Cached tokens: Reduced pricing for cached input tokens
  • Total cost: Sum of all token type costs

Costs are formatted using Intl.NumberFormat with USD currency.

Styling

The component uses Tailwind CSS classes and follows your design system:

  • Progress indicator uses currentColor for theme adaptation
  • Hover card has customizable width and padding
  • Footer has a secondary background for visual separation
  • All text sizes use the text-xs class for consistency
  • Muted foreground colors for secondary information