feat(gemini): 完善 Gemini OAuth 配额系统和用量显示
主要改动: - 后端:重构 Gemini 配额服务,支持多层级配额策略(GCP Standard/Free, Google One, AI Studio, Code Assist) - 后端:优化 OAuth 服务,增强 tier_id 识别和存储逻辑 - 后端:改进用量统计服务,支持不同平台的配额查询 - 后端:优化限流服务,增加临时解除调度状态管理 - 前端:统一四种授权方式的用量显示格式和徽标样式 - 前端:增强账户配额信息展示,支持多种配额类型 - 前端:改进创建和重新授权模态框的用户体验 - 国际化:完善中英文配额相关文案 - 移除 CHANGELOG.md 文件 测试:所有单元测试通过
This commit is contained in:
@@ -20,6 +20,7 @@ export interface GeminiAuthUrlRequest {
|
||||
proxy_id?: number
|
||||
project_id?: string
|
||||
oauth_type?: 'code_assist' | 'google_one' | 'ai_studio'
|
||||
tier_id?: string
|
||||
}
|
||||
|
||||
export interface GeminiExchangeCodeRequest {
|
||||
@@ -28,6 +29,7 @@ export interface GeminiExchangeCodeRequest {
|
||||
code: string
|
||||
proxy_id?: number
|
||||
oauth_type?: 'code_assist' | 'google_one' | 'ai_studio'
|
||||
tier_id?: string
|
||||
}
|
||||
|
||||
export type GeminiTokenInfo = {
|
||||
|
||||
@@ -65,32 +65,33 @@ const tierLabel = computed(() => {
|
||||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||||
|
||||
if (isCodeAssist.value) {
|
||||
// GCP Code Assist: 显示 GCP tier
|
||||
const tierMap: Record<string, string> = {
|
||||
LEGACY: 'Free',
|
||||
PRO: 'Pro',
|
||||
ULTRA: 'Ultra',
|
||||
'standard-tier': 'Standard',
|
||||
'pro-tier': 'Pro',
|
||||
'ultra-tier': 'Ultra'
|
||||
}
|
||||
return tierMap[creds?.tier_id || ''] || (creds?.tier_id ? 'GCP' : 'Unknown')
|
||||
const tier = (creds?.tier_id || '').toString().trim().toLowerCase()
|
||||
if (tier === 'gcp_enterprise') return 'GCP Enterprise'
|
||||
if (tier === 'gcp_standard') return 'GCP Standard'
|
||||
// Backward compatibility
|
||||
const upper = (creds?.tier_id || '').toString().trim().toUpperCase()
|
||||
if (upper.includes('ULTRA') || upper.includes('ENTERPRISE')) return 'GCP Enterprise'
|
||||
if (upper) return `GCP ${upper}`
|
||||
return 'GCP'
|
||||
}
|
||||
|
||||
if (isGoogleOne.value) {
|
||||
// Google One: tier 映射
|
||||
const tierMap: Record<string, string> = {
|
||||
AI_PREMIUM: 'AI Premium',
|
||||
GOOGLE_ONE_STANDARD: 'Standard',
|
||||
GOOGLE_ONE_BASIC: 'Basic',
|
||||
FREE: 'Free',
|
||||
GOOGLE_ONE_UNKNOWN: 'Personal',
|
||||
GOOGLE_ONE_UNLIMITED: 'Unlimited'
|
||||
}
|
||||
return tierMap[creds?.tier_id || ''] || 'Personal'
|
||||
const tier = (creds?.tier_id || '').toString().trim().toLowerCase()
|
||||
if (tier === 'google_ai_ultra') return 'Google AI Ultra'
|
||||
if (tier === 'google_ai_pro') return 'Google AI Pro'
|
||||
if (tier === 'google_one_free') return 'Google One Free'
|
||||
// Backward compatibility
|
||||
const upper = (creds?.tier_id || '').toString().trim().toUpperCase()
|
||||
if (upper === 'AI_PREMIUM') return 'Google AI Pro'
|
||||
if (upper === 'GOOGLE_ONE_UNLIMITED') return 'Google AI Ultra'
|
||||
if (upper) return `Google One ${upper}`
|
||||
return 'Google One'
|
||||
}
|
||||
|
||||
// API Key: 显示 AI Studio
|
||||
const tier = (creds?.tier_id || '').toString().trim().toLowerCase()
|
||||
if (tier === 'aistudio_paid') return 'AI Studio Pay-as-you-go'
|
||||
if (tier === 'aistudio_free') return 'AI Studio Free Tier'
|
||||
return 'AI Studio'
|
||||
})
|
||||
|
||||
@@ -99,35 +100,31 @@ const tierBadgeClass = computed(() => {
|
||||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||||
|
||||
if (isCodeAssist.value) {
|
||||
// GCP Code Assist 样式
|
||||
const tierColorMap: Record<string, string> = {
|
||||
LEGACY: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300',
|
||||
PRO: 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300',
|
||||
ULTRA: 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300',
|
||||
'standard-tier': 'bg-green-100 text-green-600 dark:bg-green-900/40 dark:text-green-300',
|
||||
'pro-tier': 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300',
|
||||
'ultra-tier': 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
}
|
||||
return (
|
||||
tierColorMap[creds?.tier_id || ''] ||
|
||||
'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
)
|
||||
const tier = (creds?.tier_id || '').toString().trim().toLowerCase()
|
||||
if (tier === 'gcp_enterprise') return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
if (tier === 'gcp_standard') return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
// Backward compatibility
|
||||
const upper = (creds?.tier_id || '').toString().trim().toUpperCase()
|
||||
if (upper.includes('ULTRA') || upper.includes('ENTERPRISE')) return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
}
|
||||
|
||||
if (isGoogleOne.value) {
|
||||
// Google One tier 样式
|
||||
const tierColorMap: Record<string, string> = {
|
||||
AI_PREMIUM: 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300',
|
||||
GOOGLE_ONE_STANDARD: 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300',
|
||||
GOOGLE_ONE_BASIC: 'bg-green-100 text-green-600 dark:bg-green-900/40 dark:text-green-300',
|
||||
FREE: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300',
|
||||
GOOGLE_ONE_UNKNOWN: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300',
|
||||
GOOGLE_ONE_UNLIMITED: 'bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-300'
|
||||
}
|
||||
return tierColorMap[creds?.tier_id || ''] || 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
const tier = (creds?.tier_id || '').toString().trim().toLowerCase()
|
||||
if (tier === 'google_ai_ultra') return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
if (tier === 'google_ai_pro') return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
if (tier === 'google_one_free') return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
// Backward compatibility
|
||||
const upper = (creds?.tier_id || '').toString().trim().toUpperCase()
|
||||
if (upper === 'GOOGLE_ONE_UNLIMITED') return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
if (upper === 'AI_PREMIUM') return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
}
|
||||
|
||||
// AI Studio 默认样式:蓝色
|
||||
const tier = (creds?.tier_id || '').toString().trim().toLowerCase()
|
||||
if (tier === 'aistudio_paid') return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
if (tier === 'aistudio_free') return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
})
|
||||
|
||||
|
||||
@@ -241,23 +241,16 @@
|
||||
<div v-else-if="error" class="text-xs text-red-500">
|
||||
{{ error }}
|
||||
</div>
|
||||
<!-- GCP & Google One: show model usage bars when available -->
|
||||
<!-- Gemini: show daily usage bars when available -->
|
||||
<div v-else-if="geminiUsageAvailable" class="space-y-1">
|
||||
<UsageProgressBar
|
||||
v-if="usageInfo?.gemini_pro_daily"
|
||||
label="Pro"
|
||||
:utilization="usageInfo.gemini_pro_daily.utilization"
|
||||
:resets-at="usageInfo.gemini_pro_daily.resets_at"
|
||||
:window-stats="usageInfo.gemini_pro_daily.window_stats"
|
||||
color="indigo"
|
||||
/>
|
||||
<UsageProgressBar
|
||||
v-if="usageInfo?.gemini_flash_daily"
|
||||
label="Flash"
|
||||
:utilization="usageInfo.gemini_flash_daily.utilization"
|
||||
:resets-at="usageInfo.gemini_flash_daily.resets_at"
|
||||
:window-stats="usageInfo.gemini_flash_daily.window_stats"
|
||||
color="emerald"
|
||||
v-for="bar in geminiUsageBars"
|
||||
:key="bar.key"
|
||||
:label="bar.label"
|
||||
:utilization="bar.utilization"
|
||||
:resets-at="bar.resetsAt"
|
||||
:window-stats="bar.windowStats"
|
||||
:color="bar.color"
|
||||
/>
|
||||
<p class="mt-1 text-[9px] leading-tight text-gray-400 dark:text-gray-500 italic">
|
||||
* {{ t('admin.accounts.gemini.quotaPolicy.simulatedNote') || 'Simulated quota' }}
|
||||
@@ -288,7 +281,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type { Account, AccountUsageInfo, GeminiCredentials } from '@/types'
|
||||
import type { Account, AccountUsageInfo, GeminiCredentials, WindowStats } from '@/types'
|
||||
import UsageProgressBar from './UsageProgressBar.vue'
|
||||
import AccountQuotaInfo from './AccountQuotaInfo.vue'
|
||||
|
||||
@@ -303,16 +296,18 @@ const error = ref<string | null>(null)
|
||||
const usageInfo = ref<AccountUsageInfo | null>(null)
|
||||
|
||||
// Show usage windows for OAuth and Setup Token accounts
|
||||
const showUsageWindows = computed(
|
||||
() => props.account.type === 'oauth' || props.account.type === 'setup-token'
|
||||
)
|
||||
const showUsageWindows = computed(() => {
|
||||
// Gemini: we can always compute local usage windows from DB logs (simulated quotas).
|
||||
if (props.account.platform === 'gemini') return true
|
||||
return props.account.type === 'oauth' || props.account.type === 'setup-token'
|
||||
})
|
||||
|
||||
const shouldFetchUsage = computed(() => {
|
||||
if (props.account.platform === 'anthropic') {
|
||||
return props.account.type === 'oauth' || props.account.type === 'setup-token'
|
||||
}
|
||||
if (props.account.platform === 'gemini') {
|
||||
return props.account.type === 'oauth'
|
||||
return true
|
||||
}
|
||||
if (props.account.platform === 'antigravity') {
|
||||
return props.account.type === 'oauth'
|
||||
@@ -322,8 +317,12 @@ const shouldFetchUsage = computed(() => {
|
||||
|
||||
const geminiUsageAvailable = computed(() => {
|
||||
return (
|
||||
!!usageInfo.value?.gemini_shared_daily ||
|
||||
!!usageInfo.value?.gemini_pro_daily ||
|
||||
!!usageInfo.value?.gemini_flash_daily
|
||||
!!usageInfo.value?.gemini_flash_daily ||
|
||||
!!usageInfo.value?.gemini_shared_minute ||
|
||||
!!usageInfo.value?.gemini_pro_minute ||
|
||||
!!usageInfo.value?.gemini_flash_minute
|
||||
)
|
||||
})
|
||||
|
||||
@@ -569,6 +568,12 @@ const geminiTier = computed(() => {
|
||||
return creds?.tier_id || null
|
||||
})
|
||||
|
||||
const geminiOAuthType = computed(() => {
|
||||
if (props.account.platform !== 'gemini') return null
|
||||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||||
return (creds?.oauth_type || '').trim() || null
|
||||
})
|
||||
|
||||
// Gemini 是否为 Code Assist OAuth
|
||||
const isGeminiCodeAssist = computed(() => {
|
||||
if (props.account.platform !== 'gemini') return false
|
||||
@@ -576,109 +581,208 @@ const isGeminiCodeAssist = computed(() => {
|
||||
return creds?.oauth_type === 'code_assist' || (!creds?.oauth_type && !!creds?.project_id)
|
||||
})
|
||||
|
||||
// Gemini 认证类型 + Tier 组合标签(简洁版)
|
||||
const geminiAuthTypeLabel = computed(() => {
|
||||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||||
const oauthType = creds?.oauth_type
|
||||
const geminiChannelShort = computed((): 'ai studio' | 'gcp' | 'google one' | 'client' | null => {
|
||||
if (props.account.platform !== 'gemini') return null
|
||||
|
||||
// For API Key accounts, don't show auth type label
|
||||
if (props.account.type !== 'oauth') return null
|
||||
// API Key accounts are AI Studio.
|
||||
if (props.account.type === 'apikey') return 'ai studio'
|
||||
|
||||
if (oauthType === 'google_one') {
|
||||
// Google One: show "Google One" + tier
|
||||
const tierMap: Record<string, string> = {
|
||||
AI_PREMIUM: 'AI Premium',
|
||||
GOOGLE_ONE_STANDARD: 'Standard',
|
||||
GOOGLE_ONE_BASIC: 'Basic',
|
||||
FREE: 'Free',
|
||||
GOOGLE_ONE_UNKNOWN: 'Personal',
|
||||
GOOGLE_ONE_UNLIMITED: 'Unlimited'
|
||||
}
|
||||
const tierLabel = geminiTier.value ? tierMap[geminiTier.value] || 'Personal' : 'Personal'
|
||||
return `Google One ${tierLabel}`
|
||||
} else if (oauthType === 'code_assist' || (!oauthType && isGeminiCodeAssist.value)) {
|
||||
// Code Assist: show "GCP" + tier
|
||||
const tierMap: Record<string, string> = {
|
||||
LEGACY: 'Free',
|
||||
PRO: 'Pro',
|
||||
ULTRA: 'Ultra'
|
||||
}
|
||||
const tierLabel = geminiTier.value ? tierMap[geminiTier.value] || 'Free' : 'Free'
|
||||
return `GCP ${tierLabel}`
|
||||
} else if (oauthType === 'ai_studio') {
|
||||
// 自定义 OAuth Client: show "Client" (no tier)
|
||||
return 'Client'
|
||||
if (geminiOAuthType.value === 'google_one') return 'google one'
|
||||
if (isGeminiCodeAssist.value) return 'gcp'
|
||||
if (geminiOAuthType.value === 'ai_studio') return 'client'
|
||||
|
||||
// Fallback (unknown legacy data): treat as AI Studio.
|
||||
return 'ai studio'
|
||||
})
|
||||
|
||||
const geminiUserLevel = computed((): string | null => {
|
||||
if (props.account.platform !== 'gemini') return null
|
||||
|
||||
const tier = (geminiTier.value || '').toString().trim()
|
||||
const tierLower = tier.toLowerCase()
|
||||
const tierUpper = tier.toUpperCase()
|
||||
|
||||
// Google One: free / pro / ultra
|
||||
if (geminiOAuthType.value === 'google_one') {
|
||||
if (tierLower === 'google_one_free') return 'free'
|
||||
if (tierLower === 'google_ai_pro') return 'pro'
|
||||
if (tierLower === 'google_ai_ultra') return 'ultra'
|
||||
|
||||
// Backward compatibility (legacy tier markers)
|
||||
if (tierUpper === 'AI_PREMIUM' || tierUpper === 'GOOGLE_ONE_STANDARD') return 'pro'
|
||||
if (tierUpper === 'GOOGLE_ONE_UNLIMITED') return 'ultra'
|
||||
if (tierUpper === 'FREE' || tierUpper === 'GOOGLE_ONE_BASIC' || tierUpper === 'GOOGLE_ONE_UNKNOWN' || tierUpper === '') return 'free'
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// GCP Code Assist: standard / enterprise
|
||||
if (isGeminiCodeAssist.value) {
|
||||
if (tierLower === 'gcp_enterprise') return 'enterprise'
|
||||
if (tierLower === 'gcp_standard') return 'standard'
|
||||
|
||||
// Backward compatibility
|
||||
if (tierUpper.includes('ULTRA') || tierUpper.includes('ENTERPRISE')) return 'enterprise'
|
||||
return 'standard'
|
||||
}
|
||||
|
||||
// AI Studio (API Key) and Client OAuth: free / paid
|
||||
if (props.account.type === 'apikey' || geminiOAuthType.value === 'ai_studio') {
|
||||
if (tierLower === 'aistudio_paid') return 'paid'
|
||||
if (tierLower === 'aistudio_free') return 'free'
|
||||
|
||||
// Backward compatibility
|
||||
if (tierUpper.includes('PAID') || tierUpper.includes('PAYG') || tierUpper.includes('PAY')) return 'paid'
|
||||
if (tierUpper.includes('FREE')) return 'free'
|
||||
if (props.account.type === 'apikey') return 'free'
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
// Gemini 认证类型(按要求:授权方式简称 + 用户等级)
|
||||
const geminiAuthTypeLabel = computed(() => {
|
||||
if (props.account.platform !== 'gemini') return null
|
||||
if (!geminiChannelShort.value) return null
|
||||
return geminiUserLevel.value ? `${geminiChannelShort.value} ${geminiUserLevel.value}` : geminiChannelShort.value
|
||||
})
|
||||
|
||||
// Gemini 账户类型徽章样式(统一样式)
|
||||
const geminiTierClass = computed(() => {
|
||||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||||
const oauthType = creds?.oauth_type
|
||||
// Use channel+level to choose a stable color without depending on raw tier_id variants.
|
||||
const channel = geminiChannelShort.value
|
||||
const level = geminiUserLevel.value
|
||||
|
||||
// Client (自定义 OAuth): 使用蓝色(与 AI Studio 一致)
|
||||
if (oauthType === 'ai_studio') {
|
||||
if (channel === 'client' || channel === 'ai studio') {
|
||||
return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
}
|
||||
|
||||
if (!geminiTier.value) return ''
|
||||
|
||||
const isGoogleOne = creds?.oauth_type === 'google_one'
|
||||
|
||||
if (isGoogleOne) {
|
||||
// Google One tier 颜色
|
||||
const colorMap: Record<string, string> = {
|
||||
AI_PREMIUM: 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300',
|
||||
GOOGLE_ONE_STANDARD: 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300',
|
||||
GOOGLE_ONE_BASIC: 'bg-green-100 text-green-600 dark:bg-green-900/40 dark:text-green-300',
|
||||
FREE: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300',
|
||||
GOOGLE_ONE_UNKNOWN: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300',
|
||||
GOOGLE_ONE_UNLIMITED: 'bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-300'
|
||||
}
|
||||
return colorMap[geminiTier.value] || 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
if (channel === 'google one') {
|
||||
if (level === 'ultra') return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
if (level === 'pro') return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
}
|
||||
|
||||
// Code Assist tier 颜色
|
||||
switch (geminiTier.value) {
|
||||
case 'LEGACY':
|
||||
return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
case 'PRO':
|
||||
return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
case 'ULTRA':
|
||||
return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
default:
|
||||
return ''
|
||||
if (channel === 'gcp') {
|
||||
if (level === 'enterprise') return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
// Gemini 配额政策信息
|
||||
const geminiQuotaPolicyChannel = computed(() => {
|
||||
if (geminiOAuthType.value === 'google_one') {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.googleOne.channel')
|
||||
}
|
||||
if (isGeminiCodeAssist.value) {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.cli.channel')
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.gcp.channel')
|
||||
}
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.channel')
|
||||
})
|
||||
|
||||
const geminiQuotaPolicyLimits = computed(() => {
|
||||
if (isGeminiCodeAssist.value) {
|
||||
if (geminiTier.value === 'PRO' || geminiTier.value === 'ULTRA') {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.cli.limitsPremium')
|
||||
const tierLower = (geminiTier.value || '').toString().trim().toLowerCase()
|
||||
|
||||
if (geminiOAuthType.value === 'google_one') {
|
||||
if (tierLower === 'google_ai_ultra' || geminiUserLevel.value === 'ultra') {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.googleOne.limitsUltra')
|
||||
}
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.cli.limitsFree')
|
||||
if (tierLower === 'google_ai_pro' || geminiUserLevel.value === 'pro') {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.googleOne.limitsPro')
|
||||
}
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.googleOne.limitsFree')
|
||||
}
|
||||
|
||||
if (isGeminiCodeAssist.value) {
|
||||
if (tierLower === 'gcp_enterprise' || geminiUserLevel.value === 'enterprise') {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.gcp.limitsEnterprise')
|
||||
}
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.gcp.limitsStandard')
|
||||
}
|
||||
|
||||
// AI Studio (API Key / custom OAuth)
|
||||
if (tierLower === 'aistudio_paid' || geminiUserLevel.value === 'paid') {
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.limitsPaid')
|
||||
}
|
||||
// AI Studio - 默认显示免费层限制
|
||||
return t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.limitsFree')
|
||||
})
|
||||
|
||||
const geminiQuotaPolicyDocsUrl = computed(() => {
|
||||
if (isGeminiCodeAssist.value) {
|
||||
return 'https://cloud.google.com/products/gemini/code-assist#pricing'
|
||||
if (geminiOAuthType.value === 'google_one' || isGeminiCodeAssist.value) {
|
||||
return 'https://developers.google.com/gemini-code-assist/resources/quotas'
|
||||
}
|
||||
return 'https://ai.google.dev/pricing'
|
||||
})
|
||||
|
||||
const geminiUsesSharedDaily = computed(() => {
|
||||
if (props.account.platform !== 'gemini') return false
|
||||
// Per requirement: Google One & GCP are shared RPD pools (no per-model breakdown).
|
||||
return (
|
||||
!!usageInfo.value?.gemini_shared_daily ||
|
||||
!!usageInfo.value?.gemini_shared_minute ||
|
||||
geminiOAuthType.value === 'google_one' ||
|
||||
isGeminiCodeAssist.value
|
||||
)
|
||||
})
|
||||
|
||||
const geminiUsageBars = computed(() => {
|
||||
if (props.account.platform !== 'gemini') return []
|
||||
if (!usageInfo.value) return []
|
||||
|
||||
const bars: Array<{
|
||||
key: string
|
||||
label: string
|
||||
utilization: number
|
||||
resetsAt: string | null
|
||||
windowStats?: WindowStats | null
|
||||
color: 'indigo' | 'emerald'
|
||||
}> = []
|
||||
|
||||
if (geminiUsesSharedDaily.value) {
|
||||
const sharedDaily = usageInfo.value.gemini_shared_daily
|
||||
if (sharedDaily) {
|
||||
bars.push({
|
||||
key: 'shared_daily',
|
||||
label: '1d',
|
||||
utilization: sharedDaily.utilization,
|
||||
resetsAt: sharedDaily.resets_at,
|
||||
windowStats: sharedDaily.window_stats,
|
||||
color: 'indigo'
|
||||
})
|
||||
}
|
||||
return bars
|
||||
}
|
||||
|
||||
const pro = usageInfo.value.gemini_pro_daily
|
||||
if (pro) {
|
||||
bars.push({
|
||||
key: 'pro_daily',
|
||||
label: 'pro',
|
||||
utilization: pro.utilization,
|
||||
resetsAt: pro.resets_at,
|
||||
windowStats: pro.window_stats,
|
||||
color: 'indigo'
|
||||
})
|
||||
}
|
||||
|
||||
const flash = usageInfo.value.gemini_flash_daily
|
||||
if (flash) {
|
||||
bars.push({
|
||||
key: 'flash_daily',
|
||||
label: 'flash',
|
||||
utilization: flash.utilization,
|
||||
resetsAt: flash.resets_at,
|
||||
windowStats: flash.window_stats,
|
||||
color: 'emerald'
|
||||
})
|
||||
}
|
||||
|
||||
return bars
|
||||
})
|
||||
|
||||
// 账户类型显示标签
|
||||
const antigravityTierLabel = computed(() => {
|
||||
switch (antigravityTier.value) {
|
||||
|
||||
@@ -653,6 +653,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tier selection (used as fallback when auto-detection is unavailable/fails) -->
|
||||
<div class="mt-4">
|
||||
<label class="input-label">{{ t('admin.accounts.gemini.tier.label') }}</label>
|
||||
<div class="mt-2">
|
||||
<select
|
||||
v-if="geminiOAuthType === 'google_one'"
|
||||
v-model="geminiTierGoogleOne"
|
||||
class="input"
|
||||
>
|
||||
<option value="google_one_free">{{ t('admin.accounts.gemini.tier.googleOne.free') }}</option>
|
||||
<option value="google_ai_pro">{{ t('admin.accounts.gemini.tier.googleOne.pro') }}</option>
|
||||
<option value="google_ai_ultra">{{ t('admin.accounts.gemini.tier.googleOne.ultra') }}</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-else-if="geminiOAuthType === 'code_assist'"
|
||||
v-model="geminiTierGcp"
|
||||
class="input"
|
||||
>
|
||||
<option value="gcp_standard">{{ t('admin.accounts.gemini.tier.gcp.standard') }}</option>
|
||||
<option value="gcp_enterprise">{{ t('admin.accounts.gemini.tier.gcp.enterprise') }}</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-else
|
||||
v-model="geminiTierAIStudio"
|
||||
class="input"
|
||||
>
|
||||
<option value="aistudio_free">{{ t('admin.accounts.gemini.tier.aiStudio.free') }}</option>
|
||||
<option value="aistudio_paid">{{ t('admin.accounts.gemini.tier.aiStudio.paid') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="input-hint">{{ t('admin.accounts.gemini.tier.hint') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-4 text-xs text-blue-900 dark:border-blue-800/40 dark:bg-blue-900/20 dark:text-blue-200">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg
|
||||
@@ -820,6 +855,16 @@
|
||||
<p class="input-hint">{{ apiKeyHint }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Gemini API Key tier selection -->
|
||||
<div v-if="form.platform === 'gemini'">
|
||||
<label class="input-label">{{ t('admin.accounts.gemini.tier.label') }}</label>
|
||||
<select v-model="geminiTierAIStudio" class="input">
|
||||
<option value="aistudio_free">{{ t('admin.accounts.gemini.tier.aiStudio.free') }}</option>
|
||||
<option value="aistudio_paid">{{ t('admin.accounts.gemini.tier.aiStudio.paid') }}</option>
|
||||
</select>
|
||||
<p class="input-hint">{{ t('admin.accounts.gemini.tier.aiStudioHint') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Model Restriction Section (不适用于 Gemini) -->
|
||||
<div v-if="form.platform !== 'gemini'" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<label class="input-label">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
@@ -1816,6 +1861,24 @@ const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_
|
||||
const geminiAIStudioOAuthEnabled = ref(false)
|
||||
const showAdvancedOAuth = ref(false)
|
||||
|
||||
// Gemini tier selection (used as fallback when auto-detection is unavailable/fails)
|
||||
const geminiTierGoogleOne = ref<'google_one_free' | 'google_ai_pro' | 'google_ai_ultra'>('google_one_free')
|
||||
const geminiTierGcp = ref<'gcp_standard' | 'gcp_enterprise'>('gcp_standard')
|
||||
const geminiTierAIStudio = ref<'aistudio_free' | 'aistudio_paid'>('aistudio_free')
|
||||
|
||||
const geminiSelectedTier = computed(() => {
|
||||
if (form.platform !== 'gemini') return ''
|
||||
if (accountCategory.value === 'apikey') return geminiTierAIStudio.value
|
||||
switch (geminiOAuthType.value) {
|
||||
case 'google_one':
|
||||
return geminiTierGoogleOne.value
|
||||
case 'code_assist':
|
||||
return geminiTierGcp.value
|
||||
default:
|
||||
return geminiTierAIStudio.value
|
||||
}
|
||||
})
|
||||
|
||||
const geminiQuotaDocs = {
|
||||
codeAssist: 'https://developers.google.com/gemini-code-assist/resources/quotas',
|
||||
aiStudio: 'https://ai.google.dev/pricing',
|
||||
@@ -2143,6 +2206,9 @@ const resetForm = () => {
|
||||
tempUnschedEnabled.value = false
|
||||
tempUnschedRules.value = []
|
||||
geminiOAuthType.value = 'code_assist'
|
||||
geminiTierGoogleOne.value = 'google_one_free'
|
||||
geminiTierGcp.value = 'gcp_standard'
|
||||
geminiTierAIStudio.value = 'aistudio_free'
|
||||
oauth.resetState()
|
||||
openaiOAuth.resetState()
|
||||
geminiOAuth.resetState()
|
||||
@@ -2184,6 +2250,9 @@ const handleSubmit = async () => {
|
||||
base_url: apiKeyBaseUrl.value.trim() || defaultBaseUrl,
|
||||
api_key: apiKeyValue.value.trim()
|
||||
}
|
||||
if (form.platform === 'gemini') {
|
||||
credentials.tier_id = geminiTierAIStudio.value
|
||||
}
|
||||
|
||||
// Add model mapping if configured
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
@@ -2237,7 +2306,12 @@ const handleGenerateUrl = async () => {
|
||||
if (form.platform === 'openai') {
|
||||
await openaiOAuth.generateAuthUrl(form.proxy_id)
|
||||
} else if (form.platform === 'gemini') {
|
||||
await geminiOAuth.generateAuthUrl(form.proxy_id, oauthFlowRef.value?.projectId, geminiOAuthType.value)
|
||||
await geminiOAuth.generateAuthUrl(
|
||||
form.proxy_id,
|
||||
oauthFlowRef.value?.projectId,
|
||||
geminiOAuthType.value,
|
||||
geminiSelectedTier.value
|
||||
)
|
||||
} else if (form.platform === 'antigravity') {
|
||||
await antigravityOAuth.generateAuthUrl(form.proxy_id)
|
||||
} else {
|
||||
@@ -2318,7 +2392,8 @@ const handleGeminiExchange = async (authCode: string) => {
|
||||
sessionId: geminiOAuth.sessionId.value,
|
||||
state: stateToUse,
|
||||
proxyId: form.proxy_id,
|
||||
oauthType: geminiOAuthType.value
|
||||
oauthType: geminiOAuthType.value,
|
||||
tierId: geminiSelectedTier.value
|
||||
})
|
||||
if (!tokenInfo) return
|
||||
|
||||
|
||||
@@ -88,7 +88,35 @@
|
||||
<!-- Gemini OAuth Type Selection -->
|
||||
<fieldset v-if="isGemini" class="border-0 p-0">
|
||||
<legend class="input-label">{{ t('admin.accounts.oauth.gemini.oauthTypeLabel') }}</legend>
|
||||
<div class="mt-2 grid grid-cols-2 gap-3">
|
||||
<div class="mt-2 grid grid-cols-3 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="handleSelectGeminiOAuthType('google_one')"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
geminiOAuthType === 'google_one'
|
||||
? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20'
|
||||
: 'border-gray-200 hover:border-purple-300 dark:border-dark-600 dark:hover:border-purple-700'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'google_one'
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">Google One</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">个人账号</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="handleSelectGeminiOAuthType('code_assist')"
|
||||
@@ -305,7 +333,7 @@ const oauthFlowRef = ref<OAuthFlowExposed | null>(null)
|
||||
|
||||
// State
|
||||
const addMethod = ref<AddMethod>('oauth')
|
||||
const geminiOAuthType = ref<'code_assist' | 'ai_studio'>('code_assist')
|
||||
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('code_assist')
|
||||
const geminiAIStudioOAuthEnabled = ref(false)
|
||||
|
||||
// Computed - check platform
|
||||
@@ -367,7 +395,12 @@ watch(
|
||||
}
|
||||
if (isGemini.value) {
|
||||
const creds = (props.account.credentials || {}) as Record<string, unknown>
|
||||
geminiOAuthType.value = creds.oauth_type === 'ai_studio' ? 'ai_studio' : 'code_assist'
|
||||
geminiOAuthType.value =
|
||||
creds.oauth_type === 'google_one'
|
||||
? 'google_one'
|
||||
: creds.oauth_type === 'ai_studio'
|
||||
? 'ai_studio'
|
||||
: 'code_assist'
|
||||
}
|
||||
if (isGemini.value) {
|
||||
geminiOAuth.getCapabilities().then((caps) => {
|
||||
@@ -395,7 +428,7 @@ const resetState = () => {
|
||||
oauthFlowRef.value?.reset()
|
||||
}
|
||||
|
||||
const handleSelectGeminiOAuthType = (oauthType: 'code_assist' | 'ai_studio') => {
|
||||
const handleSelectGeminiOAuthType = (oauthType: 'code_assist' | 'google_one' | 'ai_studio') => {
|
||||
if (oauthType === 'ai_studio' && !geminiAIStudioOAuthEnabled.value) {
|
||||
appStore.showError(t('admin.accounts.oauth.gemini.aiStudioNotConfigured'))
|
||||
return
|
||||
@@ -413,8 +446,10 @@ const handleGenerateUrl = async () => {
|
||||
if (isOpenAI.value) {
|
||||
await openaiOAuth.generateAuthUrl(props.account.proxy_id)
|
||||
} else if (isGemini.value) {
|
||||
const creds = (props.account.credentials || {}) as Record<string, unknown>
|
||||
const tierId = typeof creds.tier_id === 'string' ? creds.tier_id : undefined
|
||||
const projectId = geminiOAuthType.value === 'code_assist' ? oauthFlowRef.value?.projectId : undefined
|
||||
await geminiOAuth.generateAuthUrl(props.account.proxy_id, projectId, geminiOAuthType.value)
|
||||
await geminiOAuth.generateAuthUrl(props.account.proxy_id, projectId, geminiOAuthType.value, tierId)
|
||||
} else if (isAntigravity.value) {
|
||||
await antigravityOAuth.generateAuthUrl(props.account.proxy_id)
|
||||
} else {
|
||||
@@ -475,7 +510,8 @@ const handleExchangeCode = async () => {
|
||||
sessionId,
|
||||
state: stateToUse,
|
||||
proxyId: props.account.proxy_id,
|
||||
oauthType: geminiOAuthType.value
|
||||
oauthType: geminiOAuthType.value,
|
||||
tierId: typeof (props.account.credentials as any)?.tier_id === 'string' ? ((props.account.credentials as any).tier_id as string) : undefined
|
||||
})
|
||||
if (!tokenInfo) return
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ export function useGeminiOAuth() {
|
||||
const generateAuthUrl = async (
|
||||
proxyId: number | null | undefined,
|
||||
projectId?: string | null,
|
||||
oauthType?: string
|
||||
oauthType?: string,
|
||||
tierId?: string
|
||||
): Promise<boolean> => {
|
||||
loading.value = true
|
||||
authUrl.value = ''
|
||||
@@ -52,6 +53,8 @@ export function useGeminiOAuth() {
|
||||
const trimmedProjectID = projectId?.trim()
|
||||
if (trimmedProjectID) payload.project_id = trimmedProjectID
|
||||
if (oauthType) payload.oauth_type = oauthType
|
||||
const trimmedTierID = tierId?.trim()
|
||||
if (trimmedTierID) payload.tier_id = trimmedTierID
|
||||
|
||||
const response = await adminAPI.gemini.generateAuthUrl(payload as any)
|
||||
authUrl.value = response.auth_url
|
||||
@@ -73,6 +76,7 @@ export function useGeminiOAuth() {
|
||||
state: string
|
||||
proxyId?: number | null
|
||||
oauthType?: string
|
||||
tierId?: string
|
||||
}): Promise<GeminiTokenInfo | null> => {
|
||||
const code = params.code?.trim()
|
||||
if (!code || !params.sessionId || !params.state) {
|
||||
@@ -91,6 +95,8 @@ export function useGeminiOAuth() {
|
||||
}
|
||||
if (params.proxyId) payload.proxy_id = params.proxyId
|
||||
if (params.oauthType) payload.oauth_type = params.oauthType
|
||||
const trimmedTierID = params.tierId?.trim()
|
||||
if (trimmedTierID) payload.tier_id = trimmedTierID
|
||||
|
||||
const tokenInfo = await adminAPI.gemini.exchangeCode(payload as any)
|
||||
return tokenInfo as GeminiTokenInfo
|
||||
|
||||
@@ -1257,6 +1257,25 @@ export default {
|
||||
'All model requests are forwarded directly to the Gemini API without model restrictions or mappings.',
|
||||
baseUrlHint: 'Leave default for official Gemini API',
|
||||
apiKeyHint: 'Your Gemini API Key (starts with AIza)',
|
||||
tier: {
|
||||
label: 'Tier (Quota Level)',
|
||||
hint: 'Tip: The system will try to auto-detect the tier first; if auto-detection is unavailable or fails, your selected tier is used as a fallback (simulated quota).',
|
||||
aiStudioHint:
|
||||
'AI Studio quotas are per-model (Pro/Flash are limited independently). If billing is enabled, choose Pay-as-you-go.',
|
||||
googleOne: {
|
||||
free: 'Google One Free (1000 RPD / 60 RPM, shared pool)',
|
||||
pro: 'Google AI Pro (1500 RPD / 120 RPM, shared pool)',
|
||||
ultra: 'Google AI Ultra (2000 RPD / 120 RPM, shared pool)'
|
||||
},
|
||||
gcp: {
|
||||
standard: 'GCP Standard (1500 RPD / 120 RPM, shared pool)',
|
||||
enterprise: 'GCP Enterprise (2000 RPD / 120 RPM, shared pool)'
|
||||
},
|
||||
aiStudio: {
|
||||
free: 'AI Studio Free Tier (Pro: 50 RPD / 2 RPM; Flash: 1500 RPD / 15 RPM)',
|
||||
paid: 'AI Studio Pay-as-you-go (Pro: ∞ RPD / 1000 RPM; Flash: ∞ RPD / 2000 RPM)'
|
||||
}
|
||||
},
|
||||
accountType: {
|
||||
oauthTitle: 'OAuth (Gemini)',
|
||||
oauthDesc: 'Authorize with your Google account and choose an OAuth type.',
|
||||
@@ -1317,6 +1336,17 @@ export default {
|
||||
},
|
||||
simulatedNote: 'Simulated quota, for reference only',
|
||||
rows: {
|
||||
googleOne: {
|
||||
channel: 'Google One OAuth (Individuals / Code Assist for Individuals)',
|
||||
limitsFree: 'Shared pool: 1000 RPD / 60 RPM',
|
||||
limitsPro: 'Shared pool: 1500 RPD / 120 RPM',
|
||||
limitsUltra: 'Shared pool: 2000 RPD / 120 RPM'
|
||||
},
|
||||
gcp: {
|
||||
channel: 'GCP Code Assist OAuth (Enterprise)',
|
||||
limitsStandard: 'Shared pool: 1500 RPD / 120 RPM',
|
||||
limitsEnterprise: 'Shared pool: 2000 RPD / 120 RPM'
|
||||
},
|
||||
cli: {
|
||||
channel: 'Gemini CLI (Official Google Login / Code Assist)',
|
||||
free: 'Free Google Account',
|
||||
@@ -1334,7 +1364,7 @@ export default {
|
||||
free: 'No billing (free tier)',
|
||||
paid: 'Billing enabled (pay-as-you-go)',
|
||||
limitsFree: 'RPD 50; RPM 2 (Pro) / 15 (Flash)',
|
||||
limitsPaid: 'RPD unlimited; RPM 1000+ (per model quota)'
|
||||
limitsPaid: 'RPD unlimited; RPM 1000 (Pro) / 2000 (Flash) (per model)'
|
||||
},
|
||||
customOAuth: {
|
||||
channel: 'Custom OAuth Client (GCP)',
|
||||
|
||||
@@ -1395,6 +1395,24 @@ export default {
|
||||
modelPassthroughDesc: '所有模型请求将直接转发至 Gemini API,不进行模型限制或映射。',
|
||||
baseUrlHint: '留空使用官方 Gemini API',
|
||||
apiKeyHint: '您的 Gemini API Key(以 AIza 开头)',
|
||||
tier: {
|
||||
label: 'Tier(配额等级)',
|
||||
hint: '提示:系统会优先尝试自动识别 Tier;若自动识别不可用或失败,则使用你选择的 Tier 作为回退(本地模拟配额)。',
|
||||
aiStudioHint: 'AI Studio 的配额是按模型分别限流(Pro/Flash 独立)。若已绑卡(按量付费),请选 Pay-as-you-go。',
|
||||
googleOne: {
|
||||
free: 'Google One Free(1000 RPD / 60 RPM,共享池)',
|
||||
pro: 'Google AI Pro(1500 RPD / 120 RPM,共享池)',
|
||||
ultra: 'Google AI Ultra(2000 RPD / 120 RPM,共享池)'
|
||||
},
|
||||
gcp: {
|
||||
standard: 'GCP Standard(1500 RPD / 120 RPM,共享池)',
|
||||
enterprise: 'GCP Enterprise(2000 RPD / 120 RPM,共享池)'
|
||||
},
|
||||
aiStudio: {
|
||||
free: 'AI Studio Free Tier(Pro: 50 RPD / 2 RPM;Flash: 1500 RPD / 15 RPM)',
|
||||
paid: 'AI Studio Pay-as-you-go(Pro: ∞ RPD / 1000 RPM;Flash: ∞ RPD / 2000 RPM)'
|
||||
}
|
||||
},
|
||||
accountType: {
|
||||
oauthTitle: 'OAuth 授权(Gemini)',
|
||||
oauthDesc: '使用 Google 账号授权,并选择 OAuth 子类型。',
|
||||
@@ -1454,6 +1472,17 @@ export default {
|
||||
},
|
||||
simulatedNote: '本地模拟配额,仅供参考',
|
||||
rows: {
|
||||
googleOne: {
|
||||
channel: 'Google One OAuth(个人版 / Code Assist for Individuals)',
|
||||
limitsFree: '共享池:1000 RPD / 60 RPM(不分模型)',
|
||||
limitsPro: '共享池:1500 RPD / 120 RPM(不分模型)',
|
||||
limitsUltra: '共享池:2000 RPD / 120 RPM(不分模型)'
|
||||
},
|
||||
gcp: {
|
||||
channel: 'GCP Code Assist OAuth(企业版)',
|
||||
limitsStandard: '共享池:1500 RPD / 120 RPM(不分模型)',
|
||||
limitsEnterprise: '共享池:2000 RPD / 120 RPM(不分模型)'
|
||||
},
|
||||
cli: {
|
||||
channel: 'Gemini CLI(官方 Google 登录 / Code Assist)',
|
||||
free: '免费 Google 账号',
|
||||
@@ -1471,7 +1500,7 @@ export default {
|
||||
free: '未绑卡(免费层)',
|
||||
paid: '已绑卡(按量付费)',
|
||||
limitsFree: 'RPD 50;RPM 2(Pro)/ 15(Flash)',
|
||||
limitsPaid: 'RPD 不限;RPM 1000+(按模型配额)'
|
||||
limitsPaid: 'RPD 不限;RPM 1000(Pro)/ 2000(Flash)(按模型配额)'
|
||||
},
|
||||
customOAuth: {
|
||||
channel: 'Custom OAuth Client(GCP)',
|
||||
|
||||
@@ -322,8 +322,19 @@ export interface GeminiCredentials {
|
||||
// OAuth authentication
|
||||
access_token?: string
|
||||
refresh_token?: string
|
||||
oauth_type?: 'code_assist' | 'ai_studio' | string
|
||||
tier_id?: 'LEGACY' | 'PRO' | 'ULTRA' | string
|
||||
oauth_type?: 'code_assist' | 'google_one' | 'ai_studio' | string
|
||||
tier_id?:
|
||||
| 'google_one_free'
|
||||
| 'google_ai_pro'
|
||||
| 'google_ai_ultra'
|
||||
| 'gcp_standard'
|
||||
| 'gcp_enterprise'
|
||||
| 'aistudio_free'
|
||||
| 'aistudio_paid'
|
||||
| 'LEGACY'
|
||||
| 'PRO'
|
||||
| 'ULTRA'
|
||||
| string
|
||||
project_id?: string
|
||||
token_type?: string
|
||||
scope?: string
|
||||
@@ -397,6 +408,8 @@ export interface UsageProgress {
|
||||
resets_at: string | null
|
||||
remaining_seconds: number
|
||||
window_stats?: WindowStats | null // 窗口期统计(从窗口开始到当前的使用量)
|
||||
used_requests?: number
|
||||
limit_requests?: number
|
||||
}
|
||||
|
||||
// Antigravity 单个模型的配额信息
|
||||
@@ -410,8 +423,12 @@ export interface AccountUsageInfo {
|
||||
five_hour: UsageProgress | null
|
||||
seven_day: UsageProgress | null
|
||||
seven_day_sonnet: UsageProgress | null
|
||||
gemini_shared_daily?: UsageProgress | null
|
||||
gemini_pro_daily?: UsageProgress | null
|
||||
gemini_flash_daily?: UsageProgress | null
|
||||
gemini_shared_minute?: UsageProgress | null
|
||||
gemini_pro_minute?: UsageProgress | null
|
||||
gemini_flash_minute?: UsageProgress | null
|
||||
antigravity_quota?: Record<string, AntigravityModelQuota> | null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user