import { type EnvironmentId, type ProviderConsumeResetCreditOutcome, ProviderConsumeResetCreditInput, ServerProvider, ServerProviderResetCredits, ServerProviderUsageWindow, UsageProviderKind, } from "@t3tools/contracts"; import { useAtomValue } from "@effect/atom-react"; import { elapsedShare, formatDuration, formatResetsIn, type LimitPace, paceOf, remainingPercent, } from "@t3tools/shared/usageLimits"; import { GaugeIcon, TrendingDownIcon, TrendingUpIcon } from "lucide-react"; import { Fragment, useState } from "react"; import { usePrimarySettings } from "../../hooks/useSettings"; import { environmentPresentations } from "../../state/presentation"; import { serverEnvironment } from "../../state/server"; import { useAtomCommand } from "../../state/use-atom-command"; import { formatUpcomingTimestamp } from "../../timestampFormat"; import { AlertDialog, AlertDialogClose, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogPopup, AlertDialogTitle, } from "../ui/alert-dialog"; import { Button } from "../ui/button"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { UsageLimitsPooled } from "./UsageLimitsPooled"; import { PROVIDER_PRESENTATION } from "./usageProviders"; const PACE: Record = { ahead: { label: "Ahead of pace: spending faster than the window elapses", icon: TrendingUpIcon }, on: { label: "On pace with the window", icon: GaugeIcon }, under: { label: "Under pace: headroom left for the rest of the window", icon: TrendingDownIcon }, }; /** The series colour the cost chart uses for this driver, so the two views read as one. */ export function barColor(driver: ServerProvider["driver"]): string { const kind: UsageProviderKind | undefined = driver === "codex" ? "codex" : driver === "claudeAgent" ? "claude" : undefined; return kind ? PROVIDER_PRESENTATION[kind].color : "var(--foreground)"; } /** Pace as a glyph with the words on hover. */ export function PaceIcon({ pace }: { readonly pace: LimitPace }) { const Icon = PACE[pace].icon; return ( } > {PACE[pace].label} ); } /** * One window as a full-width bar from the moment it opened to its reset. * The fill is the share of quota spent; the hairline is how far into the * window the clock is, which is also where even spending would have put the * fill. Hover for the exact figures and reset time. */ function WindowBar({ color, window, now, }: { readonly color: string; readonly window: ServerProviderUsageWindow; readonly now: number; }) { const timestampFormat = usePrimarySettings((settings) => settings.timestampFormat); const remaining = remainingPercent(window); const elapsed = elapsedShare(window, now); // The fill is quota left, so the even-spending mark is the time left. const timeLeft = elapsed === null ? null : Math.round((1 - elapsed) * 100); const resetsIn = formatResetsIn(window, now); const resetsAt = window.resetsAt ? formatUpcomingTimestamp(window.resetsAt, timestampFormat, now) : null; const summary = `${window.label}: ${remaining}% left${ timeLeft === null ? "" : `, ${timeLeft}% of the window left` }${resetsIn ? `, ${resetsIn}` : ""}`; return ( } >
{remaining > 0 ? (
) : null} {timeLeft !== null ? ( ) : null}
{remaining}% left{timeLeft !== null ? ` · ${timeLeft}% of the window left` : ""} {timeLeft !== null ? ( The line is where even spending would be. ) : null} {resetsAt ? ( Resets {resetsAt} {resetsIn ? ` · ${resetsIn}` : ""} ) : null}
); } /** * One account's windows as rows: label and percent, bar, pace and countdown. * Compact rows fit the composer panel with narrower columns. */ export function LimitWindows({ driver, windows, now, compact = false, }: { readonly driver: ServerProvider["driver"]; readonly windows: ReadonlyArray; readonly now: number; readonly compact?: boolean; }) { const color = barColor(driver); return (
{windows.map((window) => { const pace = paceOf(window, now); const resetsIn = formatResetsIn(window, now); return ( {window.label} {remainingPercent(window)}% left {pace ? : null} {resetsIn ?? ""} ); })}
); } const OUTCOME_TEXT: Record = { reset: "Reset applied. Your windows have cleared.", nothingToReset: "Nothing to reset right now.", noCredit: "No reset credit left.", alreadyRedeemed: "That credit was already redeemed.", }; /** Everything a redeem needs: where to send it and what to say afterwards. */ export function useResetCredit( environmentId: EnvironmentId, input: ProviderConsumeResetCreditInput, ) { const consume = useAtomCommand(serverEnvironment.consumeResetCredit, { reportFailure: false }); const [confirming, setConfirming] = useState(false); const [busy, setBusy] = useState(false); const [status, setStatus] = useState(null); const redeem = async () => { setConfirming(false); setBusy(true); setStatus(null); const result = await consume({ environmentId, input }); setBusy(false); if (result._tag === "Success") { setStatus(result.value.warning ?? OUTCOME_TEXT[result.value.outcome]); return; } setStatus( "error" in result.cause && result.cause.error instanceof Error ? result.cause.error.message : "Could not use the reset credit.", ); }; return { confirming, setConfirming, busy, status, redeem }; } /** * The confirm for a redeem. Redeeming spends a credit the provider granted the * user, so it never fires on a bare click. Mount it outside any popover that * holds the button: dialogs stack under popovers, and closing the popover * would unmount a dialog rendered inside it. */ export function ResetCreditDialog({ open, onOpenChange, onConfirm, }: { readonly open: boolean; readonly onOpenChange: (open: boolean) => void; readonly onConfirm: () => void; }) { return ( Use a reset credit? This redeems one credit on your account and clears the current rate-limit windows. It cannot be undone. }>Cancel ); } /** `2 reset credits banked · next expires in 27d 23h`, or the short form for a popover. */ export function resetCreditsSummary( credits: ServerProviderResetCredits, now: number, compact = false, ): string { const expiresIn = credits.nextExpiresAt ? formatDuration(Date.parse(credits.nextExpiresAt) - now) : null; if (credits.availableCount === 0) return "No reset credits banked"; if (compact) return `${credits.availableCount} banked${expiresIn ? ` · expires in ${expiresIn}` : ""}`; return `${credits.availableCount} ${credits.availableCount === 1 ? "reset credit" : "reset credits"} banked${ expiresIn ? ` · next expires in ${expiresIn}` : "" }`; } /** Banked reset credits with the redeem button and its confirm, self-contained. */ export function ResetCredits({ environmentId, input, credits, now, }: { readonly environmentId: EnvironmentId; readonly input: ProviderConsumeResetCreditInput; readonly credits: ServerProviderResetCredits; readonly now: number; }) { const { confirming, setConfirming, busy, status, redeem } = useResetCredit(environmentId, input); if (credits.availableCount === 0 && status === null) return null; return (
{resetCreditsSummary(credits, now)} {credits.availableCount > 0 ? ( ) : null} {status ? {status} : null} void redeem()} />
); } /** * Subscription quota across every connected environment's providers and hubs, * pooled per provider. Countdowns anchor to render time rather than ticking: a * live clock would repaint the page every minute for no decision-changing gain. */ export function UsageLimitsSection({ selectedEnvironmentIds, }: { readonly selectedEnvironmentIds: ReadonlySet | null; }) { const presentations = useAtomValue(environmentPresentations.presentationsAtom); // Anchored once per mount on purpose: countdowns must not tick (see above). const [now] = useState(() => Date.now()); const selected = selectedEnvironmentIds === null ? presentations : new Map([...presentations].filter(([id]) => selectedEnvironmentIds.has(id))); return ; }