diff --git a/frontend/src/components/account/BulkEditAccountModal.vue b/frontend/src/components/account/BulkEditAccountModal.vue index 05016a6d..c8d53220 100644 --- a/frontend/src/components/account/BulkEditAccountModal.vue +++ b/frontend/src/components/account/BulkEditAccountModal.vue @@ -779,6 +779,110 @@ + +
+
+
+ +

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

+
+ +
+
+ +
+
+
+
+ + + + +
+
+ +
+
+
@@ -989,7 +1093,7 @@ import { ref, watch, computed } from 'vue' import { useI18n } from 'vue-i18n' import { useAppStore } from '@/stores/app' import { adminAPI } from '@/api/admin' -import type { Proxy as ProxyConfig, AdminGroup, AccountPlatform, AccountType } from '@/types' +import type { Proxy as ProxyConfig, AdminGroup, AccountPlatform, AccountType, OpenAICompactMode } from '@/types' import BaseDialog from '@/components/common/BaseDialog.vue' import ConfirmDialog from '@/components/common/ConfirmDialog.vue' import Select from '@/components/common/Select.vue' @@ -1115,6 +1219,8 @@ const enableOpenAIPassthrough = ref(false) const enableOpenAIWSMode = ref(false) const enableOpenAIAPIKeyWSMode = ref(false) const enableCodexCLIOnly = ref(false) +const enableOpenAICompactMode = ref(false) +const enableOpenAICompactModelMapping = ref(false) const enableRpmLimit = ref(false) // State - field values @@ -1140,6 +1246,8 @@ const openaiPassthroughEnabled = ref(false) const openaiOAuthResponsesWebSocketV2Mode = ref(OPENAI_WS_MODE_OFF) const openaiAPIKeyResponsesWebSocketV2Mode = ref(OPENAI_WS_MODE_OFF) const codexCLIOnlyEnabled = ref(false) +const openAICompactMode = ref('auto') +const openAICompactModelMappings = ref([]) const rpmLimitEnabled = ref(false) const bulkBaseRpm = ref(null) const bulkRpmStrategy = ref<'tiered' | 'sticky_exempt'>('tiered') @@ -1178,6 +1286,11 @@ const openAIWSModeOptions = computed(() => [ { value: OPENAI_WS_MODE_CTX_POOL, label: t('admin.accounts.openai.wsModeCtxPool') }, { value: OPENAI_WS_MODE_PASSTHROUGH, label: t('admin.accounts.openai.wsModePassthrough') } ]) +const openAICompactModeOptions = computed(() => [ + { value: 'auto', label: t('admin.accounts.openai.compactModeAuto') }, + { value: 'force_on', label: t('admin.accounts.openai.compactModeForceOn') }, + { value: 'force_off', label: t('admin.accounts.openai.compactModeForceOff') } +]) const openAIWSModeConcurrencyHintKey = computed(() => resolveOpenAIWSModeConcurrencyHintKey(openaiOAuthResponsesWebSocketV2Mode.value) ) @@ -1194,6 +1307,14 @@ const removeModelMapping = (index: number) => { modelMappings.value.splice(index, 1) } +const addOpenAICompactModelMapping = () => { + openAICompactModelMappings.value.push({ from: '', to: '' }) +} + +const removeOpenAICompactModelMapping = (index: number) => { + openAICompactModelMappings.value.splice(index, 1) +} + const addPresetMapping = (from: string, to: string) => { const exists = modelMappings.value.some((m) => m.from === from) if (exists) { @@ -1262,6 +1383,10 @@ const buildModelMappingObject = (): Record | null => { ) } +const buildOpenAICompactModelMapping = (): Record | null => { + return buildModelMappingPayload('mapping', [], openAICompactModelMappings.value) +} + const buildUpdatePayload = (): Record | null => { const updates: Record = {} const credentials: Record = {} @@ -1350,10 +1475,6 @@ const buildUpdatePayload = (): Record | null => { credentialsChanged = true } - if (credentialsChanged) { - updates.credentials = credentials - } - if (enableOpenAIWSMode.value) { const extra = ensureExtra() extra.openai_oauth_responses_websockets_v2_mode = openaiOAuthResponsesWebSocketV2Mode.value @@ -1375,6 +1496,16 @@ const buildUpdatePayload = (): Record | null => { extra.codex_cli_only = codexCLIOnlyEnabled.value } + if (enableOpenAICompactMode.value) { + const extra = ensureExtra() + extra.openai_compact_mode = openAICompactMode.value + } + + if (enableOpenAICompactModelMapping.value) { + credentials.compact_model_mapping = buildOpenAICompactModelMapping() ?? {} + credentialsChanged = true + } + // RPM limit settings (写入 extra 字段) if (enableRpmLimit.value) { const extra = ensureExtra() @@ -1402,6 +1533,10 @@ const buildUpdatePayload = (): Record | null => { umqExtra.user_msg_queue_enabled = false // 清理旧字段(JSONB merge) } + if (credentialsChanged) { + updates.credentials = credentials + } + return Object.keys(updates).length > 0 ? updates : null } @@ -1467,6 +1602,8 @@ const handleSubmit = async () => { enableOpenAIWSMode.value || enableOpenAIAPIKeyWSMode.value || enableCodexCLIOnly.value || + enableOpenAICompactMode.value || + enableOpenAICompactModelMapping.value || enableRpmLimit.value || userMsgQueueMode.value !== null @@ -1567,6 +1704,8 @@ watch( enableOpenAIWSMode.value = false enableOpenAIAPIKeyWSMode.value = false enableCodexCLIOnly.value = false + enableOpenAICompactMode.value = false + enableOpenAICompactModelMapping.value = false enableRpmLimit.value = false // Reset all values @@ -1588,6 +1727,8 @@ watch( openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF codexCLIOnlyEnabled.value = false + openAICompactMode.value = 'auto' + openAICompactModelMappings.value = [] rpmLimitEnabled.value = false bulkBaseRpm.value = null bulkRpmStrategy.value = 'tiered' diff --git a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts index 50d170da..caa307fc 100644 --- a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts +++ b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts @@ -217,6 +217,44 @@ describe('BulkEditAccountModal', () => { }) }) + it('筛选 OpenAI 账号批量编辑应提交 Compact 模式和专属模型映射', async () => { + const wrapper = mountModal({ + accountIds: [], + selectedPlatforms: [], + selectedTypes: [], + target: { + mode: 'filtered', + filters: { platform: 'openai' }, + previewCount: 12, + selectedPlatforms: ['openai'], + selectedTypes: ['oauth', 'apikey'] + } + }) + + await wrapper.get('#bulk-edit-openai-compact-mode-enabled').setValue(true) + await wrapper.get('[data-testid="bulk-edit-openai-compact-mode-select"]').setValue('force_on') + await wrapper.get('#bulk-edit-openai-compact-model-mapping-enabled').setValue(true) + await wrapper.get('[data-testid="bulk-edit-openai-compact-model-mapping-add"]').trigger('click') + const inputs = wrapper.findAll('[data-testid="bulk-edit-openai-compact-model-mapping-input"]') + await inputs[0].setValue('gpt-5.4') + await inputs[1].setValue('gpt-5.4-openai-compact') + await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent') + await flushPromises() + + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1) + expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith({ + filters: { platform: 'openai' }, + extra: { + openai_compact_mode: 'force_on' + }, + credentials: { + compact_model_mapping: { + 'gpt-5.4': 'gpt-5.4-openai-compact' + } + } + }) + }) + it('OpenAI 账号批量编辑可关闭自动透传', async () => { const wrapper = mountModal({ selectedPlatforms: ['openai'],