From 6bdc87aed6b71d20f77824ce0bb511437d6e8baf Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Sat, 24 Jan 2026 16:35:59 +0800 Subject: [PATCH] fix(quota): unify Gemini CLI quota groups (Flash/Pro series) --- src/components/common/PageTransition.tsx | 33 ++++++----- src/types/quota.ts | 1 + src/utils/quota/builders.ts | 64 ++++++++++++++------ src/utils/quota/constants.ts | 74 +++++++++++------------- 4 files changed, 100 insertions(+), 72 deletions(-) diff --git a/src/components/common/PageTransition.tsx b/src/components/common/PageTransition.tsx index 6c5c133..eb0e82c 100644 --- a/src/components/common/PageTransition.tsx +++ b/src/components/common/PageTransition.tsx @@ -22,11 +22,7 @@ type Layer = { type TransitionDirection = 'forward' | 'backward'; -export function PageTransition({ - render, - getRouteOrder, - scrollContainerRef, -}: PageTransitionProps) { +export function PageTransition({ render, getRouteOrder, scrollContainerRef }: PageTransitionProps) { const location = useLocation(); const currentLayerRef = useRef(null); const exitingLayerRef = useRef(null); @@ -71,16 +67,25 @@ export function PageTransition({ : 'backward'; transitionDirectionRef.current = nextDirection; - setLayers((prev) => { - const prevCurrent = prev[prev.length - 1]; - return [ - prevCurrent - ? { ...prevCurrent, status: 'exiting' } - : { key: location.key, location, status: 'exiting' }, - { key: location.key, location, status: 'current' }, - ]; + + let cancelled = false; + queueMicrotask(() => { + if (cancelled) return; + setLayers((prev) => { + const prevCurrent = prev[prev.length - 1]; + return [ + prevCurrent + ? { ...prevCurrent, status: 'exiting' } + : { key: location.key, location, status: 'exiting' }, + { key: location.key, location, status: 'current' }, + ]; + }); + setIsAnimating(true); }); - setIsAnimating(true); + + return () => { + cancelled = true; + }; }, [ isAnimating, location, diff --git a/src/types/quota.ts b/src/types/quota.ts index 5fb2779..3e335a0 100644 --- a/src/types/quota.ts +++ b/src/types/quota.ts @@ -55,6 +55,7 @@ export interface AntigravityQuotaGroupDefinition { export interface GeminiCliQuotaGroupDefinition { id: string; label: string; + preferredModelId?: string; modelIds: string[]; } diff --git a/src/utils/quota/builders.ts b/src/utils/quota/builders.ts index 7ec80f6..39d325f 100644 --- a/src/utils/quota/builders.ts +++ b/src/utils/quota/builders.ts @@ -8,7 +8,7 @@ import type { AntigravityQuotaInfo, AntigravityModelsPayload, GeminiCliParsedBucket, - GeminiCliQuotaBucketState + GeminiCliQuotaBucketState, } from '@/types'; import { ANTIGRAVITY_QUOTA_GROUPS, GEMINI_CLI_GROUP_LOOKUP } from './constants'; import { normalizeQuotaFraction } from './parsers'; @@ -35,7 +35,19 @@ export function buildGeminiCliQuotaBuckets( ): GeminiCliQuotaBucketState[] { if (buckets.length === 0) return []; - const grouped = new Map(); + type GeminiCliQuotaBucketGroup = { + id: string; + label: string; + tokenType: string | null; + modelIds: string[]; + preferredModelId?: string; + preferredBucket?: GeminiCliParsedBucket; + fallbackRemainingFraction: number | null; + fallbackRemainingAmount: number | null; + fallbackResetTime: string | undefined; + }; + + const grouped = new Map(); buckets.forEach((bucket) => { if (isIgnoredGeminiCliModel(bucket.modelId)) return; @@ -47,37 +59,55 @@ export function buildGeminiCliQuotaBuckets( const existing = grouped.get(mapKey); if (!existing) { + const preferredModelId = group?.preferredModelId; + const preferredBucket = + preferredModelId && bucket.modelId === preferredModelId ? bucket : undefined; grouped.set(mapKey, { id: `${groupId}${tokenKey ? `-${tokenKey}` : ''}`, label, - remainingFraction: bucket.remainingFraction, - remainingAmount: bucket.remainingAmount, - resetTime: bucket.resetTime, tokenType: bucket.tokenType, - modelIds: [bucket.modelId] + modelIds: [bucket.modelId], + preferredModelId, + preferredBucket, + fallbackRemainingFraction: bucket.remainingFraction, + fallbackRemainingAmount: bucket.remainingAmount, + fallbackResetTime: bucket.resetTime, }); return; } - existing.remainingFraction = minNullableNumber( - existing.remainingFraction, + existing.fallbackRemainingFraction = minNullableNumber( + existing.fallbackRemainingFraction, bucket.remainingFraction ); - existing.remainingAmount = minNullableNumber(existing.remainingAmount, bucket.remainingAmount); - existing.resetTime = pickEarlierResetTime(existing.resetTime, bucket.resetTime); + existing.fallbackRemainingAmount = minNullableNumber( + existing.fallbackRemainingAmount, + bucket.remainingAmount + ); + existing.fallbackResetTime = pickEarlierResetTime(existing.fallbackResetTime, bucket.resetTime); existing.modelIds.push(bucket.modelId); + + if (existing.preferredModelId && bucket.modelId === existing.preferredModelId) { + existing.preferredBucket = bucket; + } }); return Array.from(grouped.values()).map((bucket) => { const uniqueModelIds = Array.from(new Set(bucket.modelIds)); + const preferred = bucket.preferredBucket; + const remainingFraction = preferred + ? preferred.remainingFraction + : bucket.fallbackRemainingFraction; + const remainingAmount = preferred ? preferred.remainingAmount : bucket.fallbackRemainingAmount; + const resetTime = preferred ? preferred.resetTime : bucket.fallbackResetTime; return { id: bucket.id, label: bucket.label, - remainingFraction: bucket.remainingFraction, - remainingAmount: bucket.remainingAmount, - resetTime: bucket.resetTime, + remainingFraction, + remainingAmount, + resetTime, tokenType: bucket.tokenType, - modelIds: uniqueModelIds + modelIds: uniqueModelIds, }; }); } @@ -101,7 +131,7 @@ export function getAntigravityQuotaInfo(entry?: AntigravityQuotaInfo): { return { remainingFraction, resetTime, - displayName + displayName, }; } @@ -150,7 +180,7 @@ export function buildAntigravityQuotaGroups( id, remainingFraction, resetTime: info.resetTime, - displayName: info.displayName + displayName: info.displayName, }; }) .filter((entry): entry is NonNullable => entry !== null); @@ -168,7 +198,7 @@ export function buildAntigravityQuotaGroups( label, models: quotaEntries.map((entry) => entry.id), remainingFraction, - resetTime + resetTime, }; }; diff --git a/src/utils/quota/constants.ts b/src/utils/quota/constants.ts index 259ebbc..330af65 100644 --- a/src/utils/quota/constants.ts +++ b/src/utils/quota/constants.ts @@ -5,64 +5,64 @@ import type { AntigravityQuotaGroupDefinition, GeminiCliQuotaGroupDefinition, - TypeColorSet + TypeColorSet, } from '@/types'; // Theme colors for type badges export const TYPE_COLORS: Record = { qwen: { light: { bg: '#e8f5e9', text: '#2e7d32' }, - dark: { bg: '#1b5e20', text: '#81c784' } + dark: { bg: '#1b5e20', text: '#81c784' }, }, gemini: { light: { bg: '#e3f2fd', text: '#1565c0' }, - dark: { bg: '#0d47a1', text: '#64b5f6' } + dark: { bg: '#0d47a1', text: '#64b5f6' }, }, 'gemini-cli': { light: { bg: '#e7efff', text: '#1e4fa3' }, - dark: { bg: '#1c3f73', text: '#a8c7ff' } + dark: { bg: '#1c3f73', text: '#a8c7ff' }, }, aistudio: { light: { bg: '#f0f2f5', text: '#2f343c' }, - dark: { bg: '#373c42', text: '#cfd3db' } + dark: { bg: '#373c42', text: '#cfd3db' }, }, claude: { light: { bg: '#fce4ec', text: '#c2185b' }, - dark: { bg: '#880e4f', text: '#f48fb1' } + dark: { bg: '#880e4f', text: '#f48fb1' }, }, codex: { light: { bg: '#fff3e0', text: '#ef6c00' }, - dark: { bg: '#e65100', text: '#ffb74d' } + dark: { bg: '#e65100', text: '#ffb74d' }, }, antigravity: { light: { bg: '#e0f7fa', text: '#006064' }, - dark: { bg: '#004d40', text: '#80deea' } + dark: { bg: '#004d40', text: '#80deea' }, }, iflow: { light: { bg: '#f3e5f5', text: '#7b1fa2' }, - dark: { bg: '#4a148c', text: '#ce93d8' } + dark: { bg: '#4a148c', text: '#ce93d8' }, }, empty: { light: { bg: '#f5f5f5', text: '#616161' }, - dark: { bg: '#424242', text: '#bdbdbd' } + dark: { bg: '#424242', text: '#bdbdbd' }, }, unknown: { light: { bg: '#f0f0f0', text: '#666666', border: '1px dashed #999999' }, - dark: { bg: '#3a3a3a', text: '#aaaaaa', border: '1px dashed #666666' } - } + dark: { bg: '#3a3a3a', text: '#aaaaaa', border: '1px dashed #666666' }, + }, }; // Antigravity API configuration export const ANTIGRAVITY_QUOTA_URLS = [ 'https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels', 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels', - 'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels' + 'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels', ]; export const ANTIGRAVITY_REQUEST_HEADERS = { Authorization: 'Bearer $TOKEN$', 'Content-Type': 'application/json', - 'User-Agent': 'antigravity/1.11.5 windows/amd64' + 'User-Agent': 'antigravity/1.11.5 windows/amd64', }; export const ANTIGRAVITY_QUOTA_GROUPS: AntigravityQuotaGroupDefinition[] = [ @@ -73,40 +73,40 @@ export const ANTIGRAVITY_QUOTA_GROUPS: AntigravityQuotaGroupDefinition[] = [ 'claude-sonnet-4-5-thinking', 'claude-opus-4-5-thinking', 'claude-sonnet-4-5', - 'gpt-oss-120b-medium' - ] + 'gpt-oss-120b-medium', + ], }, { id: 'gemini-3-pro', label: 'Gemini 3 Pro', - identifiers: ['gemini-3-pro-high', 'gemini-3-pro-low'] + identifiers: ['gemini-3-pro-high', 'gemini-3-pro-low'], }, { id: 'gemini-2-5-flash', label: 'Gemini 2.5 Flash', - identifiers: ['gemini-2.5-flash', 'gemini-2.5-flash-thinking'] + identifiers: ['gemini-2.5-flash', 'gemini-2.5-flash-thinking'], }, { id: 'gemini-2-5-flash-lite', label: 'Gemini 2.5 Flash Lite', - identifiers: ['gemini-2.5-flash-lite'] + identifiers: ['gemini-2.5-flash-lite'], }, { id: 'gemini-2-5-cu', label: 'Gemini 2.5 CU', - identifiers: ['rev19-uic3-1p'] + identifiers: ['rev19-uic3-1p'], }, { id: 'gemini-3-flash', label: 'Gemini 3 Flash', - identifiers: ['gemini-3-flash'] + identifiers: ['gemini-3-flash'], }, { id: 'gemini-image', label: 'gemini-3-pro-image', identifiers: ['gemini-3-pro-image'], - labelFromModel: true - } + labelFromModel: true, + }, ]; // Gemini CLI API configuration @@ -115,30 +115,22 @@ export const GEMINI_CLI_QUOTA_URL = export const GEMINI_CLI_REQUEST_HEADERS = { Authorization: 'Bearer $TOKEN$', - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }; export const GEMINI_CLI_QUOTA_GROUPS: GeminiCliQuotaGroupDefinition[] = [ { - id: 'gemini-2-5-flash-series', - label: 'Gemini 2.5 Flash Series', - modelIds: ['gemini-2.5-flash', 'gemini-2.5-flash-lite'] + id: 'gemini-flash-series', + label: 'Gemini Flash Series', + preferredModelId: 'gemini-3-flash-preview', + modelIds: ['gemini-3-flash-preview', 'gemini-2.5-flash', 'gemini-2.5-flash-lite'], }, { - id: 'gemini-2-5-pro', - label: 'Gemini 2.5 Pro', - modelIds: ['gemini-2.5-pro'] + id: 'gemini-pro-series', + label: 'Gemini Pro Series', + preferredModelId: 'gemini-3-pro-preview', + modelIds: ['gemini-3-pro-preview', 'gemini-2.5-pro'], }, - { - id: 'gemini-3-pro-preview', - label: 'Gemini 3 Pro Preview', - modelIds: ['gemini-3-pro-preview'] - }, - { - id: 'gemini-3-flash-preview', - label: 'Gemini 3 Flash Preview', - modelIds: ['gemini-3-flash-preview'] - } ]; export const GEMINI_CLI_GROUP_LOOKUP = new Map( @@ -155,5 +147,5 @@ export const CODEX_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage'; export const CODEX_REQUEST_HEADERS = { Authorization: 'Bearer $TOKEN$', 'Content-Type': 'application/json', - 'User-Agent': 'codex_cli_rs/0.76.0 (Debian 13.0.0; x86_64) WindowsTerminal' + 'User-Agent': 'codex_cli_rs/0.76.0 (Debian 13.0.0; x86_64) WindowsTerminal', };