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
ef5c8e68
Unverified
Commit
ef5c8e68
authored
Mar 26, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 26, 2026
Browse files
Merge pull request #1231 from LvyuanW/bulk-openai-passthrough-worktree
Support bulk editing for OpenAI passthrough
parents
d571f300
bb399e56
Changes
2
Show whitespace changes
Inline
Side-by-side
frontend/src/components/account/BulkEditAccountModal.vue
View file @
ef5c8e68
...
@@ -31,6 +31,57 @@
...
@@ -31,6 +31,57 @@
<
/p
>
<
/p
>
<
/div
>
<
/div
>
<!--
OpenAI
passthrough
-->
<
div
v
-
if
=
"
allOpenAIPassthroughCapable
"
class
=
"
border-t border-gray-200 pt-4 dark:border-dark-600
"
>
<
div
class
=
"
mb-3 flex items-center justify-between
"
>
<
div
class
=
"
flex-1 pr-4
"
>
<
label
id
=
"
bulk-edit-openai-passthrough-label
"
class
=
"
input-label mb-0
"
for
=
"
bulk-edit-openai-passthrough-enabled
"
>
{{
t
(
'
admin.accounts.openai.oauthPassthrough
'
)
}}
<
/label
>
<
p
class
=
"
mt-1 text-xs text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.accounts.openai.oauthPassthroughDesc
'
)
}}
<
/p
>
<
/div
>
<
input
v
-
model
=
"
enableOpenAIPassthrough
"
id
=
"
bulk-edit-openai-passthrough-enabled
"
type
=
"
checkbox
"
aria
-
controls
=
"
bulk-edit-openai-passthrough-body
"
class
=
"
rounded border-gray-300 text-primary-600 focus:ring-primary-500
"
/>
<
/div
>
<
div
id
=
"
bulk-edit-openai-passthrough-body
"
:
class
=
"
!enableOpenAIPassthrough && 'pointer-events-none opacity-50'
"
role
=
"
group
"
aria
-
labelledby
=
"
bulk-edit-openai-passthrough-label
"
>
<
button
id
=
"
bulk-edit-openai-passthrough-toggle
"
type
=
"
button
"
:
class
=
"
[
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
openaiPassthroughEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
]
"
@
click
=
"
openaiPassthroughEnabled = !openaiPassthroughEnabled
"
>
<
span
:
class
=
"
[
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
openaiPassthroughEnabled ? 'translate-x-5' : 'translate-x-0'
]
"
/>
<
/button
>
<
/div
>
<
/div
>
<!--
Base
URL
(
API
Key
only
)
-->
<!--
Base
URL
(
API
Key
only
)
-->
<
div
class
=
"
border-t border-gray-200 pt-4 dark:border-dark-600
"
>
<
div
class
=
"
border-t border-gray-200 pt-4 dark:border-dark-600
"
>
<
div
class
=
"
mb-3 flex items-center justify-between
"
>
<
div
class
=
"
mb-3 flex items-center justify-between
"
>
...
@@ -89,6 +140,16 @@
...
@@ -89,6 +140,16 @@
role
=
"
group
"
role
=
"
group
"
aria
-
labelledby
=
"
bulk-edit-model-restriction-label
"
aria
-
labelledby
=
"
bulk-edit-model-restriction-label
"
>
>
<
div
v
-
if
=
"
isOpenAIModelRestrictionDisabled
"
class
=
"
rounded-lg bg-amber-50 p-3 dark:bg-amber-900/20
"
>
<
p
class
=
"
text-xs text-amber-700 dark:text-amber-400
"
>
{{
t
(
'
admin.accounts.openai.modelRestrictionDisabledByPassthrough
'
)
}}
<
/p
>
<
/div
>
<
template
v
-
else
>
<!--
Mode
Toggle
-->
<!--
Mode
Toggle
-->
<
div
class
=
"
mb-4 flex gap-2
"
>
<
div
class
=
"
mb-4 flex gap-2
"
>
<
button
<
button
...
@@ -281,6 +342,7 @@
...
@@ -281,6 +342,7 @@
<
/button
>
<
/button
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
...
@@ -865,7 +927,6 @@ import {
...
@@ -865,7 +927,6 @@ import {
resolveOpenAIWSModeConcurrencyHintKey
resolveOpenAIWSModeConcurrencyHintKey
}
from
'
@/utils/openaiWsMode
'
}
from
'
@/utils/openaiWsMode
'
import
type
{
OpenAIWSMode
}
from
'
@/utils/openaiWsMode
'
import
type
{
OpenAIWSMode
}
from
'
@/utils/openaiWsMode
'
interface
Props
{
interface
Props
{
show
:
boolean
show
:
boolean
accountIds
:
number
[]
accountIds
:
number
[]
...
@@ -887,6 +948,15 @@ const appStore = useAppStore()
...
@@ -887,6 +948,15 @@ const appStore = useAppStore()
// Platform awareness
// Platform awareness
const
isMixedPlatform
=
computed
(()
=>
props
.
selectedPlatforms
.
length
>
1
)
const
isMixedPlatform
=
computed
(()
=>
props
.
selectedPlatforms
.
length
>
1
)
const
allOpenAIPassthroughCapable
=
computed
(()
=>
{
return
(
props
.
selectedPlatforms
.
length
===
1
&&
props
.
selectedPlatforms
[
0
]
===
'
openai
'
&&
props
.
selectedTypes
.
length
>
0
&&
props
.
selectedTypes
.
every
(
t
=>
t
===
'
oauth
'
||
t
===
'
apikey
'
)
)
}
)
const
allOpenAIOAuth
=
computed
(()
=>
{
const
allOpenAIOAuth
=
computed
(()
=>
{
return
(
return
(
props
.
selectedPlatforms
.
length
===
1
&&
props
.
selectedPlatforms
.
length
===
1
&&
...
@@ -939,6 +1009,7 @@ const enablePriority = ref(false)
...
@@ -939,6 +1009,7 @@ const enablePriority = ref(false)
const
enableRateMultiplier
=
ref
(
false
)
const
enableRateMultiplier
=
ref
(
false
)
const
enableStatus
=
ref
(
false
)
const
enableStatus
=
ref
(
false
)
const
enableGroups
=
ref
(
false
)
const
enableGroups
=
ref
(
false
)
const
enableOpenAIPassthrough
=
ref
(
false
)
const
enableOpenAIWSMode
=
ref
(
false
)
const
enableOpenAIWSMode
=
ref
(
false
)
const
enableRpmLimit
=
ref
(
false
)
const
enableRpmLimit
=
ref
(
false
)
...
@@ -961,6 +1032,7 @@ const priority = ref(1)
...
@@ -961,6 +1032,7 @@ const priority = ref(1)
const
rateMultiplier
=
ref
(
1
)
const
rateMultiplier
=
ref
(
1
)
const
status
=
ref
<
'
active
'
|
'
inactive
'
>
(
'
active
'
)
const
status
=
ref
<
'
active
'
|
'
inactive
'
>
(
'
active
'
)
const
groupIds
=
ref
<
number
[]
>
([])
const
groupIds
=
ref
<
number
[]
>
([])
const
openaiPassthroughEnabled
=
ref
(
false
)
const
openaiOAuthResponsesWebSocketV2Mode
=
ref
<
OpenAIWSMode
>
(
OPENAI_WS_MODE_OFF
)
const
openaiOAuthResponsesWebSocketV2Mode
=
ref
<
OpenAIWSMode
>
(
OPENAI_WS_MODE_OFF
)
const
rpmLimitEnabled
=
ref
(
false
)
const
rpmLimitEnabled
=
ref
(
false
)
const
bulkBaseRpm
=
ref
<
number
|
null
>
(
null
)
const
bulkBaseRpm
=
ref
<
number
|
null
>
(
null
)
...
@@ -988,6 +1060,13 @@ const statusOptions = computed(() => [
...
@@ -988,6 +1060,13 @@ const statusOptions = computed(() => [
{
value
:
'
active
'
,
label
:
t
(
'
common.active
'
)
}
,
{
value
:
'
active
'
,
label
:
t
(
'
common.active
'
)
}
,
{
value
:
'
inactive
'
,
label
:
t
(
'
common.inactive
'
)
}
{
value
:
'
inactive
'
,
label
:
t
(
'
common.inactive
'
)
}
])
])
const
isOpenAIModelRestrictionDisabled
=
computed
(
()
=>
allOpenAIPassthroughCapable
.
value
&&
enableOpenAIPassthrough
.
value
&&
openaiPassthroughEnabled
.
value
)
const
openAIWSModeOptions
=
computed
(()
=>
[
const
openAIWSModeOptions
=
computed
(()
=>
[
{
value
:
OPENAI_WS_MODE_OFF
,
label
:
t
(
'
admin.accounts.openai.wsModeOff
'
)
}
,
{
value
:
OPENAI_WS_MODE_OFF
,
label
:
t
(
'
admin.accounts.openai.wsModeOff
'
)
}
,
{
value
:
OPENAI_WS_MODE_PASSTHROUGH
,
label
:
t
(
'
admin.accounts.openai.wsModePassthrough
'
)
}
{
value
:
OPENAI_WS_MODE_PASSTHROUGH
,
label
:
t
(
'
admin.accounts.openai.wsModePassthrough
'
)
}
...
@@ -1123,7 +1202,15 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
...
@@ -1123,7 +1202,15 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
}
}
}
}
if
(
enableModelRestriction
.
value
)
{
if
(
enableOpenAIPassthrough
.
value
)
{
const
extra
=
ensureExtra
()
extra
.
openai_passthrough
=
openaiPassthroughEnabled
.
value
if
(
!
openaiPassthroughEnabled
.
value
)
{
extra
.
openai_oauth_passthrough
=
false
}
}
if
(
enableModelRestriction
.
value
&&
!
isOpenAIModelRestrictionDisabled
.
value
)
{
// 统一使用 model_mapping 字段
// 统一使用 model_mapping 字段
if
(
modelRestrictionMode
.
value
===
'
whitelist
'
)
{
if
(
modelRestrictionMode
.
value
===
'
whitelist
'
)
{
// 白名单模式:将模型转换为 model_mapping 格式(key=value)
// 白名单模式:将模型转换为 model_mapping 格式(key=value)
...
@@ -1243,6 +1330,7 @@ const handleSubmit = async () => {
...
@@ -1243,6 +1330,7 @@ const handleSubmit = async () => {
const
hasAnyFieldEnabled
=
const
hasAnyFieldEnabled
=
enableBaseUrl
.
value
||
enableBaseUrl
.
value
||
enableOpenAIPassthrough
.
value
||
enableModelRestriction
.
value
||
enableModelRestriction
.
value
||
enableCustomErrorCodes
.
value
||
enableCustomErrorCodes
.
value
||
enableInterceptWarmup
.
value
||
enableInterceptWarmup
.
value
||
...
@@ -1345,11 +1433,13 @@ watch(
...
@@ -1345,11 +1433,13 @@ watch(
enableRateMultiplier
.
value
=
false
enableRateMultiplier
.
value
=
false
enableStatus
.
value
=
false
enableStatus
.
value
=
false
enableGroups
.
value
=
false
enableGroups
.
value
=
false
enableOpenAIPassthrough
.
value
=
false
enableOpenAIWSMode
.
value
=
false
enableOpenAIWSMode
.
value
=
false
enableRpmLimit
.
value
=
false
enableRpmLimit
.
value
=
false
// Reset all values
// Reset all values
baseUrl
.
value
=
''
baseUrl
.
value
=
''
openaiPassthroughEnabled
.
value
=
false
modelRestrictionMode
.
value
=
'
whitelist
'
modelRestrictionMode
.
value
=
'
whitelist
'
allowedModels
.
value
=
[]
allowedModels
.
value
=
[]
modelMappings
.
value
=
[]
modelMappings
.
value
=
[]
...
...
frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts
View file @
ef5c8e68
...
@@ -130,6 +130,25 @@ describe('BulkEditAccountModal', () => {
...
@@ -130,6 +130,25 @@ describe('BulkEditAccountModal', () => {
})
})
})
})
it
(
'
OpenAI 账号批量编辑可开启自动透传
'
,
async
()
=>
{
const
wrapper
=
mountModal
({
selectedPlatforms
:
[
'
openai
'
],
selectedTypes
:
[
'
oauth
'
]
})
await
wrapper
.
get
(
'
#bulk-edit-openai-passthrough-enabled
'
).
setValue
(
true
)
await
wrapper
.
get
(
'
#bulk-edit-openai-passthrough-toggle
'
).
trigger
(
'
click
'
)
await
wrapper
.
get
(
'
#bulk-edit-account-form
'
).
trigger
(
'
submit.prevent
'
)
await
flushPromises
()
expect
(
adminAPI
.
accounts
.
bulkUpdate
).
toHaveBeenCalledTimes
(
1
)
expect
(
adminAPI
.
accounts
.
bulkUpdate
).
toHaveBeenCalledWith
([
1
,
2
],
{
extra
:
{
openai_passthrough
:
true
}
})
})
it
(
'
OpenAI OAuth 批量编辑应提交 OAuth 专属 WS mode 字段
'
,
async
()
=>
{
it
(
'
OpenAI OAuth 批量编辑应提交 OAuth 专属 WS mode 字段
'
,
async
()
=>
{
const
wrapper
=
mountModal
({
const
wrapper
=
mountModal
({
selectedPlatforms
:
[
'
openai
'
],
selectedPlatforms
:
[
'
openai
'
],
...
@@ -158,4 +177,44 @@ describe('BulkEditAccountModal', () => {
...
@@ -158,4 +177,44 @@ describe('BulkEditAccountModal', () => {
expect
(
wrapper
.
find
(
'
#bulk-edit-openai-ws-mode-enabled
'
).
exists
()).
toBe
(
false
)
expect
(
wrapper
.
find
(
'
#bulk-edit-openai-ws-mode-enabled
'
).
exists
()).
toBe
(
false
)
})
})
it
(
'
OpenAI 账号批量编辑可关闭自动透传
'
,
async
()
=>
{
const
wrapper
=
mountModal
({
selectedPlatforms
:
[
'
openai
'
],
selectedTypes
:
[
'
apikey
'
]
})
await
wrapper
.
get
(
'
#bulk-edit-openai-passthrough-enabled
'
).
setValue
(
true
)
await
wrapper
.
get
(
'
#bulk-edit-account-form
'
).
trigger
(
'
submit.prevent
'
)
await
flushPromises
()
expect
(
adminAPI
.
accounts
.
bulkUpdate
).
toHaveBeenCalledTimes
(
1
)
expect
(
adminAPI
.
accounts
.
bulkUpdate
).
toHaveBeenCalledWith
([
1
,
2
],
{
extra
:
{
openai_passthrough
:
false
,
openai_oauth_passthrough
:
false
}
})
})
it
(
'
开启 OpenAI 自动透传时不再同时提交模型限制
'
,
async
()
=>
{
const
wrapper
=
mountModal
({
selectedPlatforms
:
[
'
openai
'
],
selectedTypes
:
[
'
oauth
'
]
})
await
wrapper
.
get
(
'
#bulk-edit-openai-passthrough-enabled
'
).
setValue
(
true
)
await
wrapper
.
get
(
'
#bulk-edit-openai-passthrough-toggle
'
).
trigger
(
'
click
'
)
await
wrapper
.
get
(
'
#bulk-edit-model-restriction-enabled
'
).
setValue
(
true
)
await
wrapper
.
get
(
'
#bulk-edit-account-form
'
).
trigger
(
'
submit.prevent
'
)
await
flushPromises
()
expect
(
adminAPI
.
accounts
.
bulkUpdate
).
toHaveBeenCalledTimes
(
1
)
expect
(
adminAPI
.
accounts
.
bulkUpdate
).
toHaveBeenCalledWith
([
1
,
2
],
{
extra
:
{
openai_passthrough
:
true
}
})
expect
(
wrapper
.
text
()).
toContain
(
'
admin.accounts.openai.modelRestrictionDisabledByPassthrough
'
)
})
})
})
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