'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'; 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; /** Show token breakdown and optional cost inside hover */ showBreakdown?: boolean; }; 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}`; }; 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 ( ); }; export const Context = ({ className, maxTokens, usedTokens, usage, modelId, showBreakdown, ...props }: ContextProps) => { const safeMax = Math.max(0, Number.isFinite(maxTokens) ? maxTokens : 0); const safeUsed = Math.min( Math.max(0, Number.isFinite(usedTokens) ? usedTokens : 0), safeMax, ); const usedPercent = safeMax > 0 ? Math.min(PERCENT_MAX, Math.max(0, (safeUsed / safeMax) * PERCENT_MAX)) : 0; const displayPct = formatPercent(Math.round(usedPercent * 10) / 10); const used = formatTokens(safeUsed); const total = formatTokens(safeMax); const uNorm = normalizeUsage(usage as any); const uBreakdown = breakdownTokens(usage as any); const costUSD = modelId ? estimateCost({ modelId, usage: uNorm }).totalUSD : undefined; const costText = formatUSD(costUSD); const segInput = Math.max(0, uNorm.input ?? 0); const segOutput = Math.max(0, uNorm.output ?? 0); const segCacheR = Math.max(0, uBreakdown.cacheReads ?? 0); const segCacheW = Math.max(0, uBreakdown.cacheWrites ?? 0); const denom = safeMax > 0 ? safeMax : 1; const w = (n: number) => `${Math.min(100, Math.max(0, (n / denom) * 100)).toFixed(2)}%`; const fmtOrUnknown = (n?: number) => n === undefined ? '—' : formatTokens(n); return ( {displayPct} {displayPct} • {used} / {total} tokens {costText ? ( • {costText} ) : null} {true && ( Cache Hits {fmtOrUnknown(uBreakdown.cacheReads)} Cache Writes {fmtOrUnknown(uBreakdown.cacheWrites)} Input {formatTokens(uNorm.input)} Output {formatTokens(uNorm.output)} )} {showBreakdown && ( Cache Hits {fmtOrUnknown(uBreakdown.cacheReads)} Cache Writes {fmtOrUnknown(uBreakdown.cacheWrites)} Input {formatTokens(uNorm.input)} Output {formatTokens(uNorm.output)} {costText && ( <> Total cost {costText} > )} )} ); };
{displayPct} • {used} / {total} tokens {costText ? ( • {costText} ) : null}