mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-28 16:09:34 +00:00
Merge pull request #2454 from wucm667/codex/issue-2426-model-mapping
fix(account): 保留模型白名单和模型映射组合配置
This commit is contained in:
@@ -142,7 +142,7 @@
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="account?.platform || 'anthropic'" :account-id="account?.id" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
<span v-if="allowedModels.length === 0 && modelMappings.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
</p>
|
||||
@@ -457,7 +457,7 @@
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="account?.platform || 'anthropic'" :account-id="account?.id" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
<span v-if="allowedModels.length === 0 && modelMappings.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
</p>
|
||||
@@ -669,7 +669,7 @@
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="account?.platform || 'anthropic'" :account-id="account?.id" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
<span v-if="allowedModels.length === 0 && modelMappings.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
</p>
|
||||
@@ -891,7 +891,7 @@
|
||||
<ModelWhitelistSelector v-model="allowedModels" platform="anthropic" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{ t('admin.accounts.supportsAllModels') }}</span>
|
||||
<span v-if="allowedModels.length === 0 && modelMappings.length === 0">{{ t('admin.accounts.supportsAllModels') }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2244,6 +2244,7 @@ import {
|
||||
getPresetMappingsByPlatform,
|
||||
commonErrorCodes,
|
||||
buildModelMappingObject,
|
||||
splitModelMappingObject,
|
||||
isValidWildcardPattern
|
||||
} from '@/composables/useModelWhitelist'
|
||||
|
||||
@@ -2607,6 +2608,19 @@ const normalizePoolModeRetryCount = (value: number) => {
|
||||
return normalized
|
||||
}
|
||||
|
||||
const loadModelRestrictionFromMapping = (rawMapping?: Record<string, unknown>) => {
|
||||
const parsed = splitModelMappingObject(rawMapping)
|
||||
allowedModels.value = parsed.allowedModels
|
||||
modelMappings.value = parsed.modelMappings
|
||||
modelRestrictionMode.value =
|
||||
parsed.modelMappings.length > 0 && parsed.allowedModels.length === 0
|
||||
? 'mapping'
|
||||
: 'whitelist'
|
||||
}
|
||||
|
||||
const buildModelRestrictionMapping = () =>
|
||||
buildModelMappingObject('combined', allowedModels.value, modelMappings.value)
|
||||
|
||||
const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
if (!newAccount) {
|
||||
return
|
||||
@@ -2782,30 +2796,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
editBaseUrl.value = (credentials.base_url as string) || platformDefaultUrl
|
||||
|
||||
// Load model mappings and detect mode
|
||||
const existingMappings = credentials.model_mapping as Record<string, string> | undefined
|
||||
if (existingMappings && typeof existingMappings === 'object') {
|
||||
const entries = Object.entries(existingMappings)
|
||||
|
||||
// Detect if this is whitelist mode (all from === to) or mapping mode
|
||||
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
||||
|
||||
if (isWhitelistMode) {
|
||||
// Whitelist mode: populate allowedModels
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
allowedModels.value = entries.map(([from]) => from)
|
||||
modelMappings.value = []
|
||||
} else {
|
||||
// Mapping mode: populate modelMappings
|
||||
modelRestrictionMode.value = 'mapping'
|
||||
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||
allowedModels.value = []
|
||||
}
|
||||
} else {
|
||||
// No mappings: default to whitelist mode with empty selection (allow all)
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
modelMappings.value = []
|
||||
allowedModels.value = []
|
||||
}
|
||||
loadModelRestrictionFromMapping(credentials.model_mapping as Record<string, unknown> | undefined)
|
||||
|
||||
// Load pool mode
|
||||
poolModeEnabled.value = credentials.pool_mode === true
|
||||
@@ -2849,24 +2840,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
loadQuotaNotifyFromExtra(bedrockExtra)
|
||||
|
||||
// Load model mappings for bedrock
|
||||
const existingMappings = bedrockCreds.model_mapping as Record<string, string> | undefined
|
||||
if (existingMappings && typeof existingMappings === 'object') {
|
||||
const entries = Object.entries(existingMappings)
|
||||
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
||||
if (isWhitelistMode) {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
allowedModels.value = entries.map(([from]) => from)
|
||||
modelMappings.value = []
|
||||
} else {
|
||||
modelRestrictionMode.value = 'mapping'
|
||||
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||
allowedModels.value = []
|
||||
}
|
||||
} else {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
modelMappings.value = []
|
||||
allowedModels.value = []
|
||||
}
|
||||
loadModelRestrictionFromMapping(bedrockCreds.model_mapping as Record<string, unknown> | undefined)
|
||||
} else if (newAccount.type === 'upstream' && newAccount.credentials) {
|
||||
const credentials = newAccount.credentials as Record<string, unknown>
|
||||
editBaseUrl.value = (credentials.base_url as string) || ''
|
||||
@@ -2877,24 +2851,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
editVertexLocation.value = (credentials.location as string) || (credentials.vertex_location as string) || 'us-central1'
|
||||
|
||||
// Load model mappings for service_account
|
||||
const existingMappings = credentials.model_mapping as Record<string, string> | undefined
|
||||
if (existingMappings && typeof existingMappings === 'object') {
|
||||
const entries = Object.entries(existingMappings)
|
||||
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
||||
if (isWhitelistMode) {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
allowedModels.value = entries.map(([from]) => from)
|
||||
modelMappings.value = []
|
||||
} else {
|
||||
modelRestrictionMode.value = 'mapping'
|
||||
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||
allowedModels.value = []
|
||||
}
|
||||
} else {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
modelMappings.value = []
|
||||
allowedModels.value = []
|
||||
}
|
||||
loadModelRestrictionFromMapping(credentials.model_mapping as Record<string, unknown> | undefined)
|
||||
} else {
|
||||
const platformDefaultUrl =
|
||||
newAccount.platform === 'openai'
|
||||
@@ -2907,24 +2864,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
// Load model mappings for OpenAI OAuth accounts
|
||||
if (newAccount.platform === 'openai' && newAccount.credentials) {
|
||||
const oauthCredentials = newAccount.credentials as Record<string, unknown>
|
||||
const existingMappings = oauthCredentials.model_mapping as Record<string, string> | undefined
|
||||
if (existingMappings && typeof existingMappings === 'object') {
|
||||
const entries = Object.entries(existingMappings)
|
||||
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
||||
if (isWhitelistMode) {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
allowedModels.value = entries.map(([from]) => from)
|
||||
modelMappings.value = []
|
||||
} else {
|
||||
modelRestrictionMode.value = 'mapping'
|
||||
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||
allowedModels.value = []
|
||||
}
|
||||
} else {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
modelMappings.value = []
|
||||
allowedModels.value = []
|
||||
}
|
||||
loadModelRestrictionFromMapping(oauthCredentials.model_mapping as Record<string, unknown> | undefined)
|
||||
} else {
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
modelMappings.value = []
|
||||
@@ -3459,7 +3399,7 @@ const handleSubmit = async () => {
|
||||
|
||||
// Add model mapping if configured(OpenAI 开启自动透传时保留现有映射,不再编辑)
|
||||
if (shouldApplyModelMapping) {
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
const modelMapping = buildModelRestrictionMapping()
|
||||
if (modelMapping) {
|
||||
newCredentials.model_mapping = modelMapping
|
||||
} else {
|
||||
@@ -3547,7 +3487,7 @@ const handleSubmit = async () => {
|
||||
newCredentials.tier_id = 'vertex'
|
||||
|
||||
// Add model mapping if configured
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
const modelMapping = buildModelRestrictionMapping()
|
||||
if (modelMapping) {
|
||||
newCredentials.model_mapping = modelMapping
|
||||
} else {
|
||||
@@ -3597,7 +3537,7 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
// Model mapping
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
const modelMapping = buildModelRestrictionMapping()
|
||||
if (modelMapping) {
|
||||
newCredentials.model_mapping = modelMapping
|
||||
} else {
|
||||
@@ -3631,7 +3571,7 @@ const handleSubmit = async () => {
|
||||
const shouldApplyModelMapping = !openaiPassthroughEnabled.value
|
||||
|
||||
if (shouldApplyModelMapping) {
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
const modelMapping = buildModelRestrictionMapping()
|
||||
if (modelMapping) {
|
||||
newCredentials.model_mapping = modelMapping
|
||||
} else {
|
||||
|
||||
@@ -190,6 +190,31 @@ describe('EditAccountModal', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves model mappings when editing the whitelist', async () => {
|
||||
const account = buildAccount()
|
||||
account.credentials.model_mapping = {
|
||||
'gpt-5.2': 'gpt-5.2',
|
||||
'gpt-latest': 'gpt-5.2'
|
||||
}
|
||||
updateAccountMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
|
||||
updateAccountMock.mockResolvedValue(account)
|
||||
|
||||
const wrapper = mountModal(account)
|
||||
|
||||
expect(wrapper.get('[data-testid="model-whitelist-value"]').text()).toBe('gpt-5.2')
|
||||
|
||||
await wrapper.get('[data-testid="rewrite-to-snapshot"]').trigger('click')
|
||||
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
|
||||
|
||||
expect(updateAccountMock).toHaveBeenCalledTimes(1)
|
||||
expect(updateAccountMock.mock.calls[0]?.[1]?.credentials?.model_mapping).toEqual({
|
||||
'gpt-5.2-2025-12-11': 'gpt-5.2-2025-12-11',
|
||||
'gpt-latest': 'gpt-5.2'
|
||||
})
|
||||
})
|
||||
|
||||
it('submits OpenAI compact mode and compact-only model mapping', async () => {
|
||||
const account = buildAccount()
|
||||
account.extra = {
|
||||
|
||||
Reference in New Issue
Block a user