mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
fix(frontend): deduplicate Antigravity quota groups
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { messages } from '@/i18n/messages'
|
||||
import { resolveAntigravityQuotaGroupLabel } from '@/features/providers/utils/antigravityQuota'
|
||||
|
||||
describe('resolveAntigravityQuotaGroupLabel', () => {
|
||||
it('uses the localized compact weekly label instead of the upstream display name', () => {
|
||||
const window = {
|
||||
code: 'group:0:gemini-weekly',
|
||||
label: 'Gemini Models · Weekly Limit Remaining',
|
||||
quota_group_label: 'Gemini models',
|
||||
bucket_id: 'gemini-weekly',
|
||||
window: 'weekly',
|
||||
}
|
||||
|
||||
expect(resolveAntigravityQuotaGroupLabel(
|
||||
window,
|
||||
key => messages['zh-CN'][key],
|
||||
)).toBe('Gemini Models · 周')
|
||||
expect(resolveAntigravityQuotaGroupLabel(
|
||||
window,
|
||||
key => messages['en-US'][key],
|
||||
)).toBe('Gemini Models · Weekly')
|
||||
})
|
||||
|
||||
it('normalizes the legacy Claude family name and detects a five-hour bucket', () => {
|
||||
const window = {
|
||||
code: 'group:1:3p-5h',
|
||||
label: 'Claude & ChatGPT · 5 hour',
|
||||
quota_group_label: 'Claude & ChatGPT',
|
||||
bucket_id: '3p-5h',
|
||||
}
|
||||
|
||||
expect(resolveAntigravityQuotaGroupLabel(
|
||||
window,
|
||||
key => messages['zh-CN'][key],
|
||||
)).toBe('Claude and GPT models · 5小时')
|
||||
expect(resolveAntigravityQuotaGroupLabel(
|
||||
window,
|
||||
key => messages['en-US'][key],
|
||||
)).toBe('Claude and GPT models · 5 Hours')
|
||||
})
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
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 & ChatGPT', 82, '82%–100%'],
|
||||
])
|
||||
expect(items.map(item => item.resetSeconds)).toEqual([180, 120])
|
||||
expect(items[1]?.model).toBe('claude-sonnet-4-6')
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { QuotaWindowSnapshot } from '@/api/endpoints/types'
|
||||
|
||||
export interface AntigravityQuotaSortableItem {
|
||||
model: string
|
||||
label: string
|
||||
@@ -6,13 +8,11 @@ export interface AntigravityQuotaSortableItem {
|
||||
detail?: string
|
||||
}
|
||||
|
||||
const ANTIGRAVITY_QUOTA_GROUPS = [
|
||||
{ label: 'Gemini额度', matches: (model: string) => model.startsWith('gemini-') },
|
||||
{
|
||||
label: 'Claude & ChatGPT',
|
||||
matches: (model: string) => model.startsWith('claude-') || model.startsWith('gpt-'),
|
||||
},
|
||||
] as const
|
||||
type AntigravityQuotaPeriodMessageKey =
|
||||
| 'providers.antigravity.quota.period.weekly'
|
||||
| 'providers.antigravity.quota.period.fiveHours'
|
||||
|
||||
type AntigravityQuotaTranslator = (key: AntigravityQuotaPeriodMessageKey) => string
|
||||
|
||||
const ANTIGRAVITY_MODEL_LABELS: Record<string, string> = {
|
||||
'gemini-pro-agent': 'Gemini 3.1 Pro (High)',
|
||||
@@ -125,39 +125,63 @@ 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)
|
||||
function canonicalizeAntigravityQuotaGroupLabel(rawLabel: string): string {
|
||||
const normalized = rawLabel
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[._-]+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
|
||||
if (normalized === 'gemini额度' || /^gemini(?: models?)?$/.test(normalized)) {
|
||||
return 'Gemini Models'
|
||||
}
|
||||
if (
|
||||
normalized === 'claude & chatgpt'
|
||||
|| /^claude\s*(?:&|and)\s*(?:gpt|chatgpt)(?: models?)?$/.test(normalized)
|
||||
) {
|
||||
return 'Claude and GPT models'
|
||||
}
|
||||
return rawLabel.trim()
|
||||
}
|
||||
|
||||
function getAntigravityQuotaGroupName(window: QuotaWindowSnapshot): string {
|
||||
const explicitGroupLabel = String(window.quota_group_label || '').trim()
|
||||
if (explicitGroupLabel) {
|
||||
return canonicalizeAntigravityQuotaGroupLabel(explicitGroupLabel)
|
||||
}
|
||||
|
||||
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)
|
||||
const rawLabel = String(window.label || '').trim()
|
||||
const groupLabel = rawLabel.split(/\s*·\s*/, 1)[0]
|
||||
?.replace(/\s+(?:weekly limit remaining|weekly|5\s*hours?|5h)$/i, '')
|
||||
.trim()
|
||||
return canonicalizeAntigravityQuotaGroupLabel(groupLabel || rawLabel)
|
||||
}
|
||||
|
||||
function formatAntigravityQuotaValue(value: number): string {
|
||||
const rounded = Math.round(value)
|
||||
const formatted = Math.abs(value - rounded) < 1e-6 ? String(rounded) : value.toFixed(1)
|
||||
return `${formatted}%`
|
||||
function getAntigravityQuotaPeriodLabel(
|
||||
window: QuotaWindowSnapshot,
|
||||
translate: AntigravityQuotaTranslator,
|
||||
): string {
|
||||
const periodCandidates = [window.window, window.bucket_id, window.code, window.label]
|
||||
.map(value => String(value || '').trim())
|
||||
.filter(Boolean)
|
||||
const normalized = periodCandidates.join(' ').toLowerCase()
|
||||
|
||||
if (/(?:^|[^a-z])weekly(?:[^a-z]|$)/.test(normalized)) {
|
||||
return translate('providers.antigravity.quota.period.weekly')
|
||||
}
|
||||
if (/(?:^|[^a-z0-9])5\s*(?:h|hours?)(?:[^a-z0-9]|$)/.test(normalized)) {
|
||||
return translate('providers.antigravity.quota.period.fiveHours')
|
||||
}
|
||||
|
||||
return String(window.window || '').trim()
|
||||
}
|
||||
|
||||
export function resolveAntigravityQuotaGroupLabel(
|
||||
window: QuotaWindowSnapshot,
|
||||
translate: AntigravityQuotaTranslator,
|
||||
): string {
|
||||
const groupName = getAntigravityQuotaGroupName(window)
|
||||
const periodLabel = getAntigravityQuotaPeriodLabel(window, translate)
|
||||
if (groupName && periodLabel) return `${groupName} · ${periodLabel}`
|
||||
return groupName || String(window.label || window.code || '').trim()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user