import { useMemo } from 'react'; import type { DashboardOverview } from './api'; import { EmptyState } from './EmptyState'; import type { Messages } from './i18n'; export type UsageRow = DashboardOverview['usage']['rows'][number]; type RiskLevel = 'ok' | 'warn' | 'critical'; type UsageGroup = 'primary' | 'codex'; type UsageLimitWindow = 'h5' | 'd7'; export interface UsagePanelProps { overview: DashboardOverview; t: Messages; } function formatPct(value: number): string { if (value < 0) return '-'; return `${Math.round(value)}%`; } function usagePeak(row: UsageRow): number { return Math.max(row.h5pct, row.d7pct); } function usageLimitWindow(row: UsageRow): UsageLimitWindow { return row.d7pct >= row.h5pct ? 'd7' : 'h5'; } function usageWindowRemaining( row: UsageRow, window: UsageLimitWindow, ): number | null { const pct = window === 'h5' ? row.h5pct : row.d7pct; if (pct < 0) return null; return Math.max(0, 100 - pct); } function usageRiskLevel(row: UsageRow): RiskLevel { const peak = usagePeak(row); if (peak >= 85) return 'critical'; if (peak >= 65) return 'warn'; return 'ok'; } function usageActive(row: UsageRow): boolean { return row.name.includes('*'); } function usageLimited(row: UsageRow): boolean { return row.name.includes('!'); } function usageNameParts(row: UsageRow): { account: string; plan: string | null; } { const cleaned = row.name.replace(/[*!]/g, '').replace(/\s+/g, ' ').trim(); const parts = cleaned.split(' '); const plan = parts.at(-1) ?? null; if (plan && ['max', 'mid', 'pro', 'team'].includes(plan.toLowerCase())) { return { account: parts.slice(0, -1).join(' ') || cleaned, plan }; } return { account: cleaned, plan: null }; } function usageWindowReset(row: UsageRow, window: UsageLimitWindow): string { return (window === 'd7' ? row.d7reset : row.h5reset).trim(); } function usageBurnRate(row: UsageRow): number | null { if (row.h5pct < 0) return null; return row.h5pct / 5; } function usageSpeedLevel(rate: number | null): RiskLevel { if (rate === null) return 'ok'; if (rate >= 12) return 'critical'; if (rate >= 7) return 'warn'; return 'ok'; } function formatUsageRate(rate: number | null): string { if (rate === null) return '-'; if (rate > 0 && rate < 1) return '<1%/h'; return `${Math.round(rate)}%/h`; } function usageGroup(row: UsageRow): UsageGroup { return row.name.toLowerCase().startsWith('codex') ? 'codex' : 'primary'; } function UsageQuotaMeter({ row, rowName, window, t, }: { row: UsageRow; rowName: string; window: UsageLimitWindow; t: Messages; }) { const remaining = usageWindowRemaining(row, window); const reset = usageWindowReset(row, window); const tightest = usageLimitWindow(row) === window; const label = t.usage.quota[window]; return (
{label} {remaining === null ? '-' : formatPct(remaining)}
{reset ? `${t.usage.reset} ${reset}` : t.usage.noReset}
); } function UsageSpeed({ row, t }: { row: UsageRow; t: Messages }) { const rate = usageBurnRate(row); const level = usageSpeedLevel(rate); return (
{t.usage.speed} {formatUsageRate(rate)} {t.usage.speedLabel[level]}
); } export function UsagePanel({ overview, t }: UsagePanelProps) { const rows = useMemo( () => Array.from(overview.usage.rows).sort((a, b) => { if (usageActive(a) !== usageActive(b)) return usageActive(a) ? -1 : 1; return usagePeak(b) - usagePeak(a); }), [overview.usage.rows], ); const watched = rows.filter((row) => usagePeak(row) >= 65).length; if (rows.length === 0) { return {t.usage.empty}; } const activeRows = rows.filter(usageActive); const focusRows = activeRows.length > 0 ? activeRows : rows.slice(0, 1); const focusLabel = activeRows.length > 0 ? t.usage.current : t.usage.tightest; const focusValue = focusRows .map((row) => { const { account } = usageNameParts(row); const h5Remaining = usageWindowRemaining(row, 'h5'); const d7Remaining = usageWindowRemaining(row, 'd7'); return `${account} ${t.usage.quota.h5} ${ h5Remaining === null ? '-' : formatPct(h5Remaining) } · ${t.usage.quota.d7} ${ d7Remaining === null ? '-' : formatPct(d7Remaining) }`; }) .join(' · '); const groups = [ { key: 'primary' as const, label: t.usage.groupPrimary, rows: rows.filter((row) => usageGroup(row) === 'primary'), }, { key: 'codex' as const, label: t.usage.groupCodex, rows: rows.filter((row) => usageGroup(row) === 'codex'), }, ].filter((group) => group.rows.length > 0); return (
{focusLabel} {focusValue}
{t.usage.watch} {watched}
{groups.map((group) => ( {group.rows.map((row) => { const risk = usageRiskLevel(row); const { account, plan } = usageNameParts(row); return ( ); })} ))}
{t.usage.usage} {t.usage.quota.h5} {t.usage.quota.d7} {t.usage.speed}
{group.label}
{account}
{usageActive(row) ? ( {t.usage.inUse} ) : null} {plan ? {plan} : null} {usageLimited(row) || risk !== 'ok' ? ( {t.usage.risk[risk]} ) : null}
); }