import { Button } from "../ui/button"; import { type ContextWindowSnapshot, formatContextWindowTokens } from "~/lib/contextWindow"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; import { formatContextWindowCompactionMessage } from "./ContextWindowMeter.logic"; import { Minimize2Icon } from "lucide-react"; import { composerFloatingLayerProps } from "./composerEventScope"; function formatPercentage(value: number | null): string | null { if (value === null || !Number.isFinite(value)) { return null; } if (value < 10) { return `${value.toFixed(1).replace(/\.0$/, "")}%`; } return `${Math.round(value)}%`; } export function ContextWindowMeter(props: { usage: ContextWindowSnapshot; modelDisplayName?: string | null; onCompact?: (() => void) | undefined; compactDisabled?: boolean | undefined; compactDisabledReason?: string | null | undefined; }) { const { usage, modelDisplayName, onCompact, compactDisabled, compactDisabledReason } = props; const usedPercentage = formatPercentage(usage.usedPercentage); const normalizedPercentage = Math.max(0, Math.min(100, usage.usedPercentage ?? 0)); const radius = 9.75; const circumference = 2 * Math.PI * radius; const dashOffset = circumference * (1 - normalizedPercentage / 100); const totalProcessedTokens = usage.totalProcessedTokens ?? null; const showTotalProcessed = totalProcessedTokens !== null && totalProcessedTokens > 0; const isOverloaded = normalizedPercentage > 90; const usageColor = isOverloaded ? "var(--color-error)" : "color-mix(in oklab, var(--color-muted-foreground) 72%, transparent)"; return ( } />
Context Window
{usage.maxTokens !== null && usedPercentage ? (
{usedPercentage} ยท {formatContextWindowTokens(usage.usedTokens)}/ {formatContextWindowTokens(usage.maxTokens ?? null)}
) : (
{formatContextWindowTokens(usage.usedTokens)}
)}
{usage.maxTokens !== null ? (
) : null} {showTotalProcessed ? (
Total processed {formatContextWindowTokens(totalProcessedTokens)}
) : null} {usage.compactsAutomatically ? (
{formatContextWindowCompactionMessage(modelDisplayName, usage.autoCompactThreshold)}
) : null} {onCompact ? ( <> {compactDisabled && compactDisabledReason ? (
{compactDisabledReason}
) : null} ) : null}
); } /** Holds the meter's footprint while a thread's activities are still loading. */ export function ContextWindowMeterPlaceholder() { return