fix(quota): unify Gemini CLI quota groups (Flash/Pro series)

This commit is contained in:
LTbinglingfeng
2026-01-24 16:35:59 +08:00
parent 268b92c59b
commit 6bdc87aed6
4 changed files with 100 additions and 72 deletions

View File

@@ -22,11 +22,7 @@ type Layer = {
type TransitionDirection = 'forward' | 'backward'; type TransitionDirection = 'forward' | 'backward';
export function PageTransition({ export function PageTransition({ render, getRouteOrder, scrollContainerRef }: PageTransitionProps) {
render,
getRouteOrder,
scrollContainerRef,
}: PageTransitionProps) {
const location = useLocation(); const location = useLocation();
const currentLayerRef = useRef<HTMLDivElement>(null); const currentLayerRef = useRef<HTMLDivElement>(null);
const exitingLayerRef = useRef<HTMLDivElement>(null); const exitingLayerRef = useRef<HTMLDivElement>(null);
@@ -71,16 +67,25 @@ export function PageTransition({
: 'backward'; : 'backward';
transitionDirectionRef.current = nextDirection; transitionDirectionRef.current = nextDirection;
setLayers((prev) => {
const prevCurrent = prev[prev.length - 1]; let cancelled = false;
return [ queueMicrotask(() => {
prevCurrent if (cancelled) return;
? { ...prevCurrent, status: 'exiting' } setLayers((prev) => {
: { key: location.key, location, status: 'exiting' }, const prevCurrent = prev[prev.length - 1];
{ key: location.key, location, status: 'current' }, 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, isAnimating,
location, location,

View File

@@ -55,6 +55,7 @@ export interface AntigravityQuotaGroupDefinition {
export interface GeminiCliQuotaGroupDefinition { export interface GeminiCliQuotaGroupDefinition {
id: string; id: string;
label: string; label: string;
preferredModelId?: string;
modelIds: string[]; modelIds: string[];
} }

View File

@@ -8,7 +8,7 @@ import type {
AntigravityQuotaInfo, AntigravityQuotaInfo,
AntigravityModelsPayload, AntigravityModelsPayload,
GeminiCliParsedBucket, GeminiCliParsedBucket,
GeminiCliQuotaBucketState GeminiCliQuotaBucketState,
} from '@/types'; } from '@/types';
import { ANTIGRAVITY_QUOTA_GROUPS, GEMINI_CLI_GROUP_LOOKUP } from './constants'; import { ANTIGRAVITY_QUOTA_GROUPS, GEMINI_CLI_GROUP_LOOKUP } from './constants';
import { normalizeQuotaFraction } from './parsers'; import { normalizeQuotaFraction } from './parsers';
@@ -35,7 +35,19 @@ export function buildGeminiCliQuotaBuckets(
): GeminiCliQuotaBucketState[] { ): GeminiCliQuotaBucketState[] {
if (buckets.length === 0) return []; if (buckets.length === 0) return [];
const grouped = new Map<string, GeminiCliQuotaBucketState & { modelIds: string[] }>(); 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<string, GeminiCliQuotaBucketGroup>();
buckets.forEach((bucket) => { buckets.forEach((bucket) => {
if (isIgnoredGeminiCliModel(bucket.modelId)) return; if (isIgnoredGeminiCliModel(bucket.modelId)) return;
@@ -47,37 +59,55 @@ export function buildGeminiCliQuotaBuckets(
const existing = grouped.get(mapKey); const existing = grouped.get(mapKey);
if (!existing) { if (!existing) {
const preferredModelId = group?.preferredModelId;
const preferredBucket =
preferredModelId && bucket.modelId === preferredModelId ? bucket : undefined;
grouped.set(mapKey, { grouped.set(mapKey, {
id: `${groupId}${tokenKey ? `-${tokenKey}` : ''}`, id: `${groupId}${tokenKey ? `-${tokenKey}` : ''}`,
label, label,
remainingFraction: bucket.remainingFraction,
remainingAmount: bucket.remainingAmount,
resetTime: bucket.resetTime,
tokenType: bucket.tokenType, tokenType: bucket.tokenType,
modelIds: [bucket.modelId] modelIds: [bucket.modelId],
preferredModelId,
preferredBucket,
fallbackRemainingFraction: bucket.remainingFraction,
fallbackRemainingAmount: bucket.remainingAmount,
fallbackResetTime: bucket.resetTime,
}); });
return; return;
} }
existing.remainingFraction = minNullableNumber( existing.fallbackRemainingFraction = minNullableNumber(
existing.remainingFraction, existing.fallbackRemainingFraction,
bucket.remainingFraction bucket.remainingFraction
); );
existing.remainingAmount = minNullableNumber(existing.remainingAmount, bucket.remainingAmount); existing.fallbackRemainingAmount = minNullableNumber(
existing.resetTime = pickEarlierResetTime(existing.resetTime, bucket.resetTime); existing.fallbackRemainingAmount,
bucket.remainingAmount
);
existing.fallbackResetTime = pickEarlierResetTime(existing.fallbackResetTime, bucket.resetTime);
existing.modelIds.push(bucket.modelId); existing.modelIds.push(bucket.modelId);
if (existing.preferredModelId && bucket.modelId === existing.preferredModelId) {
existing.preferredBucket = bucket;
}
}); });
return Array.from(grouped.values()).map((bucket) => { return Array.from(grouped.values()).map((bucket) => {
const uniqueModelIds = Array.from(new Set(bucket.modelIds)); 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 { return {
id: bucket.id, id: bucket.id,
label: bucket.label, label: bucket.label,
remainingFraction: bucket.remainingFraction, remainingFraction,
remainingAmount: bucket.remainingAmount, remainingAmount,
resetTime: bucket.resetTime, resetTime,
tokenType: bucket.tokenType, tokenType: bucket.tokenType,
modelIds: uniqueModelIds modelIds: uniqueModelIds,
}; };
}); });
} }
@@ -101,7 +131,7 @@ export function getAntigravityQuotaInfo(entry?: AntigravityQuotaInfo): {
return { return {
remainingFraction, remainingFraction,
resetTime, resetTime,
displayName displayName,
}; };
} }
@@ -150,7 +180,7 @@ export function buildAntigravityQuotaGroups(
id, id,
remainingFraction, remainingFraction,
resetTime: info.resetTime, resetTime: info.resetTime,
displayName: info.displayName displayName: info.displayName,
}; };
}) })
.filter((entry): entry is NonNullable<typeof entry> => entry !== null); .filter((entry): entry is NonNullable<typeof entry> => entry !== null);
@@ -168,7 +198,7 @@ export function buildAntigravityQuotaGroups(
label, label,
models: quotaEntries.map((entry) => entry.id), models: quotaEntries.map((entry) => entry.id),
remainingFraction, remainingFraction,
resetTime resetTime,
}; };
}; };

View File

@@ -5,64 +5,64 @@
import type { import type {
AntigravityQuotaGroupDefinition, AntigravityQuotaGroupDefinition,
GeminiCliQuotaGroupDefinition, GeminiCliQuotaGroupDefinition,
TypeColorSet TypeColorSet,
} from '@/types'; } from '@/types';
// Theme colors for type badges // Theme colors for type badges
export const TYPE_COLORS: Record<string, TypeColorSet> = { export const TYPE_COLORS: Record<string, TypeColorSet> = {
qwen: { qwen: {
light: { bg: '#e8f5e9', text: '#2e7d32' }, light: { bg: '#e8f5e9', text: '#2e7d32' },
dark: { bg: '#1b5e20', text: '#81c784' } dark: { bg: '#1b5e20', text: '#81c784' },
}, },
gemini: { gemini: {
light: { bg: '#e3f2fd', text: '#1565c0' }, light: { bg: '#e3f2fd', text: '#1565c0' },
dark: { bg: '#0d47a1', text: '#64b5f6' } dark: { bg: '#0d47a1', text: '#64b5f6' },
}, },
'gemini-cli': { 'gemini-cli': {
light: { bg: '#e7efff', text: '#1e4fa3' }, light: { bg: '#e7efff', text: '#1e4fa3' },
dark: { bg: '#1c3f73', text: '#a8c7ff' } dark: { bg: '#1c3f73', text: '#a8c7ff' },
}, },
aistudio: { aistudio: {
light: { bg: '#f0f2f5', text: '#2f343c' }, light: { bg: '#f0f2f5', text: '#2f343c' },
dark: { bg: '#373c42', text: '#cfd3db' } dark: { bg: '#373c42', text: '#cfd3db' },
}, },
claude: { claude: {
light: { bg: '#fce4ec', text: '#c2185b' }, light: { bg: '#fce4ec', text: '#c2185b' },
dark: { bg: '#880e4f', text: '#f48fb1' } dark: { bg: '#880e4f', text: '#f48fb1' },
}, },
codex: { codex: {
light: { bg: '#fff3e0', text: '#ef6c00' }, light: { bg: '#fff3e0', text: '#ef6c00' },
dark: { bg: '#e65100', text: '#ffb74d' } dark: { bg: '#e65100', text: '#ffb74d' },
}, },
antigravity: { antigravity: {
light: { bg: '#e0f7fa', text: '#006064' }, light: { bg: '#e0f7fa', text: '#006064' },
dark: { bg: '#004d40', text: '#80deea' } dark: { bg: '#004d40', text: '#80deea' },
}, },
iflow: { iflow: {
light: { bg: '#f3e5f5', text: '#7b1fa2' }, light: { bg: '#f3e5f5', text: '#7b1fa2' },
dark: { bg: '#4a148c', text: '#ce93d8' } dark: { bg: '#4a148c', text: '#ce93d8' },
}, },
empty: { empty: {
light: { bg: '#f5f5f5', text: '#616161' }, light: { bg: '#f5f5f5', text: '#616161' },
dark: { bg: '#424242', text: '#bdbdbd' } dark: { bg: '#424242', text: '#bdbdbd' },
}, },
unknown: { unknown: {
light: { bg: '#f0f0f0', text: '#666666', border: '1px dashed #999999' }, 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 // Antigravity API configuration
export const ANTIGRAVITY_QUOTA_URLS = [ export const ANTIGRAVITY_QUOTA_URLS = [
'https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels', 'https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels',
'https://daily-cloudcode-pa.sandbox.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 = { export const ANTIGRAVITY_REQUEST_HEADERS = {
Authorization: 'Bearer $TOKEN$', Authorization: 'Bearer $TOKEN$',
'Content-Type': 'application/json', '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[] = [ export const ANTIGRAVITY_QUOTA_GROUPS: AntigravityQuotaGroupDefinition[] = [
@@ -73,40 +73,40 @@ export const ANTIGRAVITY_QUOTA_GROUPS: AntigravityQuotaGroupDefinition[] = [
'claude-sonnet-4-5-thinking', 'claude-sonnet-4-5-thinking',
'claude-opus-4-5-thinking', 'claude-opus-4-5-thinking',
'claude-sonnet-4-5', 'claude-sonnet-4-5',
'gpt-oss-120b-medium' 'gpt-oss-120b-medium',
] ],
}, },
{ {
id: 'gemini-3-pro', id: 'gemini-3-pro',
label: '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', id: 'gemini-2-5-flash',
label: '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', id: 'gemini-2-5-flash-lite',
label: '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', id: 'gemini-2-5-cu',
label: 'Gemini 2.5 CU', label: 'Gemini 2.5 CU',
identifiers: ['rev19-uic3-1p'] identifiers: ['rev19-uic3-1p'],
}, },
{ {
id: 'gemini-3-flash', id: 'gemini-3-flash',
label: 'Gemini 3 Flash', label: 'Gemini 3 Flash',
identifiers: ['gemini-3-flash'] identifiers: ['gemini-3-flash'],
}, },
{ {
id: 'gemini-image', id: 'gemini-image',
label: 'gemini-3-pro-image', label: 'gemini-3-pro-image',
identifiers: ['gemini-3-pro-image'], identifiers: ['gemini-3-pro-image'],
labelFromModel: true labelFromModel: true,
} },
]; ];
// Gemini CLI API configuration // Gemini CLI API configuration
@@ -115,30 +115,22 @@ export const GEMINI_CLI_QUOTA_URL =
export const GEMINI_CLI_REQUEST_HEADERS = { export const GEMINI_CLI_REQUEST_HEADERS = {
Authorization: 'Bearer $TOKEN$', Authorization: 'Bearer $TOKEN$',
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}; };
export const GEMINI_CLI_QUOTA_GROUPS: GeminiCliQuotaGroupDefinition[] = [ export const GEMINI_CLI_QUOTA_GROUPS: GeminiCliQuotaGroupDefinition[] = [
{ {
id: 'gemini-2-5-flash-series', id: 'gemini-flash-series',
label: 'Gemini 2.5 Flash Series', label: 'Gemini Flash Series',
modelIds: ['gemini-2.5-flash', 'gemini-2.5-flash-lite'] preferredModelId: 'gemini-3-flash-preview',
modelIds: ['gemini-3-flash-preview', 'gemini-2.5-flash', 'gemini-2.5-flash-lite'],
}, },
{ {
id: 'gemini-2-5-pro', id: 'gemini-pro-series',
label: 'Gemini 2.5 Pro', label: 'Gemini Pro Series',
modelIds: ['gemini-2.5-pro'] 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( 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 = { export const CODEX_REQUEST_HEADERS = {
Authorization: 'Bearer $TOKEN$', Authorization: 'Bearer $TOKEN$',
'Content-Type': 'application/json', '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',
}; };