Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions controller/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,13 +649,15 @@ func DeleteDisabledChannel(c *gin.Context) {
}

type ChannelTag struct {
Tag string `json:"tag"`
NewTag *string `json:"new_tag"`
Priority *int64 `json:"priority"`
Weight *uint `json:"weight"`
ModelMapping *string `json:"model_mapping"`
Models *string `json:"models"`
Groups *string `json:"groups"`
Tag string `json:"tag"`
NewTag *string `json:"new_tag"`
Priority *int64 `json:"priority"`
Weight *uint `json:"weight"`
ModelMapping *string `json:"model_mapping"`
Models *string `json:"models"`
Groups *string `json:"groups"`
ParamOverride *string `json:"param_override"`
HeaderOverride *string `json:"header_override"`
}

func DisableTagChannels(c *gin.Context) {
Expand Down Expand Up @@ -721,7 +723,29 @@ func EditTagChannels(c *gin.Context) {
})
return
}
err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.ModelMapping, channelTag.Models, channelTag.Groups, channelTag.Priority, channelTag.Weight)
if channelTag.ParamOverride != nil {
trimmed := strings.TrimSpace(*channelTag.ParamOverride)
if trimmed != "" && !json.Valid([]byte(trimmed)) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "参数覆盖必须是合法的 JSON 格式",
})
return
}
channelTag.ParamOverride = common.GetPointer[string](trimmed)
}
if channelTag.HeaderOverride != nil {
trimmed := strings.TrimSpace(*channelTag.HeaderOverride)
if trimmed != "" && !json.Valid([]byte(trimmed)) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "请求头覆盖必须是合法的 JSON 格式",
})
return
}
channelTag.HeaderOverride = common.GetPointer[string](trimmed)
}
err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.ModelMapping, channelTag.Models, channelTag.Groups, channelTag.Priority, channelTag.Weight, channelTag.ParamOverride, channelTag.HeaderOverride)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify function call has correct parameter order.

The call to model.EditChannelByTag now passes 9 parameters. Let me verify this matches the function signature.


🏁 Script executed:

#!/bin/bash
# Verify the EditChannelByTag function signature matches the call
rg -A 2 'func EditChannelByTag' --type go

Length of output: 298


Fix parameter name mismatch: pass channelTag.Group (singular) instead of channelTag.Groups (plural).

The function signature expects group *string as the 5th parameter, but the call passes channelTag.Groups. This singular/plural mismatch indicates the wrong field is being passed to the function.

🤖 Prompt for AI Agents
In controller/channel.go around line 748, the call to model.EditChannelByTag
passes channelTag.Models (4th) and channelTag.Groups (5th) but the function
signature expects the 5th parameter to be group *string (singular). Replace the
channelTag.Groups argument with channelTag.Group (singular) so the correct
field/type is passed; ensure channelTag.Group is a *string (or take its address)
and handle nil appropriately if required by the model.EditChannelByTag contract.

