@@ -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>
@@ -171,17 +186,17 @@
<!-- Gemini platform: show quota + local usage window -->
<template v-else-if="account.platform === 'gemini'">
<!-- 账户类型徽章 -->
<div v-if="geminiTier Label" class="mb-1 flex items-center gap-1">
<!-- Auth Type + Tier Badge (first line) -->
<div v-if="geminiAuthType Label" class="mb-1 flex items-center gap-1">
<span
:class="[
'inline-block rounded px-1.5 py-0.5 text-[10px] font-medium',
geminiTierClass
]"
>
{{ geminiTier Label }}
{{ geminiAuthType Label }}
</span>
<!-- 帮助图标 -->
<!-- Help icon -->
<span
class="group relative cursor-help"
>
@@ -205,7 +220,7 @@
<div><strong>{{ geminiQuotaPolicyChannel }}:</strong></div>
<div class="pl-2">• {{ geminiQuotaPolicyLimits }}</div>
<div class="mt-2">
<a :href="geminiQuotaPolicyDocsUrl" target="_blank" class="text-blue-400 hover:text-blue-300 underline">
<a :href="geminiQuotaPolicyDocsUrl" target="_blank" rel="noopener noreferrer" class="text-blue-400 hover:text-blue-300 underline">
{{ t('admin.accounts.gemini.quotaPolicy.columns.docs') }} →
</a>
</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 响应中提取)
@@ -565,40 +572,55 @@ const isGeminiCodeAssist = computed(() => {
return creds?.oauth_type === 'code_assist' || (!creds?.oauth_type && !!creds?.project_id)
})
// Gemini 账户类型显示标签
const geminiTierLabel = computed ( ( ) => {
if ( ! geminiTier . value ) return null
// Gemini 认证类型 + Tier 组合标签(简洁版)
const geminiAuthTypeLabel = computed(() => {
const creds = props.account.credentials as GeminiCredentials | undefined
const isGoogleOne = creds ? . oauth _type === 'google_one'
const oauthType = creds?. oauth_type
if ( isGoogleOne ) {
// Google One tier 标签
// For API Key accounts, don't show auth type label
if (props.account.type !== 'oauth') return null
if (oauthType === 'google_one') {
// Google One: show "G1" + tier
const tierMap: Record<string, string> = {
AI _PREMIUM: t ( 'admin.accounts.tier.ai Premium') ,
GOOGLE _ONE _STANDARD: t ( 'admin.accounts.tier.s tandard') ,
GOOGLE _ONE _BASIC: t ( 'admin.accounts.tier.basic' ) ,
FREE: t ( 'admin.accounts.tier.free' ) ,
GOOGLE _ONE _UNKNOWN: t ( 'admin.accounts.tier.p ersonal') ,
GOOGLE _ONE _UNLIMITED: t ( 'admin.accounts.tier.u nlimited')
AI_PREMIUM: 'AI Premium',
GOOGLE_ONE_STANDARD: 'S tandard',
GOOGLE_ONE_BASIC: 'Basic',
FREE: 'Free',
GOOGLE_ONE_UNKNOWN: 'P ersonal',
GOOGLE_ONE_UNLIMITED: 'U nlimited'
}
return tierMap[ geminiTier. value ] || t ( 'admin.accounts.tier.p ersonal')
const tierLabel = geminiTier.value ? tierMap[ geminiTier.value] || 'Personal' : 'P ersonal'
return `G1 ${tierLabel}`
} else if (oauthType === 'code_assist' || (!oauthType && isGeminiCodeAssist.value)) {
// Code Assist: show "CLI" + tier
const tierMap: Record<string, string> = {
LEGACY: 'Free',
PRO: 'Pro',
ULTRA: 'Ultra'
}
const tierLabel = geminiTier.value ? tierMap[geminiTier.value] || 'Free' : 'Free'
return `CLI ${tierLabel}`
} else if (oauthType === 'ai_studio') {
// AI Studio: just show "AI Studio" ( no tier )
return ' AI Studio '
}
// Code Assist tier 标签
const tierMap : Record < string , string > = {
LEGACY : t ( 'admin.accounts.tier.free' ) ,
PRO : t ( 'admin.accounts.tier.pro' ) ,
ULTRA : t ( 'admin.accounts.tier.ultra' )
}
return tierMap [ geminiTier . value ] || null
return null
} )
/ / Gemini 账户类型徽章样式
const geminiTierClass = computed ( ( ) = > {
const creds = props . account . credentials as GeminiCredentials | undefined
const oauthType = creds ? . oauth _type
// AI Studio: use neutral gray color (no tier)
if ( oauthType === 'ai_studio' ) {
return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
}
if ( ! geminiTier . value ) return ''
const creds = props . account . credentials as GeminiCredentials | undefined
const isGoogleOne = creds ? . oauth _type === 'google_one'
if ( isGoogleOne ) {