2026-07-05 06:34:06 +08:00
|
|
|
|
export interface AntigravityQuotaSortableItem {
|
|
|
|
|
|
model: string
|
|
|
|
|
|
label: string
|
|
|
|
|
|
remainingPercent: number
|
|
|
|
|
|
resetSeconds: number | null
|
2026-09-01 10:15:57 +08:00
|
|
|
|
detail?: string
|
2026-07-05 06:34:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-01 10:15:57 +08:00
|
|
|
|
const ANTIGRAVITY_QUOTA_GROUPS = [
|
|
|
|
|
|
{ label: 'Gemini额度', matches: (model: string) => model.startsWith('gemini-') },
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Claude额度',
|
|
|
|
|
|
matches: (model: string) => model.startsWith('claude-') || model.startsWith('gpt-'),
|
|
|
|
|
|
},
|
|
|
|
|
|
] as const
|
|
|
|
|
|
|
2026-07-05 06:34:06 +08:00
|
|
|
|
const ANTIGRAVITY_MODEL_LABELS: Record<string, string> = {
|
2026-07-06 03:43:42 +08:00
|
|
|
|
'gemini-pro-agent': 'Gemini 3.1 Pro (High)',
|
|
|
|
|
|
'gemini-3.1-pro-high': 'Gemini 3.1 Pro (High)',
|
|
|
|
|
|
'gemini-3.1-pro-low': 'Gemini 3.1 Pro (Low)',
|
|
|
|
|
|
'gemini-3-flash-agent': 'Gemini 3.5 Flash (High)',
|
|
|
|
|
|
'gemini-3.5-flash-low': 'Gemini 3.5 Flash (Medium)',
|
|
|
|
|
|
'gemini-3.5-flash-extra-low': 'Gemini 3.5 Flash (Low)',
|
|
|
|
|
|
'claude-opus-4-6-thinking': 'Claude Opus 4.6 (Thinking)',
|
|
|
|
|
|
'claude-sonnet-4-6': 'Claude Sonnet 4.6 (Thinking)',
|
|
|
|
|
|
'claude-sonnet-4-6-thinking': 'Claude Sonnet 4.6 (Thinking)',
|
2026-07-05 06:34:06 +08:00
|
|
|
|
'gemini-3.1-flash-image': 'Gemini 3.1 Flash Image',
|
|
|
|
|
|
'gemini-3.1-flash-lite': 'Gemini 3.1 Flash Lite',
|
|
|
|
|
|
'gemini-3-flash': 'Gemini 3 Flash',
|
|
|
|
|
|
'gemini-2.5-pro': 'Gemini 2.5 Pro',
|
2026-07-06 03:43:42 +08:00
|
|
|
|
'gemini-2.5-flash-thinking': 'Gemini 3.1 Flash Lite',
|
|
|
|
|
|
'gemini-2.5-flash': 'Gemini 3.1 Flash Lite',
|
|
|
|
|
|
'gemini-2.5-flash-lite': 'Gemini 3.1 Flash Lite',
|
|
|
|
|
|
'gpt-oss-120b-medium': 'GPT-OSS 120B (Medium)',
|
2026-07-05 06:34:06 +08:00
|
|
|
|
'tab_flash_lite_preview': 'Tab Flash Lite Preview',
|
|
|
|
|
|
'tab_jump_flash_lite_preview': 'Tab Jump Flash Lite Preview',
|
|
|
|
|
|
'models/proactive-observer': 'Proactive Observer',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const ANTIGRAVITY_MODEL_PRIORITY: Record<string, number> = {
|
2026-07-06 05:21:20 +08:00
|
|
|
|
'claude-opus-4-6-thinking': 10,
|
|
|
|
|
|
'claude-sonnet-4-6': 20,
|
|
|
|
|
|
'claude-sonnet-4-6-thinking': 25,
|
|
|
|
|
|
'gemini-pro-agent': 30,
|
|
|
|
|
|
'gemini-3.1-pro-high': 35,
|
|
|
|
|
|
'gemini-3.1-pro-low': 40,
|
|
|
|
|
|
'gemini-3-flash-agent': 50,
|
|
|
|
|
|
'gemini-3.5-flash-low': 60,
|
|
|
|
|
|
'gemini-3.5-flash-extra-low': 70,
|
2026-07-06 03:43:42 +08:00
|
|
|
|
'gemini-3.1-flash-image': 80,
|
|
|
|
|
|
'gemini-3.1-flash-lite': 90,
|
2026-07-05 06:34:06 +08:00
|
|
|
|
'gemini-3-flash': 180,
|
|
|
|
|
|
'gemini-2.5-pro': 300,
|
|
|
|
|
|
'gemini-2.5-flash-thinking': 310,
|
|
|
|
|
|
'gemini-2.5-flash': 320,
|
|
|
|
|
|
'gemini-2.5-flash-lite': 330,
|
|
|
|
|
|
'gpt-oss-120b-medium': 700,
|
|
|
|
|
|
'models/proactive-observer': 780,
|
|
|
|
|
|
'tab_flash_lite_preview': 800,
|
|
|
|
|
|
'tab_jump_flash_lite_preview': 810,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function isOpaqueAntigravityQuotaIdentifier(value: string): boolean {
|
|
|
|
|
|
return value.trim().startsWith('RateLimitResetCredit_')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function resolveAntigravityQuotaLabel(
|
|
|
|
|
|
model: string,
|
|
|
|
|
|
rawLabel: unknown,
|
|
|
|
|
|
opaqueDisplayIndex: { value: number },
|
|
|
|
|
|
): string {
|
|
|
|
|
|
const normalizedModel = model.trim()
|
|
|
|
|
|
const canonical = ANTIGRAVITY_MODEL_LABELS[normalizedModel]
|
|
|
|
|
|
if (canonical) return canonical
|
|
|
|
|
|
|
|
|
|
|
|
const candidate = String(rawLabel || '').trim()
|
|
|
|
|
|
if (candidate && !isOpaqueAntigravityQuotaIdentifier(candidate)) return candidate
|
|
|
|
|
|
if (isOpaqueAntigravityQuotaIdentifier(normalizedModel) || (candidate && isOpaqueAntigravityQuotaIdentifier(candidate))) {
|
|
|
|
|
|
const label = `Key-${opaqueDisplayIndex.value}`
|
|
|
|
|
|
opaqueDisplayIndex.value += 1
|
|
|
|
|
|
return label
|
|
|
|
|
|
}
|
|
|
|
|
|
return candidate || normalizedModel
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getAntigravityModelPriority(model: string): number {
|
|
|
|
|
|
const normalizedModel = model.trim()
|
|
|
|
|
|
const explicit = ANTIGRAVITY_MODEL_PRIORITY[normalizedModel]
|
|
|
|
|
|
if (explicit !== undefined) return explicit
|
2026-07-06 05:21:20 +08:00
|
|
|
|
if (normalizedModel.startsWith('claude-')) return 30
|
2026-07-05 06:34:06 +08:00
|
|
|
|
if (normalizedModel.startsWith('gemini-3.')) return 200
|
|
|
|
|
|
if (normalizedModel.startsWith('gemini-2.')) return 390
|
|
|
|
|
|
if (normalizedModel.startsWith('gemini-')) return 490
|
|
|
|
|
|
if (normalizedModel.startsWith('gpt-oss-')) return 700
|
|
|
|
|
|
if (normalizedModel.startsWith('models/')) return 780
|
|
|
|
|
|
if (normalizedModel.startsWith('tab_')) return 800
|
|
|
|
|
|
if (normalizedModel.startsWith('chat_')) return 900
|
|
|
|
|
|
if (isOpaqueAntigravityQuotaIdentifier(normalizedModel)) return 950
|
|
|
|
|
|
return 850
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function compareAntigravityQuotaItems<T extends AntigravityQuotaSortableItem>(
|
|
|
|
|
|
a: T,
|
|
|
|
|
|
b: T,
|
|
|
|
|
|
): number {
|
|
|
|
|
|
return (getAntigravityModelPriority(a.model) - getAntigravityModelPriority(b.model))
|
|
|
|
|
|
|| ((a.resetSeconds ?? Number.POSITIVE_INFINITY) - (b.resetSeconds ?? Number.POSITIVE_INFINITY))
|
|
|
|
|
|
|| (a.remainingPercent - b.remainingPercent)
|
|
|
|
|
|
|| a.label.localeCompare(b.label)
|
|
|
|
|
|
|| a.model.localeCompare(b.model)
|
|
|
|
|
|
}
|
2026-07-06 05:21:20 +08:00
|
|
|
|
|
|
|
|
|
|
export function dedupeAntigravityQuotaItemsByLabel<T extends AntigravityQuotaSortableItem>(
|
|
|
|
|
|
items: T[],
|
|
|
|
|
|
): T[] {
|
|
|
|
|
|
const selectedByLabel = new Map<string, T>()
|
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
|
const label = item.label.trim()
|
|
|
|
|
|
if (!label) continue
|
|
|
|
|
|
const selected = selectedByLabel.get(label)
|
|
|
|
|
|
if (!selected || compareAntigravityQuotaItems(item, selected) < 0) {
|
|
|
|
|
|
selectedByLabel.set(label, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return Array.from(selectedByLabel.values()).sort(compareAntigravityQuotaItems)
|
|
|
|
|
|
}
|
2026-09-01 10:15:57 +08:00
|
|
|
|
|
|
|
|
|
|
export function summarizeAntigravityQuotaItems<T extends AntigravityQuotaSortableItem>(
|
|
|
|
|
|
items: T[],
|
|
|
|
|
|
): T[] {
|
|
|
|
|
|
const itemsByGroup = new Map<string, T[]>()
|
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
|
const normalizedModel = item.model.trim().toLowerCase().replace(/^model:/, '')
|
|
|
|
|
|
const group = ANTIGRAVITY_QUOTA_GROUPS.find(candidate => candidate.matches(normalizedModel))
|
|
|
|
|
|
if (!group) continue
|
|
|
|
|
|
const groupedItems = itemsByGroup.get(group.label) ?? []
|
|
|
|
|
|
groupedItems.push(item)
|
|
|
|
|
|
itemsByGroup.set(group.label, groupedItems)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ANTIGRAVITY_QUOTA_GROUPS.map(group => group.label)
|
|
|
|
|
|
.map((label) => {
|
|
|
|
|
|
const groupedItems = itemsByGroup.get(label)
|
|
|
|
|
|
if (!groupedItems?.length) return undefined
|
|
|
|
|
|
const remainingValues = groupedItems
|
|
|
|
|
|
.map(item => item.remainingPercent)
|
|
|
|
|
|
.sort((left, right) => left - right)
|
|
|
|
|
|
const minRemaining = remainingValues[0] ?? 0
|
|
|
|
|
|
const maxRemaining = remainingValues.at(-1) ?? minRemaining
|
|
|
|
|
|
const selected = groupedItems.find(item => item.remainingPercent === minRemaining) ?? groupedItems[0]
|
|
|
|
|
|
const detail = Math.abs(maxRemaining - minRemaining) < 1e-6
|
|
|
|
|
|
? formatAntigravityQuotaValue(minRemaining)
|
|
|
|
|
|
: `${formatAntigravityQuotaValue(minRemaining)}–${formatAntigravityQuotaValue(maxRemaining)}`
|
|
|
|
|
|
return { ...selected, label, remainingPercent: minRemaining, detail }
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter((item): item is T => item !== undefined)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatAntigravityQuotaValue(value: number): string {
|
|
|
|
|
|
const rounded = Math.round(value)
|
2026-09-01 11:29:48 +08:00
|
|
|
|
const formatted = Math.abs(value - rounded) < 1e-6 ? String(rounded) : value.toFixed(1)
|
|
|
|
|
|
return `${formatted}%`
|
2026-09-01 10:15:57 +08:00
|
|
|
|
}
|