import type { CostByProviderModel, CostWindowSpendRow } from "@paperclipai/shared"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { QuotaBar } from "./QuotaBar"; import { formatCents, formatTokens } from "@/lib/utils"; interface ProviderQuotaCardProps { provider: string; rows: CostByProviderModel[]; /** company monthly budget in cents (0 means unlimited) */ budgetMonthlyCents: number; /** total company spend in this period in cents, all providers */ totalCompanySpendCents: number; /** spend in the current calendar week in cents, this provider only */ weekSpendCents: number; /** rolling window rows for this provider: 5h, 24h, 7d */ windowRows: CostWindowSpendRow[]; showDeficitNotch: boolean; } function providerLabel(provider: string): string { const map: Record = { anthropic: "Anthropic", openai: "OpenAI", google: "Google", cursor: "Cursor", jetbrains: "JetBrains AI", }; return map[provider.toLowerCase()] ?? provider; } export function ProviderQuotaCard({ provider, rows, budgetMonthlyCents, totalCompanySpendCents, weekSpendCents, windowRows, showDeficitNotch, }: ProviderQuotaCardProps) { const totalInputTokens = rows.reduce((s, r) => s + r.inputTokens, 0); const totalOutputTokens = rows.reduce((s, r) => s + r.outputTokens, 0); const totalTokens = totalInputTokens + totalOutputTokens; const totalCostCents = rows.reduce((s, r) => s + r.costCents, 0); const totalApiRuns = rows.reduce((s, r) => s + r.apiRunCount, 0); const totalSubRuns = rows.reduce((s, r) => s + r.subscriptionRunCount, 0); const totalSubInputTokens = rows.reduce((s, r) => s + r.subscriptionInputTokens, 0); const totalSubOutputTokens = rows.reduce((s, r) => s + r.subscriptionOutputTokens, 0); const totalSubTokens = totalSubInputTokens + totalSubOutputTokens; // sub share = sub tokens / (api tokens + sub tokens) const allTokens = totalTokens + totalSubTokens; const subSharePct = allTokens > 0 ? (totalSubTokens / allTokens) * 100 : 0; // budget bars: use this provider's own spend vs its pro-rata share of budget // pro-rata: if a provider is 40% of total spend, it gets 40% of the budget allocated. // falls back to raw provider spend vs total budget when totalCompanySpend is 0. const providerBudgetShare = budgetMonthlyCents > 0 && totalCompanySpendCents > 0 ? (totalCostCents / totalCompanySpendCents) * budgetMonthlyCents : budgetMonthlyCents; const budgetPct = providerBudgetShare > 0 ? Math.min(100, (totalCostCents / providerBudgetShare) * 100) : 0; const weeklyBudgetShare = providerBudgetShare > 0 ? providerBudgetShare / 4.33 : 0; const weekPct = weeklyBudgetShare > 0 ? Math.min(100, (weekSpendCents / weeklyBudgetShare) * 100) : 0; const hasBudget = budgetMonthlyCents > 0; return (
{providerLabel(provider)} {formatTokens(totalInputTokens)} in {" · "} {formatTokens(totalOutputTokens)} out {(totalApiRuns > 0 || totalSubRuns > 0) && ( ·{" "} {totalApiRuns > 0 && `~${totalApiRuns} api`} {totalApiRuns > 0 && totalSubRuns > 0 && " / "} {totalSubRuns > 0 && `~${totalSubRuns} sub`} {" runs"} )}
{formatCents(totalCostCents)}
{hasBudget && (
= 100} />
)} {/* rolling window consumption — always shown when data is available */} {windowRows.length > 0 && (() => { const WINDOWS = ["5h", "24h", "7d"] as const; const windowMap = new Map(windowRows.map((r) => [r.window, r])); const maxCents = Math.max(...windowRows.map((r) => r.costCents), 1); return ( <>

Rolling windows

{WINDOWS.map((w) => { const row = windowMap.get(w); const cents = row?.costCents ?? 0; const tokens = (row?.inputTokens ?? 0) + (row?.outputTokens ?? 0); const barPct = maxCents > 0 ? (cents / maxCents) * 100 : 0; return (
{w} {formatTokens(tokens)} tok {formatCents(cents)}
); })}
); })()} {/* subscription usage — shown when any subscription-billed runs exist */} {totalSubRuns > 0 && ( <>

Subscription

{totalSubRuns} runs {" · "} {formatTokens(totalSubInputTokens)} in {" · "} {formatTokens(totalSubOutputTokens)} out

{Math.round(subSharePct)}% of token usage via subscription

)} {/* model breakdown — always shown, with token-share bars */} {rows.length > 0 && ( <>
{rows.map((row) => { const rowTokens = row.inputTokens + row.outputTokens; const tokenPct = totalTokens > 0 ? (rowTokens / totalTokens) * 100 : 0; const costPct = totalCostCents > 0 ? (row.costCents / totalCostCents) * 100 : 0; return (
{/* model name and cost */}
{row.model}
{formatTokens(rowTokens)} tok {formatCents(row.costCents)}
{/* token share bar */}
{/* cost share overlay — narrower, opaque, shows relative cost weight */}
); })}
)} ); }