'use client'; import { Button } from '@/components/ui/button'; import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; import { Progress } from '@/components/ui/progress'; import { cn } from '@/lib/utils'; import type { LanguageModelUsage } from 'ai'; import { type ComponentProps, createContext, useContext } from 'react'; import { getUsage } from 'tokenlens'; const PERCENT_MAX = 100; const ICON_RADIUS = 10; const ICON_VIEWBOX = 24; const ICON_CENTER = 12; const ICON_STROKE_WIDTH = 2; type ModelId = string; type ContextSchema = { usedTokens: number; maxTokens: number; usage?: LanguageModelUsage; modelId?: ModelId; }; const ContextContext = createContext(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 & ContextSchema; export const Context = ({ usedTokens, maxTokens, usage, modelId, ...props }: ContextProps) => ( ); const ContextIcon = () => { const { usedTokens, maxTokens } = useContextValue(); const circumference = 2 * Math.PI * ICON_RADIUS; const usedPercent = usedTokens / maxTokens; const dashOffset = circumference * (1 - usedPercent); return ( ); }; export type ContextTriggerProps = ComponentProps; 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 ( {children ?? ( )} ); }; export type ContextContentProps = ComponentProps; export const ContextContent = ({ className, ...props }: ContextContentProps) => ( ); export type ContextContentHeaderProps = ComponentProps<'div'>; export const ContextContentHeader = ({ children, className, ...props }: ContextContentHeaderProps) => { 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 (
{children ?? ( <>

{displayPct}

{used} / {total}

)}
); }; export type ContextContentBodyProps = ComponentProps<'div'>; export const ContextContentBody = ({ children, className, ...props }: ContextContentBodyProps) => (
{children}
); export type ContextContentFooterProps = ComponentProps<'div'>; export const ContextContentFooter = ({ children, className, ...props }: ContextContentFooterProps) => { const { modelId, usage } = useContextValue(); const costUSD = modelId ? getUsage({ modelId, usage: { input: usage?.inputTokens ?? 0, output: usage?.outputTokens ?? 0, }, }).costUSD?.totalUSD : undefined; const totalCost = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(costUSD ?? 0); return (
{children ?? ( <> Total cost {totalCost} )}
); }; 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 ? getUsage({ modelId, usage: { input: inputTokens, output: 0 }, }).costUSD?.totalUSD : undefined; const inputCostText = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(inputCost ?? 0); return (
Input
); }; 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 ? getUsage({ modelId, usage: { input: 0, output: outputTokens }, }).costUSD?.totalUSD : undefined; const outputCostText = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(outputCost ?? 0); return (
Output
); }; 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 ? getUsage({ modelId, usage: { reasoningTokens }, }).costUSD?.totalUSD : undefined; const reasoningCostText = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(reasoningCost ?? 0); return (
Reasoning
); }; 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 ? getUsage({ modelId, usage: { cacheReads: cacheTokens, input: 0, output: 0 }, }).costUSD?.totalUSD : undefined; const cacheCostText = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(cacheCost ?? 0); return (
Cache
); }; const TokensWithCost = ({ tokens, costText }: { tokens?: number; costText?: string }) => ( {tokens === undefined ? '—' : new Intl.NumberFormat('en-US', { notation: 'compact', }).format(tokens)} {costText ? • {costText} : null} );