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 @@
+
+
@@ -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'],