import type { Component } from "solid-js" interface ContextMeterProps { usedTokens: number availableTokens: number | null formatTokens: (value: number) => string usedLabel: string availableLabel: string class?: string } const LABEL_CLASS = "uppercase text-[10px] tracking-wide text-muted" function clamp(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max) } function resolveFillColor(percent: number): string { if (percent >= 0.8) return "var(--status-error)" if (percent >= 0.6) return "var(--status-warning)" return "var(--status-success)" } export const ContextMeter: Component = (props) => { const hasAvailable = () => typeof props.availableTokens === "number" && props.availableTokens > 0 const used = () => (typeof props.usedTokens === "number" && props.usedTokens > 0 ? props.usedTokens : 0) const available = () => (hasAvailable() ? (props.availableTokens as number) : null) const percent = () => { const usedValue = used() const availableValue = available() if (availableValue === null || availableValue <= 0) return null // Heuristic: if available >= used, treat it like a capacity/limit. // Otherwise treat it like remaining tokens. const ratio = availableValue >= usedValue ? usedValue / availableValue : usedValue / (usedValue + availableValue) return clamp(ratio, 0, 1) } const fillColor = () => { const value = percent() if (value === null) return "var(--border-base)" return resolveFillColor(value) } const percentLabel = () => { const value = percent() if (value === null) return "--" return `${Math.round(value * 100)}%` } const containerClass = `inline-flex items-center gap-2 rounded-full border border-base px-2 py-0.5 text-xs text-primary ${props.class ?? ""}` function polarToCartesian(cx: number, cy: number, r: number, angleDeg: number) { const rad = (angleDeg * Math.PI) / 180 return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad), } } function describeSectorPath(cx: number, cy: number, r: number, startAngle: number, endAngle: number) { const start = polarToCartesian(cx, cy, r, startAngle) const end = polarToCartesian(cx, cy, r, endAngle) const delta = ((endAngle - startAngle) % 360 + 360) % 360 const largeArc = delta > 180 ? 1 : 0 return `M ${cx} ${cy} L ${start.x} ${start.y} A ${r} ${r} 0 ${largeArc} 1 ${end.x} ${end.y} Z` } const circle = () => { const value = percent() const size = 22 const r = 9 const cx = 11 const cy = 11 const progress = value === null ? 0 : value const startAngle = -90 const endAngle = startAngle + progress * 360 const isFull = progress >= 0.999 const hasFill = progress > 0.001 const sectorPath = hasFill && !isFull ? describeSectorPath(cx, cy, r, startAngle, endAngle) : null return ( ) } const tooltipText = () => `Context Used: ${percentLabel()}` return (
{circle()}
{props.usedLabel} {props.formatTokens(used())} / {props.availableLabel} {available() !== null ? props.formatTokens(available() as number) : "--"}
) } export default ContextMeter