fix(gemini): Google One 强制使用内置 OAuth client + 自动获取 project_id + UI 优化 (#212)
* fix(gemini): Google One 强制使用内置 OAuth client + 自动获取 project_id + UI 优化 ## 后端改动 ### 1. Google One 强制使用内置 Gemini CLI OAuth Client **问题**: - Google One 之前允许使用自定义 OAuth client,导致认证流程不稳定 - 与 Code Assist 的行为不一致 **解决方案**: - 修改 `gemini_oauth_service.go`: Google One 现在与 Code Assist 一样强制使用内置 client (L122-135) - 更新 `gemini_oauth_client.go`: ExchangeCode 和 RefreshToken 方法支持强制内置 client (L31-44, L77-86) - 简化 `geminicli/oauth.go`: Google One scope 选择逻辑 (L187-190) - 标记 `geminicli/constants.go`: DefaultGoogleOneScopes 为 DEPRECATED (L30-33) - 更新测试用例以反映新行为 **OAuth 类型对比**: | OAuth类型 | Client来源 | Scopes | Redirect URI | |-----------|-----------|--------|-----------------| | code_assist | 内置 Gemini CLI | DefaultCodeAssistScopes | https://codeassist.google.com/authcode | | google_one | 内置 Gemini CLI (新) | DefaultCodeAssistScopes | https://codeassist.google.com/authcode | | ai_studio | 必须自定义 | DefaultAIStudioScopes | http://localhost:1455/auth/callback | ### 2. Google One 自动获取 project_id **问题**: - Google One 个人账号测试模型时返回 403/404 错误 - 原因:cloudaicompanion API 需要 project_id,但个人账号无需手动创建 GCP 项目 **解决方案**: - 修改 `gemini_oauth_service.go`: OAuth 流程中自动调用 fetchProjectID - Google 通过 LoadCodeAssist API 自动分配 project_id - 与 Gemini CLI 行为保持一致 - 后端根据 project_id 自动选择正确的 API 端点 **影响**: - Google One 账号现在可以正常使用(需要重新授权) - Code Assist 和 AI Studio 账号不受影响 ### 3. 修复 Gemini 测试账号无内容输出问题 **问题**: - 测试 Gemini 账号时只显示"测试成功",没有显示 AI 响应内容 - 原因:processGeminiStream 在检查到 finishReason 时立即返回,跳过了内容提取 **解决方案**: - 修改 `account_test_service.go`: 调整逻辑顺序,先提取内容再检查是否完成 - 确保最后一个 chunk 的内容也能被正确显示 **影响**: - 所有 Gemini 账号类型(API Key、OAuth)的测试现在都会显示完整响应内容 - 用户可以看到流式输出效果 ## 前端改动 ### 1. 修复图标宽度压缩问题 **问题**: - 账户类型选择按钮中的图标在某些情况下会被压缩变形 **解决方案**: - 修改 `CreateAccountModal.vue`: 为所有平台图标容器添加 `shrink-0` 类 - 确保 Anthropic、OpenAI、Gemini、Antigravity 图标保持固定 8×8 尺寸 (32px × 32px) ### 2. 优化重新授权界面 **问题**: - 重新授权时显示三个可点击的授权类型选择按钮,可能导致用户误切换到不兼容的授权方式 **解决方案**: - 修改 `ReAuthAccountModal.vue` (admin 和普通用户版本): - 将可点击的授权类型选择按钮改为只读信息展示框 - 根据账号的 `credentials.oauth_type` 动态显示对应图标和文本 - 删除 `geminiAIStudioOAuthEnabled` 状态和 `handleSelectGeminiOAuthType` 方法 - 防止用户误操作 ## 测试验证 - ✅ 所有后端单元测试通过 - ✅ OAuth client 选择逻辑正确 - ✅ Google One 和 Code Assist 行为一致 - ✅ 测试账号显示完整响应内容 - ✅ UI 图标显示正常 - ✅ 重新授权界面只读展示正确 * fix(lint): 修复 golangci-lint 错误信息格式问题 - 将错误信息改为小写开头以符合 Go 代码规范 - 修复 ST1005: error strings should not be capitalized
This commit is contained in:
@@ -166,7 +166,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'oauth-based'
|
||||
? 'bg-orange-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -196,7 +196,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'apikey'
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -232,7 +232,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'oauth-based'
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -258,7 +258,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'apikey'
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -302,7 +302,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'oauth-based'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -332,7 +332,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
accountCategory === 'apikey'
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -397,7 +397,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 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'
|
||||
@@ -440,7 +440,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'code_assist'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -518,7 +518,7 @@
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'ai_studio'
|
||||
? 'bg-amber-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
@@ -621,7 +621,7 @@
|
||||
<div
|
||||
class="flex items-center gap-3 rounded-lg border-2 border-purple-500 bg-purple-50 p-3 dark:bg-purple-900/20"
|
||||
>
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-purple-500 text-white">
|
||||
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-purple-500 text-white">
|
||||
<Icon name="key" size="sm" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -73,113 +73,48 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- 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-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')"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
geminiOAuthType === 'code_assist'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||||
: 'border-gray-200 hover:border-blue-300 dark:border-dark-600 dark:hover:border-blue-700'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'code_assist'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<Icon name="cloud" size="sm" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.accounts.gemini.oauthType.builtInTitle') }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.gemini.oauthType.builtInDesc') }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!geminiAIStudioOAuthEnabled"
|
||||
@click="handleSelectGeminiOAuthType('ai_studio')"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
!geminiAIStudioOAuthEnabled ? 'cursor-not-allowed opacity-60' : '',
|
||||
geminiOAuthType === 'ai_studio'
|
||||
? '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 === 'ai_studio'
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<Icon name="sparkles" size="sm" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.accounts.gemini.oauthType.customTitle') }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.gemini.oauthType.customDesc') }}
|
||||
</span>
|
||||
<div v-if="!geminiAIStudioOAuthEnabled" class="group relative mt-1 inline-block">
|
||||
<span
|
||||
class="rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-700 dark:bg-amber-900/30 dark:text-amber-300"
|
||||
>
|
||||
{{ t('admin.accounts.oauth.gemini.aiStudioNotConfiguredShort') }}
|
||||
</span>
|
||||
<div
|
||||
class="pointer-events-none absolute left-0 top-full z-10 mt-2 w-[28rem] rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 opacity-0 shadow-sm transition-opacity group-hover:opacity-100 dark:border-amber-700 dark:bg-amber-900/40 dark:text-amber-200"
|
||||
>
|
||||
{{ t('admin.accounts.oauth.gemini.aiStudioNotConfiguredTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<!-- Gemini OAuth Type Display (read-only) -->
|
||||
<div v-if="isGemini" class="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-dark-600 dark:bg-dark-700">
|
||||
<div class="mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.accounts.oauth.gemini.oauthTypeLabel') }}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'google_one'
|
||||
? 'bg-purple-500 text-white'
|
||||
: geminiOAuthType === 'code_assist'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-amber-500 text-white'
|
||||
]"
|
||||
>
|
||||
<Icon v-if="geminiOAuthType === 'google_one'" name="user" size="sm" />
|
||||
<Icon v-else-if="geminiOAuthType === 'code_assist'" name="cloud" size="sm" />
|
||||
<Icon v-else name="sparkles" size="sm" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{
|
||||
geminiOAuthType === 'google_one'
|
||||
? 'Google One'
|
||||
: geminiOAuthType === 'code_assist'
|
||||
? t('admin.accounts.gemini.oauthType.builtInTitle')
|
||||
: t('admin.accounts.gemini.oauthType.customTitle')
|
||||
}}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
geminiOAuthType === 'google_one'
|
||||
? '个人账号'
|
||||
: geminiOAuthType === 'code_assist'
|
||||
? t('admin.accounts.gemini.oauthType.builtInDesc')
|
||||
: t('admin.accounts.gemini.oauthType.customDesc')
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OAuthAuthorizationFlow
|
||||
ref="oauthFlowRef"
|
||||
@@ -299,7 +234,6 @@ const oauthFlowRef = ref<OAuthFlowExposed | null>(null)
|
||||
// State
|
||||
const addMethod = ref<AddMethod>('oauth')
|
||||
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('code_assist')
|
||||
const geminiAIStudioOAuthEnabled = ref(false)
|
||||
|
||||
// Computed - check platform
|
||||
const isOpenAI = computed(() => props.account?.platform === 'openai')
|
||||
@@ -367,14 +301,6 @@ watch(
|
||||
? 'ai_studio'
|
||||
: 'code_assist'
|
||||
}
|
||||
if (isGemini.value) {
|
||||
geminiOAuth.getCapabilities().then((caps) => {
|
||||
geminiAIStudioOAuthEnabled.value = !!caps?.ai_studio_oauth_enabled
|
||||
if (!geminiAIStudioOAuthEnabled.value && geminiOAuthType.value === 'ai_studio') {
|
||||
geminiOAuthType.value = 'code_assist'
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
resetState()
|
||||
}
|
||||
@@ -385,7 +311,6 @@ watch(
|
||||
const resetState = () => {
|
||||
addMethod.value = 'oauth'
|
||||
geminiOAuthType.value = 'code_assist'
|
||||
geminiAIStudioOAuthEnabled.value = false
|
||||
claudeOAuth.resetState()
|
||||
openaiOAuth.resetState()
|
||||
geminiOAuth.resetState()
|
||||
@@ -393,14 +318,6 @@ const resetState = () => {
|
||||
oauthFlowRef.value?.reset()
|
||||
}
|
||||
|
||||
const handleSelectGeminiOAuthType = (oauthType: 'code_assist' | 'google_one' | 'ai_studio') => {
|
||||
if (oauthType === 'ai_studio' && !geminiAIStudioOAuthEnabled.value) {
|
||||
appStore.showError(t('admin.accounts.oauth.gemini.aiStudioNotConfigured'))
|
||||
return
|
||||
}
|
||||
geminiOAuthType.value = oauthType
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
@@ -73,111 +73,48 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- 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-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'
|
||||
]"
|
||||
>
|
||||
<Icon name="user" size="sm" />
|
||||
</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')"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
geminiOAuthType === 'code_assist'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||||
: 'border-gray-200 hover:border-blue-300 dark:border-dark-600 dark:hover:border-blue-700'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'code_assist'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<Icon name="cloud" size="sm" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.accounts.gemini.oauthType.builtInTitle') }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.gemini.oauthType.builtInDesc') }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!geminiAIStudioOAuthEnabled"
|
||||
@click="handleSelectGeminiOAuthType('ai_studio')"
|
||||
:class="[
|
||||
'flex items-center gap-3 rounded-lg border-2 p-3 text-left transition-all',
|
||||
!geminiAIStudioOAuthEnabled ? 'cursor-not-allowed opacity-60' : '',
|
||||
geminiOAuthType === 'ai_studio'
|
||||
? '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 === 'ai_studio'
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-dark-600 dark:text-gray-400'
|
||||
]"
|
||||
>
|
||||
<Icon name="sparkles" size="sm" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.accounts.gemini.oauthType.customTitle') }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.gemini.oauthType.customDesc') }}
|
||||
</span>
|
||||
<div v-if="!geminiAIStudioOAuthEnabled" class="group relative mt-1 inline-block">
|
||||
<span
|
||||
class="rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-700 dark:bg-amber-900/30 dark:text-amber-300"
|
||||
>
|
||||
{{ t('admin.accounts.oauth.gemini.aiStudioNotConfiguredShort') }}
|
||||
</span>
|
||||
<div
|
||||
class="pointer-events-none absolute left-0 top-full z-10 mt-2 w-[28rem] rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 opacity-0 shadow-sm transition-opacity group-hover:opacity-100 dark:border-amber-700 dark:bg-amber-900/40 dark:text-amber-200"
|
||||
>
|
||||
{{ t('admin.accounts.oauth.gemini.aiStudioNotConfiguredTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<!-- Gemini OAuth Type Display (read-only) -->
|
||||
<div v-if="isGemini" class="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-dark-600 dark:bg-dark-700">
|
||||
<div class="mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.accounts.oauth.gemini.oauthTypeLabel') }}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
:class="[
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||
geminiOAuthType === 'google_one'
|
||||
? 'bg-purple-500 text-white'
|
||||
: geminiOAuthType === 'code_assist'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-amber-500 text-white'
|
||||
]"
|
||||
>
|
||||
<Icon v-if="geminiOAuthType === 'google_one'" name="user" size="sm" />
|
||||
<Icon v-else-if="geminiOAuthType === 'code_assist'" name="cloud" size="sm" />
|
||||
<Icon v-else name="sparkles" size="sm" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{
|
||||
geminiOAuthType === 'google_one'
|
||||
? 'Google One'
|
||||
: geminiOAuthType === 'code_assist'
|
||||
? t('admin.accounts.gemini.oauthType.builtInTitle')
|
||||
: t('admin.accounts.gemini.oauthType.customTitle')
|
||||
}}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
geminiOAuthType === 'google_one'
|
||||
? '个人账号'
|
||||
: geminiOAuthType === 'code_assist'
|
||||
? t('admin.accounts.gemini.oauthType.builtInDesc')
|
||||
: t('admin.accounts.gemini.oauthType.customDesc')
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OAuthAuthorizationFlow
|
||||
ref="oauthFlowRef"
|
||||
@@ -297,7 +234,6 @@ const oauthFlowRef = ref<OAuthFlowExposed | null>(null)
|
||||
// State
|
||||
const addMethod = ref<AddMethod>('oauth')
|
||||
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('code_assist')
|
||||
const geminiAIStudioOAuthEnabled = ref(false)
|
||||
|
||||
// Computed - check platform
|
||||
const isOpenAI = computed(() => props.account?.platform === 'openai')
|
||||
@@ -365,14 +301,6 @@ watch(
|
||||
? 'ai_studio'
|
||||
: 'code_assist'
|
||||
}
|
||||
if (isGemini.value) {
|
||||
geminiOAuth.getCapabilities().then((caps) => {
|
||||
geminiAIStudioOAuthEnabled.value = !!caps?.ai_studio_oauth_enabled
|
||||
if (!geminiAIStudioOAuthEnabled.value && geminiOAuthType.value === 'ai_studio') {
|
||||
geminiOAuthType.value = 'code_assist'
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
resetState()
|
||||
}
|
||||
@@ -383,7 +311,6 @@ watch(
|
||||
const resetState = () => {
|
||||
addMethod.value = 'oauth'
|
||||
geminiOAuthType.value = 'code_assist'
|
||||
geminiAIStudioOAuthEnabled.value = false
|
||||
claudeOAuth.resetState()
|
||||
openaiOAuth.resetState()
|
||||
geminiOAuth.resetState()
|
||||
@@ -391,14 +318,6 @@ const resetState = () => {
|
||||
oauthFlowRef.value?.reset()
|
||||
}
|
||||
|
||||
const handleSelectGeminiOAuthType = (oauthType: 'code_assist' | 'google_one' | 'ai_studio') => {
|
||||
if (oauthType === 'ai_studio' && !geminiAIStudioOAuthEnabled.value) {
|
||||
appStore.showError(t('admin.accounts.oauth.gemini.aiStudioNotConfigured'))
|
||||
return
|
||||
}
|
||||
geminiOAuthType.value = oauthType
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user