normalize provider limits quota labels
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -10,6 +10,26 @@ const PROVIDER_PLAN_FALLBACKS = new Set([
|
||||
"github copilot",
|
||||
]);
|
||||
|
||||
const QUOTA_LABEL_MAP: Record<string, string> = {
|
||||
"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<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
@@ -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));
|
||||
|
||||
@@ -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." };
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user