diff --git a/frontend/src/components/account/BulkEditAccountModal.vue b/frontend/src/components/account/BulkEditAccountModal.vue index 5cc9c2cc..2934fbd9 100644 --- a/frontend/src/components/account/BulkEditAccountModal.vue +++ b/frontend/src/components/account/BulkEditAccountModal.vue @@ -31,6 +31,57 @@

+ +
+
+
+ +

+ {{ t('admin.accounts.openai.oauthPassthroughDesc') }} +

+
+ +
+
+ +
+
+
@@ -89,100 +140,30 @@ role="group" aria-labelledby="bulk-edit-model-restriction-label" > - -
- - -
- - -
-
-

- - - - {{ t('admin.accounts.selectAllowedModels') }} -

-
- - - -

- {{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }} - {{ - t('admin.accounts.supportsAllModels') - }} +

+

+ {{ t('admin.accounts.openai.modelRestrictionDisabledByPassthrough') }}

- -
-
-

+

@@ -865,7 +927,6 @@ import { resolveOpenAIWSModeConcurrencyHintKey } from '@/utils/openaiWsMode' import type { OpenAIWSMode } from '@/utils/openaiWsMode' - interface Props { show: boolean accountIds: number[] @@ -887,6 +948,15 @@ const appStore = useAppStore() // Platform awareness const isMixedPlatform = computed(() => props.selectedPlatforms.length > 1) +const allOpenAIPassthroughCapable = computed(() => { + return ( + props.selectedPlatforms.length === 1 && + props.selectedPlatforms[0] === 'openai' && + props.selectedTypes.length > 0 && + props.selectedTypes.every(t => t === 'oauth' || t === 'apikey') + ) +}) + const allOpenAIOAuth = computed(() => { return ( props.selectedPlatforms.length === 1 && @@ -939,6 +1009,7 @@ const enablePriority = ref(false) const enableRateMultiplier = ref(false) const enableStatus = ref(false) const enableGroups = ref(false) +const enableOpenAIPassthrough = ref(false) const enableOpenAIWSMode = ref(false) const enableRpmLimit = ref(false) @@ -961,6 +1032,7 @@ const priority = ref(1) const rateMultiplier = ref(1) const status = ref<'active' | 'inactive'>('active') const groupIds = ref([]) +const openaiPassthroughEnabled = ref(false) const openaiOAuthResponsesWebSocketV2Mode = ref(OPENAI_WS_MODE_OFF) const rpmLimitEnabled = ref(false) const bulkBaseRpm = ref(null) @@ -988,6 +1060,13 @@ const statusOptions = computed(() => [ { value: 'active', label: t('common.active') }, { value: 'inactive', label: t('common.inactive') } ]) +const isOpenAIModelRestrictionDisabled = computed( + () => + allOpenAIPassthroughCapable.value && + enableOpenAIPassthrough.value && + openaiPassthroughEnabled.value +) + const openAIWSModeOptions = computed(() => [ { value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') }, { value: OPENAI_WS_MODE_PASSTHROUGH, label: t('admin.accounts.openai.wsModePassthrough') } @@ -1123,7 +1202,15 @@ const buildUpdatePayload = (): Record | null => { } } - if (enableModelRestriction.value) { + if (enableOpenAIPassthrough.value) { + const extra = ensureExtra() + extra.openai_passthrough = openaiPassthroughEnabled.value + if (!openaiPassthroughEnabled.value) { + extra.openai_oauth_passthrough = false + } + } + + if (enableModelRestriction.value && !isOpenAIModelRestrictionDisabled.value) { // 统一使用 model_mapping 字段 if (modelRestrictionMode.value === 'whitelist') { // 白名单模式:将模型转换为 model_mapping 格式(key=value) @@ -1243,6 +1330,7 @@ const handleSubmit = async () => { const hasAnyFieldEnabled = enableBaseUrl.value || + enableOpenAIPassthrough.value || enableModelRestriction.value || enableCustomErrorCodes.value || enableInterceptWarmup.value || @@ -1345,11 +1433,13 @@ watch( enableRateMultiplier.value = false enableStatus.value = false enableGroups.value = false + enableOpenAIPassthrough.value = false enableOpenAIWSMode.value = false enableRpmLimit.value = false // Reset all values baseUrl.value = '' + openaiPassthroughEnabled.value = false modelRestrictionMode.value = 'whitelist' allowedModels.value = [] modelMappings.value = [] diff --git a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts index fae75aa8..7390e723 100644 --- a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts +++ b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts @@ -130,6 +130,25 @@ describe('BulkEditAccountModal', () => { }) }) + it('OpenAI 账号批量编辑可开启自动透传', async () => { + const wrapper = mountModal({ + selectedPlatforms: ['openai'], + selectedTypes: ['oauth'] + }) + + await wrapper.get('#bulk-edit-openai-passthrough-enabled').setValue(true) + await wrapper.get('#bulk-edit-openai-passthrough-toggle').trigger('click') + await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent') + await flushPromises() + + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1) + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], { + extra: { + openai_passthrough: true + } + }) + }) + it('OpenAI OAuth 批量编辑应提交 OAuth 专属 WS mode 字段', async () => { const wrapper = mountModal({ selectedPlatforms: ['openai'], @@ -158,4 +177,44 @@ describe('BulkEditAccountModal', () => { expect(wrapper.find('#bulk-edit-openai-ws-mode-enabled').exists()).toBe(false) }) + + it('OpenAI 账号批量编辑可关闭自动透传', async () => { + const wrapper = mountModal({ + selectedPlatforms: ['openai'], + selectedTypes: ['apikey'] + }) + + await wrapper.get('#bulk-edit-openai-passthrough-enabled').setValue(true) + await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent') + await flushPromises() + + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1) + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], { + extra: { + openai_passthrough: false, + openai_oauth_passthrough: false + } + }) + }) + + it('开启 OpenAI 自动透传时不再同时提交模型限制', async () => { + const wrapper = mountModal({ + selectedPlatforms: ['openai'], + selectedTypes: ['oauth'] + }) + + await wrapper.get('#bulk-edit-openai-passthrough-enabled').setValue(true) + await wrapper.get('#bulk-edit-openai-passthrough-toggle').trigger('click') + await wrapper.get('#bulk-edit-model-restriction-enabled').setValue(true) + await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent') + await flushPromises() + + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1) + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], { + extra: { + openai_passthrough: true + } + }) + expect(wrapper.text()).toContain('admin.accounts.openai.modelRestrictionDisabledByPassthrough') + }) })