import { Icon } from "./Icon"; import { useEffect } from "react"; import { useFetch, useModels } from "../hooks"; import { fmtCompact, fmtCost, fmtDate, fmtNumber, fmtPercent, useT } from "../i18n"; import { useStore } from "../store"; import type { Aggregate, Session } from "../types"; interface SessionStats { total: Aggregate; by_model: (Aggregate & { model: string })[]; by_agent: (Aggregate & { key: string })[]; first: number | null; last: number | null; currency: string; } interface BudgetRow { provider: string; budget: number | null; spent: number; share: number | null; warning: boolean; } function Row({ label, value, hint }: { label: string; value: React.ReactNode; hint?: string }) { return (
{label} {value}
); } const perMillion = (a: Aggregate) => { const tokens = a.input_tokens + a.cache_read_tokens + a.cache_write_tokens + a.output_tokens; return tokens > 0 ? (a.cost / tokens) * 1_000_000 : 0; }; /** Everything about the models used in a session: configuration, usage, speed, cost. */ export function UsageOverlay({ session, onClose }: { session: Session; onClose: () => void }) { const t = useT(); const { data } = useFetch(`/sessions/${session.id}/stats`); const models = useModels(); const isAdmin = useStore((s) => s.me?.role === "admin"); const budget = useFetch<{ providers: BudgetRow[]; currency: string }>(isAdmin ? "/budget" : null).data; useEffect(() => { const h = (e: KeyboardEvent) => e.key === "Escape" && onClose(); window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, [onClose]); const cur = data?.currency ?? "USD"; const info = (spec: string) => { const [p, ...rest] = spec.split("/"); return models.find((x) => x.provider === p)?.models.find((m) => m.id === rest.join("/")); }; const u = session.last_usage; const ctxTokens = u ? u.input_tokens + u.cache_read_tokens + u.cache_write_tokens + u.output_tokens : 0; return (
e.stopPropagation()}>

{t("usage.title")}

{!data ? (
{t("common.loading")}
) : ( <>
{t("stats.cost")}
{fmtCost(data.total.cost, cur)}
{t("stats.perRequest", { v: fmtCost(data.total.requests ? data.total.cost / data.total.requests : 0, cur) })}
{t("stats.requests")}
{fmtNumber(data.total.requests)}
{data.total.errors > 0 &&
{t("stats.errors", { n: data.total.errors })}
}
{t("stats.tokens")}
{fmtCompact(data.total.input_tokens + data.total.cache_read_tokens + data.total.cache_write_tokens)} {fmtCompact(data.total.output_tokens)}
{t("stats.cacheHit")}: {fmtPercent(data.total.cache_hit_rate)}
{t("usage.period")}
{data.first ? fmtDate(data.first) : "–"}
{data.last ? <> {fmtDate(data.last)} : ""}
{data.by_model.map((m) => { const mi = info(m.model); const provider = m.model.split("/")[0]; const b = budget?.providers.find((x) => x.provider === provider); const isCurrent = m.model === session.model; return (

{mi?.name ?? m.model} {isCurrent && {t("usage.current")}} {provider}

{t("usage.model")}
{m.model}} /> {mi.effort ? ` (${mi.effort})` : ""} : "–"} /> : "–"} /> {isCurrent && mi?.context ? ( ) : null}
{t("usage.prices")}
{t("usage.usage")}
{t("usage.speed")}
{t("stats.cost")}
{b && ( {fmtCost(b.spent, budget!.currency)} {b.budget ? ` / ${fmtCost(b.budget, budget!.currency)}` : ""} } /> )}
); })} {data.by_agent.length > 1 && (

{t("usage.byAgent")}

{data.by_agent.map((a) => ( ))}
{t("stats.agent")} {t("stats.requests")} {t("stats.tokens")} {t("stats.cost")}
{a.key} {fmtNumber(a.requests)} {fmtCompact(a.input_tokens + a.cache_read_tokens + a.cache_write_tokens + a.output_tokens)} {fmtCost(a.cost, cur)}
)} )}
); }