Merge pull request #927 from QuentinHsu/refactor-system-setting
# Conflicts: # web/src/App.js # web/src/components/ModelSetting.js # web/src/components/PersonalSetting.js # web/src/components/SystemSetting.js # web/src/pages/Channel/EditChannel.js
This commit is contained in:
@@ -6,8 +6,9 @@ import {
|
||||
isMobile,
|
||||
showError,
|
||||
showInfo,
|
||||
showSuccess, showWarning,
|
||||
verifyJSON
|
||||
showSuccess,
|
||||
showWarning,
|
||||
verifyJSON,
|
||||
} from '../../helpers';
|
||||
import { CHANNEL_OPTIONS } from '../../constants';
|
||||
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
|
||||
@@ -22,21 +23,22 @@ import {
|
||||
Select,
|
||||
TextArea,
|
||||
Checkbox,
|
||||
Banner, Modal
|
||||
Banner,
|
||||
Modal,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { getChannelModels, loadChannelModels } from '../../components/utils.js';
|
||||
|
||||
const MODEL_MAPPING_EXAMPLE = {
|
||||
'gpt-3.5-turbo': 'gpt-3.5-turbo-0125'
|
||||
'gpt-3.5-turbo': 'gpt-3.5-turbo-0125',
|
||||
};
|
||||
|
||||
const STATUS_CODE_MAPPING_EXAMPLE = {
|
||||
400: '500'
|
||||
400: '500',
|
||||
};
|
||||
|
||||
const REGION_EXAMPLE = {
|
||||
'default': 'us-central1',
|
||||
'claude-3-5-sonnet-20240620': 'europe-west1'
|
||||
default: 'us-central1',
|
||||
'claude-3-5-sonnet-20240620': 'europe-west1',
|
||||
};
|
||||
|
||||
function type2secretPrompt(type) {
|
||||
@@ -82,7 +84,7 @@ const EditChannel = (props) => {
|
||||
groups: ['default'],
|
||||
priority: 0,
|
||||
weight: 0,
|
||||
tag: ''
|
||||
tag: '',
|
||||
};
|
||||
const [batch, setBatch] = useState(false);
|
||||
const [autoBan, setAutoBan] = useState(true);
|
||||
@@ -98,12 +100,13 @@ const EditChannel = (props) => {
|
||||
if (name === 'base_url' && value.endsWith('/v1')) {
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '不需要在末尾加/v1,New API会自动处理,添加后可能导致请求失败,是否继续?',
|
||||
content:
|
||||
'不需要在末尾加/v1,New API会自动处理,添加后可能导致请求失败,是否继续?',
|
||||
onOk: () => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
}
|
||||
})
|
||||
return
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
if (name === 'type') {
|
||||
@@ -117,7 +120,7 @@ const EditChannel = (props) => {
|
||||
'mj_blend',
|
||||
'mj_upscale',
|
||||
'mj_describe',
|
||||
'mj_uploads'
|
||||
'mj_uploads',
|
||||
];
|
||||
break;
|
||||
case 5:
|
||||
@@ -137,14 +140,11 @@ const EditChannel = (props) => {
|
||||
'mj_high_variation',
|
||||
'mj_low_variation',
|
||||
'mj_pan',
|
||||
'mj_uploads'
|
||||
'mj_uploads',
|
||||
];
|
||||
break;
|
||||
case 36:
|
||||
localModels = [
|
||||
'suno_music',
|
||||
'suno_lyrics'
|
||||
];
|
||||
localModels = ['suno_music', 'suno_lyrics'];
|
||||
break;
|
||||
default:
|
||||
localModels = getChannelModels(value);
|
||||
@@ -180,7 +180,7 @@ const EditChannel = (props) => {
|
||||
data.model_mapping = JSON.stringify(
|
||||
JSON.parse(data.model_mapping),
|
||||
null,
|
||||
2
|
||||
2,
|
||||
);
|
||||
}
|
||||
setInputs(data);
|
||||
@@ -197,7 +197,6 @@ const EditChannel = (props) => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
const fetchUpstreamModelList = async (name) => {
|
||||
// if (inputs['type'] !== 1) {
|
||||
// showError(t('仅支持 OpenAI 接口格式'));
|
||||
@@ -225,9 +224,9 @@ const EditChannel = (props) => {
|
||||
const res = await API.post('/api/channel/fetch_models', {
|
||||
base_url: inputs['base_url'],
|
||||
type: inputs['type'],
|
||||
key: inputs['key']
|
||||
key: inputs['key'],
|
||||
});
|
||||
|
||||
|
||||
if (res.data && res.data.success) {
|
||||
models.push(...res.data.data);
|
||||
} else {
|
||||
@@ -254,7 +253,7 @@ const EditChannel = (props) => {
|
||||
let res = await API.get(`/api/channel/models`);
|
||||
let localModelOptions = res.data.data.map((model) => ({
|
||||
label: model.id,
|
||||
value: model.id
|
||||
value: model.id,
|
||||
}));
|
||||
setOriginModelOptions(localModelOptions);
|
||||
setFullModels(res.data.data.map((model) => model.id));
|
||||
@@ -263,7 +262,7 @@ const EditChannel = (props) => {
|
||||
.filter((model) => {
|
||||
return model.id.startsWith('gpt-') || model.id.startsWith('text-');
|
||||
})
|
||||
.map((model) => model.id)
|
||||
.map((model) => model.id),
|
||||
);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
@@ -279,8 +278,8 @@ const EditChannel = (props) => {
|
||||
setGroupOptions(
|
||||
res.data.data.map((group) => ({
|
||||
label: group,
|
||||
value: group
|
||||
}))
|
||||
value: group,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
@@ -293,7 +292,7 @@ const EditChannel = (props) => {
|
||||
if (!localModelOptions.find((option) => option.label === model)) {
|
||||
localModelOptions.push({
|
||||
label: model,
|
||||
value: model
|
||||
value: model,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -304,7 +303,7 @@ const EditChannel = (props) => {
|
||||
fetchModels().then();
|
||||
fetchGroups().then();
|
||||
if (isEdit) {
|
||||
loadChannel().then(() => {});
|
||||
loadChannel().then(() => { });
|
||||
} else {
|
||||
setInputs(originInputs);
|
||||
let localModels = getChannelModels(inputs.type);
|
||||
@@ -330,7 +329,7 @@ const EditChannel = (props) => {
|
||||
if (localInputs.base_url && localInputs.base_url.endsWith('/')) {
|
||||
localInputs.base_url = localInputs.base_url.slice(
|
||||
0,
|
||||
localInputs.base_url.length - 1
|
||||
localInputs.base_url.length - 1,
|
||||
);
|
||||
}
|
||||
if (localInputs.type === 18 && localInputs.other === '') {
|
||||
@@ -348,7 +347,7 @@ const EditChannel = (props) => {
|
||||
if (isEdit) {
|
||||
res = await API.put(`/api/channel/`, {
|
||||
...localInputs,
|
||||
id: parseInt(channelId)
|
||||
id: parseInt(channelId),
|
||||
});
|
||||
} else {
|
||||
res = await API.post(`/api/channel/`, localInputs);
|
||||
@@ -382,7 +381,7 @@ const EditChannel = (props) => {
|
||||
localModelOptions.push({
|
||||
key: model,
|
||||
text: model,
|
||||
value: model
|
||||
value: model,
|
||||
});
|
||||
} else if (model) {
|
||||
showError(t('某些模型已存在!'));
|
||||
@@ -397,14 +396,15 @@ const EditChannel = (props) => {
|
||||
handleInputChange('models', localModels);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<SideSheet
|
||||
maskClosable={false}
|
||||
placement={isEdit ? 'right' : 'left'}
|
||||
title={
|
||||
<Title level={3}>{isEdit ? t('更新渠道信息') : t('创建新的渠道')}</Title>
|
||||
<Title level={3}>
|
||||
{isEdit ? t('更新渠道信息') : t('创建新的渠道')}
|
||||
</Title>
|
||||
}
|
||||
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
|
||||
@@ -412,11 +412,11 @@ const EditChannel = (props) => {
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Space>
|
||||
<Button theme="solid" size={'large'} onClick={submit}>
|
||||
<Button theme='solid' size={'large'} onClick={submit}>
|
||||
{t('提交')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="solid"
|
||||
theme='solid'
|
||||
size={'large'}
|
||||
type={'tertiary'}
|
||||
onClick={handleCancel}
|
||||
@@ -432,11 +432,10 @@ const EditChannel = (props) => {
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
|
||||
<Typography.Text strong>{t('类型')}:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
name="type"
|
||||
name='type'
|
||||
required
|
||||
optionList={CHANNEL_OPTIONS}
|
||||
value={inputs.type}
|
||||
@@ -449,17 +448,17 @@ const EditChannel = (props) => {
|
||||
{inputs.type === 40 && (
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type="info"
|
||||
type='info'
|
||||
description={
|
||||
<div>
|
||||
<Typography.Text strong>
|
||||
{t('邀请链接')}:
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
<Typography.Text strong>{t('邀请链接')}:</Typography.Text>
|
||||
<Typography.Text
|
||||
link
|
||||
underline
|
||||
style={{marginLeft: 8}}
|
||||
onClick={() => window.open('https://cloud.siliconflow.cn/i/hij0YNTZ')}
|
||||
underline
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={() =>
|
||||
window.open('https://cloud.siliconflow.cn/i/hij0YNTZ')
|
||||
}
|
||||
>
|
||||
https://cloud.siliconflow.cn/i/hij0YNTZ
|
||||
</Typography.Text>
|
||||
@@ -482,27 +481,29 @@ const EditChannel = (props) => {
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="AZURE_OPENAI_ENDPOINT"
|
||||
name="azure_base_url"
|
||||
placeholder={t('请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com')}
|
||||
label='AZURE_OPENAI_ENDPOINT'
|
||||
name='azure_base_url'
|
||||
placeholder={t(
|
||||
'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com',
|
||||
)}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
value={inputs.base_url}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>{t('默认 API 版本')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label={t('默认 API 版本')}
|
||||
name="azure_other"
|
||||
name='azure_other'
|
||||
placeholder={t('请输入默认 API 版本,例如:2024-12-01-preview')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
value={inputs.other}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -511,7 +512,9 @@ const EditChannel = (props) => {
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type={'warning'}
|
||||
description={t('如果你对接的是上游One API或者New API等转发项目,请使用OpenAI类型,不要使用此类型,除非你知道你在做什么。')}
|
||||
description={t(
|
||||
'如果你对接的是上游One API或者New API等转发项目,请使用OpenAI类型,不要使用此类型,除非你知道你在做什么。',
|
||||
)}
|
||||
></Banner>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
@@ -520,13 +523,15 @@ const EditChannel = (props) => {
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="base_url"
|
||||
placeholder={t('请输入完整的URL,例如:https://api.openai.com/v1/chat/completions')}
|
||||
name='base_url'
|
||||
placeholder={t(
|
||||
'请输入完整的URL,例如:https://api.openai.com/v1/chat/completions',
|
||||
)}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
value={inputs.base_url}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -535,7 +540,9 @@ const EditChannel = (props) => {
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type={'warning'}
|
||||
description={t('Dify渠道只适配chatflow和agent,并且agent不支持图片!')}
|
||||
description={t(
|
||||
'Dify渠道只适配chatflow和agent,并且agent不支持图片!',
|
||||
)}
|
||||
></Banner>
|
||||
</div>
|
||||
</>
|
||||
@@ -545,13 +552,13 @@ const EditChannel = (props) => {
|
||||
</div>
|
||||
<Input
|
||||
required
|
||||
name="name"
|
||||
name='name'
|
||||
placeholder={t('请为渠道命名')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('name', value);
|
||||
}}
|
||||
value={inputs.name}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
{inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && inputs.type !== 45 && (
|
||||
<>
|
||||
@@ -578,7 +585,7 @@ const EditChannel = (props) => {
|
||||
{batch ? (
|
||||
<TextArea
|
||||
label={t('密钥')}
|
||||
name="key"
|
||||
name='key'
|
||||
required
|
||||
placeholder={t('请输入密钥,一行一个')}
|
||||
onChange={(value) => {
|
||||
@@ -586,16 +593,17 @@ const EditChannel = (props) => {
|
||||
}}
|
||||
value={inputs.key}
|
||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{inputs.type === 41 ? (
|
||||
<TextArea
|
||||
label={t('鉴权json')}
|
||||
name="key"
|
||||
name='key'
|
||||
required
|
||||
placeholder={'{\n' +
|
||||
placeholder={
|
||||
'{\n' +
|
||||
' "type": "service_account",\n' +
|
||||
' "project_id": "abc-bcd-123-456",\n' +
|
||||
' "private_key_id": "123xxxxx456",\n' +
|
||||
@@ -607,25 +615,26 @@ const EditChannel = (props) => {
|
||||
' "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",\n' +
|
||||
' "client_x509_cert_url": "https://xxxxx.gserviceaccount.com",\n' +
|
||||
' "universe_domain": "googleapis.com"\n' +
|
||||
'}'}
|
||||
'}'
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleInputChange('key', value);
|
||||
}}
|
||||
autosize={{ minRows: 10 }}
|
||||
value={inputs.key}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
label={t('密钥')}
|
||||
name="key"
|
||||
name='key'
|
||||
required
|
||||
placeholder={t(type2secretPrompt(inputs.type))}
|
||||
onChange={(value) => {
|
||||
handleInputChange('key', value);
|
||||
}}
|
||||
value={inputs.key}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -636,7 +645,7 @@ const EditChannel = (props) => {
|
||||
<Checkbox
|
||||
checked={batch}
|
||||
label={t('批量创建')}
|
||||
name="batch"
|
||||
name='batch'
|
||||
onChange={() => setBatch(!batch)}
|
||||
/>
|
||||
<Typography.Text strong>{t('批量创建')}</Typography.Text>
|
||||
@@ -649,13 +658,15 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>{t('私有部署地址')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="base_url"
|
||||
placeholder={t('请输入私有部署地址,格式为:https://fastgpt.run/api/openapi')}
|
||||
name='base_url'
|
||||
placeholder={t(
|
||||
'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi',
|
||||
)}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
value={inputs.base_url}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -663,17 +674,21 @@ const EditChannel = (props) => {
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
{t('注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用')}
|
||||
{t(
|
||||
'注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用',
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="base_url"
|
||||
placeholder={t('请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com')}
|
||||
name='base_url'
|
||||
placeholder={t(
|
||||
'请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com',
|
||||
)}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
value={inputs.base_url}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -682,7 +697,7 @@ const EditChannel = (props) => {
|
||||
</div>
|
||||
<Select
|
||||
placeholder={t('请选择可以使用该渠道的分组')}
|
||||
name="groups"
|
||||
name='groups'
|
||||
required
|
||||
multiple
|
||||
selection
|
||||
@@ -692,7 +707,7 @@ const EditChannel = (props) => {
|
||||
handleInputChange('groups', value);
|
||||
}}
|
||||
value={inputs.groups}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
optionList={groupOptions}
|
||||
/>
|
||||
{inputs.type === 18 && (
|
||||
@@ -701,7 +716,7 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>模型版本:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="other"
|
||||
name='other'
|
||||
placeholder={
|
||||
'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'
|
||||
}
|
||||
@@ -709,7 +724,7 @@ const EditChannel = (props) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
value={inputs.other}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -719,29 +734,31 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>{t('部署地区')}:</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
name="other"
|
||||
placeholder={t('请输入部署地区,例如:us-central1\n支持使用模型映射格式\n' +
|
||||
name='other'
|
||||
placeholder={t(
|
||||
'请输入部署地区,例如:us-central1\n支持使用模型映射格式\n' +
|
||||
'{\n' +
|
||||
' "default": "us-central1",\n' +
|
||||
' "claude-3-5-sonnet-20240620": "europe-west1"\n' +
|
||||
'}')}
|
||||
'}',
|
||||
)}
|
||||
autosize={{ minRows: 2 }}
|
||||
onChange={(value) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
value={inputs.other}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'other',
|
||||
JSON.stringify(REGION_EXAMPLE, null, 2)
|
||||
JSON.stringify(REGION_EXAMPLE, null, 2),
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -755,14 +772,14 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>知识库 ID:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label="知识库 ID"
|
||||
name="other"
|
||||
label='知识库 ID'
|
||||
name='other'
|
||||
placeholder={'请输入知识库 ID,例如:123456'}
|
||||
onChange={(value) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
value={inputs.other}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -772,7 +789,7 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>Account ID:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="other"
|
||||
name='other'
|
||||
placeholder={
|
||||
'请输入Account ID,例如:d6b5da8hk1awo8nap34ube6gh'
|
||||
}
|
||||
@@ -780,7 +797,7 @@ const EditChannel = (props) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
value={inputs.other}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -789,7 +806,7 @@ const EditChannel = (props) => {
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择该渠道所支持的模型'}
|
||||
name="models"
|
||||
name='models'
|
||||
required
|
||||
multiple
|
||||
selection
|
||||
@@ -799,13 +816,13 @@ const EditChannel = (props) => {
|
||||
handleInputChange('models', value);
|
||||
}}
|
||||
value={inputs.models}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
optionList={modelOptions}
|
||||
/>
|
||||
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
type='primary'
|
||||
onClick={() => {
|
||||
handleInputChange('models', basicModels);
|
||||
}}
|
||||
@@ -813,16 +830,20 @@ const EditChannel = (props) => {
|
||||
{t('填入相关模型')}
|
||||
</Button>
|
||||
<Button
|
||||
type="secondary"
|
||||
type='secondary'
|
||||
onClick={() => {
|
||||
handleInputChange('models', fullModels);
|
||||
}}
|
||||
>
|
||||
{t('填入所有模型')}
|
||||
</Button>
|
||||
<Tooltip content={t('新建渠道时,请求通过当前浏览器发出;编辑已有渠道,请求通过后端服务器发出')}>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'新建渠道时,请求通过当前浏览器发出;编辑已有渠道,请求通过后端服务器发出',
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
type="tertiary"
|
||||
type='tertiary'
|
||||
onClick={() => {
|
||||
fetchUpstreamModelList('models');
|
||||
}}
|
||||
@@ -831,7 +852,7 @@ const EditChannel = (props) => {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
type="warning"
|
||||
type='warning'
|
||||
onClick={() => {
|
||||
handleInputChange('models', []);
|
||||
}}
|
||||
@@ -841,7 +862,7 @@ const EditChannel = (props) => {
|
||||
</Space>
|
||||
<Input
|
||||
addonAfter={
|
||||
<Button type="primary" onClick={addCustomModels}>
|
||||
<Button type='primary' onClick={addCustomModels}>
|
||||
{t('填入')}
|
||||
</Button>
|
||||
}
|
||||
@@ -856,53 +877,53 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>{t('模型重定向')}:</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:') + `\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
|
||||
name="model_mapping"
|
||||
placeholder={
|
||||
t(
|
||||
'此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:',
|
||||
) + `\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`
|
||||
}
|
||||
name='model_mapping'
|
||||
onChange={(value) => {
|
||||
handleInputChange('model_mapping', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.model_mapping}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'model_mapping',
|
||||
JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)
|
||||
JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('填入模板')}
|
||||
</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
{t('渠道标签')}
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{t('渠道标签')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label={t('渠道标签')}
|
||||
name="tag"
|
||||
name='tag'
|
||||
placeholder={t('渠道标签')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('tag', value);
|
||||
}}
|
||||
value={inputs.tag}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
{t('渠道优先级')}
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{t('渠道优先级')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label={t('渠道优先级')}
|
||||
name="priority"
|
||||
name='priority'
|
||||
placeholder={t('渠道优先级')}
|
||||
onChange={(value) => {
|
||||
const number = parseInt(value);
|
||||
@@ -913,16 +934,14 @@ const EditChannel = (props) => {
|
||||
}
|
||||
}}
|
||||
value={inputs.priority}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
{t('渠道权重')}
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{t('渠道权重')}</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
label={t('渠道权重')}
|
||||
name="weight"
|
||||
name='weight'
|
||||
placeholder={t('渠道权重')}
|
||||
onChange={(value) => {
|
||||
const number = parseInt(value);
|
||||
@@ -933,37 +952,43 @@ const EditChannel = (props) => {
|
||||
}
|
||||
}}
|
||||
value={inputs.weight}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
{t('渠道额外设置')}:
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{t('渠道额外设置')}:</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={t('此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:') + '\n{\n "force_format": true\n}'}
|
||||
name="setting"
|
||||
placeholder={
|
||||
t(
|
||||
'此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:',
|
||||
) + '\n{\n "force_format": true\n}'
|
||||
}
|
||||
name='setting'
|
||||
onChange={(value) => {
|
||||
handleInputChange('setting', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.setting}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Space>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'setting',
|
||||
JSON.stringify({
|
||||
force_format: true
|
||||
}, null, 2)
|
||||
JSON.stringify(
|
||||
{
|
||||
force_format: true,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -973,10 +998,12 @@ const EditChannel = (props) => {
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
window.open('https://github.com/Calcium-Ion/new-api/blob/main/docs/channel/other_setting.md');
|
||||
window.open(
|
||||
'https://github.com/Calcium-Ion/new-api/blob/main/docs/channel/other_setting.md',
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('设置说明')}
|
||||
@@ -985,19 +1012,21 @@ const EditChannel = (props) => {
|
||||
</>
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
{t('参数覆盖')}:
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{t('参数覆盖')}:</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:') + '\n{\n "temperature": 0\n}'}
|
||||
name="setting"
|
||||
placeholder={
|
||||
t(
|
||||
'此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:',
|
||||
) + '\n{\n "temperature": 0\n}'
|
||||
}
|
||||
name='setting'
|
||||
onChange={(value) => {
|
||||
handleInputChange('param_override', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.param_override}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
{inputs.type === 1 && (
|
||||
@@ -1007,7 +1036,7 @@ const EditChannel = (props) => {
|
||||
</div>
|
||||
<Input
|
||||
label={t('组织,可选,不填则为默认组织')}
|
||||
name="openai_organization"
|
||||
name='openai_organization'
|
||||
placeholder={t('请输入组织org-xxx')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('openai_organization', value);
|
||||
@@ -1020,7 +1049,7 @@ const EditChannel = (props) => {
|
||||
<Typography.Text strong>{t('默认测试模型')}:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name="test_model"
|
||||
name='test_model'
|
||||
placeholder={t('不填则为模型列表第一个')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('test_model', value);
|
||||
@@ -1030,14 +1059,16 @@ const EditChannel = (props) => {
|
||||
<div style={{ marginTop: 10, display: 'flex' }}>
|
||||
<Space>
|
||||
<Checkbox
|
||||
name="auto_ban"
|
||||
name='auto_ban'
|
||||
checked={autoBan}
|
||||
onChange={() => {
|
||||
setAutoBan(!autoBan);
|
||||
}}
|
||||
/>
|
||||
<Typography.Text strong>
|
||||
{t('是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:')}
|
||||
{t(
|
||||
'是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:',
|
||||
)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -1047,26 +1078,31 @@ const EditChannel = (props) => {
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={t('此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:') +
|
||||
'\n' + JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}
|
||||
name="status_code_mapping"
|
||||
placeholder={
|
||||
t(
|
||||
'此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:',
|
||||
) +
|
||||
'\n' +
|
||||
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)
|
||||
}
|
||||
name='status_code_mapping'
|
||||
onChange={(value) => {
|
||||
handleInputChange('status_code_mapping', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.status_code_mapping}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'status_code_mapping',
|
||||
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)
|
||||
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2),
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { API, showError, showInfo, showSuccess, showWarning, verifyJSON } from '../../helpers';
|
||||
import { SideSheet, Space, Button, Input, Typography, Spin, Modal, Select, Banner, TextArea } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
API,
|
||||
showError,
|
||||
showInfo,
|
||||
showSuccess,
|
||||
showWarning,
|
||||
verifyJSON,
|
||||
} from '../../helpers';
|
||||
import {
|
||||
SideSheet,
|
||||
Space,
|
||||
Button,
|
||||
Input,
|
||||
Typography,
|
||||
Spin,
|
||||
Modal,
|
||||
Select,
|
||||
Banner,
|
||||
TextArea,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import TextInput from '../../components/custom/TextInput.js';
|
||||
import { getChannelModels } from '../../components/utils.js';
|
||||
|
||||
const MODEL_MAPPING_EXAMPLE = {
|
||||
'gpt-3.5-turbo': 'gpt-3.5-turbo-0125'
|
||||
'gpt-3.5-turbo': 'gpt-3.5-turbo-0125',
|
||||
};
|
||||
|
||||
const EditTagModal = (props) => {
|
||||
@@ -23,7 +41,7 @@ const EditTagModal = (props) => {
|
||||
model_mapping: null,
|
||||
groups: [],
|
||||
models: [],
|
||||
}
|
||||
};
|
||||
const [inputs, setInputs] = useState(originInputs);
|
||||
|
||||
const handleInputChange = (name, value) => {
|
||||
@@ -39,7 +57,7 @@ const EditTagModal = (props) => {
|
||||
'mj_blend',
|
||||
'mj_upscale',
|
||||
'mj_describe',
|
||||
'mj_uploads'
|
||||
'mj_uploads',
|
||||
];
|
||||
break;
|
||||
case 5:
|
||||
@@ -59,14 +77,11 @@ const EditTagModal = (props) => {
|
||||
'mj_high_variation',
|
||||
'mj_low_variation',
|
||||
'mj_pan',
|
||||
'mj_uploads'
|
||||
'mj_uploads',
|
||||
];
|
||||
break;
|
||||
case 36:
|
||||
localModels = [
|
||||
'suno_music',
|
||||
'suno_lyrics'
|
||||
];
|
||||
localModels = ['suno_music', 'suno_lyrics'];
|
||||
break;
|
||||
default:
|
||||
localModels = getChannelModels(value);
|
||||
@@ -84,7 +99,7 @@ const EditTagModal = (props) => {
|
||||
let res = await API.get(`/api/channel/models`);
|
||||
let localModelOptions = res.data.data.map((model) => ({
|
||||
label: model.id,
|
||||
value: model.id
|
||||
value: model.id,
|
||||
}));
|
||||
setOriginModelOptions(localModelOptions);
|
||||
setFullModels(res.data.data.map((model) => model.id));
|
||||
@@ -93,7 +108,7 @@ const EditTagModal = (props) => {
|
||||
.filter((model) => {
|
||||
return model.id.startsWith('gpt-') || model.id.startsWith('text-');
|
||||
})
|
||||
.map((model) => model.id)
|
||||
.map((model) => model.id),
|
||||
);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
@@ -109,27 +124,26 @@ const EditTagModal = (props) => {
|
||||
setGroupOptions(
|
||||
res.data.data.map((group) => ({
|
||||
label: group,
|
||||
value: group
|
||||
}))
|
||||
value: group,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSave = async () => {
|
||||
setLoading(true);
|
||||
let data = {
|
||||
tag: tag,
|
||||
}
|
||||
};
|
||||
if (inputs.model_mapping !== null && inputs.model_mapping !== '') {
|
||||
if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
|
||||
showInfo('模型映射必须是合法的 JSON 格式!');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
data.model_mapping = inputs.model_mapping
|
||||
data.model_mapping = inputs.model_mapping;
|
||||
}
|
||||
if (inputs.groups.length > 0) {
|
||||
data.groups = inputs.groups.join(',');
|
||||
@@ -139,7 +153,12 @@ const EditTagModal = (props) => {
|
||||
}
|
||||
data.new_tag = inputs.new_tag;
|
||||
// check have any change
|
||||
if (data.model_mapping === undefined && data.groups === undefined && data.models === undefined && data.new_tag === undefined) {
|
||||
if (
|
||||
data.model_mapping === undefined &&
|
||||
data.groups === undefined &&
|
||||
data.models === undefined &&
|
||||
data.new_tag === undefined
|
||||
) {
|
||||
showWarning('没有任何修改!');
|
||||
setLoading(false);
|
||||
return;
|
||||
@@ -159,7 +178,7 @@ const EditTagModal = (props) => {
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let localModelOptions = [...originModelOptions];
|
||||
@@ -167,7 +186,7 @@ const EditTagModal = (props) => {
|
||||
if (!localModelOptions.find((option) => option.label === model)) {
|
||||
localModelOptions.push({
|
||||
label: model,
|
||||
value: model
|
||||
value: model,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -179,7 +198,7 @@ const EditTagModal = (props) => {
|
||||
...originInputs,
|
||||
tag: tag,
|
||||
new_tag: tag,
|
||||
})
|
||||
});
|
||||
fetchModels().then();
|
||||
fetchGroups().then();
|
||||
}, [visible]);
|
||||
@@ -201,7 +220,7 @@ const EditTagModal = (props) => {
|
||||
// 添加到下拉选项
|
||||
key: model,
|
||||
text: model,
|
||||
value: model
|
||||
value: model,
|
||||
});
|
||||
} else if (model) {
|
||||
showError('某些模型已存在!');
|
||||
@@ -217,17 +236,18 @@ const EditTagModal = (props) => {
|
||||
handleInputChange('models', localModels);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<SideSheet
|
||||
title="编辑标签"
|
||||
title='编辑标签'
|
||||
visible={visible}
|
||||
onCancel={handleClose}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Space>
|
||||
<Button onClick={handleClose}>取消</Button>
|
||||
<Button type="primary" onClick={handleSave} loading={loading}>保存</Button>
|
||||
<Button type='primary' onClick={handleSave} loading={loading}>
|
||||
保存
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
@@ -235,27 +255,23 @@ const EditTagModal = (props) => {
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Banner
|
||||
type={'warning'}
|
||||
description={
|
||||
<>
|
||||
所有编辑均为覆盖操作,留空则不更改
|
||||
</>
|
||||
}
|
||||
description={<>所有编辑均为覆盖操作,留空则不更改</>}
|
||||
></Banner>
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<TextInput
|
||||
label="标签名,留空则解散标签"
|
||||
name="newTag"
|
||||
label='标签名,留空则解散标签'
|
||||
name='newTag'
|
||||
value={inputs.new_tag}
|
||||
onChange={(value) => setInputs({ ...inputs, new_tag: value })}
|
||||
placeholder="请输入新标签"
|
||||
placeholder='请输入新标签'
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>模型,留空则不更改:</Typography.Text>
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择该渠道所支持的模型,留空则不更改'}
|
||||
name="models"
|
||||
name='models'
|
||||
required
|
||||
multiple
|
||||
selection
|
||||
@@ -265,16 +281,16 @@ const EditTagModal = (props) => {
|
||||
handleInputChange('models', value);
|
||||
}}
|
||||
value={inputs.models}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
optionList={modelOptions}
|
||||
/>
|
||||
<Input
|
||||
addonAfter={
|
||||
<Button type="primary" onClick={addCustomModels}>
|
||||
<Button type='primary' onClick={addCustomModels}>
|
||||
填入
|
||||
</Button>
|
||||
}
|
||||
placeholder="输入自定义模型名称"
|
||||
placeholder='输入自定义模型名称'
|
||||
value={customModel}
|
||||
onChange={(value) => {
|
||||
setCustomModel(value.trim());
|
||||
@@ -285,7 +301,7 @@ const EditTagModal = (props) => {
|
||||
</div>
|
||||
<Select
|
||||
placeholder={'请选择可以使用该渠道的分组,留空则不更改'}
|
||||
name="groups"
|
||||
name='groups'
|
||||
required
|
||||
multiple
|
||||
selection
|
||||
@@ -295,7 +311,7 @@ const EditTagModal = (props) => {
|
||||
handleInputChange('groups', value);
|
||||
}}
|
||||
value={inputs.groups}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
optionList={groupOptions}
|
||||
/>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
@@ -303,25 +319,25 @@ const EditTagModal = (props) => {
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,留空则不更改`}
|
||||
name="model_mapping"
|
||||
name='model_mapping'
|
||||
onChange={(value) => {
|
||||
handleInputChange('model_mapping', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.model_mapping}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Space>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'model_mapping',
|
||||
JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)
|
||||
JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2),
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -331,13 +347,10 @@ const EditTagModal = (props) => {
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'model_mapping',
|
||||
JSON.stringify({}, null, 2)
|
||||
);
|
||||
handleInputChange('model_mapping', JSON.stringify({}, null, 2));
|
||||
}}
|
||||
>
|
||||
清空重定向
|
||||
@@ -346,13 +359,10 @@ const EditTagModal = (props) => {
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'model_mapping',
|
||||
""
|
||||
);
|
||||
handleInputChange('model_mapping', '');
|
||||
}}
|
||||
>
|
||||
不更改
|
||||
@@ -363,4 +373,4 @@ const EditTagModal = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTagModal;
|
||||
export default EditTagModal;
|
||||
|
||||
@@ -9,10 +9,10 @@ const File = () => {
|
||||
<>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<h3>{t('管理渠道')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<ChannelsTable />
|
||||
<h3>{t('管理渠道')}</h3>
|
||||
</Layout.Header>
|
||||
<Layout.Content>
|
||||
<ChannelsTable />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user