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
92d35409
"backend/ent/vscode:/vscode.git/clone" did not exist on "ff8b1b4ae33e32397b669cb19ca05d7925fa852f"
Commit
92d35409
authored
Mar 07, 2026
by
shaw
Browse files
feat: 为openai分组增加messages调度开关和默认映射模型
parent
351a08f8
Changes
27
Hide whitespace changes
Inline
Side-by-side
backend/migrations/069_add_group_messages_dispatch.sql
0 → 100644
View file @
92d35409
ALTER
TABLE
groups
ADD
COLUMN
allow_messages_dispatch
BOOLEAN
NOT
NULL
DEFAULT
false
;
ALTER
TABLE
groups
ADD
COLUMN
default_mapped_model
VARCHAR
(
100
)
NOT
NULL
DEFAULT
''
;
frontend/src/components/keys/UseKeyModal.vue
View file @
92d35409
...
@@ -146,6 +146,7 @@ interface Props {
...
@@ -146,6 +146,7 @@ interface Props {
apiKey
:
string
apiKey
:
string
baseUrl
:
string
baseUrl
:
string
platform
:
GroupPlatform
|
null
platform
:
GroupPlatform
|
null
allowMessagesDispatch
?:
boolean
}
}
interface
Emits
{
interface
Emits
{
...
@@ -265,13 +266,17 @@ const SparkleIcon = {
...
@@ -265,13 +266,17 @@ const SparkleIcon = {
const
clientTabs
=
computed
(():
TabConfig
[]
=>
{
const
clientTabs
=
computed
(():
TabConfig
[]
=>
{
if
(
!
props
.
platform
)
return
[]
if
(
!
props
.
platform
)
return
[]
switch
(
props
.
platform
)
{
switch
(
props
.
platform
)
{
case
'
openai
'
:
case
'
openai
'
:
{
return
[
const
tabs
:
TabConfig
[]
=
[
{
id
:
'
codex
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.codexCli
'
),
icon
:
TerminalIcon
},
{
id
:
'
codex
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.codexCli
'
),
icon
:
TerminalIcon
},
{
id
:
'
codex-ws
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.codexCliWs
'
),
icon
:
TerminalIcon
},
{
id
:
'
codex-ws
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.codexCliWs
'
),
icon
:
TerminalIcon
},
{
id
:
'
claude
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.claudeCode
'
),
icon
:
TerminalIcon
},
{
id
:
'
opencode
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.opencode
'
),
icon
:
TerminalIcon
}
]
]
if
(
props
.
allowMessagesDispatch
)
{
tabs
.
push
({
id
:
'
claude
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.claudeCode
'
),
icon
:
TerminalIcon
})
}
tabs
.
push
({
id
:
'
opencode
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.opencode
'
),
icon
:
TerminalIcon
})
return
tabs
}
case
'
gemini
'
:
case
'
gemini
'
:
return
[
return
[
{
id
:
'
gemini
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.geminiCli
'
),
icon
:
SparkleIcon
},
{
id
:
'
gemini
'
,
label
:
t
(
'
keys.useKeyModal.cliTabs.geminiCli
'
),
icon
:
SparkleIcon
},
...
...
frontend/src/i18n/locales/en.ts
View file @
92d35409
...
@@ -1443,6 +1443,14 @@ export default {
...
@@ -1443,6 +1443,14 @@ export default {
fallbackHint
:
'
Non-Claude Code requests will use this group. Leave empty to reject directly.
'
,
fallbackHint
:
'
Non-Claude Code requests will use this group. Leave empty to reject directly.
'
,
noFallback
:
'
No Fallback (Reject)
'
noFallback
:
'
No Fallback (Reject)
'
},
},
openaiMessages
:
{
title
:
'
OpenAI Messages Dispatch
'
,
allowDispatch
:
'
Allow /v1/messages dispatch
'
,
allowDispatchHint
:
'
When enabled, API keys in this OpenAI group can dispatch requests through /v1/messages endpoint
'
,
defaultModel
:
'
Default mapped model
'
,
defaultModelPlaceholder
:
'
e.g., gpt-4.1
'
,
defaultModelHint
:
'
When account has no model mapping configured, all request models will be mapped to this model
'
},
invalidRequestFallback
:
{
invalidRequestFallback
:
{
title
:
'
Invalid Request Fallback Group
'
,
title
:
'
Invalid Request Fallback Group
'
,
hint
:
'
Triggered only when upstream explicitly returns prompt too long. Leave empty to disable fallback.
'
,
hint
:
'
Triggered only when upstream explicitly returns prompt too long. Leave empty to disable fallback.
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
92d35409
...
@@ -1530,6 +1530,14 @@ export default {
...
@@ -1530,6 +1530,14 @@ export default {
fallbackHint
:
'
非 Claude Code 请求将使用此分组,留空则直接拒绝
'
,
fallbackHint
:
'
非 Claude Code 请求将使用此分组,留空则直接拒绝
'
,
noFallback
:
'
不降级(直接拒绝)
'
noFallback
:
'
不降级(直接拒绝)
'
},
},
openaiMessages
:
{
title
:
'
OpenAI Messages 调度配置
'
,
allowDispatch
:
'
允许 /v1/messages 调度
'
,
allowDispatchHint
:
'
启用后,此 OpenAI 分组的 API Key 可以通过 /v1/messages 端点调度请求
'
,
defaultModel
:
'
默认映射模型
'
,
defaultModelPlaceholder
:
'
例如: gpt-4.1
'
,
defaultModelHint
:
'
当账号未配置模型映射时,所有请求模型将映射到此模型
'
},
invalidRequestFallback
:
{
invalidRequestFallback
:
{
title
:
'
无效请求兜底分组
'
,
title
:
'
无效请求兜底分组
'
,
hint
:
'
仅当上游明确返回 prompt too long 时才会触发,留空表示不兜底
'
,
hint
:
'
仅当上游明确返回 prompt too long 时才会触发,留空表示不兜底
'
,
...
...
frontend/src/types/index.ts
View file @
92d35409
...
@@ -389,6 +389,8 @@ export interface Group {
...
@@ -389,6 +389,8 @@ export interface Group {
claude_code_only
:
boolean
claude_code_only
:
boolean
fallback_group_id
:
number
|
null
fallback_group_id
:
number
|
null
fallback_group_id_on_invalid_request
:
number
|
null
fallback_group_id_on_invalid_request
:
number
|
null
// OpenAI Messages 调度开关(用户侧需要此字段判断是否展示 Claude Code 教程)
allow_messages_dispatch
?:
boolean
created_at
:
string
created_at
:
string
updated_at
:
string
updated_at
:
string
}
}
...
@@ -407,6 +409,9 @@ export interface AdminGroup extends Group {
...
@@ -407,6 +409,9 @@ export interface AdminGroup extends Group {
// 分组下账号数量(仅管理员可见)
// 分组下账号数量(仅管理员可见)
account_count
?:
number
account_count
?:
number
// OpenAI Messages 调度配置(仅 openai 平台使用)
default_mapped_model
?:
string
// 分组排序
// 分组排序
sort_order
:
number
sort_order
:
number
}
}
...
...
frontend/src/views/admin/GroupsView.vue
View file @
92d35409
...
@@ -708,6 +708,44 @@
...
@@ -708,6 +708,44 @@
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<!--
OpenAI
Messages
调度配置
(
仅
openai
平台
)
-->
<
div
v
-
if
=
"
createForm.platform === 'openai'
"
class
=
"
border-t border-gray-200 dark:border-dark-400 pt-4 mt-4
"
>
<
h4
class
=
"
text-sm font-medium text-gray-700 dark:text-gray-300 mb-3
"
>
{{
t
(
'
admin.groups.openaiMessages.title
'
)
}}
<
/h4
>
<!--
允许
Messages
调度开关
-->
<
div
class
=
"
flex items-center justify-between
"
>
<
label
class
=
"
text-sm text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.groups.openaiMessages.allowDispatch
'
)
}}
<
/label
>
<
button
type
=
"
button
"
@
click
=
"
createForm.allow_messages_dispatch = !createForm.allow_messages_dispatch
"
class
=
"
relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none
"
:
class
=
"
createForm.allow_messages_dispatch ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
"
>
<
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
"
:
class
=
"
createForm.allow_messages_dispatch ? 'translate-x-6' : 'translate-x-1'
"
/>
<
/button
>
<
/div
>
<
p
class
=
"
text-xs text-gray-500 dark:text-gray-400 mt-1
"
>
{{
t
(
'
admin.groups.openaiMessages.allowDispatchHint
'
)
}}
<
/p
>
<!--
默认映射模型
(
仅当开关打开时显示
)
-->
<
div
v
-
if
=
"
createForm.allow_messages_dispatch
"
class
=
"
mt-3
"
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.groups.openaiMessages.defaultModel
'
)
}}
<
/label
>
<
input
v
-
model
=
"
createForm.default_mapped_model
"
type
=
"
text
"
:
placeholder
=
"
t('admin.groups.openaiMessages.defaultModelPlaceholder')
"
class
=
"
input
"
/>
<
p
class
=
"
input-hint
"
>
{{
t
(
'
admin.groups.openaiMessages.defaultModelHint
'
)
}}
<
/p
>
<
/div
>
<
/div
>
<!--
无效请求兜底
(
仅
anthropic
/
antigravity
平台
,
且非订阅分组
)
-->
<!--
无效请求兜底
(
仅
anthropic
/
antigravity
平台
,
且非订阅分组
)
-->
<
div
<
div
v
-
if
=
"
['anthropic', 'antigravity'].includes(createForm.platform) && createForm.subscription_type !== 'subscription'
"
v
-
if
=
"
['anthropic', 'antigravity'].includes(createForm.platform) && createForm.subscription_type !== 'subscription'
"
...
@@ -1405,6 +1443,44 @@
...
@@ -1405,6 +1443,44 @@
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<!--
OpenAI
Messages
调度配置
(
仅
openai
平台
)
-->
<
div
v
-
if
=
"
editForm.platform === 'openai'
"
class
=
"
border-t border-gray-200 dark:border-dark-400 pt-4 mt-4
"
>
<
h4
class
=
"
text-sm font-medium text-gray-700 dark:text-gray-300 mb-3
"
>
{{
t
(
'
admin.groups.openaiMessages.title
'
)
}}
<
/h4
>
<!--
允许
Messages
调度开关
-->
<
div
class
=
"
flex items-center justify-between
"
>
<
label
class
=
"
text-sm text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.groups.openaiMessages.allowDispatch
'
)
}}
<
/label
>
<
button
type
=
"
button
"
@
click
=
"
editForm.allow_messages_dispatch = !editForm.allow_messages_dispatch
"
class
=
"
relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none
"
:
class
=
"
editForm.allow_messages_dispatch ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
"
>
<
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
"
:
class
=
"
editForm.allow_messages_dispatch ? 'translate-x-6' : 'translate-x-1'
"
/>
<
/button
>
<
/div
>
<
p
class
=
"
text-xs text-gray-500 dark:text-gray-400 mt-1
"
>
{{
t
(
'
admin.groups.openaiMessages.allowDispatchHint
'
)
}}
<
/p
>
<!--
默认映射模型
(
仅当开关打开时显示
)
-->
<
div
v
-
if
=
"
editForm.allow_messages_dispatch
"
class
=
"
mt-3
"
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.groups.openaiMessages.defaultModel
'
)
}}
<
/label
>
<
input
v
-
model
=
"
editForm.default_mapped_model
"
type
=
"
text
"
:
placeholder
=
"
t('admin.groups.openaiMessages.defaultModelPlaceholder')
"
class
=
"
input
"
/>
<
p
class
=
"
input-hint
"
>
{{
t
(
'
admin.groups.openaiMessages.defaultModelHint
'
)
}}
<
/p
>
<
/div
>
<
/div
>
<!--
无效请求兜底
(
仅
anthropic
/
antigravity
平台
,
且非订阅分组
)
-->
<!--
无效请求兜底
(
仅
anthropic
/
antigravity
平台
,
且非订阅分组
)
-->
<
div
<
div
v
-
if
=
"
['anthropic', 'antigravity'].includes(editForm.platform) && editForm.subscription_type !== 'subscription'
"
v
-
if
=
"
['anthropic', 'antigravity'].includes(editForm.platform) && editForm.subscription_type !== 'subscription'
"
...
@@ -1920,6 +1996,9 @@ const createForm = reactive({
...
@@ -1920,6 +1996,9 @@ const createForm = reactive({
claude_code_only
:
false
,
claude_code_only
:
false
,
fallback_group_id
:
null
as
number
|
null
,
fallback_group_id
:
null
as
number
|
null
,
fallback_group_id_on_invalid_request
:
null
as
number
|
null
,
fallback_group_id_on_invalid_request
:
null
as
number
|
null
,
// OpenAI Messages 调度配置(仅 openai 平台使用)
allow_messages_dispatch
:
false
,
default_mapped_model
:
'
gpt-5.4
'
,
// 模型路由开关
// 模型路由开关
model_routing_enabled
:
false
,
model_routing_enabled
:
false
,
// 支持的模型系列(仅 antigravity 平台)
// 支持的模型系列(仅 antigravity 平台)
...
@@ -2161,6 +2240,9 @@ const editForm = reactive({
...
@@ -2161,6 +2240,9 @@ const editForm = reactive({
claude_code_only
:
false
,
claude_code_only
:
false
,
fallback_group_id
:
null
as
number
|
null
,
fallback_group_id
:
null
as
number
|
null
,
fallback_group_id_on_invalid_request
:
null
as
number
|
null
,
fallback_group_id_on_invalid_request
:
null
as
number
|
null
,
// OpenAI Messages 调度配置(仅 openai 平台使用)
allow_messages_dispatch
:
false
,
default_mapped_model
:
''
,
// 模型路由开关
// 模型路由开关
model_routing_enabled
:
false
,
model_routing_enabled
:
false
,
// 支持的模型系列(仅 antigravity 平台)
// 支持的模型系列(仅 antigravity 平台)
...
@@ -2260,6 +2342,8 @@ const closeCreateModal = () => {
...
@@ -2260,6 +2342,8 @@ const closeCreateModal = () => {
createForm
.
claude_code_only
=
false
createForm
.
claude_code_only
=
false
createForm
.
fallback_group_id
=
null
createForm
.
fallback_group_id
=
null
createForm
.
fallback_group_id_on_invalid_request
=
null
createForm
.
fallback_group_id_on_invalid_request
=
null
createForm
.
allow_messages_dispatch
=
false
createForm
.
default_mapped_model
=
'
gpt-5.4
'
createForm
.
supported_model_scopes
=
[
'
claude
'
,
'
gemini_text
'
,
'
gemini_image
'
]
createForm
.
supported_model_scopes
=
[
'
claude
'
,
'
gemini_text
'
,
'
gemini_image
'
]
createForm
.
mcp_xml_inject
=
true
createForm
.
mcp_xml_inject
=
true
createForm
.
copy_accounts_from_group_ids
=
[]
createForm
.
copy_accounts_from_group_ids
=
[]
...
@@ -2320,6 +2404,8 @@ const handleEdit = async (group: AdminGroup) => {
...
@@ -2320,6 +2404,8 @@ const handleEdit = async (group: AdminGroup) => {
editForm
.
claude_code_only
=
group
.
claude_code_only
||
false
editForm
.
claude_code_only
=
group
.
claude_code_only
||
false
editForm
.
fallback_group_id
=
group
.
fallback_group_id
editForm
.
fallback_group_id
=
group
.
fallback_group_id
editForm
.
fallback_group_id_on_invalid_request
=
group
.
fallback_group_id_on_invalid_request
editForm
.
fallback_group_id_on_invalid_request
=
group
.
fallback_group_id_on_invalid_request
editForm
.
allow_messages_dispatch
=
group
.
allow_messages_dispatch
||
false
editForm
.
default_mapped_model
=
group
.
default_mapped_model
||
''
editForm
.
model_routing_enabled
=
group
.
model_routing_enabled
||
false
editForm
.
model_routing_enabled
=
group
.
model_routing_enabled
||
false
editForm
.
supported_model_scopes
=
group
.
supported_model_scopes
||
[
'
claude
'
,
'
gemini_text
'
,
'
gemini_image
'
]
editForm
.
supported_model_scopes
=
group
.
supported_model_scopes
||
[
'
claude
'
,
'
gemini_text
'
,
'
gemini_image
'
]
editForm
.
mcp_xml_inject
=
group
.
mcp_xml_inject
??
true
editForm
.
mcp_xml_inject
=
group
.
mcp_xml_inject
??
true
...
@@ -2410,6 +2496,10 @@ watch(
...
@@ -2410,6 +2496,10 @@ watch(
if
(
!
[
'
anthropic
'
,
'
antigravity
'
].
includes
(
newVal
))
{
if
(
!
[
'
anthropic
'
,
'
antigravity
'
].
includes
(
newVal
))
{
createForm
.
fallback_group_id_on_invalid_request
=
null
createForm
.
fallback_group_id_on_invalid_request
=
null
}
}
if
(
newVal
!==
'
openai
'
)
{
createForm
.
allow_messages_dispatch
=
false
createForm
.
default_mapped_model
=
''
}
}
}
)
)
...
...
frontend/src/views/user/KeysView.vue
View file @
92d35409
...
@@ -899,6 +899,7 @@
...
@@ -899,6 +899,7 @@
:api-key=
"selectedKey?.key || ''"
:api-key=
"selectedKey?.key || ''"
:base-url=
"publicSettings?.api_base_url || ''"
:base-url=
"publicSettings?.api_base_url || ''"
:platform=
"selectedKey?.group?.platform || null"
:platform=
"selectedKey?.group?.platform || null"
:allow-messages-dispatch=
"selectedKey?.group?.allow_messages_dispatch || false"
@
close=
"closeUseKeyModal"
@
close=
"closeUseKeyModal"
/>
/>
...
...
Prev
1
2
Next
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