Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
21f349c0
Unverified
Commit
21f349c0
authored
Mar 18, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 18, 2026
Browse files
Merge pull request #1095 from LvyuanW/lvyuan/dev
fix(admin/accounts): reset edit modal state on reopen
parents
28e36f79
0772d925
Changes
2
Hide whitespace changes
Inline
Side-by-side
frontend/src/components/account/EditAccountModal.vue
View file @
21f349c0
...
...
@@ -1980,271 +1980,281 @@ const normalizePoolModeRetryCount = (value: number) => {
return
normalized
}
watch
(
()
=>
props
.
a
ccount
,
(
newAccount
)
=>
{
if
(
newAccount
)
{
antigravityMixedChannelConfirmed
.
value
=
false
showMixedChannelWarning
.
value
=
false
mixedChannelWarningDetails
.
value
=
null
mixedChannelWarningRawMessage
.
value
=
''
mixedChannelWarningAction
.
value
=
null
form
.
name
=
newAccount
.
name
form
.
notes
=
newAccount
.
notes
||
''
form
.
proxy_id
=
newAccount
.
proxy_id
form
.
concurrency
=
newAccount
.
concurrency
form
.
load_factor
=
newAccount
.
load_factor
??
null
form
.
priority
=
newAccount
.
priority
form
.
rate_multiplier
=
newAccount
.
rate_multiplier
??
1
form
.
status
=
(
newAccount
.
status
===
'
active
'
||
newAccount
.
status
===
'
inactive
'
||
newAccount
.
status
===
'
error
'
)
?
newAccount
.
status
:
'
active
'
form
.
group_ids
=
newAccount
.
group_ids
||
[]
form
.
expires_at
=
newAccount
.
expires_at
??
null
// Load intercept warmup requests setting (applies to all account types)
const
credentials
=
newAccount
.
credentials
as
Record
<
string
,
unknown
>
|
undefined
interceptWarmupRequests
.
value
=
credentials
?.
intercept_warmup_requests
===
true
autoPauseOnExpired
.
value
=
newAccount
.
auto_pause_on_expired
===
true
// Load mixed scheduling setting (only for antigravity accounts)
mixedScheduling
.
value
=
false
allowOverages
.
value
=
false
const
extra
=
newAccount
.
extra
as
Record
<
string
,
unknown
>
|
undefined
mixedScheduling
.
value
=
extra
?.
mixed_scheduling
===
true
allowOverages
.
value
=
extra
?.
allow_overages
===
true
// Load OpenAI passthrough toggle (OpenAI OAuth/API Key)
openaiPassthroughEnabled
.
value
=
false
openaiOAuthResponsesWebSocketV2Mode
.
value
=
OPENAI_WS_MODE_OFF
openaiAPIKeyResponsesWebSocketV2Mode
.
value
=
OPENAI_WS_MODE_OFF
codexCLIOnlyEnabled
.
value
=
false
anthropicPassthroughEnabled
.
value
=
false
if
(
newAccount
.
platform
===
'
openai
'
&&
(
newAccount
.
type
===
'
oauth
'
||
newAccount
.
type
===
'
apikey
'
))
{
openaiPassthroughEnabled
.
value
=
extra
?.
openai_passthrough
===
true
||
extra
?.
openai_oauth_passthrough
===
true
openaiOAuthResponsesWebSocketV2Mode
.
value
=
resolveOpenAIWSModeFromExtra
(
extra
,
{
modeKey
:
'
openai_oauth_responses_websockets_v2_mode
'
,
enabledKey
:
'
openai_oauth_responses_websockets_v2_enabled
'
,
fallbackEnabledKeys
:
[
'
responses_websockets_v2_enabled
'
,
'
openai_ws_enabled
'
],
defaultMode
:
OPENAI_WS_MODE_OFF
}
)
openaiAPIKeyResponsesWebSocketV2Mode
.
value
=
resolveOpenAIWSModeFromExtra
(
extra
,
{
modeKey
:
'
openai_apikey_responses_websockets_v2_mode
'
,
enabledKey
:
'
openai_apikey_responses_websockets_v2_enabled
'
,
fallbackEnabledKeys
:
[
'
responses_websockets_v2_enabled
'
,
'
openai_ws_enabled
'
],
defaultMode
:
OPENAI_WS_MODE_OFF
}
)
if
(
newAccount
.
type
===
'
oauth
'
)
{
codexCLIOnlyEnabled
.
value
=
extra
?.
codex_cli_only
===
true
}
}
if
(
newAccount
.
platform
===
'
anthropic
'
&&
newAccount
.
type
===
'
apikey
'
)
{
anthropicPassthroughEnabled
.
value
=
extra
?.
anthropic_passthrough
===
true
}
const
syncFormFromAccount
=
(
newAccount
:
Account
|
null
)
=>
{
if
(
!
newA
ccount
)
{
return
}
antigravityMixedChannelConfirmed
.
value
=
false
showMixedChannelWarning
.
value
=
false
mixedChannelWarningDetails
.
value
=
null
mixedChannelWarningRawMessage
.
value
=
''
mixedChannelWarningAction
.
value
=
null
form
.
name
=
newAccount
.
name
form
.
notes
=
newAccount
.
notes
||
''
form
.
proxy_id
=
newAccount
.
proxy_id
form
.
concurrency
=
newAccount
.
concurrency
form
.
load_factor
=
newAccount
.
load_factor
??
null
form
.
priority
=
newAccount
.
priority
form
.
rate_multiplier
=
newAccount
.
rate_multiplier
??
1
form
.
status
=
(
newAccount
.
status
===
'
active
'
||
newAccount
.
status
===
'
inactive
'
||
newAccount
.
status
===
'
error
'
)
?
newAccount
.
status
:
'
active
'
form
.
group_ids
=
newAccount
.
group_ids
||
[]
form
.
expires_at
=
newAccount
.
expires_at
??
null
// Load intercept warmup requests setting (applies to all account types)
const
credentials
=
newAccount
.
credentials
as
Record
<
string
,
unknown
>
|
undefined
interceptWarmupRequests
.
value
=
credentials
?.
intercept_warmup_requests
===
true
autoPauseOnExpired
.
value
=
newAccount
.
auto_pause_on_expired
===
true
// Load mixed scheduling setting (only for antigravity accounts)
mixedScheduling
.
value
=
false
allowOverages
.
value
=
false
const
extra
=
newAccount
.
extra
as
Record
<
string
,
unknown
>
|
undefined
mixedScheduling
.
value
=
extra
?.
mixed_scheduling
===
true
allowOverages
.
value
=
extra
?.
allow_overages
===
true
// Load OpenAI passthrough toggle (OpenAI OAuth/API Key)
openaiPassthroughEnabled
.
value
=
false
openaiOAuthResponsesWebSocketV2Mode
.
value
=
OPENAI_WS_MODE_OFF
openaiAPIKeyResponsesWebSocketV2Mode
.
value
=
OPENAI_WS_MODE_OFF
codexCLIOnlyEnabled
.
value
=
false
anthropicPassthroughEnabled
.
value
=
false
if
(
newAccount
.
platform
===
'
openai
'
&&
(
newAccount
.
type
===
'
oauth
'
||
newAccount
.
type
===
'
apikey
'
))
{
openaiPassthroughEnabled
.
value
=
extra
?.
openai_passthrough
===
true
||
extra
?.
openai_oauth_passthrough
===
true
openaiOAuthResponsesWebSocketV2Mode
.
value
=
resolveOpenAIWSModeFromExtra
(
extra
,
{
modeKey
:
'
openai_oauth_responses_websockets_v2_mode
'
,
enabledKey
:
'
openai_oauth_responses_websockets_v2_enabled
'
,
fallbackEnabledKeys
:
[
'
responses_websockets_v2_enabled
'
,
'
openai_ws_enabled
'
],
defaultMode
:
OPENAI_WS_MODE_OFF
}
)
openaiAPIKeyResponsesWebSocketV2Mode
.
value
=
resolveOpenAIWSModeFromExtra
(
extra
,
{
modeKey
:
'
openai_apikey_responses_websockets_v2_mode
'
,
enabledKey
:
'
openai_apikey_responses_websockets_v2_enabled
'
,
fallbackEnabledKeys
:
[
'
responses_websockets_v2_enabled
'
,
'
openai_ws_enabled
'
],
defaultMode
:
OPENAI_WS_MODE_OFF
}
)
if
(
newAccount
.
type
===
'
oauth
'
)
{
codexCLIOnlyEnabled
.
value
=
extra
?.
codex_cli_only
===
true
}
}
if
(
newAccount
.
platform
===
'
anthropic
'
&&
newAccount
.
type
===
'
apikey
'
)
{
anthropicPassthroughEnabled
.
value
=
extra
?.
anthropic_passthrough
===
true
}
// Load quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above)
if
(
newAccount
.
type
===
'
apikey
'
||
newAccount
.
type
===
'
bedrock
'
)
{
const
quotaVal
=
extra
?.
quota_limit
as
number
|
undefined
editQuotaLimit
.
value
=
(
quotaVal
&&
quotaVal
>
0
)
?
quotaVal
:
null
const
dailyVal
=
extra
?.
quota_daily_limit
as
number
|
undefined
editQuotaDailyLimit
.
value
=
(
dailyVal
&&
dailyVal
>
0
)
?
dailyVal
:
null
const
weeklyVal
=
extra
?.
quota_weekly_limit
as
number
|
undefined
editQuotaWeeklyLimit
.
value
=
(
weeklyVal
&&
weeklyVal
>
0
)
?
weeklyVal
:
null
// Load quota reset mode config
editDailyResetMode
.
value
=
(
extra
?.
quota_daily_reset_mode
as
'
rolling
'
|
'
fixed
'
)
||
null
editDailyResetHour
.
value
=
(
extra
?.
quota_daily_reset_hour
as
number
)
??
null
editWeeklyResetMode
.
value
=
(
extra
?.
quota_weekly_reset_mode
as
'
rolling
'
|
'
fixed
'
)
||
null
editWeeklyResetDay
.
value
=
(
extra
?.
quota_weekly_reset_day
as
number
)
??
null
editWeeklyResetHour
.
value
=
(
extra
?.
quota_weekly_reset_hour
as
number
)
??
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 quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above)
if
(
newAccount
.
type
===
'
apikey
'
||
newAccount
.
type
===
'
bedrock
'
)
{
const
quotaVal
=
extra
?.
quota_limit
as
number
|
undefined
editQuotaLimit
.
value
=
(
quotaVal
&&
quotaVal
>
0
)
?
quotaVal
:
null
const
dailyVal
=
extra
?.
quota_daily_limit
as
number
|
undefined
editQuotaDailyLimit
.
value
=
(
dailyVal
&&
dailyVal
>
0
)
?
dailyVal
:
null
const
weeklyVal
=
extra
?.
quota_weekly_limit
as
number
|
undefined
editQuotaWeeklyLimit
.
value
=
(
weeklyVal
&&
weeklyVal
>
0
)
?
weeklyVal
:
null
// Load quota reset mode config
editDailyResetMode
.
value
=
(
extra
?.
quota_daily_reset_mode
as
'
rolling
'
|
'
fixed
'
)
||
null
editDailyResetHour
.
value
=
(
extra
?.
quota_daily_reset_hour
as
number
)
??
null
editWeeklyResetMode
.
value
=
(
extra
?.
quota_weekly_reset_mode
as
'
rolling
'
|
'
fixed
'
)
||
null
editWeeklyResetDay
.
value
=
(
extra
?.
quota_weekly_reset_day
as
number
)
??
null
editWeeklyResetHour
.
value
=
(
extra
?.
quota_weekly_reset_hour
as
number
)
??
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
// Load antigravity model mapping (Antigravity 只支持映射模式)
if
(
newAccount
.
platform
===
'
antigravity
'
)
{
const
credentials
=
newAccount
.
credentials
as
Record
<
string
,
unknown
>
|
undefined
// Antigravity 始终使用映射模式
antigravityModelRestrictionMode
.
value
=
'
mapping
'
antigravityWhitelistModels
.
value
=
[]
// 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
=
[]
}
}
// 从 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
{
antigravityModelRestrictionMode
.
value
=
'
mapping
'
antigravityWhitelistModels
.
value
=
[]
antigravityModelMappings
.
value
=
[]
}
}
}
else
{
antigravityModelRestrictionMode
.
value
=
'
mapping
'
antigravityWhitelistModels
.
value
=
[]
antigravityModelMappings
.
value
=
[]
}
// Load quota control settings (Anthropic OAuth/SetupToken only)
loadQuotaControlSettings
(
newAccount
)
loadTempUnschedRules
(
credentials
)
// Initialize API Key fields for apikey type
if
(
newAccount
.
type
===
'
apikey
'
&&
newAccount
.
credentials
)
{
const
credentials
=
newAccount
.
credentials
as
Record
<
string
,
unknown
>
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
=
(
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
=
[]
}
// Load quota control settings (Anthropic OAuth/SetupToken only)
loadQuotaControlSettings
(
newAccount
)
loadTempUnschedRules
(
credentials
)
// Initialize API Key fields for apikey type
if
(
newAccount
.
type
===
'
apikey
'
&&
newAccount
.
credentials
)
{
const
credentials
=
newAccount
.
credentials
as
Record
<
string
,
unknown
>
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
=
(
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
=
[]
}
// 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
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
)
||
''
// 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
{
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
'
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
{
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
'
allowedModels
.
value
=
entries
.
map
(([
from
])
=>
from
)
modelMappings
.
value
=
[]
}
else
{
modelRestrictionMode
.
value
=
'
mapping
'
modelMappings
.
value
=
entries
.
map
(([
from
,
to
])
=>
({
from
,
to
}
))
allowedModels
.
value
=
[]
}
poolModeEnabled
.
value
=
false
poolM
odeRetr
yCount
.
value
=
DEFAULT_POOL_MODE_RETRY_COUNT
customErrorCodesEnabled
.
value
=
false
selectedErrorC
odes
.
value
=
[]
}
else
{
m
ode
l
Re
s
tr
ictionMode
.
value
=
'
whitelist
'
modelMappings
.
value
=
[]
allowedM
ode
l
s
.
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
}
...
...
frontend/src/components/account/__tests__/EditAccountModal.spec.ts
0 → 100644
View file @
21f349c0
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
'
})
})
})
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment