Spaces:
Runtime error
Runtime error
| import { formatQuotaLabel } from "./utils"; | |
| /** | |
| * Per-provider column schema for the grouped Provider Quota layout. | |
| * | |
| * Each entry lists the canonical quota keys we want to surface as | |
| * fixed-width table columns. Matching is done both exact (by `quota.name`) | |
| * and via the normalized label (so MiniMax's `"session (5h)"` still lands in | |
| * the `"session"` column). | |
| * | |
| * Providers not listed here fall back to a dynamic schema: take the first | |
| * `MAX_DYNAMIC_COLUMNS` quotas in the order returned by `parseQuotaData()` | |
| * and surface them as columns; everything else becomes "+N more". | |
| */ | |
| const PROVIDER_COLUMNS: Record<string, string[]> = { | |
| codex: ["session", "weekly"], | |
| claude: ["session", "weekly"], | |
| glm: ["session", "weekly", "mcp_monthly"], | |
| "glm-cn": ["session", "weekly", "mcp_monthly"], | |
| glmt: ["session", "weekly", "mcp_monthly"], | |
| zai: ["session", "weekly", "mcp_monthly"], | |
| github: ["chat", "completions", "premium_interactions"], | |
| minimax: ["session"], | |
| "minimax-cn": ["session"], | |
| "kimi-coding": ["session", "weekly"], | |
| }; | |
| /** Hard cap for the dynamic schema (Antigravity, Gemini-CLI, fallback). */ | |
| export const MAX_DYNAMIC_COLUMNS = 3; | |
| export interface ResolvedColumn { | |
| /** Stable column key β used for React keys and column-picker state. */ | |
| key: string; | |
| /** Human-readable header label. */ | |
| label: string; | |
| /** The matching quota for this account, or `null` if the account | |
| * doesn't expose that window. Rendered as an em-dash cell in the UI. */ | |
| quota: any | null; | |
| } | |
| export interface ResolvedSchema { | |
| columns: ResolvedColumn[]; | |
| /** Quotas present on the account but not surfaced as columns. | |
| * Rendered as "+N more" in the row's trailing cell. */ | |
| overflowCount: number; | |
| } | |
| function matchQuotaByKey(quotas: any[], key: string): any | null { | |
| // Exact match on quota.name first | |
| const exact = quotas.find( | |
| (q) => q && typeof q.name === "string" && q.name.toLowerCase() === key.toLowerCase() | |
| ); | |
| if (exact) return exact; | |
| // Then exact match on modelKey (for antigravity etc.) | |
| const byModel = quotas.find( | |
| (q) => q && typeof q.modelKey === "string" && q.modelKey.toLowerCase() === key.toLowerCase() | |
| ); | |
| if (byModel) return byModel; | |
| // Finally, normalized label match β handles "session (5h)" β "session" | |
| const normalized = formatQuotaLabel(key).toLowerCase(); | |
| return ( | |
| quotas.find( | |
| (q) => | |
| q && typeof q.name === "string" && formatQuotaLabel(q.name).toLowerCase() === normalized | |
| ) || null | |
| ); | |
| } | |
| /** | |
| * Resolve which columns to render for a given (provider, quotas) pair. | |
| * | |
| * - Named providers: use the static schema; missing windows render as `null`. | |
| * - Unknown providers: take the first N non-credit quotas in array order. | |
| * - Credits (`isCredits === true`) are never used as columns β they render | |
| * in the overflow tooltip / expanded panel as a balance, not as a %. | |
| */ | |
| export function getProviderColumns(provider: string, quotas: any[] = []): ResolvedSchema { | |
| const safe = Array.isArray(quotas) ? quotas : []; | |
| const nonCredits = safe.filter((q) => q && !q.isCredits); | |
| const credits = safe.filter((q) => q && q.isCredits); | |
| const named = PROVIDER_COLUMNS[String(provider || "").toLowerCase()]; | |
| if (named && named.length > 0) { | |
| const columns: ResolvedColumn[] = named.map((key) => ({ | |
| key, | |
| label: formatQuotaLabel(key), | |
| quota: matchQuotaByKey(nonCredits, key), | |
| })); | |
| const matchedQuotas = new Set(columns.map((c) => c.quota).filter(Boolean)); | |
| const overflowCount = nonCredits.filter((q) => !matchedQuotas.has(q)).length + credits.length; | |
| return { columns, overflowCount }; | |
| } | |
| // Dynamic schema β take the first N non-credit quotas in array order | |
| const visible = nonCredits.slice(0, MAX_DYNAMIC_COLUMNS); | |
| const columns: ResolvedColumn[] = visible.map((q) => ({ | |
| key: q.modelKey || q.name, | |
| label: q.displayName || formatQuotaLabel(q.name), | |
| quota: q, | |
| })); | |
| const overflowCount = Math.max(0, nonCredits.length - visible.length) + credits.length; | |
| return { columns, overflowCount }; | |
| } | |
| /** | |
| * Group a flat list of connection objects by their `provider` key. | |
| * Preserves the input order (the upstream sort by status + soonest reset is | |
| * meaningful inside each group; the provider order itself is controlled by | |
| * the caller via `PROVIDER_ORDER` in index.tsx). | |
| */ | |
| export function groupConnectionsByProvider<T extends { provider: string }>( | |
| connections: T[] | |
| ): Map<string, T[]> { | |
| const groups = new Map<string, T[]>(); | |
| for (const conn of connections) { | |
| const key = conn.provider || "unknown"; | |
| if (!groups.has(key)) groups.set(key, []); | |
| groups.get(key)!.push(conn); | |
| } | |
| return groups; | |
| } | |