fix(pool): isolate model quotas and compact account display

This commit is contained in:
ZheFox
2026-09-01 10:15:57 +08:00
parent 9b819169d5
commit 2fe2600021
12 changed files with 383 additions and 43 deletions
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest'
import { summarizeAntigravityQuotaItems } from '@/features/providers/utils/antigravityQuota'
describe('summarizeAntigravityQuotaItems', () => {
it('groups model families without collapsing their independent quota values', () => {
const items = summarizeAntigravityQuotaItems([
{ model: 'claude-opus-4-6-thinking', label: 'Claude Opus', remainingPercent: 100, resetSeconds: 60 },
{ model: 'claude-sonnet-4-6', label: 'Claude Sonnet', remainingPercent: 82, resetSeconds: 120 },
{ model: 'gemini-3.1-pro-high', label: 'Gemini Pro', remainingPercent: 90.6, resetSeconds: 180 },
{ model: 'gemini-3-flash-agent', label: 'Gemini Flash', remainingPercent: 95, resetSeconds: 240 },
{ model: 'gpt-oss-120b-medium', label: 'GPT-OSS', remainingPercent: 100, resetSeconds: 300 },
{ model: 'tab_flash_lite_preview', label: 'Tab', remainingPercent: 76, resetSeconds: 360 },
{ model: 'chat_20706', label: 'Chat', remainingPercent: 64, resetSeconds: 420 },
])
expect(items.map(item => [item.label, item.remainingPercent, item.detail])).toEqual([
['Gemini额度', 90.6, '90.6–95'],
['Claude额度', 82, '82–100'],
])
expect(items[1]?.model).toBe('claude-sonnet-4-6')
})
})
@@ -3,8 +3,17 @@ export interface AntigravityQuotaSortableItem {
label: string
remainingPercent: number
resetSeconds: number | null
detail?: string
}
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
const ANTIGRAVITY_MODEL_LABELS: Record<string, string> = {
'gemini-pro-agent': 'Gemini 3.1 Pro (High)',
'gemini-3.1-pro-high': 'Gemini 3.1 Pro (High)',
@@ -115,3 +124,39 @@ export function dedupeAntigravityQuotaItemsByLabel<T extends AntigravityQuotaSor
}
return Array.from(selectedByLabel.values()).sort(compareAntigravityQuotaItems)
}
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)
return Math.abs(value - rounded) < 1e-6 ? String(rounded) : value.toFixed(1)
}