import { RefreshIcon } from "~/components/ui/refresh-icon";import { useAtomValue } from "@effect/atom-react";import { USAGE_CONTRACT_VERSION, type EnvironmentId, type UsageProviderKind,} from "@t3tools/contracts";import { CircleAlertIcon, ChevronDownIcon, CircleDashedIcon, SlidersHorizontalIcon,} from "lucide-react";import { useMemo, useRef, useState } from "react"; import { isCompatibleUsageContractVersion, isModelCostUnknown, type DailyTotals, type HourlyTotals,} from "@t3tools/shared/usageMerge"; import { isElectron } from "../../env";import { cn } from "../../lib/utils";import { environmentPresentations } from "../../state/presentation";import { serverEnvironment } from "../../state/server";import { useUsage, type EnvironmentUsageStatus } from "../../state/usage";import { useAtomCommand } from "../../state/use-atom-command";import { enumerateDays, enumerateHourStarts, formatCount, formatDateTimeShort, formatDayShort, formatHourShort, formatPercent, formatTokens, formatUsd, makeWindow,} from "@t3tools/shared/usageFormat";import { Button } from "../ui/button";import { Menu, MenuCheckboxItem, MenuItem, MenuPopup, MenuSeparator, MenuTrigger,} from "../ui/menu";import { ScrollArea } from "../ui/scroll-area";import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";import { SidebarInset } from "../ui/sidebar";import { Skeleton } from "../ui/skeleton";import { Toggle, ToggleGroup } from "../ui/toggle-group";import { WorkspaceBreadcrumb, WorkspaceBreadcrumbItem, WorkspaceBreadcrumbSeparator,} from "../WorkspaceBreadcrumb";import { WorkspacePageContainer } from "../WorkspacePageContainer";import { WorkspacePageHeader } from "../WorkspacePageHeader";import { UsageLimitsSection } from "./UsageLimits";import { UsagePriceOverrides } from "./UsagePriceOverrides";import { UsageProviderChart, type UsageChartMetric } from "./UsageProviderChart";import { PROVIDER_ORDER, PROVIDER_PRESENTATION, providersWithUsage } from "./usageProviders";import { readUsagePagePreferences, saveUsagePagePreferences, type UsagePagePreferences,} from "./usagePagePreferences"; type UsageMetric = UsageChartMetric | "limits";const METRIC_OPTIONS = [ { value: "cost", label: "Cost" }, { value: "tokens", label: "Tokens" }, { value: "limits", label: "Limits" },] as const satisfies readonly { value: UsageMetric; label: string }[]; function isUsageMetric(value: string | null | undefined): value is UsageMetric { return METRIC_OPTIONS.some((option) => option.value === value);} const WINDOW_OPTIONS = [ { days: 1, label: "Past 24h" }, { days: 7, label: "7 days" }, { days: 30, label: "30 days" }, { days: 90, label: "90 days" },] as const; function isUsageWindowDays(value: number): value is UsagePagePreferences["windowDays"] { return WINDOW_OPTIONS.some((option) => option.days === value);} export function UsagePage() { const [preferences, setPreferences] = useState(readUsagePagePreferences); const [windowSelection, setWindowSelection] = useState(() => ({ days: preferences.windowDays, window: makeWindow( preferences.windowDays, undefined, preferences.windowDays === 1 ? "hour" : "day", ), })); const metric = preferences.metric; const showingLimits = metric === "limits"; const [isRefreshing, setIsRefreshing] = useState(false); const refreshingRef = useRef(false); const [breakdown, setBreakdown] = useState<"model" | "time">("model"); const [selectedEnvironmentIds, setSelectedEnvironmentIds] = useState<ReadonlySet<EnvironmentId> | null>(null); const { days: windowDays, window } = windowSelection; const isPast24Hours = windowDays === 1; const { merged, environments, selectedEnvironments, isPending, isPartial, refresh } = useUsage( window, selectedEnvironmentIds, ); const presentations = useAtomValue(environmentPresentations.presentationsAtom); const refreshProviders = useAtomCommand(serverEnvironment.refreshProviders, { reportFailure: false, }); const days = useMemo( () => enumerateDays(window.sinceDay, window.untilDay), [window.sinceDay, window.untilDay], ); const hours = useMemo( () => window.sinceTime === undefined || window.untilTime === undefined ? [] : enumerateHourStarts(window.sinceTime, window.untilTime), [window.sinceTime, window.untilTime], ); // Newest first: the window can run 90 periods, so the interesting end // belongs at the top of the table. const breakdownPeriods = useMemo<readonly (DailyTotals | HourlyTotals)[]>( () => (isPast24Hours ? merged.hourly : merged.daily).toReversed(), [isPast24Hours, merged.daily, merged.hourly], ); const breakdownModels = useMemo( () => breakdown === "model" && metric === "tokens" ? merged.models.toSorted( (left, right) => right.totalTokens - left.totalTokens || right.costUsd - left.costUsd, ) : merged.models, [breakdown, merged.models, metric], ); const activeProviders = useMemo(() => providersWithUsage(merged.providers), [merged.providers]); const timeValueColumnWidth = `${60 / (activeProviders.length + 2)}%`; const selectWindow = (days: number) => { if (!isUsageWindowDays(days)) return; const nextPreferences = { metric, windowDays: days }; setPreferences(nextPreferences); saveUsagePagePreferences(nextPreferences); setWindowSelection({ days, window: makeWindow(days, undefined, days === 1 ? "hour" : "day"), }); }; const selectMetric = (nextMetric: UsageMetric) => { const nextPreferences = { metric: nextMetric, windowDays }; setPreferences(nextPreferences); saveUsagePagePreferences(nextPreferences); }; const refreshWindow = () => { if (refreshingRef.current) return; if (showingLimits) { refreshingRef.current = true; setIsRefreshing(true); void Promise.all( Array.from(presentations, ([environmentId, presentation]) => { if (selectedEnvironmentIds !== null && !selectedEnvironmentIds.has(environmentId)) return; if (presentation.connection.phase === "connected" && presentation.serverConfig !== null) { return refreshProviders({ environmentId, input: {} }); } }), ).finally(() => { refreshingRef.current = false; setIsRefreshing(false); }); return; } const nextWindow = makeWindow(windowDays, undefined, isPast24Hours ? "hour" : "day"); if ( nextWindow.sinceDay !== window.sinceDay || nextWindow.untilDay !== window.untilDay || nextWindow.sinceTime !== window.sinceTime || nextWindow.untilTime !== window.untilTime ) { setWindowSelection({ days: windowDays, window: nextWindow }); } refreshingRef.current = true; setIsRefreshing(true); void refresh(nextWindow).finally(() => { refreshingRef.current = false; setIsRefreshing(false); }); }; const windowLabel = isPast24Hours && window.sinceTime !== undefined && window.untilTime !== undefined ? `${formatDateTimeShort(window.sinceTime, window.timeZone)} to ${formatDateTimeShort(window.untilTime, window.timeZone)}` : `${formatDayShort(window.sinceDay)} to ${formatDayShort(window.untilDay)}`; const topbarContent = ( <div className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-2 py-2 xl:flex"> <WorkspaceBreadcrumb ariaLabel="Usage breadcrumb" className="col-span-2 min-w-0"> <WorkspaceBreadcrumbItem> <h1>Usage</h1> </WorkspaceBreadcrumbItem> <WorkspaceBreadcrumbSeparator /> <WorkspaceBreadcrumbItem current className="min-w-10"> <UsageEnvironmentFilter environments={environments} selectedEnvironments={selectedEnvironments} selectedEnvironmentIds={selectedEnvironmentIds} onSelectionChange={setSelectedEnvironmentIds} showUsageStatus={!showingLimits} isPartial={isPartial} duplicateSources={merged.duplicateSources} staleEnvironments={merged.staleEnvironments} /> </WorkspaceBreadcrumbItem> </WorkspaceBreadcrumb> {!showingLimits ? ( <span className="hidden min-w-0 truncate text-xs text-muted-foreground 2xl:block"> {windowLabel} </span> ) : null} <div className="ms-auto hidden min-w-0 items-center justify-end gap-2 xl:flex"> <ToggleGroup aria-label="Usage metric" variant="segmented" value={[metric]} onValueChange={(next) => { const value = next[0]; if (isUsageMetric(value)) selectMetric(value); }} > {METRIC_OPTIONS.map((option) => ( <Toggle key={option.value} value={option.value}> {option.label} </Toggle> ))} </ToggleGroup> {/* The period does not apply to Limits, so it stays in place but disabled; unmounting it shifted the metric toggle ~300px. */} <ToggleGroup aria-label="Usage period" variant="segmented" value={[String(windowDays)]} disabled={showingLimits} onValueChange={(next) => { const value = next[0]; if (value) selectWindow(Number(value)); }} > {WINDOW_OPTIONS.map((option) => ( <Toggle key={option.days} value={String(option.days)}> {option.label} </Toggle> ))} </ToggleGroup> <Button onClick={refreshWindow} aria-label={showingLimits ? "Refresh limits" : "Refresh usage"} aria-busy={isRefreshing} disabled={isRefreshing} size="icon-sm" variant="ghost" > <RefreshIcon className="size-3.5" refreshing={isRefreshing} /> </Button> </div> <div className="col-span-2 ms-auto flex min-w-0 items-center justify-end gap-1 xl:hidden"> <Select value={metric} onValueChange={(value) => { if (isUsageMetric(value)) selectMetric(value); }} > <SelectTrigger aria-label="Usage metric" size="compact" variant="ghost" className="w-auto min-w-0" > <SelectValue> {METRIC_OPTIONS.find((option) => option.value === metric)?.label} </SelectValue> </SelectTrigger> <SelectPopup align="end" alignItemWithTrigger={false}> {METRIC_OPTIONS.map((option) => ( <SelectItem key={option.value} value={option.value}> {option.label} </SelectItem> ))} </SelectPopup> </Select> <Select value={String(windowDays)} disabled={showingLimits} onValueChange={(value) => selectWindow(Number(value))} > <SelectTrigger aria-label="Usage period" size="compact" variant="ghost" className="w-auto min-w-0" > <SelectValue> {WINDOW_OPTIONS.find((option) => option.days === windowDays)?.label} </SelectValue> </SelectTrigger> <SelectPopup align="end" alignItemWithTrigger={false}> {WINDOW_OPTIONS.map((option) => ( <SelectItem key={option.days} value={String(option.days)}> {option.label} </SelectItem> ))} </SelectPopup> </Select> <Button onClick={refreshWindow} aria-label={showingLimits ? "Refresh limits" : "Refresh usage"} aria-busy={isRefreshing} disabled={isRefreshing} size="icon-sm" variant="ghost" > <RefreshIcon className="size-3.5" refreshing={isRefreshing} /> </Button> </div> </div> ); return ( <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate"> <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground"> <WorkspacePageHeader electron={isElectron} className="h-auto"> {topbarContent} </WorkspacePageHeader> <ScrollArea className="min-h-0 flex-1"> <WorkspacePageContainer width="wide"> {selectedEnvironments.length === 0 ? ( <p className="text-sm text-muted-foreground"> {environments.length === 0 ? `Connect an environment to see ${showingLimits ? "limits" : "usage"}.` : `Select an environment to see ${showingLimits ? "limits" : "usage"}.`} </p> ) : showingLimits ? ( <UsageLimitsSection selectedEnvironmentIds={selectedEnvironmentIds} /> ) : isPending ? ( <UsageSkeleton /> ) : ( <> <section className="grid gap-6 lg:grid-cols-[minmax(0,18rem)_minmax(0,1fr)]"> <div className="flex min-w-0 flex-col gap-5"> <div className="flex flex-col gap-1"> <span className="text-4xl font-semibold text-foreground tabular-nums"> {metric === "cost" ? formatUsd(merged.costUsd) : formatTokens(merged.totalTokens)} </span> <span className="text-xs text-muted-foreground"> {metric !== "cost" ? `${formatCount(merged.sessions)} sessions` : merged.costQuality.unpricedShare > 0 ? `${formatCount(merged.sessions)} sessions · API estimate excludes ${formatPercent( merged.costQuality.unpricedShare, )} unpriced records` : `${formatCount(merged.sessions)} sessions · API estimate`} </span> </div> {activeProviders.map((provider) => { const totals = merged.providers.find((entry) => entry.provider === provider); const share = metric === "cost" ? (totals?.costShare ?? 0) : (totals?.tokenShare ?? 0); const providerSessions = totals?.sessions ?? 0; const sessionLabel = `${formatCount(providerSessions)} ${ providerSessions === 1 ? "session" : "sessions" }`; return ( <div key={provider} className="flex flex-col gap-1"> <div className="flex items-baseline justify-between gap-4"> <span className="flex min-w-0 items-center gap-2 text-sm text-foreground"> <span aria-hidden className="size-2 shrink-0 rounded-full" style={{ backgroundColor: PROVIDER_PRESENTATION[provider].color, }} /> <ProviderMark provider={provider} className="size-4" /> <span className="flex min-w-0 items-baseline gap-1.5"> <span className="truncate"> {PROVIDER_PRESENTATION[provider].label} </span> <span className="shrink-0 whitespace-nowrap text-[11px] text-muted-foreground tabular-nums"> {sessionLabel} </span> </span> </span> <span className="shrink-0 text-sm font-medium text-foreground tabular-nums"> {metric === "cost" ? formatUsd(totals?.costUsd ?? 0) : formatTokens(totals?.totalTokens ?? 0)} </span> </div> <span className="text-xs text-muted-foreground"> {metric === "cost" ? `${formatPercent(share)} of cost · ${formatTokens(totals?.totalTokens ?? 0)} tokens` : `${formatPercent(share)} of tokens · ${formatUsd(totals?.costUsd ?? 0)}`} </span> </div> ); })} </div> <div className="flex min-w-0 flex-col gap-3"> <h2 className="text-sm font-medium text-foreground"> {isPast24Hours ? "Hourly" : "Daily"}{" "} {metric === "tokens" ? "processed tokens" : "cost"} </h2> <UsageProviderChart providers={activeProviders} days={days} daily={merged.daily} hours={hours} hourly={merged.hourly} metric={metric} referenceTime={window.untilTime} resolution={isPast24Hours ? "hour" : "day"} timeZone={window.timeZone} /> </div> </section> <section className="flex flex-col gap-2"> <h2 className="text-sm font-medium text-foreground">Totals</h2> <div className="grid grid-cols-2 gap-x-6 gap-y-4 py-1 md:grid-cols-5"> <Metric label="Processed tokens" value={formatTokens(merged.totalTokens)} /> <Metric label="Cached input" value={formatTokens(merged.cachedInputTokens)} /> <Metric label="Uncached input" value={formatTokens(merged.uncachedInputTokens)} /> <Metric label="Output" value={formatTokens(merged.outputTokens)} /> <Metric label="Cache savings" value={formatUsd(merged.costQuality.cacheSavingsUsd)} /> </div> </section> <section className="flex flex-col gap-3"> <div className="flex items-center justify-between gap-3"> <h2 className="text-sm font-medium text-foreground">Breakdown</h2> <ToggleGroup aria-label="Usage breakdown" variant="segmented" value={[breakdown]} onValueChange={(next) => { const value = next[0]; if (value === "model" || value === "time") setBreakdown(value); }} > {( [ { value: "model", label: "Model" }, { value: "time", label: isPast24Hours ? "Hour" : "Day" }, ] as const ).map((option) => ( <Toggle key={option.value} value={option.value}> {option.label} </Toggle> ))} </ToggleGroup> </div> {breakdown === "model" ? ( <table className="w-full table-fixed text-sm"> <colgroup> <col className="w-2/5" /> <col className="w-1/5" /> <col className="w-1/5" /> <col className="w-1/5" /> </colgroup> <thead> <tr className="border-b border-border text-left text-xs text-muted-foreground"> <th className="py-2 font-normal">Model</th> <th className="py-2 text-right font-normal">Cost</th> <th className="py-2 text-right font-normal">Share</th> <th className="py-2 text-right font-normal">Tokens</th> </tr> </thead> <tbody> {breakdownModels.length === 0 ? (Lines 1–500 of 884. Load more
import { RefreshIcon } from "~/components/ui/refresh-icon"; import { useAtomValue } from "@effect/atom-react"; import { USAGE_CONTRACT_VERSION, type EnvironmentId, type UsageProviderKind, } from "@t3tools/contracts"; import { CircleAlertIcon, ChevronDownIcon, CircleDashedIcon, SlidersHorizontalIcon, } from "lucide-react"; import { useMemo, useRef, useState } from "react"; import { isCompatibleUsageContractVersion, isModelCostUnknown, type DailyTotals, type HourlyTotals, } from "@t3tools/shared/usageMerge"; import { isElectron } from "../../env"; import { cn } from "../../lib/utils"; import { environmentPresentations } from "../../state/presentation"; import { serverEnvironment } from "../../state/server"; import { useUsage, type EnvironmentUsageStatus } from "../../state/usage"; import { useAtomCommand } from "../../state/use-atom-command"; import { enumerateDays, enumerateHourStarts, formatCount, formatDateTimeShort, formatDayShort, formatHourShort, formatPercent, formatTokens, formatUsd, makeWindow, } from "@t3tools/shared/usageFormat"; import { Button } from "../ui/button"; import { Menu, MenuCheckboxItem, MenuItem, MenuPopup, MenuSeparator, MenuTrigger, } from "../ui/menu"; import { ScrollArea } from "../ui/scroll-area"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { SidebarInset } from "../ui/sidebar"; import { Skeleton } from "../ui/skeleton"; import { Toggle, ToggleGroup } from "../ui/toggle-group"; import { WorkspaceBreadcrumb, WorkspaceBreadcrumbItem, WorkspaceBreadcrumbSeparator, } from "../WorkspaceBreadcrumb"; import { WorkspacePageContainer } from "../WorkspacePageContainer"; import { WorkspacePageHeader } from "../WorkspacePageHeader"; import { UsageLimitsSection } from "./UsageLimits"; import { UsagePriceOverrides } from "./UsagePriceOverrides"; import { UsageProviderChart, type UsageChartMetric } from "./UsageProviderChart"; import { PROVIDER_ORDER, PROVIDER_PRESENTATION, providersWithUsage } from "./usageProviders"; import { readUsagePagePreferences, saveUsagePagePreferences, type UsagePagePreferences, } from "./usagePagePreferences"; type UsageMetric = UsageChartMetric | "limits"; const METRIC_OPTIONS = [ { value: "cost", label: "Cost" }, { value: "tokens", label: "Tokens" }, { value: "limits", label: "Limits" }, ] as const satisfies readonly { value: UsageMetric; label: string }[]; function isUsageMetric(value: string | null | undefined): value is UsageMetric { return METRIC_OPTIONS.some((option) => option.value === value); } const WINDOW_OPTIONS = [ { days: 1, label: "Past 24h" }, { days: 7, label: "7 days" }, { days: 30, label: "30 days" }, { days: 90, label: "90 days" }, ] as const; function isUsageWindowDays(value: number): value is UsagePagePreferences["windowDays"] { return WINDOW_OPTIONS.some((option) => option.days === value); } export function UsagePage() { const [preferences, setPreferences] = useState(readUsagePagePreferences); const [windowSelection, setWindowSelection] = useState(() => ({ days: preferences.windowDays, window: makeWindow( preferences.windowDays, undefined, preferences.windowDays === 1 ? "hour" : "day", ), })); const metric = preferences.metric; const showingLimits = metric === "limits"; const [isRefreshing, setIsRefreshing] = useState(false); const refreshingRef = useRef(false); const [breakdown, setBreakdown] = useState<"model" | "time">("model"); const [selectedEnvironmentIds, setSelectedEnvironmentIds] = useState<ReadonlySet<EnvironmentId> | null>(null); const { days: windowDays, window } = windowSelection; const isPast24Hours = windowDays === 1; const { merged, environments, selectedEnvironments, isPending, isPartial, refresh } = useUsage( window, selectedEnvironmentIds, ); const presentations = useAtomValue(environmentPresentations.presentationsAtom); const refreshProviders = useAtomCommand(serverEnvironment.refreshProviders, { reportFailure: false, }); const days = useMemo( () => enumerateDays(window.sinceDay, window.untilDay), [window.sinceDay, window.untilDay], ); const hours = useMemo( () => window.sinceTime === undefined || window.untilTime === undefined ? [] : enumerateHourStarts(window.sinceTime, window.untilTime), [window.sinceTime, window.untilTime], ); // Newest first: the window can run 90 periods, so the interesting end // belongs at the top of the table. const breakdownPeriods = useMemo<readonly (DailyTotals | HourlyTotals)[]>( () => (isPast24Hours ? merged.hourly : merged.daily).toReversed(), [isPast24Hours, merged.daily, merged.hourly], ); const breakdownModels = useMemo( () => breakdown === "model" && metric === "tokens" ? merged.models.toSorted( (left, right) => right.totalTokens - left.totalTokens || right.costUsd - left.costUsd, ) : merged.models, [breakdown, merged.models, metric], ); const activeProviders = useMemo(() => providersWithUsage(merged.providers), [merged.providers]); const timeValueColumnWidth = `${60 / (activeProviders.length + 2)}%`; const selectWindow = (days: number) => { if (!isUsageWindowDays(days)) return; const nextPreferences = { metric, windowDays: days }; setPreferences(nextPreferences); saveUsagePagePreferences(nextPreferences); setWindowSelection({ days, window: makeWindow(days, undefined, days === 1 ? "hour" : "day"), }); }; const selectMetric = (nextMetric: UsageMetric) => { const nextPreferences = { metric: nextMetric, windowDays }; setPreferences(nextPreferences); saveUsagePagePreferences(nextPreferences); }; const refreshWindow = () => { if (refreshingRef.current) return; if (showingLimits) { refreshingRef.current = true; setIsRefreshing(true); void Promise.all( Array.from(presentations, ([environmentId, presentation]) => { if (selectedEnvironmentIds !== null && !selectedEnvironmentIds.has(environmentId)) return; if (presentation.connection.phase === "connected" && presentation.serverConfig !== null) { return refreshProviders({ environmentId, input: {} }); } }), ).finally(() => { refreshingRef.current = false; setIsRefreshing(false); }); return; } const nextWindow = makeWindow(windowDays, undefined, isPast24Hours ? "hour" : "day"); if ( nextWindow.sinceDay !== window.sinceDay || nextWindow.untilDay !== window.untilDay || nextWindow.sinceTime !== window.sinceTime || nextWindow.untilTime !== window.untilTime ) { setWindowSelection({ days: windowDays, window: nextWindow }); } refreshingRef.current = true; setIsRefreshing(true); void refresh(nextWindow).finally(() => { refreshingRef.current = false; setIsRefreshing(false); }); }; const windowLabel = isPast24Hours && window.sinceTime !== undefined && window.untilTime !== undefined ? `${formatDateTimeShort(window.sinceTime, window.timeZone)} to ${formatDateTimeShort(window.untilTime, window.timeZone)}` : `${formatDayShort(window.sinceDay)} to ${formatDayShort(window.untilDay)}`; const topbarContent = ( <div className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-2 py-2 xl:flex"> <WorkspaceBreadcrumb ariaLabel="Usage breadcrumb" className="col-span-2 min-w-0"> <WorkspaceBreadcrumbItem> <h1>Usage</h1> </WorkspaceBreadcrumbItem> <WorkspaceBreadcrumbSeparator /> <WorkspaceBreadcrumbItem current className="min-w-10"> <UsageEnvironmentFilter environments={environments} selectedEnvironments={selectedEnvironments} selectedEnvironmentIds={selectedEnvironmentIds} onSelectionChange={setSelectedEnvironmentIds} showUsageStatus={!showingLimits} isPartial={isPartial} duplicateSources={merged.duplicateSources} staleEnvironments={merged.staleEnvironments} /> </WorkspaceBreadcrumbItem> </WorkspaceBreadcrumb> {!showingLimits ? ( <span className="hidden min-w-0 truncate text-xs text-muted-foreground 2xl:block"> {windowLabel} </span> ) : null} <div className="ms-auto hidden min-w-0 items-center justify-end gap-2 xl:flex"> <ToggleGroup aria-label="Usage metric" variant="segmented" value={[metric]} onValueChange={(next) => { const value = next[0]; if (isUsageMetric(value)) selectMetric(value); }} > {METRIC_OPTIONS.map((option) => ( <Toggle key={option.value} value={option.value}> {option.label} </Toggle> ))} </ToggleGroup> {/* The period does not apply to Limits, so it stays in place but disabled; unmounting it shifted the metric toggle ~300px. */} <ToggleGroup aria-label="Usage period" variant="segmented" value={[String(windowDays)]} disabled={showingLimits} onValueChange={(next) => { const value = next[0]; if (value) selectWindow(Number(value)); }} > {WINDOW_OPTIONS.map((option) => ( <Toggle key={option.days} value={String(option.days)}> {option.label} </Toggle> ))} </ToggleGroup> <Button onClick={refreshWindow} aria-label={showingLimits ? "Refresh limits" : "Refresh usage"} aria-busy={isRefreshing} disabled={isRefreshing} size="icon-sm" variant="ghost" > <RefreshIcon className="size-3.5" refreshing={isRefreshing} /> </Button> </div> <div className="col-span-2 ms-auto flex min-w-0 items-center justify-end gap-1 xl:hidden"> <Select value={metric} onValueChange={(value) => { if (isUsageMetric(value)) selectMetric(value); }} > <SelectTrigger aria-label="Usage metric" size="compact" variant="ghost" className="w-auto min-w-0" > <SelectValue> {METRIC_OPTIONS.find((option) => option.value === metric)?.label} </SelectValue> </SelectTrigger> <SelectPopup align="end" alignItemWithTrigger={false}> {METRIC_OPTIONS.map((option) => ( <SelectItem key={option.value} value={option.value}> {option.label} </SelectItem> ))} </SelectPopup> </Select> <Select value={String(windowDays)} disabled={showingLimits} onValueChange={(value) => selectWindow(Number(value))} > <SelectTrigger aria-label="Usage period" size="compact" variant="ghost" className="w-auto min-w-0" > <SelectValue> {WINDOW_OPTIONS.find((option) => option.days === windowDays)?.label} </SelectValue> </SelectTrigger> <SelectPopup align="end" alignItemWithTrigger={false}> {WINDOW_OPTIONS.map((option) => ( <SelectItem key={option.days} value={String(option.days)}> {option.label} </SelectItem> ))} </SelectPopup> </Select> <Button onClick={refreshWindow} aria-label={showingLimits ? "Refresh limits" : "Refresh usage"} aria-busy={isRefreshing} disabled={isRefreshing} size="icon-sm" variant="ghost" > <RefreshIcon className="size-3.5" refreshing={isRefreshing} /> </Button> </div> </div> ); return ( <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate"> <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground"> <WorkspacePageHeader electron={isElectron} className="h-auto"> {topbarContent} </WorkspacePageHeader> <ScrollArea className="min-h-0 flex-1"> <WorkspacePageContainer width="wide"> {selectedEnvironments.length === 0 ? ( <p className="text-sm text-muted-foreground"> {environments.length === 0 ? `Connect an environment to see ${showingLimits ? "limits" : "usage"}.` : `Select an environment to see ${showingLimits ? "limits" : "usage"}.`} </p> ) : showingLimits ? ( <UsageLimitsSection selectedEnvironmentIds={selectedEnvironmentIds} /> ) : isPending ? ( <UsageSkeleton /> ) : ( <> <section className="grid gap-6 lg:grid-cols-[minmax(0,18rem)_minmax(0,1fr)]"> <div className="flex min-w-0 flex-col gap-5"> <div className="flex flex-col gap-1"> <span className="text-4xl font-semibold text-foreground tabular-nums"> {metric === "cost" ? formatUsd(merged.costUsd) : formatTokens(merged.totalTokens)} </span> <span className="text-xs text-muted-foreground"> {metric !== "cost" ? `${formatCount(merged.sessions)} sessions` : merged.costQuality.unpricedShare > 0 ? `${formatCount(merged.sessions)} sessions · API estimate excludes ${formatPercent( merged.costQuality.unpricedShare, )} unpriced records` : `${formatCount(merged.sessions)} sessions · API estimate`} </span> </div> {activeProviders.map((provider) => { const totals = merged.providers.find((entry) => entry.provider === provider); const share = metric === "cost" ? (totals?.costShare ?? 0) : (totals?.tokenShare ?? 0); const providerSessions = totals?.sessions ?? 0; const sessionLabel = `${formatCount(providerSessions)} ${ providerSessions === 1 ? "session" : "sessions" }`; return ( <div key={provider} className="flex flex-col gap-1"> <div className="flex items-baseline justify-between gap-4"> <span className="flex min-w-0 items-center gap-2 text-sm text-foreground"> <span aria-hidden className="size-2 shrink-0 rounded-full" style={{ backgroundColor: PROVIDER_PRESENTATION[provider].color, }} /> <ProviderMark provider={provider} className="size-4" /> <span className="flex min-w-0 items-baseline gap-1.5"> <span className="truncate"> {PROVIDER_PRESENTATION[provider].label} </span> <span className="shrink-0 whitespace-nowrap text-[11px] text-muted-foreground tabular-nums"> {sessionLabel} </span> </span> </span> <span className="shrink-0 text-sm font-medium text-foreground tabular-nums"> {metric === "cost" ? formatUsd(totals?.costUsd ?? 0) : formatTokens(totals?.totalTokens ?? 0)} </span> </div> <span className="text-xs text-muted-foreground"> {metric === "cost" ? `${formatPercent(share)} of cost · ${formatTokens(totals?.totalTokens ?? 0)} tokens` : `${formatPercent(share)} of tokens · ${formatUsd(totals?.costUsd ?? 0)}`} </span> </div> ); })} </div> <div className="flex min-w-0 flex-col gap-3"> <h2 className="text-sm font-medium text-foreground"> {isPast24Hours ? "Hourly" : "Daily"}{" "} {metric === "tokens" ? "processed tokens" : "cost"} </h2> <UsageProviderChart providers={activeProviders} days={days} daily={merged.daily} hours={hours} hourly={merged.hourly} metric={metric} referenceTime={window.untilTime} resolution={isPast24Hours ? "hour" : "day"} timeZone={window.timeZone} /> </div> </section> <section className="flex flex-col gap-2"> <h2 className="text-sm font-medium text-foreground">Totals</h2> <div className="grid grid-cols-2 gap-x-6 gap-y-4 py-1 md:grid-cols-5"> <Metric label="Processed tokens" value={formatTokens(merged.totalTokens)} /> <Metric label="Cached input" value={formatTokens(merged.cachedInputTokens)} /> <Metric label="Uncached input" value={formatTokens(merged.uncachedInputTokens)} /> <Metric label="Output" value={formatTokens(merged.outputTokens)} /> <Metric label="Cache savings" value={formatUsd(merged.costQuality.cacheSavingsUsd)} /> </div> </section> <section className="flex flex-col gap-3"> <div className="flex items-center justify-between gap-3"> <h2 className="text-sm font-medium text-foreground">Breakdown</h2> <ToggleGroup aria-label="Usage breakdown" variant="segmented" value={[breakdown]} onValueChange={(next) => { const value = next[0]; if (value === "model" || value === "time") setBreakdown(value); }} > {( [ { value: "model", label: "Model" }, { value: "time", label: isPast24Hours ? "Hour" : "Day" }, ] as const ).map((option) => ( <Toggle key={option.value} value={option.value}> {option.label} </Toggle> ))} </ToggleGroup> </div> {breakdown === "model" ? ( <table className="w-full table-fixed text-sm"> <colgroup> <col className="w-2/5" /> <col className="w-1/5" /> <col className="w-1/5" /> <col className="w-1/5" /> </colgroup> <thead> <tr className="border-b border-border text-left text-xs text-muted-foreground"> <th className="py-2 font-normal">Model</th> <th className="py-2 text-right font-normal">Cost</th> <th className="py-2 text-right font-normal">Share</th> <th className="py-2 text-right font-normal">Tokens</th> </tr> </thead> <tbody> {breakdownModels.length === 0 ? (