mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-14 23:20:19 +08:00
fix(frontend): align Antigravity quota summaries
This commit is contained in:
@@ -104,7 +104,7 @@ describe('pool key display panels', () => {
|
|||||||
meterClass: 'text-emerald-600',
|
meterClass: 'text-emerald-600',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Claude额度',
|
label: 'Claude & ChatGPT',
|
||||||
remainingPercent: 100,
|
remainingPercent: 100,
|
||||||
resetText: '1h 后重置',
|
resetText: '1h 后重置',
|
||||||
meterText: '100%',
|
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(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([
|
expect(Array.from(root.querySelectorAll('[data-testid="pool-quota-period-label"]')).map(node => node.textContent)).toEqual([
|
||||||
'Gemini额度',
|
'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(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)
|
expect(root.querySelectorAll('[data-testid="pool-quota-progress-track"]')).toHaveLength(2)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
@update:model-value="$emit('update:open', $event)"
|
@update:model-value="$emit('update:open', $event)"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-if="providerId && items.length > 0"
|
v-if="providerId && rawItems.length > 0"
|
||||||
#header-actions
|
#header-actions
|
||||||
>
|
>
|
||||||
<DropdownMenu :modal="false">
|
<DropdownMenu :modal="false">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
v-for="item in items"
|
v-for="item in rawItems"
|
||||||
:key="item.model"
|
:key="item.model"
|
||||||
:title="item.model"
|
:title="item.model"
|
||||||
@select="handleTestModel(item.model)"
|
@select="handleTestModel(item.model)"
|
||||||
@@ -56,13 +56,13 @@
|
|||||||
<div class="min-w-0 flex-1 mr-2">
|
<div class="min-w-0 flex-1 mr-2">
|
||||||
<div
|
<div
|
||||||
class="text-muted-foreground truncate"
|
class="text-muted-foreground truncate"
|
||||||
:title="item.model"
|
:title="item.label"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span :class="getQuotaRemainingClass(item.usedPercent)">
|
<span :class="getQuotaRemainingClass(item.usedPercent)">
|
||||||
{{ item.remainingPercent.toFixed(1) }}%
|
{{ item.detail || `${item.remainingPercent.toFixed(1)}%` }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative w-full h-1.5 bg-border rounded-full overflow-hidden">
|
<div class="relative w-full h-1.5 bg-border rounded-full overflow-hidden">
|
||||||
@@ -122,6 +122,7 @@ import {
|
|||||||
compareAntigravityQuotaItems,
|
compareAntigravityQuotaItems,
|
||||||
dedupeAntigravityQuotaItemsByLabel,
|
dedupeAntigravityQuotaItemsByLabel,
|
||||||
resolveAntigravityQuotaLabel,
|
resolveAntigravityQuotaLabel,
|
||||||
|
summarizeAntigravityQuotaItems,
|
||||||
} from '@/features/providers/utils/antigravityQuota'
|
} from '@/features/providers/utils/antigravityQuota'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -143,6 +144,7 @@ interface QuotaItem {
|
|||||||
usedPercent: number
|
usedPercent: number
|
||||||
remainingPercent: number
|
remainingPercent: number
|
||||||
resetSeconds: number | null
|
resetSeconds: number | null
|
||||||
|
detail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error: showError, success: showSuccess } = useToast()
|
const { error: showError, success: showSuccess } = useToast()
|
||||||
@@ -236,7 +238,7 @@ function buildItemsFromQuotaSnapshot(quota: QuotaStatusSnapshot | null | undefin
|
|||||||
return dedupeAntigravityQuotaItemsByLabel(items)
|
return dedupeAntigravityQuotaItemsByLabel(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = computed<QuotaItem[]>(() => {
|
const rawItems = computed<QuotaItem[]>(() => {
|
||||||
const snapshotItems = buildItemsFromQuotaSnapshot(props.quotaSnapshot)
|
const snapshotItems = buildItemsFromQuotaSnapshot(props.quotaSnapshot)
|
||||||
if (snapshotItems.length > 0) return snapshotItems
|
if (snapshotItems.length > 0) return snapshotItems
|
||||||
|
|
||||||
@@ -288,6 +290,8 @@ const items = computed<QuotaItem[]>(() => {
|
|||||||
return dedupeAntigravityQuotaItemsByLabel(result)
|
return dedupeAntigravityQuotaItemsByLabel(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const items = computed<QuotaItem[]>(() => summarizeAntigravityQuotaItems(rawItems.value))
|
||||||
|
|
||||||
async function handleTestModel(modelName: string) {
|
async function handleTestModel(modelName: string) {
|
||||||
if (!props.providerId || testingModel.value) return
|
if (!props.providerId || testingModel.value) return
|
||||||
|
|
||||||
|
|||||||
@@ -376,12 +376,13 @@
|
|||||||
/>
|
/>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<ProviderQuotaProgressRow
|
<ProviderQuotaProgressRow
|
||||||
v-for="item in getAntigravityQuotaPreviewForKey(key)"
|
v-for="item in getAntigravityQuotaSummaryForKey(key)"
|
||||||
:key="item.model"
|
:key="item.model"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:title="item.model"
|
:title="item.label"
|
||||||
:used-percent="item.usedPercent"
|
:used-percent="item.usedPercent"
|
||||||
:remaining-percent="item.remainingPercent"
|
:remaining-percent="item.remainingPercent"
|
||||||
|
:meter-text="item.detail"
|
||||||
:meter-class="getQuotaRemainingClass(item.usedPercent)"
|
:meter-class="getQuotaRemainingClass(item.usedPercent)"
|
||||||
:bar-class="getQuotaRemainingBarColor(item.usedPercent)"
|
:bar-class="getQuotaRemainingBarColor(item.usedPercent)"
|
||||||
>
|
>
|
||||||
@@ -401,14 +402,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ProviderQuotaProgressRow>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -989,6 +982,7 @@ import {
|
|||||||
compareAntigravityQuotaItems,
|
compareAntigravityQuotaItems,
|
||||||
dedupeAntigravityQuotaItemsByLabel,
|
dedupeAntigravityQuotaItemsByLabel,
|
||||||
resolveAntigravityQuotaLabel,
|
resolveAntigravityQuotaLabel,
|
||||||
|
summarizeAntigravityQuotaItems,
|
||||||
} from '@/features/providers/utils/antigravityQuota'
|
} from '@/features/providers/utils/antigravityQuota'
|
||||||
import {
|
import {
|
||||||
deleteEndpointKey,
|
deleteEndpointKey,
|
||||||
@@ -3374,6 +3368,7 @@ interface AntigravityQuotaItem {
|
|||||||
usedPercent: number
|
usedPercent: number
|
||||||
remainingPercent: number
|
remainingPercent: number
|
||||||
resetSeconds: number | null
|
resetSeconds: number | null
|
||||||
|
detail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GeminiCliQuotaItem {
|
interface GeminiCliQuotaItem {
|
||||||
@@ -3579,20 +3574,14 @@ function getAntigravityQuotaItemsFromSnapshot(key: EndpointAPIKey): AntigravityQ
|
|||||||
return dedupeAntigravityQuotaItemsByLabel(items)
|
return dedupeAntigravityQuotaItemsByLabel(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ANTIGRAVITY_QUOTA_PREVIEW_LIMIT = 6
|
|
||||||
|
|
||||||
function getAntigravityQuotaItemsForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
|
function getAntigravityQuotaItemsForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
|
||||||
const snapshotItems = getAntigravityQuotaItemsFromSnapshot(key)
|
const snapshotItems = getAntigravityQuotaItemsFromSnapshot(key)
|
||||||
if (snapshotItems.length > 0) return snapshotItems
|
if (snapshotItems.length > 0) return snapshotItems
|
||||||
return getAntigravityQuotaItems(key.upstream_metadata)
|
return getAntigravityQuotaItems(key.upstream_metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAntigravityQuotaPreviewForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
|
function getAntigravityQuotaSummaryForKey(key: EndpointAPIKey): AntigravityQuotaItem[] {
|
||||||
return getAntigravityQuotaItemsForKey(key).slice(0, ANTIGRAVITY_QUOTA_PREVIEW_LIMIT)
|
return summarizeAntigravityQuotaItems(getAntigravityQuotaItemsForKey(key))
|
||||||
}
|
|
||||||
|
|
||||||
function getAntigravityQuotaHiddenCountForKey(key: EndpointAPIKey): number {
|
|
||||||
return Math.max(getAntigravityQuotaItemsForKey(key).length - ANTIGRAVITY_QUOTA_PREVIEW_LIMIT, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResetCountdownText(
|
function getResetCountdownText(
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
:class="meterClass"
|
:class="meterClass"
|
||||||
data-testid="provider-quota-progress-meter"
|
data-testid="provider-quota-progress-meter"
|
||||||
>
|
>
|
||||||
{{ normalizedRemainingPercent.toFixed(1) }}%
|
{{ meterText || `${normalizedRemainingPercent.toFixed(1)}%` }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative w-full h-1.5 bg-border rounded-full overflow-hidden">
|
<div class="relative w-full h-1.5 bg-border rounded-full overflow-hidden">
|
||||||
@@ -47,6 +47,7 @@ const props = withDefaults(defineProps<{
|
|||||||
barClass?: string
|
barClass?: string
|
||||||
footerClass?: string
|
footerClass?: string
|
||||||
resetText?: string | null
|
resetText?: string | null
|
||||||
|
meterText?: string | null
|
||||||
}>(), {
|
}>(), {
|
||||||
usedPercent: null,
|
usedPercent: null,
|
||||||
remainingPercent: null,
|
remainingPercent: null,
|
||||||
@@ -55,6 +56,7 @@ const props = withDefaults(defineProps<{
|
|||||||
barClass: '',
|
barClass: '',
|
||||||
footerClass: '',
|
footerClass: '',
|
||||||
resetText: null,
|
resetText: null,
|
||||||
|
meterText: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
function normalizePercent(value: number | null | undefined): number | null {
|
function normalizePercent(value: number | null | undefined): number | null {
|
||||||
|
|||||||
+19
-13
@@ -102,7 +102,7 @@ function mount(metadata: UpstreamMetadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('AntigravityQuotaDialog', () => {
|
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 rawIdentifier = 'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb'
|
||||||
const { root, unmount } = mount({
|
const { root, unmount } = mount({
|
||||||
antigravity: {
|
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).not.toContain(rawIdentifier)
|
||||||
expect(root.textContent).toContain('25.0%')
|
expect(root.textContent).not.toContain('25.0%')
|
||||||
|
|
||||||
unmount()
|
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({
|
const { root, unmount } = mount({
|
||||||
antigravity: {
|
antigravity: {
|
||||||
quota_by_model: {
|
quota_by_model: {
|
||||||
@@ -164,16 +165,20 @@ describe('AntigravityQuotaDialog', () => {
|
|||||||
|
|
||||||
expect(text).not.toContain('gemini-pro-agent')
|
expect(text).not.toContain('gemini-pro-agent')
|
||||||
expect(text).not.toContain('claude-opus-4-6-thinking')
|
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).toContain('Gemini额度')
|
||||||
expect(text.indexOf('Gemini 3.1 Pro (High)')).toBeLessThan(text.indexOf('Gemini 3.5 Flash (High)'))
|
expect(text).toContain('80%–95%')
|
||||||
expect(text.indexOf('Gemini 3.5 Flash (High)')).toBeLessThan(text.indexOf('Gemini 3.5 Flash (Medium)'))
|
expect(text).toContain('Claude & ChatGPT')
|
||||||
expect(text.indexOf('Gemini 3.5 Flash (Medium)')).toBeLessThan(text.indexOf('Tab Flash Lite Preview'))
|
expect(text).toContain('100%')
|
||||||
expect(text.indexOf('Tab Flash Lite Preview')).toBeLessThan(text.indexOf('chat_20706'))
|
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()
|
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({
|
const { root, unmount } = mount({
|
||||||
antigravity: {
|
antigravity: {
|
||||||
quota_by_model: {
|
quota_by_model: {
|
||||||
@@ -197,10 +202,11 @@ describe('AntigravityQuotaDialog', () => {
|
|||||||
})
|
})
|
||||||
const text = root.textContent || ''
|
const text = root.textContent || ''
|
||||||
|
|
||||||
expect(text.match(/Gemini 3\.1 Pro \(High\)/g)).toHaveLength(1)
|
expect(text.match(/Gemini额度/g)).toHaveLength(1)
|
||||||
expect(text).toContain('40.0%')
|
expect(text).toContain('40%–90%')
|
||||||
expect(text).not.toContain('95.0%')
|
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()
|
unmount()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe('summarizeAntigravityQuotaItems', () => {
|
|||||||
|
|
||||||
expect(items.map(item => [item.label, item.remainingPercent, item.detail])).toEqual([
|
expect(items.map(item => [item.label, item.remainingPercent, item.detail])).toEqual([
|
||||||
['Gemini额度', 90.6, '90.6%–95%'],
|
['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.map(item => item.resetSeconds)).toEqual([180, 120])
|
||||||
expect(items[1]?.model).toBe('claude-sonnet-4-6')
|
expect(items[1]?.model).toBe('claude-sonnet-4-6')
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface AntigravityQuotaSortableItem {
|
|||||||
const ANTIGRAVITY_QUOTA_GROUPS = [
|
const ANTIGRAVITY_QUOTA_GROUPS = [
|
||||||
{ label: 'Gemini额度', matches: (model: string) => model.startsWith('gemini-') },
|
{ label: 'Gemini额度', matches: (model: string) => model.startsWith('gemini-') },
|
||||||
{
|
{
|
||||||
label: 'Claude额度',
|
label: 'Claude & ChatGPT',
|
||||||
matches: (model: string) => model.startsWith('claude-') || model.startsWith('gpt-'),
|
matches: (model: string) => model.startsWith('claude-') || model.startsWith('gpt-'),
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|||||||
Reference in New Issue
Block a user