feat: 新增支持codex转发

This commit is contained in:
shaw
2025-12-22 22:58:31 +08:00
parent dacf3a2a6e
commit 6c469b42ed
46 changed files with 3749 additions and 477 deletions

View File

@@ -47,83 +47,161 @@
/>
</div>
<!-- Platform Selection - Segmented Control Style -->
<div>
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
<div class="grid grid-cols-2 gap-3 mt-2">
<label
<label class="input-label">{{ t('admin.accounts.platform') }}</label>
<div class="flex rounded-lg bg-gray-100 dark:bg-dark-700 p-1 mt-2">
<button
type="button"
@click="form.platform = 'anthropic'"
:class="[
'relative flex cursor-pointer rounded-lg border-2 p-4 transition-all',
accountCategory === 'oauth-based'
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200 dark:border-dark-600 hover:border-primary-300'
'flex-1 flex items-center justify-center gap-2 rounded-md px-4 py-2.5 text-sm font-medium transition-all',
form.platform === 'anthropic'
? 'bg-white dark:bg-dark-600 text-orange-600 dark:text-orange-400 shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
]"
>
<input
v-model="accountCategory"
type="radio"
value="oauth-based"
class="sr-only"
/>
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500 to-orange-600">
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z" />
</svg>
</div>
<div>
<span class="block text-sm font-semibold text-gray-900 dark:text-white">{{ t('admin.accounts.claudeCode') }}</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.oauthSetupToken') }}</span>
</div>
</div>
<div
v-if="accountCategory === 'oauth-based'"
class="absolute right-2 top-2 flex h-5 w-5 items-center justify-center rounded-full bg-primary-500"
>
<svg class="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
</div>
</label>
<label
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z" />
</svg>
Anthropic
</button>
<button
type="button"
@click="form.platform = 'openai'"
:class="[
'relative flex cursor-pointer rounded-lg border-2 p-4 transition-all',
accountCategory === 'apikey'
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200 dark:border-dark-600 hover:border-primary-300'
'flex-1 flex items-center justify-center gap-2 rounded-md px-4 py-2.5 text-sm font-medium transition-all',
form.platform === 'openai'
? 'bg-white dark:bg-dark-600 text-green-600 dark:text-green-400 shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
]"
>
<input
v-model="accountCategory"
type="radio"
value="apikey"
class="sr-only"
/>
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-purple-500 to-purple-600">
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
</svg>
</div>
<div>
<span class="block text-sm font-semibold text-gray-900 dark:text-white">{{ t('admin.accounts.claudeConsole') }}</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.apiKey') }}</span>
</div>
</div>
<div
v-if="accountCategory === 'apikey'"
class="absolute right-2 top-2 flex h-5 w-5 items-center justify-center rounded-full bg-primary-500"
>
<svg class="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
</div>
</label>
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
</svg>
OpenAI
</button>
</div>
</div>
<!-- Add Method (only for OAuth-based type) -->
<div v-if="isOAuthFlow">
<!-- Account Type Selection (Anthropic) -->
<div v-if="form.platform === 'anthropic'">
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
<div class="grid grid-cols-2 gap-3 mt-2">
<button
type="button"
@click="accountCategory = 'oauth-based'"
:class="[
'flex items-center gap-3 rounded-lg border-2 p-3 transition-all text-left',
accountCategory === 'oauth-based'
? 'border-orange-500 bg-orange-50 dark:bg-orange-900/20'
: 'border-gray-200 dark:border-dark-600 hover:border-orange-300 dark:hover:border-orange-700'
]"
>
<div :class="[
'flex h-8 w-8 items-center justify-center rounded-lg',
accountCategory === 'oauth-based'
? 'bg-orange-500 text-white'
: 'bg-gray-100 dark:bg-dark-600 text-gray-500 dark:text-gray-400'
]">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z" />
</svg>
</div>
<div>
<span class="block text-sm font-medium text-gray-900 dark:text-white">{{ t('admin.accounts.claudeCode') }}</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.oauthSetupToken') }}</span>
</div>
</button>
<button
type="button"
@click="accountCategory = 'apikey'"
:class="[
'flex items-center gap-3 rounded-lg border-2 p-3 transition-all text-left',
accountCategory === 'apikey'
? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20'
: 'border-gray-200 dark:border-dark-600 hover:border-purple-300 dark:hover:border-purple-700'
]"
>
<div :class="[
'flex h-8 w-8 items-center justify-center rounded-lg',
accountCategory === 'apikey'
? 'bg-purple-500 text-white'
: 'bg-gray-100 dark:bg-dark-600 text-gray-500 dark:text-gray-400'
]">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
</svg>
</div>
<div>
<span class="block text-sm font-medium text-gray-900 dark:text-white">{{ t('admin.accounts.claudeConsole') }}</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('admin.accounts.apiKey') }}</span>
</div>
</button>
</div>
</div>
<!-- Account Type Selection (OpenAI) -->
<div v-if="form.platform === 'openai'">
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
<div class="grid grid-cols-2 gap-3 mt-2">
<button
type="button"
@click="accountCategory = 'oauth-based'"
:class="[
'flex items-center gap-3 rounded-lg border-2 p-3 transition-all text-left',
accountCategory === 'oauth-based'
? 'border-green-500 bg-green-50 dark:bg-green-900/20'
: 'border-gray-200 dark:border-dark-600 hover:border-green-300 dark:hover:border-green-700'
]"
>
<div :class="[
'flex h-8 w-8 items-center justify-center rounded-lg',
accountCategory === 'oauth-based'
? 'bg-green-500 text-white'
: 'bg-gray-100 dark:bg-dark-600 text-gray-500 dark:text-gray-400'
]">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
</svg>
</div>
<div>
<span class="block text-sm font-medium text-gray-900 dark:text-white">OAuth</span>
<span class="text-xs text-gray-500 dark:text-gray-400">ChatGPT Plus</span>
</div>
</button>
<button
type="button"
@click="accountCategory = 'apikey'"
:class="[
'flex items-center gap-3 rounded-lg border-2 p-3 transition-all text-left',
accountCategory === 'apikey'
? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20'
: 'border-gray-200 dark:border-dark-600 hover:border-purple-300 dark:hover:border-purple-700'
]"
>
<div :class="[
'flex h-8 w-8 items-center justify-center rounded-lg',
accountCategory === 'apikey'
? 'bg-purple-500 text-white'
: 'bg-gray-100 dark:bg-dark-600 text-gray-500 dark:text-gray-400'
]">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
</svg>
</div>
<div>
<span class="block text-sm font-medium text-gray-900 dark:text-white">API Key</span>
<span class="text-xs text-gray-500 dark:text-gray-400">Responses API</span>
</div>
</button>
</div>
</div>
<!-- Add Method (only for Anthropic OAuth-based type) -->
<div v-if="form.platform === 'anthropic' && isOAuthFlow">
<label class="input-label">{{ t('admin.accounts.addMethod') }}</label>
<div class="flex gap-4 mt-2">
<label class="flex cursor-pointer items-center">
@@ -155,7 +233,7 @@
v-model="apiKeyBaseUrl"
type="text"
class="input"
placeholder="https://api.anthropic.com"
:placeholder="form.platform === 'openai' ? 'https://api.openai.com' : 'https://api.anthropic.com'"
/>
<p class="input-hint">{{ t('admin.accounts.baseUrlHint') }}</p>
</div>
@@ -166,7 +244,7 @@
type="password"
required
class="input font-mono"
:placeholder="t('admin.accounts.apiKeyPlaceholder')"
:placeholder="form.platform === 'openai' ? 'sk-proj-...' : 'sk-ant-...'"
/>
<p class="input-hint">{{ t('admin.accounts.apiKeyHint') }}</p>
</div>
@@ -418,8 +496,8 @@
</div>
</div>
<!-- Intercept Warmup Requests (all account types) -->
<div class="border-t border-gray-200 dark:border-dark-600 pt-4">
<!-- Intercept Warmup Requests (Anthropic only) -->
<div v-if="form.platform === 'anthropic'" class="border-t border-gray-200 dark:border-dark-600 pt-4">
<div class="flex items-center justify-between">
<div>
<label class="input-label mb-0">{{ t('admin.accounts.interceptWarmupRequests') }}</label>
@@ -477,6 +555,7 @@
<GroupSelector
v-model="form.group_ids"
:groups="groups"
:platform="form.platform"
/>
<div class="flex justify-end gap-3 pt-4">
@@ -510,14 +589,16 @@
<div v-else class="space-y-5">
<OAuthAuthorizationFlow
ref="oauthFlowRef"
:add-method="addMethod"
:auth-url="oauth.authUrl.value"
:session-id="oauth.sessionId.value"
:loading="oauth.loading.value"
:error="oauth.error.value"
:show-help="true"
:add-method="form.platform === 'openai' ? 'oauth' : addMethod"
:auth-url="currentAuthUrl"
:session-id="currentSessionId"
:loading="currentOAuthLoading"
:error="currentOAuthError"
:show-help="form.platform !== 'openai'"
:show-proxy-warning="!!form.proxy_id"
:allow-multiple="true"
:allow-multiple="form.platform !== 'openai'"
:show-cookie-option="form.platform !== 'openai'"
:platform="form.platform"
@generate-url="handleGenerateUrl"
@cookie-auth="handleCookieAuth"
/>
@@ -538,7 +619,7 @@
@click="handleExchangeCode"
>
<svg
v-if="oauth.loading.value"
v-if="currentOAuthLoading"
class="animate-spin -ml-1 mr-2 h-4 w-4"
fill="none"
viewBox="0 0 24 24"
@@ -546,7 +627,7 @@
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{{ oauth.loading.value ? t('admin.accounts.oauth.verifying') : t('admin.accounts.oauth.completeAuth') }}
{{ currentOAuthLoading ? t('admin.accounts.oauth.verifying') : t('admin.accounts.oauth.completeAuth') }}
</button>
</div>
</div>
@@ -559,6 +640,7 @@ import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
import { useAccountOAuth, type AddMethod, type AuthInputMethod } from '@/composables/useAccountOAuth'
import { useOpenAIOAuth } from '@/composables/useOpenAIOAuth'
import type { Proxy, Group, AccountPlatform, AccountType } from '@/types'
import Modal from '@/components/common/Modal.vue'
import ProxySelector from '@/components/common/ProxySelector.vue'
@@ -590,8 +672,26 @@ const emit = defineEmits<{
const appStore = useAppStore()
// OAuth composable
const oauth = useAccountOAuth()
// OAuth composables
const oauth = useAccountOAuth() // For Anthropic OAuth
const openaiOAuth = useOpenAIOAuth() // For OpenAI OAuth
// Computed: current OAuth state for template binding
const currentAuthUrl = computed(() => {
return form.platform === 'openai' ? openaiOAuth.authUrl.value : oauth.authUrl.value
})
const currentSessionId = computed(() => {
return form.platform === 'openai' ? openaiOAuth.sessionId.value : oauth.sessionId.value
})
const currentOAuthLoading = computed(() => {
return form.platform === 'openai' ? openaiOAuth.loading.value : oauth.loading.value
})
const currentOAuthError = computed(() => {
return form.platform === 'openai' ? openaiOAuth.error.value : oauth.error.value
})
// Refs
const oauthFlowRef = ref<OAuthFlowExposed | null>(null)
@@ -617,8 +717,8 @@ const selectedErrorCodes = ref<number[]>([])
const customErrorCodeInput = ref<number | null>(null)
const interceptWarmupRequests = ref(false)
// Common models for whitelist
const commonModels = [
// Common models for whitelist - Anthropic
const anthropicModels = [
{ value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5' },
{ value: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' },
{ value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5' },
@@ -629,8 +729,24 @@ const commonModels = [
{ value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku' }
]
// Preset mappings for quick add
const presetMappings = [
// Common models for whitelist - OpenAI
const openaiModels = [
{ value: 'gpt-5.2-2025-12-11', label: 'GPT-5.2' },
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
{ value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
{ value: 'gpt-5.1-2025-11-13', label: 'GPT-5.1' },
{ value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
{ value: 'gpt-5-2025-08-07', label: 'GPT-5' }
]
// Computed: current models based on platform
const commonModels = computed(() => {
return form.platform === 'openai' ? openaiModels : anthropicModels
})
// Preset mappings for quick add - Anthropic
const anthropicPresetMappings = [
{ label: 'Sonnet 4', from: 'claude-sonnet-4-20250514', to: 'claude-sonnet-4-20250514', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4-5-20250929', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
{ label: 'Opus 4.5', from: 'claude-opus-4-5-20251101', to: 'claude-opus-4-5-20251101', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
@@ -639,6 +755,21 @@ const presetMappings = [
{ label: 'Opus->Sonnet', from: 'claude-opus-4-5-20251101', to: 'claude-sonnet-4-5-20250929', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' }
]
// Preset mappings for quick add - OpenAI
const openaiPresetMappings = [
{ label: 'GPT-5.2', from: 'gpt-5.2-2025-12-11', to: 'gpt-5.2-2025-12-11', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' },
{ label: 'GPT-5.2 Codex', from: 'gpt-5.2-codex', to: 'gpt-5.2-codex', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
{ label: 'GPT-5.1 Codex', from: 'gpt-5.1-codex', to: 'gpt-5.1-codex', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
{ label: 'Codex Max', from: 'gpt-5.1-codex-max', to: 'gpt-5.1-codex-max', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
{ label: 'Codex Mini', from: 'gpt-5.1-codex-mini', to: 'gpt-5.1-codex-mini', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
{ label: 'Max->Codex', from: 'gpt-5.1-codex-max', to: 'gpt-5.1-codex', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' }
]
// Computed: current preset mappings based on platform
const presetMappings = computed(() => {
return form.platform === 'openai' ? openaiPresetMappings : anthropicPresetMappings
})
// Common HTTP error codes for quick selection
const commonErrorCodes = [
{ value: 401, label: 'Unauthorized' },
@@ -670,6 +801,9 @@ const isManualInputMethod = computed(() => {
const canExchangeCode = computed(() => {
const authCode = oauthFlowRef.value?.authCode || ''
if (form.platform === 'openai') {
return authCode.trim() && openaiOAuth.sessionId.value && !openaiOAuth.loading.value
}
return authCode.trim() && oauth.sessionId.value && !oauth.loading.value
})
@@ -689,6 +823,20 @@ watch([accountCategory, addMethod], ([category, method]) => {
}
}, { immediate: true })
// Reset platform-specific settings when platform changes
watch(() => form.platform, (newPlatform) => {
// Reset base URL based on platform
apiKeyBaseUrl.value = newPlatform === 'openai'
? 'https://api.openai.com'
: 'https://api.anthropic.com'
// Clear model-related settings
allowedModels.value = []
modelMappings.value = []
// Reset OAuth states
oauth.resetState()
openaiOAuth.resetState()
})
// Model mapping helpers
const addModelMapping = () => {
modelMappings.value.push({ from: '', to: '' })
@@ -786,6 +934,7 @@ const resetForm = () => {
customErrorCodeInput.value = null
interceptWarmupRequests.value = false
oauth.resetState()
openaiOAuth.resetState()
oauthFlowRef.value?.reset()
}
@@ -810,9 +959,14 @@ const handleSubmit = async () => {
return
}
// Determine default base URL based on platform
const defaultBaseUrl = form.platform === 'openai'
? 'https://api.openai.com'
: 'https://api.anthropic.com'
// Build credentials with optional model mapping
const credentials: Record<string, unknown> = {
base_url: apiKeyBaseUrl.value.trim() || 'https://api.anthropic.com',
base_url: apiKeyBaseUrl.value.trim() || defaultBaseUrl,
api_key: apiKeyValue.value.trim()
}
@@ -837,7 +991,10 @@ const handleSubmit = async () => {
submitting.value = true
try {
await adminAPI.accounts.create(form)
await adminAPI.accounts.create({
...form,
group_ids: form.group_ids
})
appStore.showSuccess(t('admin.accounts.accountCreated'))
emit('created')
handleClose()
@@ -851,15 +1008,72 @@ const handleSubmit = async () => {
const goBackToBasicInfo = () => {
step.value = 1
oauth.resetState()
openaiOAuth.resetState()
oauthFlowRef.value?.reset()
}
const handleGenerateUrl = async () => {
await oauth.generateAuthUrl(addMethod.value, form.proxy_id)
if (form.platform === 'openai') {
await openaiOAuth.generateAuthUrl(form.proxy_id)
} else {
await oauth.generateAuthUrl(addMethod.value, form.proxy_id)
}
}
const handleExchangeCode = async () => {
const authCode = oauthFlowRef.value?.authCode || ''
// For OpenAI
if (form.platform === 'openai') {
if (!authCode.trim() || !openaiOAuth.sessionId.value) return
openaiOAuth.loading.value = true
openaiOAuth.error.value = ''
try {
const tokenInfo = await openaiOAuth.exchangeAuthCode(
authCode.trim(),
openaiOAuth.sessionId.value,
form.proxy_id
)
if (!tokenInfo) {
return // Error already handled by composable
}
const credentials = openaiOAuth.buildCredentials(tokenInfo)
const extra = openaiOAuth.buildExtraInfo(tokenInfo)
// Merge interceptWarmupRequests into credentials
if (interceptWarmupRequests.value) {
credentials.intercept_warmup_requests = true
}
await adminAPI.accounts.create({
name: form.name,
platform: 'openai',
type: 'oauth',
credentials,
extra,
proxy_id: form.proxy_id,
concurrency: form.concurrency,
priority: form.priority,
group_ids: form.group_ids
})
appStore.showSuccess(t('admin.accounts.accountCreated'))
emit('created')
handleClose()
} catch (error: any) {
openaiOAuth.error.value = error.response?.data?.detail || t('admin.accounts.oauth.authFailed')
appStore.showError(openaiOAuth.error.value)
} finally {
openaiOAuth.loading.value = false
}
return
}
// For Anthropic
if (!authCode.trim() || !oauth.sessionId.value) return
oauth.loading.value = true
@@ -893,7 +1107,8 @@ const handleExchangeCode = async () => {
extra,
proxy_id: form.proxy_id,
concurrency: form.concurrency,
priority: form.priority
priority: form.priority,
group_ids: form.group_ids
})
appStore.showSuccess(t('admin.accounts.accountCreated'))