'use client'; import { HoverCard, HoverCardContent, HoverCardTrigger, } from '@/components/ui/hover-card'; import { cn } from '@/lib/utils'; import type { ComponentProps } from 'react'; import type { LanguageModelUsage } from 'ai'; import { breakdownTokens, estimateCost, normalizeUsage } from 'tokenlens'; import { Separator } from '@/components/ui/separator'; import { Progress } from '@/components/ui/progress'; export type ContextProps = ComponentProps<'button'> & { /** Total context window size in tokens */ maxTokens: number; /** Tokens used so far */ usedTokens: number; /** Optional full usage payload to enable breakdown view */ usage?: LanguageModelUsage | undefined; /** Optional model id (canonical or alias) to compute cost */ modelId?: string; }; const THOUSAND = 1000; const MILLION = 1_000_000; const BILLION = 1_000_000_000; const PERCENT_MAX = 100; // Lucide CircleIcon geometry const ICON_VIEWBOX = 24; const ICON_CENTER = 12; const ICON_RADIUS = 10; const ICON_STROKE_WIDTH = 2; const formatTokens = (tokens?: number) => { if (tokens === undefined) { return; } if (!Number.isFinite(tokens)) { return; } const abs = Math.abs(tokens); if (abs < THOUSAND) { return `${tokens}`; } if (abs < MILLION) { return `${(tokens / THOUSAND).toFixed(1)}K`; } if (abs < BILLION) { return `${(tokens / MILLION).toFixed(1)}M`; } return `${(tokens / BILLION).toFixed(1)}B`; }; const formatPercent = (value: number) => { if (!Number.isFinite(value)) { return '0%'; } const rounded = Math.round(value * 10) / 10; return Number.isInteger(rounded) ? `${Math.trunc(rounded)}%` : `${rounded.toFixed(1)}%`; }; const formatUSD = (value?: number) => { if (value === undefined || !Number.isFinite(value)) return undefined; const abs = Math.abs(value); // Finer precision for very small amounts common in LLM pricing let decimals = 2; if (abs < 0.001) decimals = 5; else if (abs < 0.01) decimals = 4; else if (abs < 0.1) decimals = 3; else if (abs < 10) decimals = 2; else decimals = 1; const text = value.toFixed(decimals); // Trim trailing zeros/decimal if not needed (e.g., 1.2300 -> 1.23, 2.0 -> 2) const trimmed = text.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1'); return `$${trimmed}`; }; const formatUSDFixed = (value?: number, decimals = 5) => { if (value === undefined || !Number.isFinite(value)) return undefined; return `$${Number(value).toFixed(decimals)}`; }; type ContextIconProps = { percent: number; // 0 - 100 }; export const ContextIcon = ({ percent }: ContextIconProps) => { const radius = ICON_RADIUS; const circumference = 2 * Math.PI * radius; const dashOffset = circumference * (1 - percent / PERCENT_MAX); return ( ); }; function TokensWithCost({ tokens, costText, }: { tokens?: number; costText?: string; }) { return ( {tokens === undefined ? '—' : formatTokens(tokens)} {costText ? ( • {costText} ) : null} ); } function InfoRow({ label, tokens, costText, }: { label: string; tokens?: number; costText?: string; }) { return (
{displayPct} • {used} / {total} tokens