mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 新增Droid cli支持
This commit is contained in:
@@ -473,9 +473,11 @@
|
||||
type="radio"
|
||||
value="oauth"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300"
|
||||
>OAuth 授权 (用量可视化)</span
|
||||
>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||
OAuth 授权<span v-if="form.platform === 'claude' || form.platform === 'openai'">
|
||||
(用量可视化)</span
|
||||
>
|
||||
</span>
|
||||
</label>
|
||||
<label v-if="form.platform === 'claude'" class="flex cursor-pointer items-center">
|
||||
<input
|
||||
@@ -1448,6 +1450,12 @@
|
||||
请输入有效的 OpenAI Access Token。如果您有 Refresh
|
||||
Token,建议也一并填写以支持自动刷新。
|
||||
</p>
|
||||
<p
|
||||
v-else-if="form.platform === 'droid'"
|
||||
class="mb-2 text-sm text-blue-800 dark:text-blue-300"
|
||||
>
|
||||
请输入有效的 Droid Access Token,并同时提供 Refresh Token 以支持自动刷新。
|
||||
</p>
|
||||
<div
|
||||
class="mb-2 mt-2 rounded-lg border border-blue-300 bg-white/80 p-3 dark:border-blue-600 dark:bg-gray-800/80"
|
||||
>
|
||||
@@ -1482,10 +1490,23 @@
|
||||
请从已登录 OpenAI 账户的机器上获取认证凭证, 或通过 OAuth 授权流程获取 Access
|
||||
Token。
|
||||
</p>
|
||||
<p
|
||||
v-else-if="form.platform === 'droid'"
|
||||
class="text-xs text-blue-800 dark:text-blue-300"
|
||||
>
|
||||
请从已完成授权的 Droid CLI 或 Factory.ai 导出的凭证中获取 Access Token 与
|
||||
Refresh Token。
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-xs text-blue-600 dark:text-blue-400">
|
||||
<p
|
||||
v-if="form.platform !== 'droid'"
|
||||
class="text-xs text-blue-600 dark:text-blue-400"
|
||||
>
|
||||
💡 如果未填写 Refresh Token,Token 过期后需要手动更新。
|
||||
</p>
|
||||
<p v-else class="text-xs text-red-600 dark:text-red-400">
|
||||
⚠️ Droid 账户必须填写 Refresh Token,缺失将导致无法自动刷新 Access Token。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1522,7 +1543,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="form.platform === 'openai'">
|
||||
<div v-if="form.platform === 'openai' || form.platform === 'droid'">
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>Refresh Token *</label
|
||||
>
|
||||
@@ -1539,7 +1560,12 @@
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-info-circle mr-1" />
|
||||
系统将使用 Refresh Token 自动获取 Access Token 和用户信息
|
||||
<template v-if="form.platform === 'openai'">
|
||||
系统将使用 Refresh Token 自动获取 Access Token 和用户信息
|
||||
</template>
|
||||
<template v-else>
|
||||
系统将使用 Refresh Token 自动刷新 Factory.ai 访问令牌,确保账户保持可用。
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2769,6 +2795,8 @@ const determinePlatformGroup = (platform) => {
|
||||
return 'openai'
|
||||
} else if (platform === 'gemini') {
|
||||
return 'gemini'
|
||||
} else if (platform === 'droid') {
|
||||
return 'droid'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -2822,6 +2850,7 @@ const form = ref({
|
||||
apiUrl: props.account?.apiUrl || '',
|
||||
apiKey: props.account?.apiKey || '',
|
||||
priority: props.account?.priority || 50,
|
||||
endpointType: props.account?.endpointType || 'anthropic',
|
||||
// OpenAI-Responses 特定字段
|
||||
baseApi: props.account?.baseApi || '',
|
||||
rateLimitDuration: props.account?.rateLimitDuration || 60,
|
||||
@@ -3155,7 +3184,9 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
: null
|
||||
}
|
||||
|
||||
if (form.value.platform === 'claude') {
|
||||
const currentPlatform = form.value.platform
|
||||
|
||||
if (currentPlatform === 'claude') {
|
||||
// Claude使用claudeAiOauth字段
|
||||
data.claudeAiOauth = tokenInfo.claudeAiOauth || tokenInfo
|
||||
data.priority = form.value.priority || 50
|
||||
@@ -3170,7 +3201,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
hasClaudePro: form.value.subscriptionType === 'claude_pro',
|
||||
manuallySet: true // 标记为手动设置
|
||||
}
|
||||
} else if (form.value.platform === 'gemini') {
|
||||
} else if (currentPlatform === 'gemini') {
|
||||
// Gemini使用geminiOauth字段
|
||||
data.geminiOauth = tokenInfo.tokens || tokenInfo
|
||||
if (form.value.projectId) {
|
||||
@@ -3178,17 +3209,85 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
}
|
||||
// 添加 Gemini 优先级
|
||||
data.priority = form.value.priority || 50
|
||||
} else if (form.value.platform === 'openai') {
|
||||
} else if (currentPlatform === 'openai') {
|
||||
data.openaiOauth = tokenInfo.tokens || tokenInfo
|
||||
data.accountInfo = tokenInfo.accountInfo
|
||||
data.priority = form.value.priority || 50
|
||||
} else if (currentPlatform === 'droid') {
|
||||
const rawTokens = tokenInfo.tokens || tokenInfo || {}
|
||||
|
||||
const normalizedTokens = {
|
||||
accessToken: rawTokens.accessToken || rawTokens.access_token || '',
|
||||
refreshToken: rawTokens.refreshToken || rawTokens.refresh_token || '',
|
||||
expiresAt: rawTokens.expiresAt || rawTokens.expires_at || '',
|
||||
expiresIn: rawTokens.expiresIn || rawTokens.expires_in || null,
|
||||
tokenType: rawTokens.tokenType || rawTokens.token_type || 'Bearer',
|
||||
organizationId: rawTokens.organizationId || rawTokens.organization_id || '',
|
||||
authenticationMethod:
|
||||
rawTokens.authenticationMethod || rawTokens.authentication_method || ''
|
||||
}
|
||||
|
||||
if (!normalizedTokens.refreshToken) {
|
||||
loading.value = false
|
||||
showToast('授权成功但未返回 Refresh Token,请确认已授予离线访问权限后重试。', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
data.refreshToken = normalizedTokens.refreshToken
|
||||
data.accessToken = normalizedTokens.accessToken
|
||||
data.expiresAt = normalizedTokens.expiresAt
|
||||
if (normalizedTokens.expiresIn !== null && normalizedTokens.expiresIn !== undefined) {
|
||||
data.expiresIn = normalizedTokens.expiresIn
|
||||
}
|
||||
data.priority = form.value.priority || 50
|
||||
data.endpointType = form.value.endpointType || 'anthropic'
|
||||
data.platform = 'droid'
|
||||
data.tokenType = normalizedTokens.tokenType
|
||||
data.authenticationMethod = normalizedTokens.authenticationMethod
|
||||
|
||||
if (normalizedTokens.organizationId) {
|
||||
data.organizationId = normalizedTokens.organizationId
|
||||
}
|
||||
|
||||
if (rawTokens.user) {
|
||||
const user = rawTokens.user
|
||||
const nameParts = []
|
||||
if (typeof user.first_name === 'string' && user.first_name.trim()) {
|
||||
nameParts.push(user.first_name.trim())
|
||||
}
|
||||
if (typeof user.last_name === 'string' && user.last_name.trim()) {
|
||||
nameParts.push(user.last_name.trim())
|
||||
}
|
||||
const derivedName =
|
||||
nameParts.join(' ').trim() ||
|
||||
(typeof user.name === 'string' ? user.name.trim() : '') ||
|
||||
(typeof user.display_name === 'string' ? user.display_name.trim() : '')
|
||||
|
||||
if (typeof user.email === 'string' && user.email.trim()) {
|
||||
data.ownerEmail = user.email.trim()
|
||||
}
|
||||
if (derivedName) {
|
||||
data.ownerName = derivedName
|
||||
data.ownerDisplayName = derivedName
|
||||
} else if (data.ownerEmail) {
|
||||
data.ownerName = data.ownerName || data.ownerEmail
|
||||
data.ownerDisplayName = data.ownerDisplayName || data.ownerEmail
|
||||
}
|
||||
if (typeof user.id === 'string' && user.id.trim()) {
|
||||
data.userId = user.id.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result
|
||||
if (form.value.platform === 'claude') {
|
||||
if (currentPlatform === 'claude') {
|
||||
result = await accountsStore.createClaudeAccount(data)
|
||||
} else if (form.value.platform === 'openai') {
|
||||
} else if (currentPlatform === 'gemini') {
|
||||
result = await accountsStore.createGeminiAccount(data)
|
||||
} else if (currentPlatform === 'openai') {
|
||||
result = await accountsStore.createOpenAIAccount(data)
|
||||
} else if (currentPlatform === 'droid') {
|
||||
result = await accountsStore.createDroidAccount(data)
|
||||
} else {
|
||||
result = await accountsStore.createGeminiAccount(data)
|
||||
}
|
||||
@@ -3227,6 +3326,7 @@ const createAccount = async () => {
|
||||
// 清除之前的错误
|
||||
errors.value.name = ''
|
||||
errors.value.accessToken = ''
|
||||
errors.value.refreshToken = ''
|
||||
errors.value.apiUrl = ''
|
||||
errors.value.apiKey = ''
|
||||
|
||||
@@ -3314,6 +3414,15 @@ const createAccount = async () => {
|
||||
errors.value.accessToken = '请填写 Access Token'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.platform === 'droid') {
|
||||
if (!form.value.accessToken || form.value.accessToken.trim() === '') {
|
||||
errors.value.accessToken = '请填写 Access Token'
|
||||
hasError = true
|
||||
}
|
||||
if (!form.value.refreshToken || form.value.refreshToken.trim() === '') {
|
||||
errors.value.refreshToken = '请填写 Refresh Token'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.platform === 'claude') {
|
||||
// Claude 平台需要 Access Token
|
||||
if (!form.value.accessToken || form.value.accessToken.trim() === '') {
|
||||
@@ -3443,6 +3552,20 @@ const createAccount = async () => {
|
||||
data.needsImmediateRefresh = true
|
||||
data.requireRefreshSuccess = true // 必须刷新成功才能创建账户
|
||||
data.priority = form.value.priority || 50
|
||||
} else if (form.value.platform === 'droid') {
|
||||
const accessToken = form.value.accessToken?.trim() || ''
|
||||
const refreshToken = form.value.refreshToken?.trim() || ''
|
||||
const expiresAt = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
data.accessToken = accessToken
|
||||
data.refreshToken = refreshToken
|
||||
data.expiresAt = expiresAt
|
||||
data.expiresIn = 8 * 60 * 60
|
||||
data.priority = form.value.priority || 50
|
||||
data.endpointType = form.value.endpointType || 'anthropic'
|
||||
data.platform = 'droid'
|
||||
data.tokenType = 'Bearer'
|
||||
data.authenticationMethod = 'manual'
|
||||
} else if (form.value.platform === 'claude-console' || form.value.platform === 'ccr') {
|
||||
// Claude Console 和 CCR 账户特定数据(CCR 使用 Claude Console 的后端逻辑)
|
||||
data.apiUrl = form.value.apiUrl
|
||||
@@ -3497,6 +3620,8 @@ const createAccount = async () => {
|
||||
} else if (form.value.platform === 'claude-console' || form.value.platform === 'ccr') {
|
||||
// CCR 使用 Claude Console 的后端 API
|
||||
result = await accountsStore.createClaudeConsoleAccount(data)
|
||||
} else if (form.value.platform === 'droid') {
|
||||
result = await accountsStore.createDroidAccount(data)
|
||||
} else if (form.value.platform === 'openai-responses') {
|
||||
result = await accountsStore.createOpenAIResponsesAccount(data)
|
||||
} else if (form.value.platform === 'bedrock') {
|
||||
@@ -3606,6 +3731,9 @@ const updateAccount = async () => {
|
||||
|
||||
// 只有非空时才更新token
|
||||
if (form.value.accessToken || form.value.refreshToken) {
|
||||
const trimmedAccessToken = form.value.accessToken?.trim() || ''
|
||||
const trimmedRefreshToken = form.value.refreshToken?.trim() || ''
|
||||
|
||||
if (props.account.platform === 'claude') {
|
||||
// Claude需要构建claudeAiOauth对象
|
||||
const expiresInMs = form.value.refreshToken
|
||||
@@ -3613,8 +3741,8 @@ const updateAccount = async () => {
|
||||
: 365 * 24 * 60 * 60 * 1000 // 1年
|
||||
|
||||
data.claudeAiOauth = {
|
||||
accessToken: form.value.accessToken || '',
|
||||
refreshToken: form.value.refreshToken || '',
|
||||
accessToken: trimmedAccessToken || '',
|
||||
refreshToken: trimmedRefreshToken || '',
|
||||
expiresAt: Date.now() + expiresInMs,
|
||||
scopes: props.account.scopes || [] // 保持原有的 scopes,如果没有则为空数组
|
||||
}
|
||||
@@ -3625,8 +3753,8 @@ const updateAccount = async () => {
|
||||
: 365 * 24 * 60 * 60 * 1000 // 1年
|
||||
|
||||
data.geminiOauth = {
|
||||
access_token: form.value.accessToken || '',
|
||||
refresh_token: form.value.refreshToken || '',
|
||||
access_token: trimmedAccessToken || '',
|
||||
refresh_token: trimmedRefreshToken || '',
|
||||
scope: 'https://www.googleapis.com/auth/cloud-platform',
|
||||
token_type: 'Bearer',
|
||||
expiry_date: Date.now() + expiresInMs
|
||||
@@ -3639,16 +3767,23 @@ const updateAccount = async () => {
|
||||
|
||||
data.openaiOauth = {
|
||||
idToken: '', // 不需要用户输入
|
||||
accessToken: form.value.accessToken || '',
|
||||
refreshToken: form.value.refreshToken || '',
|
||||
accessToken: trimmedAccessToken || '',
|
||||
refreshToken: trimmedRefreshToken || '',
|
||||
expires_in: Math.floor(expiresInMs / 1000) // 转换为秒
|
||||
}
|
||||
|
||||
// 编辑 OpenAI 账户时,如果更新了 Refresh Token,也需要验证
|
||||
if (form.value.refreshToken && form.value.refreshToken !== props.account.refreshToken) {
|
||||
if (trimmedRefreshToken && trimmedRefreshToken !== props.account.refreshToken) {
|
||||
data.needsImmediateRefresh = true
|
||||
data.requireRefreshSuccess = true
|
||||
}
|
||||
} else if (props.account.platform === 'droid') {
|
||||
if (trimmedAccessToken) {
|
||||
data.accessToken = trimmedAccessToken
|
||||
}
|
||||
if (trimmedRefreshToken) {
|
||||
data.refreshToken = trimmedRefreshToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3656,6 +3791,11 @@ const updateAccount = async () => {
|
||||
data.projectId = form.value.projectId || ''
|
||||
}
|
||||
|
||||
if (props.account.platform === 'droid') {
|
||||
data.priority = form.value.priority || 50
|
||||
data.endpointType = form.value.endpointType || 'anthropic'
|
||||
}
|
||||
|
||||
// Claude 官方账号优先级和订阅类型更新
|
||||
if (props.account.platform === 'claude') {
|
||||
// 更新模式也需要确保生成客户端ID
|
||||
@@ -3771,6 +3911,8 @@ const updateAccount = async () => {
|
||||
await accountsStore.updateAzureOpenAIAccount(props.account.id, data)
|
||||
} else if (props.account.platform === 'gemini') {
|
||||
await accountsStore.updateGeminiAccount(props.account.id, data)
|
||||
} else if (props.account.platform === 'droid') {
|
||||
await accountsStore.updateDroidAccount(props.account.id, data)
|
||||
} else {
|
||||
throw new Error(`不支持的平台: ${props.account.platform}`)
|
||||
}
|
||||
@@ -3824,6 +3966,16 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 监听Refresh Token变化,清除错误
|
||||
watch(
|
||||
() => form.value.refreshToken,
|
||||
() => {
|
||||
if (errors.value.refreshToken && form.value.refreshToken?.trim()) {
|
||||
errors.value.refreshToken = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 监听API URL变化,清除错误
|
||||
watch(
|
||||
() => form.value.apiUrl,
|
||||
|
||||
@@ -464,6 +464,170 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Droid OAuth流程 -->
|
||||
<div v-else-if="platform === 'droid'">
|
||||
<div
|
||||
class="rounded-lg border border-cyan-200 bg-cyan-50 p-6 dark:border-cyan-700 dark:bg-cyan-900/30"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-cyan-500"
|
||||
>
|
||||
<i class="fas fa-robot text-white" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="mb-3 font-semibold text-cyan-900 dark:text-cyan-200">Droid 账户授权</h4>
|
||||
<p class="mb-4 text-sm text-cyan-800 dark:text-cyan-300">
|
||||
请按照以下步骤完成 Factory (Droid) 账户的授权:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 步骤1: 生成授权链接 -->
|
||||
<div
|
||||
class="rounded-lg border border-cyan-300 bg-white/80 p-4 dark:border-cyan-600 dark:bg-gray-800/80"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-cyan-600 text-xs font-bold text-white"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="mb-2 font-medium text-cyan-900 dark:text-cyan-200">
|
||||
点击下方按钮生成授权链接
|
||||
</p>
|
||||
<button
|
||||
v-if="!authUrl"
|
||||
class="btn btn-primary px-4 py-2 text-sm"
|
||||
:disabled="loading"
|
||||
@click="generateAuthUrl"
|
||||
>
|
||||
<i v-if="!loading" class="fas fa-link mr-2" />
|
||||
<div v-else class="loading-spinner mr-2" />
|
||||
{{ loading ? '生成中...' : '生成授权链接' }}
|
||||
</button>
|
||||
<div v-else class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
>授权链接</label
|
||||
>
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-md border border-cyan-200 bg-white p-3 dark:border-cyan-700 dark:bg-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
class="form-input flex-1 bg-gray-50 font-mono text-xs dark:bg-gray-700"
|
||||
readonly
|
||||
type="text"
|
||||
:value="authUrl"
|
||||
/>
|
||||
<button
|
||||
class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
title="复制链接"
|
||||
@click="copyAuthUrl"
|
||||
>
|
||||
<i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
class="inline-flex items-center gap-1 rounded-md border border-cyan-200 bg-white px-3 py-1.5 text-xs font-medium text-cyan-600 shadow-sm transition-colors hover:border-cyan-300 hover:bg-cyan-50 dark:border-cyan-700 dark:bg-cyan-900/40 dark:text-cyan-200 dark:hover:border-cyan-500 dark:hover:bg-cyan-900/60"
|
||||
@click="openVerificationPage"
|
||||
>
|
||||
<i class="fas fa-external-link-alt text-xs" /> 在新标签中打开
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-xs font-medium text-cyan-600 transition-colors hover:text-cyan-700 dark:text-cyan-300 dark:hover:text-cyan-200"
|
||||
@click="regenerateAuthUrl"
|
||||
>
|
||||
<i class="fas fa-sync-alt text-xs" />重新生成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
>授权验证码</label
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between rounded-md border border-cyan-200 bg-cyan-50 px-4 py-3 dark:border-cyan-700 dark:bg-cyan-900/30"
|
||||
>
|
||||
<span
|
||||
class="font-mono text-xl font-semibold text-cyan-700 dark:text-cyan-200"
|
||||
>
|
||||
{{ userCode || '------' }}
|
||||
</span>
|
||||
<button
|
||||
class="rounded-lg bg-white px-3 py-1 text-sm text-cyan-600 transition-colors hover:bg-cyan-100 dark:bg-cyan-800 dark:text-cyan-200 dark:hover:bg-cyan-700"
|
||||
@click="copyUserCode"
|
||||
>
|
||||
<i class="fas fa-copy mr-1" />复制
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>
|
||||
<i class="fas fa-hourglass-half mr-1 text-cyan-500" />
|
||||
剩余有效期:{{ formattedCountdown }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤2: 访问链接并授权 -->
|
||||
<div
|
||||
class="rounded-lg border border-cyan-300 bg-white/80 p-4 dark:border-cyan-600 dark:bg-gray-800/80"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-cyan-600 text-xs font-bold text-white"
|
||||
>
|
||||
2
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="mb-2 font-medium text-cyan-900 dark:text-cyan-200">
|
||||
在浏览器中打开链接并完成授权
|
||||
</p>
|
||||
<div class="space-y-2 text-sm text-cyan-700 dark:text-cyan-300">
|
||||
<p>
|
||||
在浏览器中打开授权页面,输入上方验证码并登录 Factory / Droid
|
||||
账户,最后点击允许授权。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤3: 输入授权结果 -->
|
||||
<div
|
||||
class="rounded-lg border border-cyan-300 bg-white/80 p-4 dark:border-cyan-600 dark:bg-gray-800/80"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-cyan-600 text-xs font-bold text-white"
|
||||
>
|
||||
3
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="mb-2 font-medium text-cyan-900 dark:text-cyan-200">
|
||||
完成授权后点击下方“完成授权”按钮,系统会自动获取访问令牌。
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
若提示授权仍在等待确认,请稍候片刻后系统会自动重试。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-4">
|
||||
<button
|
||||
class="flex-1 rounded-xl bg-gray-100 px-6 py-3 font-semibold text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
||||
@@ -486,7 +650,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
||||
import { showToast } from '@/utils/toast'
|
||||
import { useAccountsStore } from '@/stores/accounts'
|
||||
|
||||
@@ -512,14 +676,59 @@ const authUrl = ref('')
|
||||
const authCode = ref('')
|
||||
const copied = ref(false)
|
||||
const sessionId = ref('') // 保存sessionId用于后续交换
|
||||
const userCode = ref('')
|
||||
const verificationUri = ref('')
|
||||
const verificationUriComplete = ref('')
|
||||
const pollInterval = ref(5)
|
||||
const remainingSeconds = ref(0)
|
||||
let countdownTimer = null
|
||||
let pendingTimer = null
|
||||
|
||||
// 计算是否可以交换code
|
||||
const canExchange = computed(() => {
|
||||
if (props.platform === 'droid') {
|
||||
return !!sessionId.value
|
||||
}
|
||||
return authUrl.value && authCode.value.trim()
|
||||
})
|
||||
|
||||
const formattedCountdown = computed(() => {
|
||||
if (!remainingSeconds.value || remainingSeconds.value <= 0) {
|
||||
return '00:00'
|
||||
}
|
||||
const minutes = Math.floor(remainingSeconds.value / 60)
|
||||
const seconds = remainingSeconds.value % 60
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||
})
|
||||
|
||||
const startCountdown = (seconds) => {
|
||||
stopCountdown()
|
||||
if (!seconds || seconds <= 0) {
|
||||
remainingSeconds.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
remainingSeconds.value = Math.floor(seconds)
|
||||
countdownTimer = setInterval(() => {
|
||||
if (remainingSeconds.value <= 1) {
|
||||
remainingSeconds.value = 0
|
||||
stopCountdown()
|
||||
} else {
|
||||
remainingSeconds.value -= 1
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const stopCountdown = () => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer)
|
||||
countdownTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 监听授权码输入,自动提取URL中的code参数
|
||||
watch(authCode, (newValue) => {
|
||||
if (props.platform === 'droid') return
|
||||
if (!newValue || typeof newValue !== 'string') return
|
||||
|
||||
const trimmedValue = newValue.trim()
|
||||
@@ -579,6 +788,20 @@ watch(authCode, (newValue) => {
|
||||
|
||||
// 生成授权URL
|
||||
const generateAuthUrl = async () => {
|
||||
if (pendingTimer) {
|
||||
clearTimeout(pendingTimer)
|
||||
pendingTimer = null
|
||||
}
|
||||
stopCountdown()
|
||||
authUrl.value = ''
|
||||
authCode.value = ''
|
||||
userCode.value = ''
|
||||
verificationUri.value = ''
|
||||
verificationUriComplete.value = ''
|
||||
remainingSeconds.value = 0
|
||||
sessionId.value = ''
|
||||
pollInterval.value = 5
|
||||
copied.value = false
|
||||
loading.value = true
|
||||
try {
|
||||
const proxyConfig = props.proxy?.enabled
|
||||
@@ -605,6 +828,15 @@ const generateAuthUrl = async () => {
|
||||
const result = await accountsStore.generateOpenAIAuthUrl(proxyConfig)
|
||||
authUrl.value = result.authUrl
|
||||
sessionId.value = result.sessionId
|
||||
} else if (props.platform === 'droid') {
|
||||
const result = await accountsStore.generateDroidAuthUrl(proxyConfig)
|
||||
authUrl.value = result.verificationUriComplete || result.verificationUri
|
||||
verificationUri.value = result.verificationUri
|
||||
verificationUriComplete.value = result.verificationUriComplete || result.verificationUri
|
||||
userCode.value = result.userCode
|
||||
pollInterval.value = Number(result.interval || 5) || 5
|
||||
startCountdown(result.expiresIn || 300)
|
||||
sessionId.value = result.sessionId
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message || '生成授权链接失败', 'error')
|
||||
@@ -615,13 +847,27 @@ const generateAuthUrl = async () => {
|
||||
|
||||
// 重新生成授权URL
|
||||
const regenerateAuthUrl = () => {
|
||||
stopCountdown()
|
||||
if (pendingTimer) {
|
||||
clearTimeout(pendingTimer)
|
||||
pendingTimer = null
|
||||
}
|
||||
authUrl.value = ''
|
||||
authCode.value = ''
|
||||
userCode.value = ''
|
||||
verificationUri.value = ''
|
||||
verificationUriComplete.value = ''
|
||||
remainingSeconds.value = 0
|
||||
sessionId.value = ''
|
||||
generateAuthUrl()
|
||||
}
|
||||
|
||||
// 复制授权URL
|
||||
const copyAuthUrl = async () => {
|
||||
if (!authUrl.value) {
|
||||
showToast('请先生成授权链接', 'warning')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(authUrl.value)
|
||||
copied.value = true
|
||||
@@ -645,6 +891,33 @@ const copyAuthUrl = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const copyUserCode = async () => {
|
||||
if (!userCode.value) {
|
||||
showToast('请先生成授权验证码', 'warning')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(userCode.value)
|
||||
showToast('验证码已复制', 'success')
|
||||
} catch (error) {
|
||||
const input = document.createElement('input')
|
||||
input.value = userCode.value
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(input)
|
||||
showToast('验证码已复制', 'success')
|
||||
}
|
||||
}
|
||||
|
||||
const openVerificationPage = () => {
|
||||
if (verificationUriComplete.value) {
|
||||
window.open(verificationUriComplete.value, '_blank', 'noopener')
|
||||
} else if (verificationUri.value) {
|
||||
window.open(verificationUri.value, '_blank', 'noopener')
|
||||
}
|
||||
}
|
||||
|
||||
// 交换授权码
|
||||
const exchangeCode = async () => {
|
||||
if (!canExchange.value) return
|
||||
@@ -671,6 +944,10 @@ const exchangeCode = async () => {
|
||||
code: authCode.value.trim(),
|
||||
sessionId: sessionId.value
|
||||
}
|
||||
} else if (props.platform === 'droid') {
|
||||
data = {
|
||||
sessionId: sessionId.value
|
||||
}
|
||||
}
|
||||
|
||||
// 添加代理配置(如果启用)
|
||||
@@ -691,6 +968,38 @@ const exchangeCode = async () => {
|
||||
tokenInfo = await accountsStore.exchangeGeminiCode(data)
|
||||
} else if (props.platform === 'openai') {
|
||||
tokenInfo = await accountsStore.exchangeOpenAICode(data)
|
||||
} else if (props.platform === 'droid') {
|
||||
const response = await accountsStore.exchangeDroidCode(data)
|
||||
if (!response.success) {
|
||||
if (response.pending) {
|
||||
const retrySeconds = Number(response.retryAfter || pollInterval.value || 5)
|
||||
const message = response.message || '授权尚未完成,请在浏览器确认后稍候再次尝试。'
|
||||
showToast(message, 'info')
|
||||
if (typeof response.expiresIn === 'number' && response.expiresIn >= 0) {
|
||||
startCountdown(response.expiresIn)
|
||||
}
|
||||
// 等待建议的轮询间隔后自动重试
|
||||
if (Number.isFinite(retrySeconds) && retrySeconds > 0) {
|
||||
if (pendingTimer) {
|
||||
clearTimeout(pendingTimer)
|
||||
}
|
||||
pendingTimer = setTimeout(() => {
|
||||
pendingTimer = null
|
||||
if (!exchanging.value) {
|
||||
exchangeCode()
|
||||
}
|
||||
}, retrySeconds * 1000)
|
||||
}
|
||||
return
|
||||
}
|
||||
throw new Error(response.message || '授权失败,请重试')
|
||||
}
|
||||
tokenInfo = response.data
|
||||
stopCountdown()
|
||||
if (pendingTimer) {
|
||||
clearTimeout(pendingTimer)
|
||||
pendingTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
emit('success', tokenInfo)
|
||||
@@ -700,4 +1009,12 @@ const exchangeCode = async () => {
|
||||
exchanging.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopCountdown()
|
||||
if (pendingTimer) {
|
||||
clearTimeout(pendingTimer)
|
||||
pendingTimer = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -11,6 +11,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
const openaiAccounts = ref([])
|
||||
const azureOpenaiAccounts = ref([])
|
||||
const openaiResponsesAccounts = ref([])
|
||||
const droidAccounts = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const sortBy = ref('')
|
||||
@@ -151,6 +152,25 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Droid账户列表
|
||||
const fetchDroidAccounts = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.get('/admin/droid-accounts')
|
||||
if (response.success) {
|
||||
droidAccounts.value = response.data || []
|
||||
} else {
|
||||
throw new Error(response.message || '获取Droid账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有账户
|
||||
const fetchAllAccounts = async () => {
|
||||
loading.value = true
|
||||
@@ -163,7 +183,8 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
fetchGeminiAccounts(),
|
||||
fetchOpenAIAccounts(),
|
||||
fetchAzureOpenAIAccounts(),
|
||||
fetchOpenAIResponsesAccounts()
|
||||
fetchOpenAIResponsesAccounts(),
|
||||
fetchDroidAccounts()
|
||||
])
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
@@ -273,6 +294,46 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Droid账户
|
||||
const createDroidAccount = async (data) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.post('/admin/droid-accounts', data)
|
||||
if (response.success) {
|
||||
await fetchDroidAccounts()
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '创建Droid账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Droid账户
|
||||
const updateDroidAccount = async (id, data) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.put(`/admin/droid-accounts/${id}`, data)
|
||||
if (response.success) {
|
||||
await fetchDroidAccounts()
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '更新Droid账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Azure OpenAI账户
|
||||
const createAzureOpenAIAccount = async (data) => {
|
||||
loading.value = true
|
||||
@@ -694,6 +755,22 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成Droid OAuth URL
|
||||
const generateDroidAuthUrl = async (proxyConfig) => {
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.post('/admin/droid-accounts/generate-auth-url', proxyConfig)
|
||||
if (response.success) {
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '生成授权URL失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 交换OpenAI OAuth Code
|
||||
const exchangeOpenAICode = async (data) => {
|
||||
try {
|
||||
@@ -709,6 +786,18 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 交换Droid OAuth Code
|
||||
const exchangeDroidCode = async (data) => {
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.post('/admin/droid-accounts/exchange-code', data)
|
||||
return response
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 排序账户
|
||||
const sortAccounts = (field) => {
|
||||
if (sortBy.value === field) {
|
||||
@@ -728,6 +817,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
openaiAccounts.value = []
|
||||
azureOpenaiAccounts.value = []
|
||||
openaiResponsesAccounts.value = []
|
||||
droidAccounts.value = []
|
||||
loading.value = false
|
||||
error.value = null
|
||||
sortBy.value = ''
|
||||
@@ -743,6 +833,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
openaiAccounts,
|
||||
azureOpenaiAccounts,
|
||||
openaiResponsesAccounts,
|
||||
droidAccounts,
|
||||
loading,
|
||||
error,
|
||||
sortBy,
|
||||
@@ -756,12 +847,15 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
fetchOpenAIAccounts,
|
||||
fetchAzureOpenAIAccounts,
|
||||
fetchOpenAIResponsesAccounts,
|
||||
fetchDroidAccounts,
|
||||
fetchAllAccounts,
|
||||
createClaudeAccount,
|
||||
createClaudeConsoleAccount,
|
||||
createBedrockAccount,
|
||||
createGeminiAccount,
|
||||
createOpenAIAccount,
|
||||
createDroidAccount,
|
||||
updateDroidAccount,
|
||||
createAzureOpenAIAccount,
|
||||
createOpenAIResponsesAccount,
|
||||
updateClaudeAccount,
|
||||
@@ -782,6 +876,8 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
exchangeGeminiCode,
|
||||
generateOpenAIAuthUrl,
|
||||
exchangeOpenAICode,
|
||||
generateDroidAuthUrl,
|
||||
exchangeDroidCode,
|
||||
sortAccounts,
|
||||
reset
|
||||
}
|
||||
|
||||
@@ -537,6 +537,17 @@
|
||||
<span class="mx-1 h-4 w-px bg-teal-300 dark:bg-teal-600" />
|
||||
<span class="text-xs font-medium text-teal-700 dark:text-teal-300">Relay</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="account.platform === 'droid'"
|
||||
class="flex items-center gap-1.5 rounded-lg border border-cyan-200 bg-gradient-to-r from-cyan-100 to-sky-100 px-2.5 py-1 dark:border-cyan-700 dark:from-cyan-900/20 dark:to-sky-900/20"
|
||||
>
|
||||
<i class="fas fa-robot text-xs text-cyan-700 dark:text-cyan-400" />
|
||||
<span class="text-xs font-semibold text-cyan-800 dark:text-cyan-300"
|
||||
>Droid</span
|
||||
>
|
||||
<span class="mx-1 h-4 w-px bg-cyan-300 dark:bg-cyan-600" />
|
||||
<span class="text-xs font-medium text-cyan-700 dark:text-cyan-300">OAuth</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center gap-1.5 rounded-lg border border-gray-200 bg-gradient-to-r from-gray-100 to-gray-200 px-2.5 py-1"
|
||||
@@ -646,7 +657,8 @@
|
||||
account.platform === 'openai' ||
|
||||
account.platform === 'openai-responses' ||
|
||||
account.platform === 'azure_openai' ||
|
||||
account.platform === 'ccr'
|
||||
account.platform === 'ccr' ||
|
||||
account.platform === 'droid'
|
||||
"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
@@ -1098,7 +1110,9 @@
|
||||
? 'bg-gradient-to-br from-gray-600 to-gray-700'
|
||||
: account.platform === 'ccr'
|
||||
? 'bg-gradient-to-br from-teal-500 to-emerald-600'
|
||||
: 'bg-gradient-to-br from-blue-500 to-blue-600'
|
||||
: account.platform === 'droid'
|
||||
? 'bg-gradient-to-br from-cyan-500 to-sky-600'
|
||||
: 'bg-gradient-to-br from-blue-500 to-blue-600'
|
||||
]"
|
||||
>
|
||||
<i
|
||||
@@ -1114,7 +1128,9 @@
|
||||
? 'fas fa-openai'
|
||||
: account.platform === 'ccr'
|
||||
? 'fas fa-code-branch'
|
||||
: 'fas fa-robot'
|
||||
: account.platform === 'droid'
|
||||
? 'fas fa-robot'
|
||||
: 'fas fa-robot'
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
@@ -1712,7 +1728,8 @@ const platformOptions = ref([
|
||||
{ value: 'azure_openai', label: 'Azure OpenAI', icon: 'fab fa-microsoft' },
|
||||
{ value: 'bedrock', label: 'Bedrock', icon: 'fab fa-aws' },
|
||||
{ value: 'openai-responses', label: 'OpenAI-Responses', icon: 'fa-server' },
|
||||
{ value: 'ccr', label: 'CCR', icon: 'fa-code-branch' }
|
||||
{ value: 'ccr', label: 'CCR', icon: 'fa-code-branch' },
|
||||
{ value: 'droid', label: 'Droid', icon: 'fa-robot' }
|
||||
])
|
||||
|
||||
const groupOptions = computed(() => {
|
||||
@@ -2034,7 +2051,8 @@ const loadAccounts = async (forceReload = false) => {
|
||||
apiClient.get('/admin/openai-accounts', { params }),
|
||||
apiClient.get('/admin/azure-openai-accounts', { params }),
|
||||
apiClient.get('/admin/openai-responses-accounts', { params }),
|
||||
apiClient.get('/admin/ccr-accounts', { params })
|
||||
apiClient.get('/admin/ccr-accounts', { params }),
|
||||
apiClient.get('/admin/droid-accounts', { params })
|
||||
)
|
||||
} else {
|
||||
// 只请求指定平台,其他平台设为null占位
|
||||
@@ -2047,7 +2065,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'claude-console':
|
||||
@@ -2058,7 +2078,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'bedrock':
|
||||
@@ -2069,7 +2091,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'gemini':
|
||||
@@ -2080,7 +2104,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
apiClient.get('/admin/gemini-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'openai':
|
||||
@@ -2091,7 +2117,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
apiClient.get('/admin/openai-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'azure_openai':
|
||||
@@ -2102,7 +2130,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
apiClient.get('/admin/azure-openai-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }) // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'openai-responses':
|
||||
@@ -2113,7 +2143,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
|
||||
apiClient.get('/admin/openai-responses-accounts', { params })
|
||||
apiClient.get('/admin/openai-responses-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'ccr':
|
||||
@@ -2124,7 +2156,22 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure 占位
|
||||
apiClient.get('/admin/ccr-accounts', { params })
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
apiClient.get('/admin/ccr-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }) // droid 占位
|
||||
)
|
||||
break
|
||||
case 'droid':
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }), // azure 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
|
||||
Promise.resolve({ success: true, data: [] }), // ccr 占位
|
||||
apiClient.get('/admin/droid-accounts', { params })
|
||||
)
|
||||
break
|
||||
default:
|
||||
@@ -2136,6 +2183,8 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] })
|
||||
)
|
||||
break
|
||||
@@ -2156,7 +2205,8 @@ const loadAccounts = async (forceReload = false) => {
|
||||
openaiData,
|
||||
azureOpenaiData,
|
||||
openaiResponsesData,
|
||||
ccrData
|
||||
ccrData,
|
||||
droidData
|
||||
] = await Promise.all(requests)
|
||||
|
||||
const allAccounts = []
|
||||
@@ -2250,6 +2300,15 @@ const loadAccounts = async (forceReload = false) => {
|
||||
allAccounts.push(...ccrAccounts)
|
||||
}
|
||||
|
||||
// Droid 账户
|
||||
if (droidData && droidData.success) {
|
||||
const droidAccounts = (droidData.data || []).map((acc) => {
|
||||
// Droid 不支持 API Key 绑定,固定为 0
|
||||
return { ...acc, platform: 'droid', boundApiKeysCount: 0 }
|
||||
})
|
||||
allAccounts.push(...droidAccounts)
|
||||
}
|
||||
|
||||
// 根据分组筛选器过滤账户
|
||||
let filteredAccounts = allAccounts
|
||||
if (groupFilter.value !== 'all') {
|
||||
@@ -2542,6 +2601,8 @@ const resolveAccountDeleteEndpoint = (account) => {
|
||||
return `/admin/ccr-accounts/${account.id}`
|
||||
case 'gemini':
|
||||
return `/admin/gemini-accounts/${account.id}`
|
||||
case 'droid':
|
||||
return `/admin/droid-accounts/${account.id}`
|
||||
default:
|
||||
return null
|
||||
}
|
||||
@@ -2720,6 +2781,8 @@ const resetAccountStatus = async (account) => {
|
||||
endpoint = `/admin/claude-console-accounts/${account.id}/reset-status`
|
||||
} else if (account.platform === 'ccr') {
|
||||
endpoint = `/admin/ccr-accounts/${account.id}/reset-status`
|
||||
} else if (account.platform === 'droid') {
|
||||
endpoint = `/admin/droid-accounts/${account.id}/reset-status`
|
||||
} else {
|
||||
showToast('不支持的账户类型', 'error')
|
||||
account.isResetting = false
|
||||
@@ -2766,6 +2829,8 @@ const toggleSchedulable = async (account) => {
|
||||
endpoint = `/admin/openai-responses-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'ccr') {
|
||||
endpoint = `/admin/ccr-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'droid') {
|
||||
endpoint = `/admin/droid-accounts/${account.id}/toggle-schedulable`
|
||||
} else {
|
||||
showToast('该账户类型暂不支持调度控制', 'warning')
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user