feat(gemini): 完善 Gemini OAuth 配额系统和用量显示
主要改动: - 后端:重构 Gemini 配额服务,支持多层级配额策略(GCP Standard/Free, Google One, AI Studio, Code Assist) - 后端:优化 OAuth 服务,增强 tier_id 识别和存储逻辑 - 后端:改进用量统计服务,支持不同平台的配额查询 - 后端:优化限流服务,增加临时解除调度状态管理 - 前端:统一四种授权方式的用量显示格式和徽标样式 - 前端:增强账户配额信息展示,支持多种配额类型 - 前端:改进创建和重新授权模态框的用户体验 - 国际化:完善中英文配额相关文案 - 移除 CHANGELOG.md 文件 测试:所有单元测试通过
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user