fix(frontend): align Antigravity quota summaries

This commit is contained in:
ZheFox
2026-09-01 15:38:00 +08:00
parent 57abb20778
commit 6c71f87589
7 changed files with 42 additions and 41 deletions
@@ -104,7 +104,7 @@ describe('pool key display panels', () => {
meterClass: 'text-emerald-600',
},
{
label: 'Claude额度',
label: 'Claude & ChatGPT',
remainingPercent: 100,
resetText: '1h 后重置',
meterText: '100%',
@@ -119,7 +119,7 @@ describe('pool key display panels', () => {
expect(root.querySelector('[data-testid="pool-quota-rows"]')?.className).toContain('space-y-2')
expect(Array.from(root.querySelectorAll('[data-testid="pool-quota-period-label"]')).map(node => node.textContent)).toEqual([
'Gemini额度',
'Claude额度',
'Claude & ChatGPT',
])
expect(Array.from(root.querySelectorAll('[data-testid="pool-quota-meter-text"]')).map(node => node.textContent)).toEqual(['90.6%100%', '100%'])
expect(root.querySelectorAll('[data-testid="pool-quota-progress-track"]')).toHaveLength(2)
@@ -8,7 +8,7 @@
@update:model-value="$emit('update:open', $event)"
>
<template
v-if="providerId && items.length > 0"
v-if="providerId && rawItems.length > 0"
#header-actions
>
<DropdownMenu :modal="false">
@@ -32,7 +32,7 @@
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
v-for="item in items"
v-for="item in rawItems"
:key="item.model"
:title="item.model"
@select="handleTestModel(item.model)"
@@ -56,13 +56,13 @@
<div class="min-w-0 flex-1 mr-2">
<div
class="text-muted-foreground truncate"
:title="item.model"
:title="item.label"
>
{{ item.label }}
</div>
</div>
<span :class="getQuotaRemainingClass(item.usedPercent)">
{{ item.remainingPercent.toFixed(1) }}%
{{ item.detail || `${item.remainingPercent.toFixed(1)}%` }}
</span>
</div>
<div class="relative w-full h-1.5 bg-border rounded-full overflow-hidden">
@@ -122,6 +122,7 @@ import {
compareAntigravityQuotaItems,
dedupeAntigravityQuotaItemsByLabel,
resolveAntigravityQuotaLabel,
summarizeAntigravityQuotaItems,
} from '@/features/providers/utils/antigravityQuota'
const props = defineProps<{
@@ -143,6 +144,7 @@ interface QuotaItem {
usedPercent: number
remainingPercent: number
resetSeconds: number | null
detail?: string
}
const { error: showError, success: showSuccess } = useToast()
@@ -236,7 +238,7 @@ function buildItemsFromQuotaSnapshot(quota: QuotaStatusSnapshot | null | undefin
return dedupeAntigravityQuotaItemsByLabel(items)
}
const items = computed<QuotaItem[]>(() => {
const rawItems = computed<QuotaItem[]>(() => {
const snapshotItems = buildItemsFromQuotaSnapshot(props.quotaSnapshot)
if (snapshotItems.length > 0) return snapshotItems
@@ -288,6 +290,8 @@ const items = computed<QuotaItem[]>(() => {
return dedupeAntigravityQuotaItemsByLabel(result)
})
const items = computed<QuotaItem[]>(() => summarizeAntigravityQuotaItems(rawItems.value))
async function handleTestModel(modelName: string) {
if (!props.providerId || testingModel.value) return
@@ -376,12 +376,13 @@
/>
<div class="grid grid-cols-2 gap-3">
<ProviderQuotaProgressRow
v-for="item in getAntigravityQuotaPreviewForKey(key)"
v-for="item in getAntigravityQuotaSummaryForKey(key)"
:key="item.model"
:label="item.label"
:title="item.model"
:title="item.label"
:used-percent="item.usedPercent"
:remaining-percent="item.remainingPercent"
:meter-text="item.detail"
:meter-class="getQuotaRemainingClass(item.usedPercent)"
:bar-class="getQuotaRemainingBarColor(item.usedPercent)"
>
@@ -401,14 +402,6 @@
</div>
</template>
</ProviderQuotaProgressRow>
<button
v-if="getAntigravityQuotaHiddenCountForKey(key) > 0"
type="button"
class="col-span-2 text-left text-[10px] text-muted-foreground hover:text-foreground transition-colors"
@click="openAntigravityQuotaDialog(key)"
>
{{ legacyT('另有') }} {{ getAntigravityQuotaHiddenCountForKey(key) }} {{ legacyT('个模型,查看全部') }}
</button>
</div>
</template>
</div>
@@ -989,6 +982,7 @@ import {
compareAntigravityQuotaItems,
dedupeAntigravityQuotaItemsByLabel,
resolveAntigravityQuotaLabel,
summarizeAntigravityQuotaItems,
} from '@/features/providers/utils/antigravityQuota'
import {
deleteEndpointKey,
@@ -3374,6 +3368,7 @@ interface AntigravityQuotaItem {
usedPercent: number
remainingPercent: number
resetSeconds: number | null
detail?: string
}
interface GeminiCliQuotaItem {
@@ -3579,20 +3574,14 @@ function getAntigravityQuotaItemsFromSnapshot(key: EndpointAPIKey): AntigravityQ
return dedupeAntigravityQuotaItemsByLabel(items)
}
const ANTIGRAVITY_QUOTA_PREVIEW_LIMIT = 6
function getAntigravityQuotaItemsForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
const snapshotItems = getAntigravityQuotaItemsFromSnapshot(key)
if (snapshotItems.length > 0) return snapshotItems
return getAntigravityQuotaItems(key.upstream_metadata)
}
function getAntigravityQuotaPreviewForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
return getAntigravityQuotaItemsForKey(key).slice(0, ANTIGRAVITY_QUOTA_PREVIEW_LIMIT)
}
function getAntigravityQuotaHiddenCountForKey(key: EndpointAPIKey): number {
return Math.max(getAntigravityQuotaItemsForKey(key).length - ANTIGRAVITY_QUOTA_PREVIEW_LIMIT, 0)
function getAntigravityQuotaSummaryForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
return summarizeAntigravityQuotaItems(getAntigravityQuotaItemsForKey(key))
}
function getResetCountdownText(
@@ -11,7 +11,7 @@
:class="meterClass"
data-testid="provider-quota-progress-meter"
>
{{ normalizedRemainingPercent.toFixed(1) }}%
{{ meterText || `${normalizedRemainingPercent.toFixed(1)}%` }}
</span>
</div>
<div class="relative w-full h-1.5 bg-border rounded-full overflow-hidden">
@@ -47,6 +47,7 @@ const props = withDefaults(defineProps<{
barClass?: string
footerClass?: string
resetText?: string | null
meterText?: string | null
}>(), {
usedPercent: null,
remainingPercent: null,
@@ -55,6 +56,7 @@ const props = withDefaults(defineProps<{
barClass: '',
footerClass: '',
resetText: null,
meterText: null,
})
function normalizePercent(value: number | null | undefined): number | null {
@@ -102,7 +102,7 @@ function mount(metadata: UpstreamMetadata) {
}
describe('AntigravityQuotaDialog', () => {
it('renders opaque quota identifiers with concise visible labels', () => {
it('hides quota buckets outside the shared pool summary families', () => {
const rawIdentifier = 'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb'
const { root, unmount } = mount({
antigravity: {
@@ -116,14 +116,15 @@ describe('AntigravityQuotaDialog', () => {
},
})
expect(root.textContent).toContain('Key-1')
expect(root.textContent).toContain('暂无配额数据')
expect(root.textContent).not.toContain('Key-1')
expect(root.textContent).not.toContain(rawIdentifier)
expect(root.textContent).toContain('25.0%')
expect(root.textContent).not.toContain('25.0%')
unmount()
})
it('orders important Gemini and Claude quota rows before low-priority rows', () => {
it('renders only the shared Gemini and Claude ChatGPT family summaries', () => {
const { root, unmount } = mount({
antigravity: {
quota_by_model: {
@@ -164,16 +165,20 @@ describe('AntigravityQuotaDialog', () => {
expect(text).not.toContain('gemini-pro-agent')
expect(text).not.toContain('claude-opus-4-6-thinking')
expect(text.indexOf('Claude Opus 4.6 (Thinking)')).toBeLessThan(text.indexOf('Gemini 3.1 Pro (High)'))
expect(text.indexOf('Gemini 3.1 Pro (High)')).toBeLessThan(text.indexOf('Gemini 3.5 Flash (High)'))
expect(text.indexOf('Gemini 3.5 Flash (High)')).toBeLessThan(text.indexOf('Gemini 3.5 Flash (Medium)'))
expect(text.indexOf('Gemini 3.5 Flash (Medium)')).toBeLessThan(text.indexOf('Tab Flash Lite Preview'))
expect(text.indexOf('Tab Flash Lite Preview')).toBeLessThan(text.indexOf('chat_20706'))
expect(text).toContain('Gemini额度')
expect(text).toContain('80%95%')
expect(text).toContain('Claude & ChatGPT')
expect(text).toContain('100%')
expect(text).not.toContain('Gemini 3.1 Pro (High)')
expect(text).not.toContain('Gemini 3.5 Flash (High)')
expect(text).not.toContain('Gemini 3.5 Flash (Medium)')
expect(text).not.toContain('Tab Flash Lite Preview')
expect(text).not.toContain('chat_20706')
unmount()
})
it('renders one row for duplicate quota labels and keeps the preferred active bucket', () => {
it('collapses duplicate model buckets into one family range', () => {
const { root, unmount } = mount({
antigravity: {
quota_by_model: {
@@ -197,10 +202,11 @@ describe('AntigravityQuotaDialog', () => {
})
const text = root.textContent || ''
expect(text.match(/Gemini 3\.1 Pro \(High\)/g)).toHaveLength(1)
expect(text).toContain('40.0%')
expect(text.match(/Gemini额度/g)).toHaveLength(1)
expect(text).toContain('40%90%')
expect(text).not.toContain('95.0%')
expect(text.indexOf('Gemini 3.1 Pro (High)')).toBeLessThan(text.indexOf('Gemini 3.5 Flash (High)'))
expect(text).not.toContain('Gemini 3.1 Pro (High)')
expect(text).not.toContain('Gemini 3.5 Flash (High)')
unmount()
})
@@ -16,7 +16,7 @@ describe('summarizeAntigravityQuotaItems', () => {
expect(items.map(item => [item.label, item.remainingPercent, item.detail])).toEqual([
['Gemini额度', 90.6, '90.6%95%'],
['Claude额度', 82, '82%100%'],
['Claude & ChatGPT', 82, '82%100%'],
])
expect(items.map(item => item.resetSeconds)).toEqual([180, 120])
expect(items[1]?.model).toBe('claude-sonnet-4-6')
@@ -9,7 +9,7 @@ export interface AntigravityQuotaSortableItem {
const ANTIGRAVITY_QUOTA_GROUPS = [
{ label: 'Gemini额度', matches: (model: string) => model.startsWith('gemini-') },
{
label: 'Claude额度',
label: 'Claude & ChatGPT',
matches: (model: string) => model.startsWith('claude-') || model.startsWith('gpt-'),
},
] as const