"use client"; import { motion, useReducedMotion } from "motion/react"; import { type KeyboardEvent, type PointerEvent, useId, useMemo, useState } from "react"; import { SPRING_LAYOUT } from "@/lib/ease"; import { cn } from "@/lib/utils"; import { summarizeUsage, type DailyUsage, type UsageBreakdown, type UsageRecord, type UsageTotals, } from "./usage-dashboard-data"; import { UsageProviderMark } from "./usage-provider-mark"; export type { UsageRecord } from "./usage-dashboard-data"; export interface UsageDashboardProps { records: UsageRecord[]; title?: string; /** Controlled range, in UTC calendar days. */ range?: 7 | 30; defaultRange?: 7 | 30; onRangeChange?: (range: 7 | 30) => void; className?: string; } type AgentSeries = { name: string; values: number[] }; const seriesColors = [ "var(--usage-first)", "var(--usage-second)", "var(--usage-third)", "var(--usage-fourth)", ]; const compact = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1, }); const integer = new Intl.NumberFormat("en-US"); const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, maximumFractionDigits: 2, }); function formatMoney(value: number) { return value > 0 && value < 0.01 ? "<$0.01" : money.format(value); } function formatDate(date: string, options: Intl.DateTimeFormatOptions) { return new Intl.DateTimeFormat("en-US", { timeZone: "UTC", ...options }).format( new Date(`${date}T00:00:00.000Z`), ); } function tokenCount(totals: UsageTotals) { return totals.inputTokens + totals.outputTokens; } function chartPath(values: number[], max: number) { return values .map((value, index) => { const x = 46 + (index / Math.max(values.length - 1, 1)) * 684; const y = 22 + 178 * (1 - value / max); return `${index === 0 ? "M" : "L"}${x.toFixed(2)} ${y.toFixed(2)}`; }) .join(" "); } function AgentChart({ daily, series, }: { daily: DailyUsage[]; series: AgentSeries[]; }) { const gradientId = useId().replaceAll(":", ""); const [activeIndex, setActiveIndex] = useState(null); const peak = Math.max(1, ...series.flatMap((item) => item.values)); const step = 10 ** Math.floor(Math.log10(peak)); const max = Math.ceil((peak * 1.16) / step) * step; const activeDay = activeIndex === null ? null : daily[activeIndex]; const activeX = activeIndex === null ? 0 : 46 + (activeIndex / Math.max(daily.length - 1, 1)) * 684; const labels = [0, Math.floor((daily.length - 1) / 2), daily.length - 1]; const inspectPointer = (event: PointerEvent) => { const bounds = event.currentTarget.getBoundingClientRect(); const plotX = ((event.clientX - bounds.left) / bounds.width) * 760; const index = Math.round(((plotX - 46) / 684) * (daily.length - 1)); setActiveIndex(Math.max(0, Math.min(daily.length - 1, index))); }; const inspectKeyboard = (event: KeyboardEvent) => { if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return; event.preventDefault(); setActiveIndex((previous) => { if (event.key === "Home") return 0; if (event.key === "End") return daily.length - 1; const current = previous ?? daily.length - 1; return Math.max(0, Math.min(daily.length - 1, current + (event.key === "ArrowRight" ? 1 : -1))); }); }; return (

Daily processed tokens

{series.map((item, index) => ( {item.name} ))}
{activeDay ? (

{formatDate(activeDay.date, { month: "short", day: "numeric" })}

{series.map((item) => (

{item.name} {compact.format(item.values[activeIndex ?? 0])}

))}
) : (

Inspect a day

)}

Use the Day tab in Breakdown for exact daily values.

); } function AgentSummary({ agents, totals }: { agents: UsageBreakdown[]; totals: UsageTotals }) { return (

Processed tokens

{compact.format(tokenCount(totals))}

Across {integer.format(totals.requests)} requests

    {agents.map((agent, index) => (
  • {agent.name} {compact.format(tokenCount(agent))}
    {agent.provider} · {Math.round((tokenCount(agent) / Math.max(tokenCount(totals), 1)) * 100)}% {formatMoney(agent.costUsd)}
  • ))}
); } function Total({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function Breakdown({ models, daily, totals, reduceMotion, layoutId, view, onViewChange, }: { models: UsageBreakdown[]; daily: DailyUsage[]; totals: UsageTotals; reduceMotion: boolean; layoutId: string; view: "model" | "day"; onViewChange: (view: "model" | "day") => void; }) { const [dayPage, setDayPage] = useState(0); const pageCount = Math.ceil(daily.length / 7); const pageIndex = Math.min(dayPage, pageCount - 1); const pageEnd = daily.length - pageIndex * 7; const visibleDays = daily.slice(Math.max(0, pageEnd - 7), pageEnd); return (

Breakdown

Breakdown view {(["model", "day"] as const).map((option) => ( ))}
{view === "model" ? ( {models.map((model) => ( ))}
ModelShareCostTokens
{model.name}{model.provider} {Math.round((tokenCount(model) / Math.max(tokenCount(totals), 1)) * 100)}% {formatMoney(model.costUsd)} {compact.format(tokenCount(model))}
) : (
{visibleDays.map((day) => ( ))}
Day · UTCInputOutputCostTokens
{formatDate(day.date, { month: "short", day: "numeric" })} {integer.format(day.inputTokens)} {integer.format(day.outputTokens)} {formatMoney(day.costUsd)} {compact.format(tokenCount(day))}
{pageCount > 1 && (

{formatDate(visibleDays[0].date, { month: "short", day: "numeric" })} – {formatDate(visibleDays[visibleDays.length - 1].date, { month: "short", day: "numeric" })} · {visibleDays.length} of {daily.length} days

)}
)}
); } /** Provider-billed usage overview. Pass real per-day records; the component never estimates prices. */ export function UsageDashboard({ records, title = "Usage overview", range, defaultRange = 7, onRangeChange, className, }: UsageDashboardProps) { const reduceMotion = useReducedMotion() ?? false; const rangeId = useId(); const breakdownId = useId(); const [localRange, setLocalRange] = useState<7 | 30>(defaultRange); const [breakdownView, setBreakdownView] = useState<"model" | "day">("model"); const selectedRange = range ?? localRange; const usage = useMemo(() => summarizeUsage(records, selectedRange), [records, selectedRange]); const changeRange = (next: 7 | 30) => { if (range === undefined) setLocalRange(next); onRangeChange?.(next); }; return (

{title}

{usage ? `${formatDate(usage.startDate, { month: "short", day: "numeric" })} – ${formatDate(usage.endDate, { month: "short", day: "numeric", year: "numeric" })} · UTC` : "No usage data yet"}

Usage period {([7, 30] as const).map((days) => ( ))}
{!usage || usage.recordCount === 0 ? (

No usage in this period

Pass daily, provider-billed records to see agent and model usage.

) : (

Totals

)}
); }