✨ refactor(EditChannel&EditToken): refactor Channel & Token edit pages with Semi Form and UX enhancements
Overview • Migrated both `EditChannel.js` and `EditToken.js` to fully leverage Semi UI `Form.*` components, removing legacy `Input/Select/TextArea` + manual labels. • Unified data-loading strategy: when the drawer becomes visible we load (or reset) data via `props.visible + id` effect and `formApi.setValues()`, guaranteeing fields are always populated; form resets on close. • Fixed blank-form bug when opening the same record twice. Key improvements 1. Validation • `type`, `models` always required. • `key` required only while creating (not on edit). 2. Batch key creation • Checkbox moved into `extraText`; hidden when editing or when channel type = 41. 3. Layout & UI • `Row / Col` (12 + 12) for “Priority” and “Weight”. • Placeholders revised; model selector now shows creation hint; removed obsolete banner. • Help / extraText used for long hints, template buttons (`model_mapping`, `status_code_mapping`, `param_override`, etc.), and API address notice. • Added `showClear`, `min`, rounded card class names for consistency. 4. Reusable helpers • `batchAllowed`, `batchExtra` utilities. • `getInitValues()` + centralized `inputs`→form synchronization. 5. Token editor aligned to the same pattern (`props.visiable` watcher). Result Cleaner code, consistent UX, instant field population on every open, and clearer validation/error feedback across both editors.
This commit is contained in:
@@ -1143,8 +1143,8 @@
|
|||||||
"默认测试模型": "Default Test Model",
|
"默认测试模型": "Default Test Model",
|
||||||
"不填则为模型列表第一个": "First model in list if empty",
|
"不填则为模型列表第一个": "First model in list if empty",
|
||||||
"是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道": "Auto-disable (only effective when auto-disable is enabled). When turned off, this channel will not be automatically disabled",
|
"是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道": "Auto-disable (only effective when auto-disable is enabled). When turned off, this channel will not be automatically disabled",
|
||||||
"状态码复写(仅影响本地判断,不修改返回到上游的状态码)": "Status Code Override (only affects local judgment, does not modify status code returned upstream)",
|
"状态码复写": "Status Code Override",
|
||||||
"此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:": "Optional, used to override returned status codes, e.g. rewriting Claude channel's 400 error to 500 (for retry). Do not abuse this feature. Example:",
|
"此项可选,用于复写返回的状态码,仅影响本地判断,不修改返回到上游的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:": "Optional, used to override returned status codes, only affects local judgment, does not modify status code returned upstream, e.g. rewriting Claude channel's 400 error to 500 (for retry). Do not abuse this feature. Example:",
|
||||||
"渠道标签": "Channel Tag",
|
"渠道标签": "Channel Tag",
|
||||||
"渠道优先级": "Channel Priority",
|
"渠道优先级": "Channel Priority",
|
||||||
"渠道权重": "Channel Weight",
|
"渠道权重": "Channel Weight",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@@ -15,10 +15,7 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
Button,
|
Button,
|
||||||
Input,
|
|
||||||
Typography,
|
Typography,
|
||||||
Select,
|
|
||||||
TextArea,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Banner,
|
Banner,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -26,6 +23,9 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Tag,
|
Tag,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Form,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import { getChannelModels, copy } from '../../helpers';
|
import { getChannelModels, copy } from '../../helpers';
|
||||||
import {
|
import {
|
||||||
@@ -113,7 +113,12 @@ const EditChannel = (props) => {
|
|||||||
const [customModel, setCustomModel] = useState('');
|
const [customModel, setCustomModel] = useState('');
|
||||||
const [modalImageUrl, setModalImageUrl] = useState('');
|
const [modalImageUrl, setModalImageUrl] = useState('');
|
||||||
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
||||||
|
const formApiRef = useRef(null);
|
||||||
|
const getInitValues = () => ({ ...originInputs });
|
||||||
const handleInputChange = (name, value) => {
|
const handleInputChange = (name, value) => {
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValue(name, value);
|
||||||
|
}
|
||||||
if (name === 'models' && Array.isArray(value)) {
|
if (name === 'models' && Array.isArray(value)) {
|
||||||
value = Array.from(new Set(value.map((m) => (m || '').trim())));
|
value = Array.from(new Set(value.map((m) => (m || '').trim())));
|
||||||
}
|
}
|
||||||
@@ -205,6 +210,9 @@ const EditChannel = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
setInputs(data);
|
setInputs(data);
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValues(data);
|
||||||
|
}
|
||||||
if (data.auto_ban === 0) {
|
if (data.auto_ban === 0) {
|
||||||
setAutoBan(false);
|
setAutoBan(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -338,30 +346,51 @@ const EditChannel = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchModels().then();
|
fetchModels().then();
|
||||||
fetchGroups().then();
|
fetchGroups().then();
|
||||||
if (isEdit) {
|
if (!isEdit) {
|
||||||
loadChannel().then(() => { });
|
|
||||||
} else {
|
|
||||||
setInputs(originInputs);
|
setInputs(originInputs);
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValues(originInputs);
|
||||||
|
}
|
||||||
let localModels = getChannelModels(inputs.type);
|
let localModels = getChannelModels(inputs.type);
|
||||||
setBasicModels(localModels);
|
setBasicModels(localModels);
|
||||||
setInputs((inputs) => ({ ...inputs, models: localModels }));
|
setInputs((inputs) => ({ ...inputs, models: localModels }));
|
||||||
}
|
}
|
||||||
}, [props.editingChannel.id]);
|
}, [props.editingChannel.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formApiRef.current) {
|
||||||
|
formApiRef.current.setValues(inputs);
|
||||||
|
}
|
||||||
|
}, [inputs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.visible) {
|
||||||
|
if (isEdit) {
|
||||||
|
loadChannel();
|
||||||
|
} else {
|
||||||
|
formApiRef.current?.setValues(getInitValues());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formApiRef.current?.reset();
|
||||||
|
}
|
||||||
|
}, [props.visible, channelId]);
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!isEdit && (inputs.name === '' || inputs.key === '')) {
|
const formValues = formApiRef.current ? formApiRef.current.getValues() : {};
|
||||||
|
let localInputs = { ...formValues };
|
||||||
|
|
||||||
|
if (!isEdit && (!localInputs.name || !localInputs.key)) {
|
||||||
showInfo(t('请填写渠道名称和渠道密钥!'));
|
showInfo(t('请填写渠道名称和渠道密钥!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (inputs.models.length === 0) {
|
if (!Array.isArray(localInputs.models) || localInputs.models.length === 0) {
|
||||||
showInfo(t('请至少选择一个模型!'));
|
showInfo(t('请至少选择一个模型!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
|
if (localInputs.model_mapping && localInputs.model_mapping !== '' && !verifyJSON(localInputs.model_mapping)) {
|
||||||
showInfo(t('模型映射必须是合法的 JSON 格式!'));
|
showInfo(t('模型映射必须是合法的 JSON 格式!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let localInputs = { ...inputs };
|
|
||||||
if (localInputs.base_url && localInputs.base_url.endsWith('/')) {
|
if (localInputs.base_url && localInputs.base_url.endsWith('/')) {
|
||||||
localInputs.base_url = localInputs.base_url.slice(
|
localInputs.base_url = localInputs.base_url.slice(
|
||||||
0,
|
0,
|
||||||
@@ -372,14 +401,9 @@ const EditChannel = (props) => {
|
|||||||
localInputs.other = 'v2.1';
|
localInputs.other = 'v2.1';
|
||||||
}
|
}
|
||||||
let res;
|
let res;
|
||||||
if (!Array.isArray(localInputs.models)) {
|
localInputs.auto_ban = localInputs.auto_ban ? 1 : 0;
|
||||||
showError(t('提交失败,请勿重复提交!'));
|
|
||||||
handleCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
localInputs.auto_ban = autoBan ? 1 : 0;
|
|
||||||
localInputs.models = localInputs.models.join(',');
|
localInputs.models = localInputs.models.join(',');
|
||||||
localInputs.group = localInputs.groups.join(',');
|
localInputs.group = (localInputs.groups || []).join(',');
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
res = await API.put(`/api/channel/`, {
|
res = await API.put(`/api/channel/`, {
|
||||||
...localInputs,
|
...localInputs,
|
||||||
@@ -439,6 +463,11 @@ const EditChannel = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchAllowed = !isEdit && inputs.type !== 41;
|
||||||
|
const batchExtra = batchAllowed ? (
|
||||||
|
<Checkbox checked={batch} onChange={() => setBatch(!batch)}>{t('批量创建')}</Checkbox>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SideSheet
|
<SideSheet
|
||||||
@@ -459,7 +488,7 @@ const EditChannel = (props) => {
|
|||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
theme="solid"
|
theme="solid"
|
||||||
onClick={submit}
|
onClick={() => formApiRef.current?.submitForm()}
|
||||||
icon={<IconSave />}
|
icon={<IconSave />}
|
||||||
>
|
>
|
||||||
{t('提交')}
|
{t('提交')}
|
||||||
@@ -478,6 +507,13 @@ const EditChannel = (props) => {
|
|||||||
closeIcon={null}
|
closeIcon={null}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
>
|
>
|
||||||
|
<Form
|
||||||
|
key={isEdit ? 'edit' : 'new'}
|
||||||
|
initValues={originInputs}
|
||||||
|
getFormApi={(api) => (formApiRef.current = api)}
|
||||||
|
onSubmit={submit}
|
||||||
|
>
|
||||||
|
{() => (
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
<Card className="!rounded-2xl shadow-sm border-0 mb-6">
|
||||||
@@ -492,56 +528,45 @@ const EditChannel = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<Form.Select
|
||||||
<div>
|
field='type'
|
||||||
<Text strong className="block mb-2">{t('类型')}</Text>
|
label={t('类型')}
|
||||||
<Select
|
placeholder={t('请选择渠道类型')}
|
||||||
name='type'
|
rules={[{ required: true, message: t('请选择渠道类型') }]}
|
||||||
required
|
|
||||||
optionList={CHANNEL_OPTIONS}
|
optionList={CHANNEL_OPTIONS}
|
||||||
value={inputs.type}
|
|
||||||
onChange={(value) => handleInputChange('type', value)}
|
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
filter
|
filter
|
||||||
searchPosition='dropdown'
|
searchPosition='dropdown'
|
||||||
placeholder={t('请选择渠道类型')}
|
onChange={(value) => handleInputChange('type', value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Form.Input
|
||||||
<Text strong className="block mb-2">{t('名称')}</Text>
|
field='name'
|
||||||
<Input
|
label={t('名称')}
|
||||||
required
|
|
||||||
name='name'
|
|
||||||
placeholder={t('请为渠道命名')}
|
placeholder={t('请为渠道命名')}
|
||||||
onChange={(value) => {
|
rules={[{ required: true, message: t('请为渠道命名') }]}
|
||||||
handleInputChange('name', value);
|
showClear
|
||||||
}}
|
onChange={(value) => handleInputChange('name', value)}
|
||||||
value={inputs.name}
|
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">{t('密钥')}</Text>
|
|
||||||
{batch ? (
|
{batch ? (
|
||||||
<TextArea
|
<Form.TextArea
|
||||||
name='key'
|
field='key'
|
||||||
required
|
label={t('密钥')}
|
||||||
placeholder={t('请输入密钥,一行一个')}
|
placeholder={t('请输入密钥,一行一个')}
|
||||||
onChange={(value) => {
|
rules={isEdit ? [] : [{ required: true, message: t('请输入密钥') }]}
|
||||||
handleInputChange('key', value);
|
|
||||||
}}
|
|
||||||
value={inputs.key}
|
|
||||||
autosize={{ minRows: 6, maxRows: 6 }}
|
autosize={{ minRows: 6, maxRows: 6 }}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
|
onChange={(value) => handleInputChange('key', value)}
|
||||||
|
extraText={batchExtra}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{inputs.type === 41 ? (
|
{inputs.type === 41 ? (
|
||||||
<TextArea
|
<Form.TextArea
|
||||||
name='key'
|
field='key'
|
||||||
required
|
label={t('密钥')}
|
||||||
placeholder={
|
placeholder={
|
||||||
'{\n' +
|
'{\n' +
|
||||||
' "type": "service_account",\n' +
|
' "type": "service_account",\n' +
|
||||||
@@ -557,39 +582,25 @@ const EditChannel = (props) => {
|
|||||||
' "universe_domain": "googleapis.com"\n' +
|
' "universe_domain": "googleapis.com"\n' +
|
||||||
'}'
|
'}'
|
||||||
}
|
}
|
||||||
onChange={(value) => {
|
rules={isEdit ? [] : [{ required: true, message: t('请输入密钥') }]}
|
||||||
handleInputChange('key', value);
|
|
||||||
}}
|
|
||||||
autosize={{ minRows: 10 }}
|
autosize={{ minRows: 10 }}
|
||||||
value={inputs.key}
|
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
|
onChange={(value) => handleInputChange('key', value)}
|
||||||
|
extraText={batchExtra}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Form.Input
|
||||||
name='key'
|
field='key'
|
||||||
required
|
label={t('密钥')}
|
||||||
placeholder={t(type2secretPrompt(inputs.type))}
|
placeholder={t(type2secretPrompt(inputs.type))}
|
||||||
onChange={(value) => {
|
rules={isEdit ? [] : [{ required: true, message: t('请输入密钥') }]}
|
||||||
handleInputChange('key', value);
|
|
||||||
}}
|
|
||||||
value={inputs.key}
|
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
|
onChange={(value) => handleInputChange('key', value)}
|
||||||
|
extraText={batchExtra}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isEdit && (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
checked={batch}
|
|
||||||
onChange={() => setBatch(!batch)}
|
|
||||||
/>
|
|
||||||
<Text strong className="ml-2">{t('批量创建')}</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* API Configuration Card */}
|
{/* API Configuration Card */}
|
||||||
@@ -605,7 +616,6 @@ const EditChannel = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{inputs.type === 40 && (
|
{inputs.type === 40 && (
|
||||||
<Banner
|
<Banner
|
||||||
type='info'
|
type='info'
|
||||||
@@ -622,6 +632,7 @@ const EditChannel = (props) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
className='!rounded-lg'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -633,23 +644,21 @@ const EditChannel = (props) => {
|
|||||||
className='!rounded-lg'
|
className='!rounded-lg'
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Text strong className="block mb-2">AZURE_OPENAI_ENDPOINT</Text>
|
<Form.Input
|
||||||
<Input
|
field='base_url'
|
||||||
name='azure_base_url'
|
label='AZURE_OPENAI_ENDPOINT'
|
||||||
placeholder={t('请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com')}
|
placeholder={t('请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com')}
|
||||||
onChange={(value) => handleInputChange('base_url', value)}
|
onChange={(value) => handleInputChange('base_url', value)}
|
||||||
value={inputs.base_url}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Text strong className="block mb-2">{t('默认 API 版本')}</Text>
|
<Form.Input
|
||||||
<Input
|
field='other'
|
||||||
name='azure_other'
|
label={t('默认 API 版本')}
|
||||||
placeholder={t('请输入默认 API 版本,例如:2025-04-01-preview')}
|
placeholder={t('请输入默认 API 版本,例如:2025-04-01-preview')}
|
||||||
onChange={(value) => handleInputChange('other', value)}
|
onChange={(value) => handleInputChange('other', value)}
|
||||||
value={inputs.other}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -663,13 +672,12 @@ const EditChannel = (props) => {
|
|||||||
className='!rounded-lg'
|
className='!rounded-lg'
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Text strong className="block mb-2">{t('完整的 Base URL,支持变量{model}')}</Text>
|
<Form.Input
|
||||||
<Input
|
field='base_url'
|
||||||
name='base_url'
|
label={t('完整的 Base URL,支持变量{model}')}
|
||||||
placeholder={t('请输入完整的URL,例如:https://api.openai.com/v1/chat/completions')}
|
placeholder={t('请输入完整的URL,例如:https://api.openai.com/v1/chat/completions')}
|
||||||
onChange={(value) => handleInputChange('base_url', value)}
|
onChange={(value) => handleInputChange('base_url', value)}
|
||||||
value={inputs.base_url}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -685,48 +693,40 @@ const EditChannel = (props) => {
|
|||||||
|
|
||||||
{inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && inputs.type !== 45 && (
|
{inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && inputs.type !== 45 && (
|
||||||
<div>
|
<div>
|
||||||
<Text strong className="block mb-2">{t('API地址')}</Text>
|
<Form.Input
|
||||||
<Input
|
field='base_url'
|
||||||
name='base_url'
|
label={t('API地址')}
|
||||||
placeholder={t('此项可选,用于通过自定义API地址来进行 API 调用,末尾不要带/v1和/')}
|
placeholder={t('此项可选,用于通过自定义API地址来进行 API 调用,末尾不要带/v1和/')}
|
||||||
onChange={(value) => handleInputChange('base_url', value)}
|
onChange={(value) => handleInputChange('base_url', value)}
|
||||||
value={inputs.base_url}
|
showClear
|
||||||
autoComplete='new-password'
|
extraText={t('对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写')}
|
||||||
/>
|
/>
|
||||||
<Text type="tertiary" className="mt-1 text-xs">
|
|
||||||
{t('对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputs.type === 22 && (
|
{inputs.type === 22 && (
|
||||||
<div>
|
<div>
|
||||||
<Text strong className="block mb-2">{t('私有部署地址')}</Text>
|
<Form.Input
|
||||||
<Input
|
field='base_url'
|
||||||
name='base_url'
|
label={t('私有部署地址')}
|
||||||
placeholder={t('请输入私有部署地址,格式为:https://fastgpt.run/api/openapi')}
|
placeholder={t('请输入私有部署地址,格式为:https://fastgpt.run/api/openapi')}
|
||||||
onChange={(value) => handleInputChange('base_url', value)}
|
onChange={(value) => handleInputChange('base_url', value)}
|
||||||
value={inputs.base_url}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputs.type === 36 && (
|
{inputs.type === 36 && (
|
||||||
<div>
|
<div>
|
||||||
<Text strong className="block mb-2">
|
<Form.Input
|
||||||
{t('注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用')}
|
field='base_url'
|
||||||
</Text>
|
label={t('注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用')}
|
||||||
<Input
|
|
||||||
name='base_url'
|
|
||||||
placeholder={t('请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com')}
|
placeholder={t('请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com')}
|
||||||
onChange={(value) => handleInputChange('base_url', value)}
|
onChange={(value) => handleInputChange('base_url', value)}
|
||||||
value={inputs.base_url}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Model Configuration Card */}
|
{/* Model Configuration Card */}
|
||||||
@@ -742,52 +742,35 @@ const EditChannel = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<Form.Select
|
||||||
<div>
|
field='models'
|
||||||
<Text strong className="block mb-2">{t('模型')}</Text>
|
label={t('模型')}
|
||||||
<Select
|
placeholder={isEdit ? t('请选择该渠道所支持的模型') : t('创建后可在编辑渠道时获取上游模型列表')}
|
||||||
placeholder={t('请选择该渠道所支持的模型')}
|
rules={[{ required: true, message: t('请选择模型') }]}
|
||||||
name='models'
|
|
||||||
required
|
|
||||||
multiple
|
multiple
|
||||||
selection
|
|
||||||
filter
|
filter
|
||||||
searchPosition='dropdown'
|
searchPosition='dropdown'
|
||||||
onChange={(value) => handleInputChange('models', value)}
|
|
||||||
value={inputs.models}
|
|
||||||
autoComplete='new-password'
|
|
||||||
optionList={modelOptions}
|
optionList={modelOptions}
|
||||||
/>
|
style={{ width: '100%' }}
|
||||||
</div>
|
onChange={(value) => handleInputChange('models', value)}
|
||||||
|
extraText={(
|
||||||
<div className="flex flex-wrap gap-2">
|
<Space wrap>
|
||||||
<Button
|
<Button size='small' type='primary' onClick={() => handleInputChange('models', basicModels)}>
|
||||||
type='primary'
|
|
||||||
onClick={() => handleInputChange('models', basicModels)}
|
|
||||||
>
|
|
||||||
{t('填入相关模型')}
|
{t('填入相关模型')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button size='small' type='secondary' onClick={() => handleInputChange('models', fullModels)}>
|
||||||
type='secondary'
|
|
||||||
onClick={() => handleInputChange('models', fullModels)}
|
|
||||||
>
|
|
||||||
{t('填入所有模型')}
|
{t('填入所有模型')}
|
||||||
</Button>
|
</Button>
|
||||||
{isEdit ? (
|
{isEdit && (
|
||||||
<Button
|
<Button size='small' type='tertiary' onClick={() => fetchUpstreamModelList('models')}>
|
||||||
type='tertiary'
|
|
||||||
onClick={() => fetchUpstreamModelList('models')}
|
|
||||||
>
|
|
||||||
{t('获取模型列表')}
|
{t('获取模型列表')}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
)}
|
||||||
<Button
|
<Button size='small' type='warning' onClick={() => handleInputChange('models', [])}>
|
||||||
type='warning'
|
|
||||||
onClick={() => handleInputChange('models', [])}
|
|
||||||
>
|
|
||||||
{t('清除所有模型')}
|
{t('清除所有模型')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
size='small'
|
||||||
type='tertiary'
|
type='tertiary'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (inputs.models.length === 0) {
|
if (inputs.models.length === 0) {
|
||||||
@@ -804,60 +787,50 @@ const EditChannel = (props) => {
|
|||||||
>
|
>
|
||||||
{t('复制所有模型')}
|
{t('复制所有模型')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Space>
|
||||||
|
|
||||||
{!isEdit && (
|
|
||||||
<Banner
|
|
||||||
type='info'
|
|
||||||
description={t('创建后可在编辑渠道时获取上游模型列表')}
|
|
||||||
className='!rounded-lg'
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div>
|
<Form.Input
|
||||||
<Input
|
field='custom_model'
|
||||||
addonAfter={
|
label={t('自定义模型名称')}
|
||||||
<Button type='primary' onClick={addCustomModels} className="!rounded-r-lg">
|
placeholder={t('输入自定义模型名称')}
|
||||||
|
onChange={(value) => setCustomModel(value.trim())}
|
||||||
|
value={customModel}
|
||||||
|
suffix={
|
||||||
|
<Button size='small' type='primary' onClick={addCustomModels}>
|
||||||
{t('填入')}
|
{t('填入')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
placeholder={t('输入自定义模型名称')}
|
|
||||||
value={customModel}
|
|
||||||
onChange={(value) => setCustomModel(value.trim())}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Form.Input
|
||||||
<Text strong className="block mb-2">{t('模型重定向')}</Text>
|
field='test_model'
|
||||||
<TextArea
|
label={t('默认测试模型')}
|
||||||
|
placeholder={t('不填则为模型列表第一个')}
|
||||||
|
onChange={(value) => handleInputChange('test_model', value)}
|
||||||
|
showClear
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.TextArea
|
||||||
|
field='model_mapping'
|
||||||
|
label={t('模型重定向')}
|
||||||
placeholder={
|
placeholder={
|
||||||
t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:') +
|
t('此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:') +
|
||||||
`\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`
|
`\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`
|
||||||
}
|
}
|
||||||
name='model_mapping'
|
|
||||||
onChange={(value) => handleInputChange('model_mapping', value)}
|
|
||||||
autosize
|
autosize
|
||||||
value={inputs.model_mapping}
|
onChange={(value) => handleInputChange('model_mapping', value)}
|
||||||
autoComplete='new-password'
|
extraText={
|
||||||
/>
|
|
||||||
<Text
|
<Text
|
||||||
className="!text-semi-color-primary cursor-pointer mt-1 block"
|
className="!text-semi-color-primary cursor-pointer"
|
||||||
onClick={() => handleInputChange('model_mapping', JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2))}
|
onClick={() => handleInputChange('model_mapping', JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2))}
|
||||||
>
|
>
|
||||||
{t('填入模板')}
|
{t('填入模板')}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
}
|
||||||
|
showClear
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">{t('默认测试模型')}</Text>
|
|
||||||
<Input
|
|
||||||
name='test_model'
|
|
||||||
placeholder={t('不填则为模型列表第一个')}
|
|
||||||
onChange={(value) => handleInputChange('test_model', value)}
|
|
||||||
value={inputs.test_model}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Advanced Settings Card */}
|
{/* Advanced Settings Card */}
|
||||||
@@ -873,259 +846,203 @@ const EditChannel = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<Form.Select
|
||||||
<div>
|
field='groups'
|
||||||
<Text strong className="block mb-2">{t('分组')}</Text>
|
label={t('分组')}
|
||||||
<Select
|
|
||||||
placeholder={t('请选择可以使用该渠道的分组')}
|
placeholder={t('请选择可以使用该渠道的分组')}
|
||||||
name='groups'
|
|
||||||
required
|
|
||||||
multiple
|
multiple
|
||||||
selection
|
|
||||||
allowAdditions
|
allowAdditions
|
||||||
additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
|
additionLabel={t('请在系统设置页面编辑分组倍率以添加新的分组:')}
|
||||||
onChange={(value) => handleInputChange('groups', value)}
|
|
||||||
value={inputs.groups}
|
|
||||||
autoComplete='new-password'
|
|
||||||
optionList={groupOptions}
|
optionList={groupOptions}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onChange={(value) => handleInputChange('groups', value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{inputs.type === 18 && (
|
{inputs.type === 18 && (
|
||||||
<div>
|
<Form.Input
|
||||||
<Text strong className="block mb-2">{t('模型版本')}</Text>
|
field='other'
|
||||||
<Input
|
label={t('模型版本')}
|
||||||
name='other'
|
|
||||||
placeholder={'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'}
|
placeholder={'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'}
|
||||||
onChange={(value) => handleInputChange('other', value)}
|
onChange={(value) => handleInputChange('other', value)}
|
||||||
value={inputs.other}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputs.type === 41 && (
|
{inputs.type === 41 && (
|
||||||
<div>
|
<Form.TextArea
|
||||||
<Text strong className="block mb-2">{t('部署地区')}</Text>
|
field='other'
|
||||||
<TextArea
|
label={t('部署地区')}
|
||||||
name='other'
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
'请输入部署地区,例如:us-central1\n支持使用模型映射格式\n' +
|
'请输入部署地区,例如:us-central1\n支持使用模型映射格式\n{\n "default": "us-central1",\n "claude-3-5-sonnet-20240620": "europe-west1"\n}'
|
||||||
'{\n' +
|
|
||||||
' "default": "us-central1",\n' +
|
|
||||||
' "claude-3-5-sonnet-20240620": "europe-west1"\n' +
|
|
||||||
'}'
|
|
||||||
)}
|
)}
|
||||||
autosize={{ minRows: 2 }}
|
autosize={{ minRows: 2 }}
|
||||||
onChange={(value) => handleInputChange('other', value)}
|
onChange={(value) => handleInputChange('other', value)}
|
||||||
value={inputs.other}
|
extraText={
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
|
||||||
<Text
|
<Text
|
||||||
className="!text-semi-color-primary cursor-pointer mt-1 block"
|
className="!text-semi-color-primary cursor-pointer"
|
||||||
onClick={() => handleInputChange('other', JSON.stringify(REGION_EXAMPLE, null, 2))}
|
onClick={() => handleInputChange('other', JSON.stringify(REGION_EXAMPLE, null, 2))}
|
||||||
>
|
>
|
||||||
{t('填入模板')}
|
{t('填入模板')}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputs.type === 21 && (
|
{inputs.type === 21 && (
|
||||||
<div>
|
<Form.Input
|
||||||
<Text strong className="block mb-2">{t('知识库 ID')}</Text>
|
field='other'
|
||||||
<Input
|
label={t('知识库 ID')}
|
||||||
name='other'
|
|
||||||
placeholder={'请输入知识库 ID,例如:123456'}
|
placeholder={'请输入知识库 ID,例如:123456'}
|
||||||
onChange={(value) => handleInputChange('other', value)}
|
onChange={(value) => handleInputChange('other', value)}
|
||||||
value={inputs.other}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputs.type === 39 && (
|
{inputs.type === 39 && (
|
||||||
<div>
|
<Form.Input
|
||||||
<Text strong className="block mb-2">Account ID</Text>
|
field='other'
|
||||||
<Input
|
label='Account ID'
|
||||||
name='other'
|
|
||||||
placeholder={'请输入Account ID,例如:d6b5da8hk1awo8nap34ube6gh'}
|
placeholder={'请输入Account ID,例如:d6b5da8hk1awo8nap34ube6gh'}
|
||||||
onChange={(value) => handleInputChange('other', value)}
|
onChange={(value) => handleInputChange('other', value)}
|
||||||
value={inputs.other}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputs.type === 49 && (
|
{inputs.type === 49 && (
|
||||||
<div>
|
<Form.Input
|
||||||
<Text strong className="block mb-2">{t('智能体ID')}</Text>
|
field='other'
|
||||||
<Input
|
label={t('智能体ID')}
|
||||||
name='other'
|
|
||||||
placeholder={'请输入智能体ID,例如:7342866812345'}
|
placeholder={'请输入智能体ID,例如:7342866812345'}
|
||||||
onChange={(value) => handleInputChange('other', value)}
|
onChange={(value) => handleInputChange('other', value)}
|
||||||
value={inputs.other}
|
showClear
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
{inputs.type === 1 && (
|
||||||
<Text strong className="block mb-2">{t('渠道标签')}</Text>
|
<Form.Input
|
||||||
<Input
|
field='openai_organization'
|
||||||
name='tag'
|
label={t('组织')}
|
||||||
|
placeholder={t('请输入组织org-xxx')}
|
||||||
|
showClear
|
||||||
|
helpText={t('组织,可选,不填则为默认组织')}
|
||||||
|
onChange={(value) => handleInputChange('openai_organization', value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Input
|
||||||
|
field='tag'
|
||||||
|
label={t('渠道标签')}
|
||||||
placeholder={t('渠道标签')}
|
placeholder={t('渠道标签')}
|
||||||
|
showClear
|
||||||
onChange={(value) => handleInputChange('tag', value)}
|
onChange={(value) => handleInputChange('tag', value)}
|
||||||
value={inputs.tag}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Row gutter={12}>
|
||||||
<Text strong className="block mb-2">{t('渠道优先级')}</Text>
|
<Col span={12}>
|
||||||
<Input
|
<Form.InputNumber
|
||||||
name='priority'
|
field='priority'
|
||||||
|
label={t('渠道优先级')}
|
||||||
placeholder={t('渠道优先级')}
|
placeholder={t('渠道优先级')}
|
||||||
onChange={(value) => {
|
min={0}
|
||||||
const number = parseInt(value);
|
onNumberChange={(value) => handleInputChange('priority', value)}
|
||||||
if (isNaN(number)) {
|
style={{ width: '100%' }}
|
||||||
handleInputChange('priority', value);
|
|
||||||
} else {
|
|
||||||
handleInputChange('priority', number);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={inputs.priority}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
<div>
|
<Form.InputNumber
|
||||||
<Text strong className="block mb-2">{t('渠道权重')}</Text>
|
field='weight'
|
||||||
<Input
|
label={t('渠道权重')}
|
||||||
name='weight'
|
|
||||||
placeholder={t('渠道权重')}
|
placeholder={t('渠道权重')}
|
||||||
onChange={(value) => {
|
min={0}
|
||||||
const number = parseInt(value);
|
onNumberChange={(value) => handleInputChange('weight', value)}
|
||||||
if (isNaN(number)) {
|
style={{ width: '100%' }}
|
||||||
handleInputChange('weight', value);
|
|
||||||
} else {
|
|
||||||
handleInputChange('weight', number);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={inputs.weight}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<div>
|
<Form.Switch
|
||||||
<Text strong className="block mb-2">{t('渠道额外设置')}</Text>
|
field='auto_ban'
|
||||||
<TextArea
|
label={t('是否自动禁用')}
|
||||||
placeholder={
|
checkedText={t('开')}
|
||||||
t('此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:') +
|
uncheckedText={t('关')}
|
||||||
'\n{\n "force_format": true\n}'
|
onChange={(val) => setAutoBan(val)}
|
||||||
}
|
extraText={t('仅当自动禁用开启时有效,关闭后不会自动禁用该渠道')}
|
||||||
name='setting'
|
initValue={autoBan}
|
||||||
onChange={(value) => handleInputChange('setting', value)}
|
|
||||||
autosize
|
|
||||||
value={inputs.setting}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-2 mt-1">
|
|
||||||
<Text
|
|
||||||
className="!text-semi-color-primary cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
handleInputChange(
|
|
||||||
'setting',
|
|
||||||
JSON.stringify({ force_format: true }, null, 2),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('填入模板')}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
className="!text-semi-color-primary cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
window.open(
|
|
||||||
'https://github.com/QuantumNous/new-api/blob/main/docs/channel/other_setting.md',
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('设置说明')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Form.TextArea
|
||||||
<Text strong className="block mb-2">{t('参数覆盖')}</Text>
|
field='param_override'
|
||||||
<TextArea
|
label={t('参数覆盖')}
|
||||||
placeholder={
|
placeholder={
|
||||||
t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:') +
|
t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:') +
|
||||||
'\n{\n "temperature": 0\n}'
|
'\n{\n "temperature": 0\n}'
|
||||||
}
|
}
|
||||||
name='param_override'
|
autosize
|
||||||
onChange={(value) => handleInputChange('param_override', value)}
|
onChange={(value) => handleInputChange('param_override', value)}
|
||||||
autosize
|
extraText={
|
||||||
value={inputs.param_override}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{inputs.type === 1 && (
|
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">{t('组织')}</Text>
|
|
||||||
<Input
|
|
||||||
name='openai_organization'
|
|
||||||
placeholder={t('请输入组织org-xxx')}
|
|
||||||
onChange={(value) => handleInputChange('openai_organization', value)}
|
|
||||||
value={inputs.openai_organization}
|
|
||||||
/>
|
|
||||||
<Text type="tertiary" className="mt-1 text-xs">
|
|
||||||
{t('组织,可选,不填则为默认组织')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
checked={autoBan}
|
|
||||||
onChange={() => setAutoBan(!autoBan)}
|
|
||||||
/>
|
|
||||||
<Text strong className="ml-2">
|
|
||||||
{t('是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text strong className="block mb-2">
|
|
||||||
{t('状态码复写(仅影响本地判断,不修改返回到上游的状态码)')}
|
|
||||||
</Text>
|
|
||||||
<TextArea
|
|
||||||
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'
|
|
||||||
/>
|
|
||||||
<Text
|
<Text
|
||||||
className="!text-semi-color-primary cursor-pointer mt-1 block"
|
className="!text-semi-color-primary cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => handleInputChange('param_override', JSON.stringify({ temperature: 0 }, null, 2))}
|
||||||
handleInputChange(
|
|
||||||
'status_code_mapping',
|
|
||||||
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t('填入模板')}
|
{t('填入模板')}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
}
|
||||||
</div>
|
showClear
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.TextArea
|
||||||
|
field='status_code_mapping'
|
||||||
|
label={t('状态码复写')}
|
||||||
|
placeholder={
|
||||||
|
t('此项可选,用于复写返回的状态码,仅影响本地判断,不修改返回到上游的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:') +
|
||||||
|
'\n' +
|
||||||
|
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)
|
||||||
|
}
|
||||||
|
autosize
|
||||||
|
onChange={(value) => handleInputChange('status_code_mapping', value)}
|
||||||
|
extraText={
|
||||||
|
<Text
|
||||||
|
className="!text-semi-color-primary cursor-pointer"
|
||||||
|
onClick={() => handleInputChange('status_code_mapping', JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2))}
|
||||||
|
>
|
||||||
|
{t('填入模板')}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
showClear
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.TextArea
|
||||||
|
field='setting'
|
||||||
|
label={t('渠道额外设置')}
|
||||||
|
placeholder={
|
||||||
|
t('此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:') +
|
||||||
|
'\n{\n "force_format": true\n}'
|
||||||
|
}
|
||||||
|
autosize
|
||||||
|
onChange={(value) => handleInputChange('setting', value)}
|
||||||
|
extraText={(
|
||||||
|
<Space wrap>
|
||||||
|
<Text
|
||||||
|
className="!text-semi-color-primary cursor-pointer"
|
||||||
|
onClick={() => handleInputChange('setting', JSON.stringify({ force_format: true }, null, 2))}
|
||||||
|
>
|
||||||
|
{t('填入模板')}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
className="!text-semi-color-primary cursor-pointer"
|
||||||
|
onClick={() => window.open('https://github.com/QuantumNous/new-api/blob/main/docs/channel/other_setting.md')}
|
||||||
|
>
|
||||||
|
{t('设置说明')}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
showClear
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
src={modalImageUrl}
|
src={modalImageUrl}
|
||||||
visible={isModalOpenurl}
|
visible={isModalOpenurl}
|
||||||
|
|||||||
@@ -139,14 +139,24 @@ const EditToken = (props) => {
|
|||||||
if (formApiRef.current) {
|
if (formApiRef.current) {
|
||||||
if (!isEdit) {
|
if (!isEdit) {
|
||||||
formApiRef.current.setValues(getInitValues());
|
formApiRef.current.setValues(getInitValues());
|
||||||
} else {
|
|
||||||
loadToken();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadModels();
|
loadModels();
|
||||||
loadGroups();
|
loadGroups();
|
||||||
}, [props.editingToken.id]);
|
}, [props.editingToken.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.visiable) {
|
||||||
|
if (isEdit) {
|
||||||
|
loadToken();
|
||||||
|
} else {
|
||||||
|
formApiRef.current?.setValues(getInitValues());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formApiRef.current?.reset();
|
||||||
|
}
|
||||||
|
}, [props.visiable, props.editingToken.id]);
|
||||||
|
|
||||||
const generateRandomSuffix = () => {
|
const generateRandomSuffix = () => {
|
||||||
const characters =
|
const characters =
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
|||||||
Reference in New Issue
Block a user