if err != nil {
common.ApiError(c, err)
return
Expand Down
8 changes: 7 additions & 1 deletion model/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ func DisableChannelByTag(tag string) error {
return err
}

func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *string, group *string, priority *int64, weight *uint) error {
func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *string, group *string, priority *int64, weight *uint, paramOverride *string, headerOverride *string) error {
updateData := Channel{}
shouldReCreateAbilities := false
updatedTag := tag
Expand All @@ -714,6 +714,12 @@ func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *
if weight != nil {
updateData.Weight = weight
}
if paramOverride != nil {
updateData.ParamOverride = paramOverride
}
if headerOverride != nil {
updateData.HeaderOverride = headerOverride
}

err := DB.Model(&Channel{}).Where("tag = ?", tag).Updates(updateData).Error
if err != nil {
Expand Down
192 changes: 191 additions & 1 deletion web/src/components/table/channels/modals/EditTagModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
IconBookmark,
IconUser,
IconCode,
IconSetting,
} from '@douyinfe/semi-icons';
import { getChannelModels } from '../../../../helpers';
import { useTranslation } from 'react-i18next';
Expand All @@ -69,6 +70,8 @@ const EditTagModal = (props) => {
model_mapping: null,
groups: [],
models: [],
param_override: null,
header_override: null,
};
const [inputs, setInputs] = useState(originInputs);
const formApiRef = useRef(null);
Expand Down Expand Up @@ -190,12 +193,48 @@ const EditTagModal = (props) => {
if (formVals.models && formVals.models.length > 0) {
data.models = formVals.models.join(',');
}
if (
formVals.param_override !== undefined &&
formVals.param_override !== null
) {
if (typeof formVals.param_override !== 'string') {
showInfo('参数覆盖必须是合法的 JSON 格式!');
setLoading(false);
return;
}
const trimmedParamOverride = formVals.param_override.trim();
if (trimmedParamOverride !== '' && !verifyJSON(trimmedParamOverride)) {
showInfo('参数覆盖必须是合法的 JSON 格式!');
setLoading(false);
return;
}
data.param_override = trimmedParamOverride;
}
if (
formVals.header_override !== undefined &&
formVals.header_override !== null
) {
if (typeof formVals.header_override !== 'string') {
showInfo('请求头覆盖必须是合法的 JSON 格式!');
setLoading(false);
return;
}
const trimmedHeaderOverride = formVals.header_override.trim();
if (trimmedHeaderOverride !== '' && !verifyJSON(trimmedHeaderOverride)) {
showInfo('请求头覆盖必须是合法的 JSON 格式!');
setLoading(false);
return;
}
data.header_override = trimmedHeaderOverride;
}
Comment on lines +196 to +229

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Internationalize validation error messages.

The validation error messages at lines 201, 207-208, 218, and 224-225 are hardcoded in Chinese, but the rest of the file consistently uses the t() translation function.

       if (typeof formVals.param_override !== 'string') {
-        showInfo('参数覆盖必须是合法的 JSON 格式!');
+        showInfo(t('参数覆盖必须是合法的 JSON 格式!'));
         setLoading(false);
         return;
       }
       const trimmedParamOverride = formVals.param_override.trim();
       if (trimmedParamOverride !== '' && !verifyJSON(trimmedParamOverride)) {
-        showInfo('参数覆盖必须是合法的 JSON 格式!');
+        showInfo(t('参数覆盖必须是合法的 JSON 格式!'));
         setLoading(false);
         return;
       }

Apply similar changes for header_override messages at lines 218, 224-225.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In web/src/components/table/channels/modals/EditTagModal.jsx around lines 196 to
229, the validation messages for param_override and header_override are
hardcoded in Chinese; replace those four fixed strings with calls to the
translation function t(...) to match the file's i18n pattern. Specifically,
change the messages at lines ~201 and ~207-208 for param_override and at ~218
and ~224-225 for header_override to use t(...) (use the existing translation
keys used elsewhere in this file for JSON/validation errors or add new keys in
the locale files and call them here), keeping the same control flow and
setLoading(false)/return behavior intact.

data.new_tag = formVals.new_tag;
if (
data.model_mapping === undefined &&
data.groups === undefined &&
data.models === undefined &&
data.new_tag === undefined
data.new_tag === undefined &&
data.param_override === undefined &&
data.header_override === undefined
) {
showWarning('没有任何修改!');
setLoading(false);
Expand Down Expand Up @@ -491,6 +530,157 @@ const EditTagModal = (props) => {
</div>
</Card>

<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
{/* Header: Advanced Settings */}
<div className='flex items-center mb-2'>
<Avatar size='small' color='orange' className='mr-2 shadow-md'>
<IconSetting size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>{t('高级设置')}</Text>
<div className='text-xs text-gray-600'>
{t('渠道的高级配置选项')}
</div>
</div>
</div>

<div className='space-y-4'>
<Form.TextArea
field='param_override'
label={t('参数覆盖')}
placeholder={
t(
'此项可选,用于覆盖请求参数。不支持覆盖 stream 参数',
) +
'\n' +
t('旧格式(直接覆盖):') +
'\n{\n "temperature": 0,\n "max_tokens": 1000\n}' +
'\n\n' +
t('新格式(支持条件判断与json自定义):') +
'\n{\n "operations": [\n {\n "path": "temperature",\n "mode": "set",\n "value": 0.7,\n "conditions": [\n {\n "path": "model",\n "mode": "prefix",\n "value": "gpt"\n }\n ]\n }\n ]\n}'
}
autosize
showClear
onChange={(value) =>
handleInputChange('param_override', value)
}
extraText={
<div className='flex gap-2 flex-wrap'>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange(
'param_override',
JSON.stringify({ temperature: 0 }, null, 2),
)
}
>
{t('旧格式模板')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange(
'param_override',
JSON.stringify(
{
operations: [
{
path: 'temperature',
mode: 'set',
value: 0.7,
conditions: [
{
path: 'model',
mode: 'prefix',
value: 'gpt',
},
],
logic: 'AND',
},
],
},
null,
2,
),
)
}
>
{t('新格式模板')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange('param_override', null)
}
>
{t('不更改')}
</Text>
</div>
}
/>

<Form.TextArea
field='header_override'
label={t('请求头覆盖')}
placeholder={
t('此项可选,用于覆盖请求头参数') +
'\n' +
t('格式示例:') +
'\n{\n "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",\n "Authorization": "Bearer {api_key}"\n}'
}
autosize
showClear
onChange={(value) =>
handleInputChange('header_override', value)
}
extraText={
<div className='flex flex-col gap-1'>
<div className='flex gap-2 flex-wrap items-center'>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange(
'header_override',
JSON.stringify(
{
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
Authorization: 'Bearer {api_key}',
},
null,
2,
),
)
}
>
{t('填入模板')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange('header_override', null)
}
>
{t('不更改')}
</Text>
</div>
<div>
<Text type='tertiary' size='small'>
{t('支持变量:')}
</Text>
<div className='text-xs text-tertiary ml-2'>
<div>
{t('渠道密钥')}: {'{api_key}'}
</div>
</div>
</div>
</div>
}
/>
</div>
</Card>

<Card className='!rounded-2xl shadow-sm border-0'>
{/* Header: Group Settings */}
<div className='flex items-center mb-2'>
Expand Down