Merge pull request #1095 from LvyuanW/lvyuan/dev
fix(admin/accounts): reset edit modal state on reopen
This commit is contained in:
@@ -1980,271 +1980,281 @@ const normalizePoolModeRetryCount = (value: number) => {
|
|||||||
return normalized
|
return normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
const syncFormFromAccount = (newAccount: Account | null) => {
|
||||||
() => props.account,
|
if (!newAccount) {
|
||||||
(newAccount) => {
|
return
|
||||||
if (newAccount) {
|
}
|
||||||
antigravityMixedChannelConfirmed.value = false
|
antigravityMixedChannelConfirmed.value = false
|
||||||
showMixedChannelWarning.value = false
|
showMixedChannelWarning.value = false
|
||||||
mixedChannelWarningDetails.value = null
|
mixedChannelWarningDetails.value = null
|
||||||
mixedChannelWarningRawMessage.value = ''
|
mixedChannelWarningRawMessage.value = ''
|
||||||
mixedChannelWarningAction.value = null
|
mixedChannelWarningAction.value = null
|
||||||
form.name = newAccount.name
|
form.name = newAccount.name
|
||||||
form.notes = newAccount.notes || ''
|
form.notes = newAccount.notes || ''
|
||||||
form.proxy_id = newAccount.proxy_id
|
form.proxy_id = newAccount.proxy_id
|
||||||
form.concurrency = newAccount.concurrency
|
form.concurrency = newAccount.concurrency
|
||||||
form.load_factor = newAccount.load_factor ?? null
|
form.load_factor = newAccount.load_factor ?? null
|
||||||
form.priority = newAccount.priority
|
form.priority = newAccount.priority
|
||||||
form.rate_multiplier = newAccount.rate_multiplier ?? 1
|
form.rate_multiplier = newAccount.rate_multiplier ?? 1
|
||||||
form.status = (newAccount.status === 'active' || newAccount.status === 'inactive' || newAccount.status === 'error')
|
form.status = (newAccount.status === 'active' || newAccount.status === 'inactive' || newAccount.status === 'error')
|
||||||
? newAccount.status
|
? newAccount.status
|
||||||
: 'active'
|
: 'active'
|
||||||
form.group_ids = newAccount.group_ids || []
|
form.group_ids = newAccount.group_ids || []
|
||||||
form.expires_at = newAccount.expires_at ?? null
|
form.expires_at = newAccount.expires_at ?? null
|
||||||
|
|
||||||
// Load intercept warmup requests setting (applies to all account types)
|
// Load intercept warmup requests setting (applies to all account types)
|
||||||
const credentials = newAccount.credentials as Record<string, unknown> | undefined
|
const credentials = newAccount.credentials as Record<string, unknown> | undefined
|
||||||
interceptWarmupRequests.value = credentials?.intercept_warmup_requests === true
|
interceptWarmupRequests.value = credentials?.intercept_warmup_requests === true
|
||||||
autoPauseOnExpired.value = newAccount.auto_pause_on_expired === true
|
autoPauseOnExpired.value = newAccount.auto_pause_on_expired === true
|
||||||
|
|
||||||
// Load mixed scheduling setting (only for antigravity accounts)
|
// Load mixed scheduling setting (only for antigravity accounts)
|
||||||
mixedScheduling.value = false
|
mixedScheduling.value = false
|
||||||
allowOverages.value = false
|
allowOverages.value = false
|
||||||
const extra = newAccount.extra as Record<string, unknown> | undefined
|
const extra = newAccount.extra as Record<string, unknown> | undefined
|
||||||
mixedScheduling.value = extra?.mixed_scheduling === true
|
mixedScheduling.value = extra?.mixed_scheduling === true
|
||||||
allowOverages.value = extra?.allow_overages === true
|
allowOverages.value = extra?.allow_overages === true
|
||||||
|
|
||||||
// Load OpenAI passthrough toggle (OpenAI OAuth/API Key)
|
// Load OpenAI passthrough toggle (OpenAI OAuth/API Key)
|
||||||
openaiPassthroughEnabled.value = false
|
openaiPassthroughEnabled.value = false
|
||||||
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||||
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||||
codexCLIOnlyEnabled.value = false
|
codexCLIOnlyEnabled.value = false
|
||||||
anthropicPassthroughEnabled.value = false
|
anthropicPassthroughEnabled.value = false
|
||||||
if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'apikey')) {
|
if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'apikey')) {
|
||||||
openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true
|
openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true
|
||||||
openaiOAuthResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
|
openaiOAuthResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
|
||||||
modeKey: 'openai_oauth_responses_websockets_v2_mode',
|
modeKey: 'openai_oauth_responses_websockets_v2_mode',
|
||||||
enabledKey: 'openai_oauth_responses_websockets_v2_enabled',
|
enabledKey: 'openai_oauth_responses_websockets_v2_enabled',
|
||||||
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'],
|
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'],
|
||||||
defaultMode: OPENAI_WS_MODE_OFF
|
defaultMode: OPENAI_WS_MODE_OFF
|
||||||
})
|
})
|
||||||
openaiAPIKeyResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
|
openaiAPIKeyResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
|
||||||
modeKey: 'openai_apikey_responses_websockets_v2_mode',
|
modeKey: 'openai_apikey_responses_websockets_v2_mode',
|
||||||
enabledKey: 'openai_apikey_responses_websockets_v2_enabled',
|
enabledKey: 'openai_apikey_responses_websockets_v2_enabled',
|
||||||
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'],
|
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'],
|
||||||
defaultMode: OPENAI_WS_MODE_OFF
|
defaultMode: OPENAI_WS_MODE_OFF
|
||||||
})
|
})
|
||||||
if (newAccount.type === 'oauth') {
|
if (newAccount.type === 'oauth') {
|
||||||
codexCLIOnlyEnabled.value = extra?.codex_cli_only === true
|
codexCLIOnlyEnabled.value = extra?.codex_cli_only === true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newAccount.platform === 'anthropic' && newAccount.type === 'apikey') {
|
if (newAccount.platform === 'anthropic' && newAccount.type === 'apikey') {
|
||||||
anthropicPassthroughEnabled.value = extra?.anthropic_passthrough === true
|
anthropicPassthroughEnabled.value = extra?.anthropic_passthrough === true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above)
|
// Load quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above)
|
||||||
if (newAccount.type === 'apikey' || newAccount.type === 'bedrock') {
|
if (newAccount.type === 'apikey' || newAccount.type === 'bedrock') {
|
||||||
const quotaVal = extra?.quota_limit as number | undefined
|
const quotaVal = extra?.quota_limit as number | undefined
|
||||||
editQuotaLimit.value = (quotaVal && quotaVal > 0) ? quotaVal : null
|
editQuotaLimit.value = (quotaVal && quotaVal > 0) ? quotaVal : null
|
||||||
const dailyVal = extra?.quota_daily_limit as number | undefined
|
const dailyVal = extra?.quota_daily_limit as number | undefined
|
||||||
editQuotaDailyLimit.value = (dailyVal && dailyVal > 0) ? dailyVal : null
|
editQuotaDailyLimit.value = (dailyVal && dailyVal > 0) ? dailyVal : null
|
||||||
const weeklyVal = extra?.quota_weekly_limit as number | undefined
|
const weeklyVal = extra?.quota_weekly_limit as number | undefined
|
||||||
editQuotaWeeklyLimit.value = (weeklyVal && weeklyVal > 0) ? weeklyVal : null
|
editQuotaWeeklyLimit.value = (weeklyVal && weeklyVal > 0) ? weeklyVal : null
|
||||||
// Load quota reset mode config
|
// Load quota reset mode config
|
||||||
editDailyResetMode.value = (extra?.quota_daily_reset_mode as 'rolling' | 'fixed') || null
|
editDailyResetMode.value = (extra?.quota_daily_reset_mode as 'rolling' | 'fixed') || null
|
||||||
editDailyResetHour.value = (extra?.quota_daily_reset_hour as number) ?? null
|
editDailyResetHour.value = (extra?.quota_daily_reset_hour as number) ?? null
|
||||||
editWeeklyResetMode.value = (extra?.quota_weekly_reset_mode as 'rolling' | 'fixed') || null
|
editWeeklyResetMode.value = (extra?.quota_weekly_reset_mode as 'rolling' | 'fixed') || null
|
||||||
editWeeklyResetDay.value = (extra?.quota_weekly_reset_day as number) ?? null
|
editWeeklyResetDay.value = (extra?.quota_weekly_reset_day as number) ?? null
|
||||||
editWeeklyResetHour.value = (extra?.quota_weekly_reset_hour as number) ?? null
|
editWeeklyResetHour.value = (extra?.quota_weekly_reset_hour as number) ?? null
|
||||||
editResetTimezone.value = (extra?.quota_reset_timezone as string) || null
|
editResetTimezone.value = (extra?.quota_reset_timezone as string) || null
|
||||||
|
} else {
|
||||||
|
editQuotaLimit.value = null
|
||||||
|
editQuotaDailyLimit.value = null
|
||||||
|
editQuotaWeeklyLimit.value = null
|
||||||
|
editDailyResetMode.value = null
|
||||||
|
editDailyResetHour.value = null
|
||||||
|
editWeeklyResetMode.value = null
|
||||||
|
editWeeklyResetDay.value = null
|
||||||
|
editWeeklyResetHour.value = null
|
||||||
|
editResetTimezone.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load antigravity model mapping (Antigravity 只支持映射模式)
|
||||||
|
if (newAccount.platform === 'antigravity') {
|
||||||
|
const credentials = newAccount.credentials as Record<string, unknown> | undefined
|
||||||
|
|
||||||
|
// Antigravity 始终使用映射模式
|
||||||
|
antigravityModelRestrictionMode.value = 'mapping'
|
||||||
|
antigravityWhitelistModels.value = []
|
||||||
|
|
||||||
|
// 从 model_mapping 读取映射配置
|
||||||
|
const rawAgMapping = credentials?.model_mapping as Record<string, string> | undefined
|
||||||
|
if (rawAgMapping && typeof rawAgMapping === 'object') {
|
||||||
|
const entries = Object.entries(rawAgMapping)
|
||||||
|
// 无论是白名单样式(key===value)还是真正的映射,都统一转换为映射列表
|
||||||
|
antigravityModelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||||
|
} else {
|
||||||
|
// 兼容旧数据:从 model_whitelist 读取,转换为映射格式
|
||||||
|
const rawWhitelist = credentials?.model_whitelist
|
||||||
|
if (Array.isArray(rawWhitelist) && rawWhitelist.length > 0) {
|
||||||
|
antigravityModelMappings.value = rawWhitelist
|
||||||
|
.map((v) => String(v).trim())
|
||||||
|
.filter((v) => v.length > 0)
|
||||||
|
.map((m) => ({ from: m, to: m }))
|
||||||
} else {
|
} else {
|
||||||
editQuotaLimit.value = null
|
|
||||||
editQuotaDailyLimit.value = null
|
|
||||||
editQuotaWeeklyLimit.value = null
|
|
||||||
editDailyResetMode.value = null
|
|
||||||
editDailyResetHour.value = null
|
|
||||||
editWeeklyResetMode.value = null
|
|
||||||
editWeeklyResetDay.value = null
|
|
||||||
editWeeklyResetHour.value = null
|
|
||||||
editResetTimezone.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load antigravity model mapping (Antigravity 只支持映射模式)
|
|
||||||
if (newAccount.platform === 'antigravity') {
|
|
||||||
const credentials = newAccount.credentials as Record<string, unknown> | undefined
|
|
||||||
|
|
||||||
// Antigravity 始终使用映射模式
|
|
||||||
antigravityModelRestrictionMode.value = 'mapping'
|
|
||||||
antigravityWhitelistModels.value = []
|
|
||||||
|
|
||||||
// 从 model_mapping 读取映射配置
|
|
||||||
const rawAgMapping = credentials?.model_mapping as Record<string, string> | undefined
|
|
||||||
if (rawAgMapping && typeof rawAgMapping === 'object') {
|
|
||||||
const entries = Object.entries(rawAgMapping)
|
|
||||||
// 无论是白名单样式(key===value)还是真正的映射,都统一转换为映射列表
|
|
||||||
antigravityModelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
|
||||||
} else {
|
|
||||||
// 兼容旧数据:从 model_whitelist 读取,转换为映射格式
|
|
||||||
const rawWhitelist = credentials?.model_whitelist
|
|
||||||
if (Array.isArray(rawWhitelist) && rawWhitelist.length > 0) {
|
|
||||||
antigravityModelMappings.value = rawWhitelist
|
|
||||||
.map((v) => String(v).trim())
|
|
||||||
.filter((v) => v.length > 0)
|
|
||||||
.map((m) => ({ from: m, to: m }))
|
|
||||||
} else {
|
|
||||||
antigravityModelMappings.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
antigravityModelRestrictionMode.value = 'mapping'
|
|
||||||
antigravityWhitelistModels.value = []
|
|
||||||
antigravityModelMappings.value = []
|
antigravityModelMappings.value = []
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
antigravityModelRestrictionMode.value = 'mapping'
|
||||||
|
antigravityWhitelistModels.value = []
|
||||||
|
antigravityModelMappings.value = []
|
||||||
|
}
|
||||||
|
|
||||||
// Load quota control settings (Anthropic OAuth/SetupToken only)
|
// Load quota control settings (Anthropic OAuth/SetupToken only)
|
||||||
loadQuotaControlSettings(newAccount)
|
loadQuotaControlSettings(newAccount)
|
||||||
|
|
||||||
loadTempUnschedRules(credentials)
|
loadTempUnschedRules(credentials)
|
||||||
|
|
||||||
// Initialize API Key fields for apikey type
|
// Initialize API Key fields for apikey type
|
||||||
if (newAccount.type === 'apikey' && newAccount.credentials) {
|
if (newAccount.type === 'apikey' && newAccount.credentials) {
|
||||||
const credentials = newAccount.credentials as Record<string, unknown>
|
const credentials = newAccount.credentials as Record<string, unknown>
|
||||||
const platformDefaultUrl =
|
const platformDefaultUrl =
|
||||||
newAccount.platform === 'openai' || newAccount.platform === 'sora'
|
newAccount.platform === 'openai' || newAccount.platform === 'sora'
|
||||||
? 'https://api.openai.com'
|
? 'https://api.openai.com'
|
||||||
: newAccount.platform === 'gemini'
|
: newAccount.platform === 'gemini'
|
||||||
? 'https://generativelanguage.googleapis.com'
|
? 'https://generativelanguage.googleapis.com'
|
||||||
: 'https://api.anthropic.com'
|
: 'https://api.anthropic.com'
|
||||||
editBaseUrl.value = (credentials.base_url as string) || platformDefaultUrl
|
editBaseUrl.value = (credentials.base_url as string) || platformDefaultUrl
|
||||||
|
|
||||||
// Load model mappings and detect mode
|
// Load model mappings and detect mode
|
||||||
const existingMappings = credentials.model_mapping as Record<string, string> | undefined
|
const existingMappings = credentials.model_mapping as Record<string, string> | undefined
|
||||||
if (existingMappings && typeof existingMappings === 'object') {
|
if (existingMappings && typeof existingMappings === 'object') {
|
||||||
const entries = Object.entries(existingMappings)
|
const entries = Object.entries(existingMappings)
|
||||||
|
|
||||||
// Detect if this is whitelist mode (all from === to) or mapping mode
|
// Detect if this is whitelist mode (all from === to) or mapping mode
|
||||||
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
||||||
|
|
||||||
if (isWhitelistMode) {
|
if (isWhitelistMode) {
|
||||||
// Whitelist mode: populate allowedModels
|
// Whitelist mode: populate allowedModels
|
||||||
modelRestrictionMode.value = 'whitelist'
|
modelRestrictionMode.value = 'whitelist'
|
||||||
allowedModels.value = entries.map(([from]) => from)
|
allowedModels.value = entries.map(([from]) => from)
|
||||||
modelMappings.value = []
|
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 = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load pool mode
|
|
||||||
poolModeEnabled.value = credentials.pool_mode === true
|
|
||||||
poolModeRetryCount.value = normalizePoolModeRetryCount(
|
|
||||||
Number(credentials.pool_mode_retry_count ?? DEFAULT_POOL_MODE_RETRY_COUNT)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Load custom error codes
|
|
||||||
customErrorCodesEnabled.value = credentials.custom_error_codes_enabled === true
|
|
||||||
const existingErrorCodes = credentials.custom_error_codes as number[] | undefined
|
|
||||||
if (existingErrorCodes && Array.isArray(existingErrorCodes)) {
|
|
||||||
selectedErrorCodes.value = [...existingErrorCodes]
|
|
||||||
} else {
|
|
||||||
selectedErrorCodes.value = []
|
|
||||||
}
|
|
||||||
} else if (newAccount.type === 'bedrock' && newAccount.credentials) {
|
|
||||||
const bedrockCreds = newAccount.credentials as Record<string, unknown>
|
|
||||||
const authMode = (bedrockCreds.auth_mode as string) || 'sigv4'
|
|
||||||
editBedrockRegion.value = (bedrockCreds.aws_region as string) || ''
|
|
||||||
editBedrockForceGlobal.value = (bedrockCreds.aws_force_global as string) === 'true'
|
|
||||||
|
|
||||||
if (authMode === 'apikey') {
|
|
||||||
editBedrockApiKeyValue.value = ''
|
|
||||||
} else {
|
|
||||||
editBedrockAccessKeyId.value = (bedrockCreds.aws_access_key_id as string) || ''
|
|
||||||
editBedrockSecretAccessKey.value = ''
|
|
||||||
editBedrockSessionToken.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load pool mode for bedrock
|
|
||||||
poolModeEnabled.value = bedrockCreds.pool_mode === true
|
|
||||||
const retryCount = bedrockCreds.pool_mode_retry_count
|
|
||||||
poolModeRetryCount.value = (typeof retryCount === 'number' && retryCount >= 0) ? retryCount : DEFAULT_POOL_MODE_RETRY_COUNT
|
|
||||||
|
|
||||||
// Load quota limits for bedrock
|
|
||||||
const bedrockExtra = (newAccount.extra as Record<string, unknown>) || {}
|
|
||||||
editQuotaLimit.value = typeof bedrockExtra.quota_limit === 'number' ? bedrockExtra.quota_limit : null
|
|
||||||
editQuotaDailyLimit.value = typeof bedrockExtra.quota_daily_limit === 'number' ? bedrockExtra.quota_daily_limit : null
|
|
||||||
editQuotaWeeklyLimit.value = typeof bedrockExtra.quota_weekly_limit === 'number' ? bedrockExtra.quota_weekly_limit : null
|
|
||||||
|
|
||||||
// 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 = []
|
|
||||||
}
|
|
||||||
} else if (newAccount.type === 'upstream' && newAccount.credentials) {
|
|
||||||
const credentials = newAccount.credentials as Record<string, unknown>
|
|
||||||
editBaseUrl.value = (credentials.base_url as string) || ''
|
|
||||||
} else {
|
} else {
|
||||||
const platformDefaultUrl =
|
// Mapping mode: populate modelMappings
|
||||||
newAccount.platform === 'openai' || newAccount.platform === 'sora'
|
modelRestrictionMode.value = 'mapping'
|
||||||
? 'https://api.openai.com'
|
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||||
: newAccount.platform === 'gemini'
|
allowedModels.value = []
|
||||||
? 'https://generativelanguage.googleapis.com'
|
}
|
||||||
: 'https://api.anthropic.com'
|
} else {
|
||||||
editBaseUrl.value = platformDefaultUrl
|
// No mappings: default to whitelist mode with empty selection (allow all)
|
||||||
|
modelRestrictionMode.value = 'whitelist'
|
||||||
|
modelMappings.value = []
|
||||||
|
allowedModels.value = []
|
||||||
|
}
|
||||||
|
|
||||||
// Load model mappings for OpenAI OAuth accounts
|
// Load pool mode
|
||||||
if (newAccount.platform === 'openai' && newAccount.credentials) {
|
poolModeEnabled.value = credentials.pool_mode === true
|
||||||
const oauthCredentials = newAccount.credentials as Record<string, unknown>
|
poolModeRetryCount.value = normalizePoolModeRetryCount(
|
||||||
const existingMappings = oauthCredentials.model_mapping as Record<string, string> | undefined
|
Number(credentials.pool_mode_retry_count ?? DEFAULT_POOL_MODE_RETRY_COUNT)
|
||||||
if (existingMappings && typeof existingMappings === 'object') {
|
)
|
||||||
const entries = Object.entries(existingMappings)
|
|
||||||
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
|
// Load custom error codes
|
||||||
if (isWhitelistMode) {
|
customErrorCodesEnabled.value = credentials.custom_error_codes_enabled === true
|
||||||
modelRestrictionMode.value = 'whitelist'
|
const existingErrorCodes = credentials.custom_error_codes as number[] | undefined
|
||||||
allowedModels.value = entries.map(([from]) => from)
|
if (existingErrorCodes && Array.isArray(existingErrorCodes)) {
|
||||||
modelMappings.value = []
|
selectedErrorCodes.value = [...existingErrorCodes]
|
||||||
} else {
|
} else {
|
||||||
modelRestrictionMode.value = 'mapping'
|
selectedErrorCodes.value = []
|
||||||
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
}
|
||||||
allowedModels.value = []
|
} else if (newAccount.type === 'bedrock' && newAccount.credentials) {
|
||||||
}
|
const bedrockCreds = newAccount.credentials as Record<string, unknown>
|
||||||
} else {
|
const authMode = (bedrockCreds.auth_mode as string) || 'sigv4'
|
||||||
modelRestrictionMode.value = 'whitelist'
|
editBedrockRegion.value = (bedrockCreds.aws_region as string) || ''
|
||||||
modelMappings.value = []
|
editBedrockForceGlobal.value = (bedrockCreds.aws_force_global as string) === 'true'
|
||||||
allowedModels.value = []
|
|
||||||
}
|
if (authMode === 'apikey') {
|
||||||
} else {
|
editBedrockApiKeyValue.value = ''
|
||||||
|
} else {
|
||||||
|
editBedrockAccessKeyId.value = (bedrockCreds.aws_access_key_id as string) || ''
|
||||||
|
editBedrockSecretAccessKey.value = ''
|
||||||
|
editBedrockSessionToken.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load pool mode for bedrock
|
||||||
|
poolModeEnabled.value = bedrockCreds.pool_mode === true
|
||||||
|
const retryCount = bedrockCreds.pool_mode_retry_count
|
||||||
|
poolModeRetryCount.value = (typeof retryCount === 'number' && retryCount >= 0) ? retryCount : DEFAULT_POOL_MODE_RETRY_COUNT
|
||||||
|
|
||||||
|
// Load quota limits for bedrock
|
||||||
|
const bedrockExtra = (newAccount.extra as Record<string, unknown>) || {}
|
||||||
|
editQuotaLimit.value = typeof bedrockExtra.quota_limit === 'number' ? bedrockExtra.quota_limit : null
|
||||||
|
editQuotaDailyLimit.value = typeof bedrockExtra.quota_daily_limit === 'number' ? bedrockExtra.quota_daily_limit : null
|
||||||
|
editQuotaWeeklyLimit.value = typeof bedrockExtra.quota_weekly_limit === 'number' ? bedrockExtra.quota_weekly_limit : null
|
||||||
|
|
||||||
|
// 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 = []
|
||||||
|
}
|
||||||
|
} else if (newAccount.type === 'upstream' && newAccount.credentials) {
|
||||||
|
const credentials = newAccount.credentials as Record<string, unknown>
|
||||||
|
editBaseUrl.value = (credentials.base_url as string) || ''
|
||||||
|
} else {
|
||||||
|
const platformDefaultUrl =
|
||||||
|
newAccount.platform === 'openai' || newAccount.platform === 'sora'
|
||||||
|
? 'https://api.openai.com'
|
||||||
|
: newAccount.platform === 'gemini'
|
||||||
|
? 'https://generativelanguage.googleapis.com'
|
||||||
|
: 'https://api.anthropic.com'
|
||||||
|
editBaseUrl.value = platformDefaultUrl
|
||||||
|
|
||||||
|
// 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'
|
modelRestrictionMode.value = 'whitelist'
|
||||||
|
allowedModels.value = entries.map(([from]) => from)
|
||||||
modelMappings.value = []
|
modelMappings.value = []
|
||||||
|
} else {
|
||||||
|
modelRestrictionMode.value = 'mapping'
|
||||||
|
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
|
||||||
allowedModels.value = []
|
allowedModels.value = []
|
||||||
}
|
}
|
||||||
poolModeEnabled.value = false
|
} else {
|
||||||
poolModeRetryCount.value = DEFAULT_POOL_MODE_RETRY_COUNT
|
modelRestrictionMode.value = 'whitelist'
|
||||||
customErrorCodesEnabled.value = false
|
modelMappings.value = []
|
||||||
selectedErrorCodes.value = []
|
allowedModels.value = []
|
||||||
}
|
}
|
||||||
editApiKey.value = ''
|
} else {
|
||||||
|
modelRestrictionMode.value = 'whitelist'
|
||||||
|
modelMappings.value = []
|
||||||
|
allowedModels.value = []
|
||||||
|
}
|
||||||
|
poolModeEnabled.value = false
|
||||||
|
poolModeRetryCount.value = DEFAULT_POOL_MODE_RETRY_COUNT
|
||||||
|
customErrorCodesEnabled.value = false
|
||||||
|
selectedErrorCodes.value = []
|
||||||
|
}
|
||||||
|
editApiKey.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.show, () => props.account],
|
||||||
|
([show, newAccount], [wasShow, previousAccount]) => {
|
||||||
|
if (!show || !newAccount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!wasShow || newAccount !== previousAccount) {
|
||||||
|
syncFormFromAccount(newAccount)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
|
||||||
|
const { updateAccountMock, checkMixedChannelRiskMock } = vi.hoisted(() => ({
|
||||||
|
updateAccountMock: vi.fn(),
|
||||||
|
checkMixedChannelRiskMock: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/stores/app', () => ({
|
||||||
|
useAppStore: () => ({
|
||||||
|
showError: vi.fn(),
|
||||||
|
showSuccess: vi.fn(),
|
||||||
|
showInfo: vi.fn()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/stores/auth', () => ({
|
||||||
|
useAuthStore: () => ({
|
||||||
|
isSimpleMode: true
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/api/admin', () => ({
|
||||||
|
adminAPI: {
|
||||||
|
accounts: {
|
||||||
|
update: updateAccountMock,
|
||||||
|
checkMixedChannelRisk: checkMixedChannelRiskMock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/api/admin/accounts', () => ({
|
||||||
|
getAntigravityDefaultModelMapping: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('vue-i18n', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useI18n: () => ({
|
||||||
|
t: (key: string) => key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
import EditAccountModal from '../EditAccountModal.vue'
|
||||||
|
|
||||||
|
const BaseDialogStub = defineComponent({
|
||||||
|
name: 'BaseDialog',
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: '<div v-if="show"><slot /><slot name="footer" /></div>'
|
||||||
|
})
|
||||||
|
|
||||||
|
const ModelWhitelistSelectorStub = defineComponent({
|
||||||
|
name: 'ModelWhitelistSelector',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="rewrite-to-snapshot"
|
||||||
|
@click="$emit('update:modelValue', ['gpt-5.2-2025-12-11'])"
|
||||||
|
>
|
||||||
|
rewrite
|
||||||
|
</button>
|
||||||
|
<span data-testid="model-whitelist-value">
|
||||||
|
{{ Array.isArray(modelValue) ? modelValue.join(',') : '' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
function buildAccount() {
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
name: 'OpenAI Key',
|
||||||
|
notes: '',
|
||||||
|
platform: 'openai',
|
||||||
|
type: 'apikey',
|
||||||
|
credentials: {
|
||||||
|
api_key: 'sk-test',
|
||||||
|
base_url: 'https://api.openai.com',
|
||||||
|
model_mapping: {
|
||||||
|
'gpt-5.2': 'gpt-5.2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extra: {},
|
||||||
|
proxy_id: null,
|
||||||
|
concurrency: 1,
|
||||||
|
priority: 1,
|
||||||
|
rate_multiplier: 1,
|
||||||
|
status: 'active',
|
||||||
|
group_ids: [],
|
||||||
|
expires_at: null,
|
||||||
|
auto_pause_on_expired: false
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountModal(account = buildAccount()) {
|
||||||
|
return mount(EditAccountModal, {
|
||||||
|
props: {
|
||||||
|
show: true,
|
||||||
|
account,
|
||||||
|
proxies: [],
|
||||||
|
groups: []
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
BaseDialog: BaseDialogStub,
|
||||||
|
Select: true,
|
||||||
|
Icon: true,
|
||||||
|
ProxySelector: true,
|
||||||
|
GroupSelector: true,
|
||||||
|
ModelWhitelistSelector: ModelWhitelistSelectorStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('EditAccountModal', () => {
|
||||||
|
it('reopening the same account rehydrates the OpenAI whitelist from props', async () => {
|
||||||
|
const account = buildAccount()
|
||||||
|
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')
|
||||||
|
expect(wrapper.get('[data-testid="model-whitelist-value"]').text()).toBe('gpt-5.2-2025-12-11')
|
||||||
|
|
||||||
|
await wrapper.setProps({ show: false })
|
||||||
|
await wrapper.setProps({ show: true })
|
||||||
|
|
||||||
|
expect(wrapper.get('[data-testid="model-whitelist-value"]').text()).toBe('gpt-5.2')
|
||||||
|
|
||||||
|
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': 'gpt-5.2'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user