mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-29 21:38:38 +00:00
feat: EditTagModal header && param (#2159)
This commit is contained in:
@@ -649,13 +649,15 @@ func DeleteDisabledChannel(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChannelTag struct {
|
type ChannelTag struct {
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
NewTag *string `json:"new_tag"`
|
NewTag *string `json:"new_tag"`
|
||||||
Priority *int64 `json:"priority"`
|
Priority *int64 `json:"priority"`
|
||||||
Weight *uint `json:"weight"`
|
Weight *uint `json:"weight"`
|
||||||
ModelMapping *string `json:"model_mapping"`
|
ModelMapping *string `json:"model_mapping"`
|
||||||
Models *string `json:"models"`
|
Models *string `json:"models"`
|
||||||
Groups *string `json:"groups"`
|
Groups *string `json:"groups"`
|
||||||
|
ParamOverride *string `json:"param_override"`
|
||||||
|
HeaderOverride *string `json:"header_override"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableTagChannels(c *gin.Context) {
|
func DisableTagChannels(c *gin.Context) {
|
||||||
@@ -721,7 +723,29 @@ func EditTagChannels(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ApiError(c, err)
|
common.ApiError(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ func DisableChannelByTag(tag string) error {
|
|||||||
return err
|
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{}
|
updateData := Channel{}
|
||||||
shouldReCreateAbilities := false
|
shouldReCreateAbilities := false
|
||||||
updatedTag := tag
|
updatedTag := tag
|
||||||
@@ -714,6 +714,12 @@ func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *
|
|||||||
if weight != nil {
|
if weight != nil {
|
||||||
updateData.Weight = weight
|
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
|
err := DB.Model(&Channel{}).Where("tag = ?", tag).Updates(updateData).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
IconBookmark,
|
IconBookmark,
|
||||||
IconUser,
|
IconUser,
|
||||||
IconCode,
|
IconCode,
|
||||||
|
IconSetting,
|
||||||
} from '@douyinfe/semi-icons';
|
} from '@douyinfe/semi-icons';
|
||||||
import { getChannelModels } from '../../../../helpers';
|
import { getChannelModels } from '../../../../helpers';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -69,6 +70,8 @@ const EditTagModal = (props) => {
|
|||||||
model_mapping: null,
|
model_mapping: null,
|
||||||
groups: [],
|
groups: [],
|
||||||
models: [],
|
models: [],
|
||||||
|
param_override: null,
|
||||||
|
header_override: null,
|
||||||
};
|
};
|
||||||
const [inputs, setInputs] = useState(originInputs);
|
const [inputs, setInputs] = useState(originInputs);
|
||||||
const formApiRef = useRef(null);
|
const formApiRef = useRef(null);
|
||||||
@@ -190,12 +193,48 @@ const EditTagModal = (props) => {
|
|||||||
if (formVals.models && formVals.models.length > 0) {
|
if (formVals.models && formVals.models.length > 0) {
|
||||||
data.models = formVals.models.join(',');
|
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;
|
||||||
|
}
|
||||||
data.new_tag = formVals.new_tag;
|
data.new_tag = formVals.new_tag;
|
||||||
if (
|
if (
|
||||||
data.model_mapping === undefined &&
|
data.model_mapping === undefined &&
|
||||||
data.groups === undefined &&
|
data.groups === undefined &&
|
||||||
data.models === undefined &&
|
data.models === undefined &&
|
||||||
data.new_tag === undefined
|
data.new_tag === undefined &&
|
||||||
|
data.param_override === undefined &&
|
||||||
|
data.header_override === undefined
|
||||||
) {
|
) {
|
||||||
showWarning('没有任何修改!');
|
showWarning('没有任何修改!');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -491,6 +530,157 @@ const EditTagModal = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</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'>
|
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||||
{/* Header: Group Settings */}
|
{/* Header: Group Settings */}
|
||||||
<div className='flex items-center mb-2'>
|
<div className='flex items-center mb-2'>
|
||||||
|
|||||||
Reference in New Issue
Block a user