refactor(antigravity): 统一额度刷新机制与 Claude 一致
将 Antigravity 的额度刷新从后台定时刷新改为按需获取模式,与 Claude 统一: - 删除 AntigravityQuotaRefresher 后台服务 - 新增 QuotaFetcher 接口和 AntigravityQuotaFetcher 实现 - 前端改为调用 usage API 获取额度,支持 loading/error 状态 - 统一使用内存缓存(10 分钟 TTL)
This commit is contained in:
@@ -93,7 +93,7 @@
|
||||
<div v-else class="text-xs text-gray-400">-</div>
|
||||
</template>
|
||||
|
||||
<!-- Antigravity OAuth accounts: show quota from extra field -->
|
||||
<!-- Antigravity OAuth accounts: fetch usage from API -->
|
||||
<template v-else-if="account.platform === 'antigravity' && account.type === 'oauth'">
|
||||
<!-- 账户类型徽章 -->
|
||||
<div v-if="antigravityTierLabel" class="mb-1 flex items-center gap-1">
|
||||
@@ -129,40 +129,55 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="hasAntigravityQuota" class="space-y-1">
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="space-y-1.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="h-3 w-[32px] animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div class="h-1.5 w-8 animate-pulse rounded-full bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div class="h-3 w-[32px] animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div v-else-if="error" class="text-xs text-red-500">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<!-- Usage data from API -->
|
||||
<div v-else-if="hasAntigravityQuotaFromAPI" class="space-y-1">
|
||||
<!-- Gemini 3 Pro -->
|
||||
<UsageProgressBar
|
||||
v-if="antigravity3ProUsage !== null"
|
||||
v-if="antigravity3ProUsageFromAPI !== null"
|
||||
:label="t('admin.accounts.usageWindow.gemini3Pro')"
|
||||
:utilization="antigravity3ProUsage.utilization"
|
||||
:resets-at="antigravity3ProUsage.resetTime"
|
||||
:utilization="antigravity3ProUsageFromAPI.utilization"
|
||||
:resets-at="antigravity3ProUsageFromAPI.resetTime"
|
||||
color="indigo"
|
||||
/>
|
||||
|
||||
<!-- Gemini 3 Flash -->
|
||||
<UsageProgressBar
|
||||
v-if="antigravity3FlashUsage !== null"
|
||||
v-if="antigravity3FlashUsageFromAPI !== null"
|
||||
:label="t('admin.accounts.usageWindow.gemini3Flash')"
|
||||
:utilization="antigravity3FlashUsage.utilization"
|
||||
:resets-at="antigravity3FlashUsage.resetTime"
|
||||
:utilization="antigravity3FlashUsageFromAPI.utilization"
|
||||
:resets-at="antigravity3FlashUsageFromAPI.resetTime"
|
||||
color="emerald"
|
||||
/>
|
||||
|
||||
<!-- Gemini 3 Image -->
|
||||
<UsageProgressBar
|
||||
v-if="antigravity3ImageUsage !== null"
|
||||
v-if="antigravity3ImageUsageFromAPI !== null"
|
||||
:label="t('admin.accounts.usageWindow.gemini3Image')"
|
||||
:utilization="antigravity3ImageUsage.utilization"
|
||||
:resets-at="antigravity3ImageUsage.resetTime"
|
||||
:utilization="antigravity3ImageUsageFromAPI.utilization"
|
||||
:resets-at="antigravity3ImageUsageFromAPI.resetTime"
|
||||
color="purple"
|
||||
/>
|
||||
|
||||
<!-- Claude 4.5 -->
|
||||
<UsageProgressBar
|
||||
v-if="antigravityClaude45Usage !== null"
|
||||
v-if="antigravityClaude45UsageFromAPI !== null"
|
||||
:label="t('admin.accounts.usageWindow.claude45')"
|
||||
:utilization="antigravityClaude45Usage.utilization"
|
||||
:resets-at="antigravityClaude45Usage.resetTime"
|
||||
:utilization="antigravityClaude45UsageFromAPI.utilization"
|
||||
:resets-at="antigravityClaude45UsageFromAPI.resetTime"
|
||||
color="amber"
|
||||
/>
|
||||
</div>
|
||||
@@ -295,6 +310,9 @@ const shouldFetchUsage = computed(() => {
|
||||
if (props.account.platform === 'gemini') {
|
||||
return props.account.type === 'oauth'
|
||||
}
|
||||
if (props.account.platform === 'antigravity') {
|
||||
return props.account.type === 'oauth'
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -453,45 +471,35 @@ const codex7dResetAt = computed(() => {
|
||||
return null
|
||||
})
|
||||
|
||||
// Antigravity quota types
|
||||
interface AntigravityModelQuota {
|
||||
remaining: number // 剩余百分比 0-100
|
||||
reset_time: string // ISO 8601 重置时间
|
||||
}
|
||||
|
||||
interface AntigravityQuotaData {
|
||||
[model: string]: AntigravityModelQuota
|
||||
}
|
||||
|
||||
// Antigravity quota types (用于 API 返回的数据)
|
||||
interface AntigravityUsageResult {
|
||||
utilization: number
|
||||
resetTime: string | null
|
||||
}
|
||||
|
||||
// Antigravity quota computed properties
|
||||
const hasAntigravityQuota = computed(() => {
|
||||
const extra = props.account.extra as Record<string, unknown> | undefined
|
||||
return extra && typeof extra.quota === 'object' && extra.quota !== null
|
||||
// ===== Antigravity quota from API (usageInfo.antigravity_quota) =====
|
||||
|
||||
// 检查是否有从 API 获取的配额数据
|
||||
const hasAntigravityQuotaFromAPI = computed(() => {
|
||||
return usageInfo.value?.antigravity_quota && Object.keys(usageInfo.value.antigravity_quota).length > 0
|
||||
})
|
||||
|
||||
// 从配额数据中获取使用率(多模型取最低剩余 = 最高使用)
|
||||
const getAntigravityUsage = (
|
||||
// 从 API 配额数据中获取使用率(多模型取最高使用率)
|
||||
const getAntigravityUsageFromAPI = (
|
||||
modelNames: string[]
|
||||
): AntigravityUsageResult | null => {
|
||||
const extra = props.account.extra as Record<string, unknown> | undefined
|
||||
if (!extra || typeof extra.quota !== 'object' || extra.quota === null) return null
|
||||
const quota = usageInfo.value?.antigravity_quota
|
||||
if (!quota) return null
|
||||
|
||||
const quota = extra.quota as AntigravityQuotaData
|
||||
|
||||
let minRemaining = 100
|
||||
let maxUtilization = 0
|
||||
let earliestReset: string | null = null
|
||||
|
||||
for (const model of modelNames) {
|
||||
const modelQuota = quota[model]
|
||||
if (!modelQuota) continue
|
||||
|
||||
if (modelQuota.remaining < minRemaining) {
|
||||
minRemaining = modelQuota.remaining
|
||||
if (modelQuota.utilization > maxUtilization) {
|
||||
maxUtilization = modelQuota.utilization
|
||||
}
|
||||
if (modelQuota.reset_time) {
|
||||
if (!earliestReset || modelQuota.reset_time < earliestReset) {
|
||||
@@ -501,32 +509,31 @@ const getAntigravityUsage = (
|
||||
}
|
||||
|
||||
// 如果没有找到任何匹配的模型
|
||||
if (minRemaining === 100 && earliestReset === null) {
|
||||
// 检查是否至少有一个模型有数据
|
||||
if (maxUtilization === 0 && earliestReset === null) {
|
||||
const hasAnyData = modelNames.some((m) => quota[m])
|
||||
if (!hasAnyData) return null
|
||||
}
|
||||
|
||||
return {
|
||||
utilization: 100 - minRemaining,
|
||||
utilization: maxUtilization,
|
||||
resetTime: earliestReset
|
||||
}
|
||||
}
|
||||
|
||||
// Gemini 3 Pro: gemini-3-pro-low, gemini-3-pro-high, gemini-3-pro-preview
|
||||
const antigravity3ProUsage = computed(() =>
|
||||
getAntigravityUsage(['gemini-3-pro-low', 'gemini-3-pro-high', 'gemini-3-pro-preview'])
|
||||
// Gemini 3 Pro from API
|
||||
const antigravity3ProUsageFromAPI = computed(() =>
|
||||
getAntigravityUsageFromAPI(['gemini-3-pro-low', 'gemini-3-pro-high', 'gemini-3-pro-preview'])
|
||||
)
|
||||
|
||||
// Gemini 3 Flash: gemini-3-flash
|
||||
const antigravity3FlashUsage = computed(() => getAntigravityUsage(['gemini-3-flash']))
|
||||
// Gemini 3 Flash from API
|
||||
const antigravity3FlashUsageFromAPI = computed(() => getAntigravityUsageFromAPI(['gemini-3-flash']))
|
||||
|
||||
// Gemini 3 Image: gemini-3-pro-image
|
||||
const antigravity3ImageUsage = computed(() => getAntigravityUsage(['gemini-3-pro-image']))
|
||||
// Gemini 3 Image from API
|
||||
const antigravity3ImageUsageFromAPI = computed(() => getAntigravityUsageFromAPI(['gemini-3-pro-image']))
|
||||
|
||||
// Claude 4.5: claude-sonnet-4-5, claude-opus-4-5-thinking
|
||||
const antigravityClaude45Usage = computed(() =>
|
||||
getAntigravityUsage(['claude-sonnet-4-5', 'claude-opus-4-5-thinking'])
|
||||
// Claude 4.5 from API
|
||||
const antigravityClaude45UsageFromAPI = computed(() =>
|
||||
getAntigravityUsageFromAPI(['claude-sonnet-4-5', 'claude-opus-4-5-thinking'])
|
||||
)
|
||||
|
||||
// Antigravity 账户类型(从 load_code_assist 响应中提取)
|
||||
|
||||
@@ -376,6 +376,12 @@ export interface UsageProgress {
|
||||
window_stats?: WindowStats | null // 窗口期统计(从窗口开始到当前的使用量)
|
||||
}
|
||||
|
||||
// Antigravity 单个模型的配额信息
|
||||
export interface AntigravityModelQuota {
|
||||
utilization: number // 使用率 0-100
|
||||
reset_time: string // 重置时间 ISO8601
|
||||
}
|
||||
|
||||
export interface AccountUsageInfo {
|
||||
updated_at: string | null
|
||||
five_hour: UsageProgress | null
|
||||
@@ -383,6 +389,7 @@ export interface AccountUsageInfo {
|
||||
seven_day_sonnet: UsageProgress | null
|
||||
gemini_pro_daily?: UsageProgress | null
|
||||
gemini_flash_daily?: UsageProgress | null
|
||||
antigravity_quota?: Record<string, AntigravityModelQuota> | null
|
||||
}
|
||||
|
||||
// OpenAI Codex usage snapshot (from response headers)
|
||||
|
||||
Reference in New Issue
Block a user