Skip to content

Commit 4015f31

Browse files
authored
Merge pull request Wei-Shaw#1157 from LvyuanW/fix-bulk-model-restriction-empty
fix: allow clearing model restriction in bulk edit when whitelist is empty
2 parents 9dccbe1 + 1de18b8 commit 4015f31

2 files changed

Lines changed: 65 additions & 26 deletions

File tree

frontend/src/components/account/BulkEditAccountModal.vue

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,24 +1056,21 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
10561056
}
10571057
10581058
if (enableModelRestriction.value) {
1059-
const modelMapping = buildModelMappingObject()
1060-
10611059
// 统一使用 model_mapping 字段
10621060
if (modelRestrictionMode.value === 'whitelist') {
1063-
if (allowedModels.value.length > 0) {
1064-
// 白名单模式:将模型转换为 model_mapping 格式(key=value)
1065-
const mapping: Record<string, string> = {}
1066-
for (const m of allowedModels.value) {
1067-
mapping[m] = m
1068-
}
1069-
credentials.model_mapping = mapping
1070-
credentialsChanged = true
1061+
// 白名单模式:将模型转换为 model_mapping 格式(key=value)
1062+
// 空白名单表示“支持所有模型”,需显式发送空对象以覆盖已有限制。
1063+
const mapping: Record<string, string> = {}
1064+
for (const m of allowedModels.value) {
1065+
mapping[m] = m
10711066
}
1067+
credentials.model_mapping = mapping
1068+
credentialsChanged = true
10721069
} else {
1073-
if (modelMapping) {
1074-
credentials.model_mapping = modelMapping
1075-
credentialsChanged = true
1076-
}
1070+
// 映射模式下空配置同样表示“支持所有模型”。
1071+
const modelMapping = buildModelMappingObject()
1072+
credentials.model_mapping = modelMapping ?? {}
1073+
credentialsChanged = true
10771074
}
10781075
}
10791076

frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { describe, expect, it, vi } from 'vitest'
2-
import { mount } from '@vue/test-utils'
1+
import { describe, expect, it, vi, beforeEach } from 'vitest'
2+
import { flushPromises, mount } from '@vue/test-utils'
33
import BulkEditAccountModal from '../BulkEditAccountModal.vue'
4+
import ModelWhitelistSelector from '../ModelWhitelistSelector.vue'
5+
import { adminAPI } from '@/api/admin'
46

57
vi.mock('@/stores/app', () => ({
68
useAppStore: () => ({
@@ -13,7 +15,8 @@ vi.mock('@/stores/app', () => ({
1315
vi.mock('@/api/admin', () => ({
1416
adminAPI: {
1517
accounts: {
16-
bulkEdit: vi.fn()
18+
bulkUpdate: vi.fn(),
19+
checkMixedChannelRisk: vi.fn()
1720
}
1821
}
1922
}))
@@ -32,18 +35,21 @@ vi.mock('vue-i18n', async () => {
3235
}
3336
})
3437

35-
function mountModal() {
38+
function mountModal(extraProps: Record<string, unknown> = {}) {
3639
return mount(BulkEditAccountModal, {
3740
props: {
3841
show: true,
3942
accountIds: [1, 2],
4043
selectedPlatforms: ['antigravity'],
44+
selectedTypes: ['apikey'],
4145
proxies: [],
42-
groups: []
46+
groups: [],
47+
...extraProps
4348
} as any,
4449
global: {
4550
stubs: {
4651
BaseDialog: { template: '<div><slot /><slot name="footer" /></div>' },
52+
ConfirmDialog: true,
4753
Select: true,
4854
ProxySelector: true,
4955
GroupSelector: true,
@@ -54,12 +60,30 @@ function mountModal() {
5460
}
5561

5662
describe('BulkEditAccountModal', () => {
57-
it('antigravity 白名单包含 Gemini 图片模型且过滤掉普通 GPT 模型', () => {
63+
beforeEach(() => {
64+
vi.mocked(adminAPI.accounts.bulkUpdate).mockReset()
65+
vi.mocked(adminAPI.accounts.checkMixedChannelRisk).mockReset()
66+
67+
vi.mocked(adminAPI.accounts.bulkUpdate).mockResolvedValue({
68+
success: 2,
69+
failed: 0,
70+
results: []
71+
} as any)
72+
vi.mocked(adminAPI.accounts.checkMixedChannelRisk).mockResolvedValue({
73+
has_risk: false
74+
} as any)
75+
})
76+
77+
it('antigravity 白名单包含 Gemini 图片模型且过滤掉普通 GPT 模型', async () => {
5878
const wrapper = mountModal()
79+
const selector = wrapper.findComponent(ModelWhitelistSelector)
80+
expect(selector.exists()).toBe(true)
5981

60-
expect(wrapper.text()).toContain('Gemini 3.1 Flash Image')
61-
expect(wrapper.text()).toContain('Gemini 3 Pro Image (Legacy)')
62-
expect(wrapper.text()).not.toContain('GPT-5.3 Codex')
82+
await selector.find('div.cursor-pointer').trigger('click')
83+
84+
expect(wrapper.text()).toContain('gemini-3.1-flash-image')
85+
expect(wrapper.text()).toContain('gemini-2.5-flash-image')
86+
expect(wrapper.text()).not.toContain('gpt-5.3-codex')
6387
})
6488

6589
it('antigravity 映射预设包含图片映射并过滤 OpenAI 预设', async () => {
@@ -69,8 +93,26 @@ describe('BulkEditAccountModal', () => {
6993
expect(mappingTab).toBeTruthy()
7094
await mappingTab!.trigger('click')
7195

72-
expect(wrapper.text()).toContain('Gemini 3.1 Image')
73-
expect(wrapper.text()).toContain('G3 Image→3.1')
74-
expect(wrapper.text()).not.toContain('GPT-5.3 Codex')
96+
expect(wrapper.text()).toContain('3.1-Flash-Image透传')
97+
expect(wrapper.text()).toContain('3-Pro-Image→3.1')
98+
expect(wrapper.text()).not.toContain('GPT-5.3 Codex Spark')
99+
})
100+
101+
it('仅勾选模型限制且白名单留空时,应提交空 model_mapping 以支持所有模型', async () => {
102+
const wrapper = mountModal({
103+
selectedPlatforms: ['anthropic'],
104+
selectedTypes: ['apikey']
105+
})
106+
107+
await wrapper.get('#bulk-edit-model-restriction-enabled').setValue(true)
108+
await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent')
109+
await flushPromises()
110+
111+
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1)
112+
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], {
113+
credentials: {
114+
model_mapping: {}
115+
}
116+
})
75117
})
76118
})

0 commit comments

Comments
 (0)