From 538028c150e5fff8a7aa628d780c6645f484c307 Mon Sep 17 00:00:00 2001 From: "R.D." Date: Sat, 28 Mar 2026 21:17:07 -0400 Subject: [PATCH] normalize provider limits quota labels --- .../usage/components/ProviderLimits/index.tsx | 35 ++++--------- .../usage/components/ProviderLimits/utils.tsx | 52 +++++++++++++++++++ src/lib/usage/fetcher.ts | 12 +++-- tests/unit/provider-limits-ui.test.mjs | 9 ++++ 4 files changed, 77 insertions(+), 31 deletions(-) diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx index 31643f78..a779997e 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx @@ -4,7 +4,13 @@ import { useTranslations } from "next-intl"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import Image from "next/image"; -import { parseQuotaData, calculatePercentage, normalizePlanTier, resolvePlanValue } from "./utils"; +import { + parseQuotaData, + calculatePercentage, + formatQuotaLabel, + normalizePlanTier, + resolvePlanValue, +} from "./utils"; import Card from "@/shared/components/Card"; import Badge from "@/shared/components/Badge"; import { CardSkeleton } from "@/shared/components/Loading"; @@ -26,7 +32,7 @@ const PROVIDER_CONFIG = { kiro: { label: "Kiro AI", color: "#FF6B35" }, codex: { label: "OpenAI Codex", color: "#10A37F" }, claude: { label: "Claude Code", color: "#D97757" }, - glm: { label: "GLM Coding", color: "#4A90D9" }, + glm: { label: "GLM (Z.AI)", color: "#4A90D9" }, "kimi-coding": { label: "Kimi Coding", color: "#1E3A8A" }, }; @@ -42,29 +48,6 @@ const TIER_FILTERS = [ { key: "unknown", labelKey: "tierUnknown" }, ]; -// Short model display names for quota bars -function getShortModelName(name) { - const map = { - "gemini-3-pro-high": "G3 Pro", - "gemini-3-pro-low": "G3 Pro Low", - "gemini-3-flash": "G3 Flash", - "gemini-2.5-flash": "G2.5 Flash", - "claude-opus-4-6-thinking": "Opus 4.6 Tk", - "claude-opus-4-5-thinking": "Opus 4.5 Tk", - "claude-opus-4-5": "Opus 4.5", - "claude-sonnet-4-5-thinking": "Sonnet 4.5 Tk", - "claude-sonnet-4-5": "Sonnet 4.5", - chat: "Chat", - completions: "Completions", - premium_interactions: "Premium", - session: "Session", - weekly: "Weekly", - agentic_request: "Agentic", - agentic_request_freetrial: "Agentic (Trial)", - }; - return map[name] || name; -} - // Get bar color based on remaining percentage function getBarColor(remainingPercentage) { if (remainingPercentage > QUOTA_BAR_GREEN_THRESHOLD) { @@ -624,7 +607,7 @@ export default function ProviderLimits() { const remainingPercentage = calculatePercentage(q.used, q.total); const colors = getBarColor(remainingPercentage); const cd = formatCountdown(q.resetAt); - const shortName = getShortModelName(q.name); + const shortName = formatQuotaLabel(q.name); const staleAfterReset = q.staleAfterReset === true; return ( diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx index a7bf49b8..a14643b7 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx @@ -10,6 +10,26 @@ const PROVIDER_PLAN_FALLBACKS = new Set([ "github copilot", ]); +const QUOTA_LABEL_MAP: Record = { + "gemini-3-pro-high": "G3 Pro", + "gemini-3-pro-low": "G3 Pro Low", + "gemini-3-flash": "G3 Flash", + "gemini-2.5-flash": "G2.5 Flash", + "claude-opus-4-6-thinking": "Opus 4.6 Tk", + "claude-opus-4-5-thinking": "Opus 4.5 Tk", + "claude-opus-4-5": "Opus 4.5", + "claude-sonnet-4-5-thinking": "Sonnet 4.5 Tk", + "claude-sonnet-4-5": "Sonnet 4.5", + chat: "Chat", + completions: "Completions", + premium_interactions: "Premium", + session: "Session", + weekly: "Weekly", + code_review: "Code Review", + agentic_request: "Agentic", + agentic_request_freetrial: "Agentic (Trial)", +}; + function toRecord(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) @@ -25,6 +45,37 @@ function normalizePlanCandidate(value: unknown) { return trimmed; } +function toTitleCaseWords(value: string) { + return value + .split(/[\s_-]+/) + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(" "); +} + +export function formatQuotaLabel(name: string) { + const trimmed = typeof name === "string" ? name.trim() : ""; + if (!trimmed) return ""; + + const mapped = QUOTA_LABEL_MAP[trimmed]; + if (mapped) return mapped; + + if (/^session\s*\(\d+[hm]\)$/i.test(trimmed)) { + return "Session"; + } + + if (/^weekly\s*\(\d+d\)$/i.test(trimmed)) { + return "Weekly"; + } + + const weeklyModelMatch = trimmed.match(/^weekly\s+(.+?)\s*\(\d+d\)$/i); + if (weeklyModelMatch) { + return `Weekly ${toTitleCaseWords(weeklyModelMatch[1])}`; + } + + return trimmed; +} + /** * Format ISO date string to countdown format (inspired by vscode-antigravity-cockpit) * @param {string|Date} date - ISO date string or Date object @@ -204,6 +255,7 @@ export function parseQuotaData(provider, data) { break; default: + // Generic fallback for unknown providers if (data.quotas) { Object.entries(data.quotas).forEach(([name, quota]: [string, any]) => { normalizedQuotas.push(normalizeQuotaEntry(name, quota)); diff --git a/src/lib/usage/fetcher.ts b/src/lib/usage/fetcher.ts index 35467387..38f8e019 100644 --- a/src/lib/usage/fetcher.ts +++ b/src/lib/usage/fetcher.ts @@ -157,13 +157,15 @@ async function getAntigravityUsage(accessToken) { } /** - * Claude Usage + * Claude Usage (legacy fallback) + * Real Claude OAuth quota windows are fetched in @omniroute/open-sse/services/usage.ts. */ -async function getClaudeUsage(accessToken) { +async function getClaudeUsage() { try { - // Claude OAuth doesn't expose usage API directly - // Could potentially check via inference endpoint - return { message: "Claude connected. Usage tracked per request." }; + return { + message: + "Claude connected. Detailed quota windows are handled by the open-sse usage service.", + }; } catch (error) { return { message: "Unable to fetch Claude usage." }; } diff --git a/tests/unit/provider-limits-ui.test.mjs b/tests/unit/provider-limits-ui.test.mjs index f393269f..8a833849 100644 --- a/tests/unit/provider-limits-ui.test.mjs +++ b/tests/unit/provider-limits-ui.test.mjs @@ -44,3 +44,12 @@ test("remaining percentage helpers reflect remaining quota and stale resets refi assert.equal(parsed.length, 1); assert.equal(providerLimitUtils.calculatePercentage(parsed[0].used, parsed[0].total), 100); }); + +test("quota labels normalize session and weekly windows while preserving readable titles", () => { + assert.equal(providerLimitUtils.formatQuotaLabel("session"), "Session"); + assert.equal(providerLimitUtils.formatQuotaLabel("session (5h)"), "Session"); + assert.equal(providerLimitUtils.formatQuotaLabel("weekly"), "Weekly"); + assert.equal(providerLimitUtils.formatQuotaLabel("weekly (7d)"), "Weekly"); + assert.equal(providerLimitUtils.formatQuotaLabel("weekly sonnet (7d)"), "Weekly Sonnet"); + assert.equal(providerLimitUtils.formatQuotaLabel("code_review"), "Code Review"); +});