Skip to content

Commit 019412c

Browse files
authored
feat: EditTagModal header && param (#2159)
1 parent 96a2b81 commit 019412c

File tree

3 files changed

+230
-10
lines changed

3 files changed

+230
-10
lines changed

controller/channel.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -649,13 +649,15 @@ func DeleteDisabledChannel(c *gin.Context) {
649649
}
650650

651651
type ChannelTag struct {
652-
Tag string `json:"tag"`
653-
NewTag *string `json:"new_tag"`
654-
Priority *int64 `json:"priority"`
655-
Weight *uint `json:"weight"`
656-
ModelMapping *string `json:"model_mapping"`
657-
Models *string `json:"models"`
658-
Groups *string `json:"groups"`
652+
Tag string `json:"tag"`
653+
NewTag *string `json:"new_tag"`
654+
Priority *int64 `json:"priority"`
655+
Weight *uint `json:"weight"`
656+
ModelMapping *string `json:"model_mapping"`
657+
Models *string `json:"models"`
658+
Groups *string `json:"groups"`
659+
ParamOverride *string `json:"param_override"`
660+
HeaderOverride *string `json:"header_override"`
659661
}
660662

661663
func DisableTagChannels(c *gin.Context) {
@@ -721,7 +723,29 @@ func EditTagChannels(c *gin.Context) {
721723
})
722724
return
723725
}
724-
err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.ModelMapping, channelTag.Models, channelTag.Groups, channelTag.Priority, channelTag.Weight)
726+
if channelTag.ParamOverride != nil {
727+
trimmed := strings.TrimSpace(*channelTag.ParamOverride)
728+
if trimmed != "" && !json.Valid([]byte(trimmed)) {
729+
c.JSON(http.StatusOK, gin.H{
730+
"success": false,
731+
"message": "参数覆盖必须是合法的 JSON 格式",
732+
})
733+
return
734+
}
735+
channelTag.ParamOverride = common.GetPointer[string](trimmed)
736+
}
737+
if channelTag.HeaderOverride != nil {
738+
trimmed := strings.TrimSpace(*channelTag.HeaderOverride)
739+
if trimmed != "" && !json.Valid([]byte(trimmed)) {
740+
c.JSON(http.StatusOK, gin.H{
741+
"success": false,
742+
"message": "请求头覆盖必须是合法的 JSON 格式",
743+
})
744+
return
745+
}
746+
channelTag.HeaderOverride = common.GetPointer[string](trimmed)
747+
}
748+
err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.ModelMapping, channelTag.Models, channelTag.Groups, channelTag.Priority, channelTag.Weight, channelTag.ParamOverride, channelTag.HeaderOverride)
725749
if err != nil {
726750
common.ApiError(c, err)
727751
return

model/channel.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ func DisableChannelByTag(tag string) error {
688688
return err
689689
}
690690

691-
func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *string, group *string, priority *int64, weight *uint) error {
691+
func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *string, group *string, priority *int64, weight *uint, paramOverride *string, headerOverride *string) error {
692692
updateData := Channel{}
693693
shouldReCreateAbilities := false
694694
updatedTag := tag
@@ -714,6 +714,12 @@ func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *
714714
if weight != nil {
715715
updateData.Weight = weight
716716
}
717+
if paramOverride != nil {
718+
updateData.ParamOverride = paramOverride
719+
}
720+
if headerOverride != nil {
721+
updateData.HeaderOverride = headerOverride
722+
}
717723

718724
err := DB.Model(&Channel{}).Where("tag = ?", tag).Updates(updateData).Error
719725
if err != nil {

web/src/components/table/channels/modals/EditTagModal.jsx

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
IconBookmark,
4646
IconUser,
4747
IconCode,
48+
IconSetting,
4849
} from '@douyinfe/semi-icons';
4950
import { getChannelModels } from '../../../../helpers';
5051
import { useTranslation } from 'react-i18next';
@@ -69,6 +70,8 @@ const EditTagModal = (props) => {
6970
model_mapping: null,
7071
groups: [],
7172
models: [],
73+
param_override: null,
74+
header_override: null,
7275
};
7376
const [inputs, setInputs] = useState(originInputs);
7477
const formApiRef = useRef(null);
@@ -190,12 +193,48 @@ const EditTagModal = (props) => {
190193
if (formVals.models && formVals.models.length > 0) {
191194
data.models = formVals.models.join(',');
192195
}
196+
if (
197+
formVals.param_override !== undefined &&
198+
formVals.param_override !== null
199+
) {
200+
if (typeof formVals.param_override !== 'string') {
201+
showInfo('参数覆盖必须是合法的 JSON 格式!');
202+
setLoading(false);
203+
return;
204+
}
205+
const trimmedParamOverride = formVals.param_override.trim();
206+
if (trimmedParamOverride !== '' && !verifyJSON(trimmedParamOverride)) {
207+
showInfo('参数覆盖必须是合法的 JSON 格式!');
208+
setLoading(false);
209+
return;
210+
}
211+
data.param_override = trimmedParamOverride;
212+
}
213+
if (
214+
formVals.header_override !== undefined &&
215+
formVals.header_override !== null
216+
) {
217+
if (typeof formVals.header_override !== 'string') {
218+
showInfo('请求头覆盖必须是合法的 JSON 格式!');
219+
setLoading(false);
220+
return;
221+
}
222+
const trimmedHeaderOverride = formVals.header_override.trim();
223+
if (trimmedHeaderOverride !== '' && !verifyJSON(trimmedHeaderOverride)) {
224+
showInfo('请求头覆盖必须是合法的 JSON 格式!');
225+
setLoading(false);
226+
return;
227+
}
228+
data.header_override = trimmedHeaderOverride;
229+
}
193230
data.new_tag = formVals.new_tag;
194231
if (
195232
data.model_mapping === undefined &&
196233
data.groups === undefined &&
197234
data.models === undefined &&
198-
data.new_tag === undefined
235+
data.new_tag === undefined &&
236+
data.param_override === undefined &&
237+
data.header_override === undefined
199238
) {
200239
showWarning('没有任何修改!');
201240
setLoading(false);
@@ -491,6 +530,157 @@ const EditTagModal = (props) => {
491530
</div>
492531
</Card>
493532

533+
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
534+
{/* Header: Advanced Settings */}
535+
<div className='flex items-center mb-2'>
536+
<Avatar size='small' color='orange' className='mr-2 shadow-md'>
537+
<IconSetting size={16} />
538+
</Avatar>
539+
<div>
540+
<Text className='text-lg font-medium'>{t('高级设置')}</Text>
541+
<div className='text-xs text-gray-600'>
542+
{t('渠道的高级配置选项')}
543+
</div>
544+
</div>
545+
</div>
546+
547+
<div className='space-y-4'>
548+
<Form.TextArea
549+
field='param_override'
550+
label={t('参数覆盖')}
551+
placeholder={
552+
t(
553+
'此项可选,用于覆盖请求参数。不支持覆盖 stream 参数',
554+
) +
555+
'\n' +
556+
t('旧格式(直接覆盖):') +
557+
'\n{\n "temperature": 0,\n "max_tokens": 1000\n}' +
558+
'\n\n' +
559+
t('新格式(支持条件判断与json自定义):') +
560+
'\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}'
561+
}
562+
autosize
563+
showClear
564+
onChange={(value) =>
565+
handleInputChange('param_override', value)
566+
}
567+
extraText={
568+
<div className='flex gap-2 flex-wrap'>
569+
<Text
570+
className='!text-semi-color-primary cursor-pointer'
571+
onClick={() =>
572+
handleInputChange(
573+
'param_override',
574+
JSON.stringify({ temperature: 0 }, null, 2),
575+
)
576+
}
577+
>
578+
{t('旧格式模板')}
579+
</Text>
580+
<Text
581+
className='!text-semi-color-primary cursor-pointer'
582+
onClick={() =>
583+
handleInputChange(
584+
'param_override',
585+
JSON.stringify(
586+
{
587+
operations: [
588+
{
589+
path: 'temperature',
590+
mode: 'set',
591+
value: 0.7,
592+
conditions: [
593+
{
594+
path: 'model',
595+
mode: 'prefix',
596+
value: 'gpt',
597+
},
598+
],
599+
logic: 'AND',
600+
},
601+
],
602+
},
603+
null,
604+
2,
605+
),
606+
)
607+
}
608+
>
609+
{t('新格式模板')}
610+
</Text>
611+
<Text
612+
className='!text-semi-color-primary cursor-pointer'
613+
onClick={() =>
614+
handleInputChange('param_override', null)
615+
}
616+
>
617+
{t('不更改')}
618+
</Text>
619+
</div>
620+
}
621+
/>
622+
623+
<Form.TextArea
624+
field='header_override'
625+
label={t('请求头覆盖')}
626+
placeholder={
627+
t('此项可选,用于覆盖请求头参数') +
628+
'\n' +
629+
t('格式示例:') +
630+
'\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}'
631+
}
632+
autosize
633+
showClear
634+
onChange={(value) =>
635+
handleInputChange('header_override', value)
636+
}
637+
extraText={
638+
<div className='flex flex-col gap-1'>
639+
<div className='flex gap-2 flex-wrap items-center'>
640+
<Text
641+
className='!text-semi-color-primary cursor-pointer'
642+
onClick={() =>
643+
handleInputChange(
644+
'header_override',
645+
JSON.stringify(
646+
{
647+
'User-Agent':
648+
'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',
649+
Authorization: 'Bearer {api_key}',
650+
},
651+
null,
652+
2,
653+
),
654+
)
655+
}
656+
>
657+
{t('填入模板')}
658+
</Text>
659+
<Text
660+
className='!text-semi-color-primary cursor-pointer'
661+
onClick={() =>
662+
handleInputChange('header_override', null)
663+
}
664+
>
665+
{t('不更改')}
666+
</Text>
667+
</div>
668+
<div>
669+
<Text type='tertiary' size='small'>
670+
{t('支持变量:')}
671+
</Text>
672+
<div className='text-xs text-tertiary ml-2'>
673+
<div>
674+
{t('渠道密钥')}: {'{api_key}'}
675+
</div>
676+
</div>
677+
</div>
678+
</div>
679+
}
680+
/>
681+
</div>
682+
</Card>
683+
494684
<Card className='!rounded-2xl shadow-sm border-0'>
495685
{/* Header: Group Settings */}
496686
<div className='flex items-center mb-2'>

0 commit comments

Comments
 (0)