"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} );