mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
优化 Antigravity 配额展示顺序
This commit is contained in:
@@ -60,13 +60,6 @@
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item.label !== item.model"
|
||||
class="text-[9px] leading-tight text-muted-foreground/55 truncate font-mono"
|
||||
:title="item.model"
|
||||
>
|
||||
{{ item.model }}
|
||||
</div>
|
||||
</div>
|
||||
<span :class="getQuotaRemainingClass(item.usedPercent)">
|
||||
{{ item.remainingPercent.toFixed(1) }}%
|
||||
@@ -125,6 +118,10 @@ import { testModel } from '@/api/endpoints/providers'
|
||||
import type { UpstreamMetadata, QuotaStatusSnapshot, QuotaWindowSnapshot } from '@/api/endpoints/types'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import {
|
||||
compareAntigravityQuotaItems,
|
||||
resolveAntigravityQuotaLabel,
|
||||
} from '@/features/providers/utils/antigravityQuota'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
@@ -194,32 +191,6 @@ function secondsUntilUnixReset(resetAt: unknown): number | null {
|
||||
return Math.max(Math.floor(numericResetAt - now), 0)
|
||||
}
|
||||
|
||||
function sortQuotaItems(a: QuotaItem, b: QuotaItem): number {
|
||||
return (a.remainingPercent - b.remainingPercent)
|
||||
|| ((a.resetSeconds ?? Number.POSITIVE_INFINITY) - (b.resetSeconds ?? Number.POSITIVE_INFINITY))
|
||||
|| a.label.localeCompare(b.label)
|
||||
|| a.model.localeCompare(b.model)
|
||||
}
|
||||
|
||||
function isOpaqueAntigravityQuotaIdentifier(value: string): boolean {
|
||||
return value.trim().startsWith('RateLimitResetCredit_')
|
||||
}
|
||||
|
||||
function resolveQuotaLabel(
|
||||
model: string,
|
||||
rawLabel: unknown,
|
||||
opaqueDisplayIndex: { value: number },
|
||||
): string {
|
||||
const candidate = String(rawLabel || '').trim()
|
||||
if (candidate && !isOpaqueAntigravityQuotaIdentifier(candidate)) return candidate
|
||||
if (isOpaqueAntigravityQuotaIdentifier(model) || (candidate && isOpaqueAntigravityQuotaIdentifier(candidate))) {
|
||||
const label = `Key-${opaqueDisplayIndex.value}`
|
||||
opaqueDisplayIndex.value += 1
|
||||
return label
|
||||
}
|
||||
return candidate || model
|
||||
}
|
||||
|
||||
function buildItemsFromQuotaSnapshot(quota: QuotaStatusSnapshot | null | undefined): QuotaItem[] {
|
||||
if (!quota) return []
|
||||
|
||||
@@ -252,7 +223,7 @@ function buildItemsFromQuotaSnapshot(quota: QuotaStatusSnapshot | null | undefin
|
||||
|
||||
return {
|
||||
model,
|
||||
label: resolveQuotaLabel(model, window.label || window.model, opaqueDisplayIndex),
|
||||
label: resolveAntigravityQuotaLabel(model, window.label || window.model, opaqueDisplayIndex),
|
||||
usedPercent,
|
||||
remainingPercent,
|
||||
resetSeconds: getQuotaWindowLiveResetSeconds(quota, window),
|
||||
@@ -260,7 +231,7 @@ function buildItemsFromQuotaSnapshot(quota: QuotaStatusSnapshot | null | undefin
|
||||
})
|
||||
.filter((item): item is QuotaItem => item !== null)
|
||||
|
||||
items.sort(sortQuotaItems)
|
||||
items.sort(compareAntigravityQuotaItems)
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -305,14 +276,14 @@ const items = computed<QuotaItem[]>(() => {
|
||||
|
||||
result.push({
|
||||
model,
|
||||
label: resolveQuotaLabel(model, info['display_name'], opaqueDisplayIndex),
|
||||
label: resolveAntigravityQuotaLabel(model, info['display_name'], opaqueDisplayIndex),
|
||||
usedPercent,
|
||||
remainingPercent,
|
||||
resetSeconds,
|
||||
})
|
||||
}
|
||||
|
||||
result.sort(sortQuotaItems)
|
||||
result.sort(compareAntigravityQuotaItems)
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
@@ -372,13 +372,6 @@
|
||||
>
|
||||
<template #footer>
|
||||
<div class="mt-0.5 space-y-0.5">
|
||||
<div
|
||||
v-if="item.label !== item.model"
|
||||
class="text-[9px] text-muted-foreground/55 truncate font-mono"
|
||||
:title="item.model"
|
||||
>
|
||||
{{ item.model }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item.resetSeconds !== null || item.usedPercent > 0"
|
||||
class="text-[9px] text-muted-foreground/70"
|
||||
@@ -967,6 +960,10 @@ import ProviderMonthlyQuotaCard from '@/features/providers/components/ProviderMo
|
||||
import ProviderQuotaProgressRow from '@/features/providers/components/ProviderQuotaProgressRow.vue'
|
||||
import ProviderQuotaSectionHeader from '@/features/providers/components/ProviderQuotaSectionHeader.vue'
|
||||
import { useProxyNodesStore } from '@/stores/proxy-nodes'
|
||||
import {
|
||||
compareAntigravityQuotaItems,
|
||||
resolveAntigravityQuotaLabel,
|
||||
} from '@/features/providers/utils/antigravityQuota'
|
||||
import {
|
||||
deleteEndpointKey,
|
||||
recoverKeyHealth,
|
||||
@@ -3385,32 +3382,6 @@ function coerceAntigravityRemainingFraction(value: number | string | null | unde
|
||||
return Math.min(Math.max(numericValue, 0), 1)
|
||||
}
|
||||
|
||||
function antigravityQuotaItemSort(a: AntigravityQuotaItem, b: AntigravityQuotaItem): number {
|
||||
return (a.remainingPercent - b.remainingPercent)
|
||||
|| ((a.resetSeconds ?? Number.POSITIVE_INFINITY) - (b.resetSeconds ?? Number.POSITIVE_INFINITY))
|
||||
|| a.label.localeCompare(b.label)
|
||||
|| a.model.localeCompare(b.model)
|
||||
}
|
||||
|
||||
function isOpaqueAntigravityQuotaIdentifier(value: string): boolean {
|
||||
return value.trim().startsWith('RateLimitResetCredit_')
|
||||
}
|
||||
|
||||
function resolveAntigravityQuotaLabel(
|
||||
model: string,
|
||||
rawLabel: unknown,
|
||||
opaqueDisplayIndex: { value: number },
|
||||
): string {
|
||||
const candidate = String(rawLabel || '').trim()
|
||||
if (candidate && !isOpaqueAntigravityQuotaIdentifier(candidate)) return candidate
|
||||
if (isOpaqueAntigravityQuotaIdentifier(model) || (candidate && isOpaqueAntigravityQuotaIdentifier(candidate))) {
|
||||
const label = `Key-${opaqueDisplayIndex.value}`
|
||||
opaqueDisplayIndex.value += 1
|
||||
return label
|
||||
}
|
||||
return candidate || model
|
||||
}
|
||||
|
||||
function getAntigravityQuotaItems(metadata: UpstreamMetadata | null | undefined): AntigravityQuotaItem[] {
|
||||
const quotaByModel = metadata?.antigravity?.quota_by_model
|
||||
if (!quotaByModel || typeof quotaByModel !== 'object') return []
|
||||
@@ -3449,7 +3420,7 @@ function getAntigravityQuotaItems(metadata: UpstreamMetadata | null | undefined)
|
||||
})
|
||||
}
|
||||
|
||||
items.sort(antigravityQuotaItemSort)
|
||||
items.sort(compareAntigravityQuotaItems)
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -3493,7 +3464,7 @@ function getAntigravityQuotaItemsFromSnapshot(key: EndpointAPIKey): AntigravityQ
|
||||
})
|
||||
.filter((item): item is AntigravityQuotaItem => item !== null)
|
||||
|
||||
items.sort(antigravityQuotaItemSort)
|
||||
items.sort(compareAntigravityQuotaItems)
|
||||
return items
|
||||
}
|
||||
|
||||
|
||||
+41
-4
@@ -102,12 +102,13 @@ function mount(metadata: UpstreamMetadata) {
|
||||
}
|
||||
|
||||
describe('AntigravityQuotaDialog', () => {
|
||||
it('renders model display names without losing raw model identifiers', () => {
|
||||
it('renders opaque quota identifiers with concise visible labels', () => {
|
||||
const rawIdentifier = 'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb'
|
||||
const { root, unmount } = mount({
|
||||
antigravity: {
|
||||
quota_by_model: {
|
||||
'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb': {
|
||||
display_name: 'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb',
|
||||
[rawIdentifier]: {
|
||||
display_name: rawIdentifier,
|
||||
remaining_fraction: 0.25,
|
||||
used_percent: 75,
|
||||
},
|
||||
@@ -116,9 +117,45 @@ describe('AntigravityQuotaDialog', () => {
|
||||
})
|
||||
|
||||
expect(root.textContent).toContain('Key-1')
|
||||
expect(root.textContent).toContain('RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb')
|
||||
expect(root.textContent).not.toContain(rawIdentifier)
|
||||
expect(root.textContent).toContain('25.0%')
|
||||
|
||||
unmount()
|
||||
})
|
||||
|
||||
it('orders important Gemini and Claude quota rows before low-priority rows', () => {
|
||||
const { root, unmount } = mount({
|
||||
antigravity: {
|
||||
quota_by_model: {
|
||||
tab_flash_lite_preview: {
|
||||
display_name: 'Tab Flash Lite Preview',
|
||||
remaining_fraction: 0.01,
|
||||
used_percent: 99,
|
||||
},
|
||||
'gemini-3.5-flash-low': {
|
||||
display_name: 'Gemini 3.5 Flash Low',
|
||||
remaining_fraction: 0.8,
|
||||
used_percent: 20,
|
||||
},
|
||||
'claude-opus-4-6-thinking': {
|
||||
display_name: 'Claude Opus 4.6 Thinking',
|
||||
remaining_fraction: 1,
|
||||
used_percent: 0,
|
||||
},
|
||||
chat_20706: {
|
||||
display_name: 'chat_20706',
|
||||
remaining_fraction: 0,
|
||||
used_percent: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
const text = root.textContent || ''
|
||||
|
||||
expect(text.indexOf('Claude Opus 4.6 Thinking')).toBeLessThan(text.indexOf('Gemini 3.5 Flash Low'))
|
||||
expect(text.indexOf('Gemini 3.5 Flash Low')).toBeLessThan(text.indexOf('Tab Flash Lite Preview'))
|
||||
expect(text.indexOf('Tab Flash Lite Preview')).toBeLessThan(text.indexOf('chat_20706'))
|
||||
|
||||
unmount()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
compareAntigravityQuotaItems,
|
||||
resolveAntigravityQuotaLabel,
|
||||
} from '@/features/providers/utils/antigravityQuota'
|
||||
|
||||
describe('antigravityQuota', () => {
|
||||
it('uses canonical labels from model ids before stale upstream labels', () => {
|
||||
const opaqueDisplayIndex = { value: 1 }
|
||||
|
||||
expect(
|
||||
resolveAntigravityQuotaLabel(
|
||||
'gemini-2.5-flash',
|
||||
'Gemini 3.1 Flash Lite',
|
||||
opaqueDisplayIndex,
|
||||
),
|
||||
).toBe('Gemini 2.5 Flash')
|
||||
expect(
|
||||
resolveAntigravityQuotaLabel(
|
||||
'claude-sonnet-4-6',
|
||||
'Claude Sonnet 4.6 (Thinking)',
|
||||
opaqueDisplayIndex,
|
||||
),
|
||||
).toBe('Claude Sonnet 4.6')
|
||||
expect(
|
||||
resolveAntigravityQuotaLabel(
|
||||
'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb',
|
||||
'RateLimitResetCredit_05cbb6eeeb9c81918e011d8300f9ebfb',
|
||||
opaqueDisplayIndex,
|
||||
),
|
||||
).toBe('Key-1')
|
||||
})
|
||||
|
||||
it('keeps latest Gemini and Claude quota buckets before low-priority buckets', () => {
|
||||
const items = [
|
||||
{
|
||||
model: 'tab_flash_lite_preview',
|
||||
label: 'Tab Flash Lite Preview',
|
||||
remainingPercent: 1,
|
||||
resetSeconds: 10,
|
||||
},
|
||||
{
|
||||
model: 'gemini-3.5-flash-low',
|
||||
label: 'Gemini 3.5 Flash Low',
|
||||
remainingPercent: 90,
|
||||
resetSeconds: null,
|
||||
},
|
||||
{
|
||||
model: 'chat_20706',
|
||||
label: 'chat_20706',
|
||||
remainingPercent: 0,
|
||||
resetSeconds: 1,
|
||||
},
|
||||
{
|
||||
model: 'claude-opus-4-6-thinking',
|
||||
label: 'Claude Opus 4.6 Thinking',
|
||||
remainingPercent: 100,
|
||||
resetSeconds: null,
|
||||
},
|
||||
{
|
||||
model: 'gemini-2.5-flash-lite',
|
||||
label: 'Gemini 2.5 Flash Lite',
|
||||
remainingPercent: 5,
|
||||
resetSeconds: 5,
|
||||
},
|
||||
].sort(compareAntigravityQuotaItems)
|
||||
|
||||
expect(items.map(item => item.model)).toEqual([
|
||||
'claude-opus-4-6-thinking',
|
||||
'gemini-3.5-flash-low',
|
||||
'gemini-2.5-flash-lite',
|
||||
'tab_flash_lite_preview',
|
||||
'chat_20706',
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,100 @@
|
||||
export interface AntigravityQuotaSortableItem {
|
||||
model: string
|
||||
label: string
|
||||
remainingPercent: number
|
||||
resetSeconds: number | null
|
||||
}
|
||||
|
||||
const ANTIGRAVITY_MODEL_LABELS: Record<string, string> = {
|
||||
'claude-opus-4-6-thinking': 'Claude Opus 4.6 Thinking',
|
||||
'claude-sonnet-4-6': 'Claude Sonnet 4.6',
|
||||
'gemini-3.5-flash-low': 'Gemini 3.5 Flash Low',
|
||||
'gemini-3.5-flash-extra-low': 'Gemini 3.5 Flash Extra Low',
|
||||
'gemini-3.1-pro-high': 'Gemini 3.1 Pro High',
|
||||
'gemini-3.1-pro-low': 'Gemini 3.1 Pro Low',
|
||||
'gemini-pro-agent': 'Gemini Pro Agent',
|
||||
'gemini-3.1-flash-image': 'Gemini 3.1 Flash Image',
|
||||
'gemini-3.1-flash-lite': 'Gemini 3.1 Flash Lite',
|
||||
'gemini-3-flash-agent': 'Gemini 3 Flash Agent',
|
||||
'gemini-3-flash': 'Gemini 3 Flash',
|
||||
'gemini-2.5-pro': 'Gemini 2.5 Pro',
|
||||
'gemini-2.5-flash-thinking': 'Gemini 2.5 Flash Thinking',
|
||||
'gemini-2.5-flash': 'Gemini 2.5 Flash',
|
||||
'gemini-2.5-flash-lite': 'Gemini 2.5 Flash Lite',
|
||||
'gpt-oss-120b-medium': 'GPT-OSS 120B Medium',
|
||||
'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> = {
|
||||
'claude-opus-4-6-thinking': 10,
|
||||
'claude-sonnet-4-6': 20,
|
||||
'gemini-3.5-flash-low': 100,
|
||||
'gemini-3.5-flash-extra-low': 110,
|
||||
'gemini-3.1-pro-high': 120,
|
||||
'gemini-3.1-pro-low': 130,
|
||||
'gemini-pro-agent': 140,
|
||||
'gemini-3.1-flash-image': 150,
|
||||
'gemini-3.1-flash-lite': 160,
|
||||
'gemini-3-flash-agent': 170,
|
||||
'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
|
||||
if (normalizedModel.startsWith('claude-')) return 90
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user