mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-18 10:07:27 +00:00
Merge branch 'main' into french-translation-final
This commit is contained in:
@@ -81,6 +81,9 @@ const PersonalSetting = () => {
|
||||
webhookSecret: '',
|
||||
notificationEmail: '',
|
||||
barkUrl: '',
|
||||
gotifyUrl: '',
|
||||
gotifyToken: '',
|
||||
gotifyPriority: 5,
|
||||
acceptUnsetModelRatioModel: false,
|
||||
recordIpLog: false,
|
||||
});
|
||||
@@ -149,6 +152,12 @@ const PersonalSetting = () => {
|
||||
webhookSecret: settings.webhook_secret || '',
|
||||
notificationEmail: settings.notification_email || '',
|
||||
barkUrl: settings.bark_url || '',
|
||||
gotifyUrl: settings.gotify_url || '',
|
||||
gotifyToken: settings.gotify_token || '',
|
||||
gotifyPriority:
|
||||
settings.gotify_priority !== undefined
|
||||
? settings.gotify_priority
|
||||
: 5,
|
||||
acceptUnsetModelRatioModel:
|
||||
settings.accept_unset_model_ratio_model || false,
|
||||
recordIpLog: settings.record_ip_log || false,
|
||||
@@ -406,6 +415,12 @@ const PersonalSetting = () => {
|
||||
webhook_secret: notificationSettings.webhookSecret,
|
||||
notification_email: notificationSettings.notificationEmail,
|
||||
bark_url: notificationSettings.barkUrl,
|
||||
gotify_url: notificationSettings.gotifyUrl,
|
||||
gotify_token: notificationSettings.gotifyToken,
|
||||
gotify_priority: (() => {
|
||||
const parsed = parseInt(notificationSettings.gotifyPriority);
|
||||
return isNaN(parsed) ? 5 : parsed;
|
||||
})(),
|
||||
accept_unset_model_ratio_model:
|
||||
notificationSettings.acceptUnsetModelRatioModel,
|
||||
record_ip_log: notificationSettings.recordIpLog,
|
||||
|
||||
@@ -400,6 +400,7 @@ const NotificationSettings = ({
|
||||
<Radio value='email'>{t('邮件通知')}</Radio>
|
||||
<Radio value='webhook'>{t('Webhook通知')}</Radio>
|
||||
<Radio value='bark'>{t('Bark通知')}</Radio>
|
||||
<Radio value='gotify'>{t('Gotify通知')}</Radio>
|
||||
</Form.RadioGroup>
|
||||
|
||||
<Form.AutoComplete
|
||||
@@ -589,7 +590,108 @@ const NotificationSettings = ({
|
||||
rel='noopener noreferrer'
|
||||
className='text-blue-500 hover:text-blue-600 font-medium'
|
||||
>
|
||||
Bark 官方文档
|
||||
Bark {t('官方文档')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Gotify推送设置 */}
|
||||
{notificationSettings.warningType === 'gotify' && (
|
||||
<>
|
||||
<Form.Input
|
||||
field='gotifyUrl'
|
||||
label={t('Gotify服务器地址')}
|
||||
placeholder={t(
|
||||
'请输入Gotify服务器地址,例如: https://gotify.example.com',
|
||||
)}
|
||||
onChange={(val) => handleFormChange('gotifyUrl', val)}
|
||||
prefix={<IconLink />}
|
||||
extraText={t(
|
||||
'支持HTTP和HTTPS,填写Gotify服务器的完整URL地址',
|
||||
)}
|
||||
showClear
|
||||
rules={[
|
||||
{
|
||||
required:
|
||||
notificationSettings.warningType === 'gotify',
|
||||
message: t('请输入Gotify服务器地址'),
|
||||
},
|
||||
{
|
||||
pattern: /^https?:\/\/.+/,
|
||||
message: t('Gotify服务器地址必须以http://或https://开头'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Form.Input
|
||||
field='gotifyToken'
|
||||
label={t('Gotify应用令牌')}
|
||||
placeholder={t('请输入Gotify应用令牌')}
|
||||
onChange={(val) => handleFormChange('gotifyToken', val)}
|
||||
prefix={<IconKey />}
|
||||
extraText={t(
|
||||
'在Gotify服务器创建应用后获得的令牌,用于发送通知',
|
||||
)}
|
||||
showClear
|
||||
rules={[
|
||||
{
|
||||
required:
|
||||
notificationSettings.warningType === 'gotify',
|
||||
message: t('请输入Gotify应用令牌'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Form.AutoComplete
|
||||
field='gotifyPriority'
|
||||
label={t('消息优先级')}
|
||||
placeholder={t('请选择消息优先级')}
|
||||
data={[
|
||||
{ value: 0, label: t('0 - 最低') },
|
||||
{ value: 2, label: t('2 - 低') },
|
||||
{ value: 5, label: t('5 - 正常(默认)') },
|
||||
{ value: 8, label: t('8 - 高') },
|
||||
{ value: 10, label: t('10 - 最高') },
|
||||
]}
|
||||
onChange={(val) =>
|
||||
handleFormChange('gotifyPriority', val)
|
||||
}
|
||||
prefix={<IconBell />}
|
||||
extraText={t('消息优先级,范围0-10,默认为5')}
|
||||
style={{ width: '100%', maxWidth: '300px' }}
|
||||
/>
|
||||
|
||||
<div className='mt-3 p-4 bg-gray-50/50 rounded-xl'>
|
||||
<div className='text-sm text-gray-700 mb-3'>
|
||||
<strong>{t('配置说明')}</strong>
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 space-y-2'>
|
||||
<div>
|
||||
1. {t('在Gotify服务器的应用管理中创建新应用')}
|
||||
</div>
|
||||
<div>
|
||||
2.{' '}
|
||||
{t(
|
||||
'复制应用的令牌(Token)并填写到上方的应用令牌字段',
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
3. {t('填写Gotify服务器的完整URL地址')}
|
||||
</div>
|
||||
<div className='mt-3 pt-3 border-t border-gray-200'>
|
||||
<span className='text-gray-400'>
|
||||
{t('更多信息请参考')}
|
||||
</span>{' '}
|
||||
<a
|
||||
href='https://gotify.net/'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='text-blue-500 hover:text-blue-600 font-medium'
|
||||
>
|
||||
Gotify {t('官方文档')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,8 @@ import {
|
||||
IconCode,
|
||||
IconGlobe,
|
||||
IconBolt,
|
||||
IconChevronUp,
|
||||
IconChevronDown,
|
||||
} from '@douyinfe/semi-icons';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
@@ -169,6 +171,10 @@ const EditChannelModal = (props) => {
|
||||
vertex_key_type: 'json',
|
||||
// 企业账户设置
|
||||
is_enterprise_account: false,
|
||||
// 字段透传控制默认值
|
||||
allow_service_tier: false,
|
||||
disable_store: false, // false = 允许透传(默认开启)
|
||||
allow_safety_identifier: false,
|
||||
};
|
||||
const [batch, setBatch] = useState(false);
|
||||
const [multiToSingle, setMultiToSingle] = useState(false);
|
||||
@@ -202,6 +208,27 @@ const EditChannelModal = (props) => {
|
||||
keyData: '',
|
||||
});
|
||||
|
||||
// 专门的2FA验证状态(用于TwoFactorAuthModal)
|
||||
const [show2FAVerifyModal, setShow2FAVerifyModal] = useState(false);
|
||||
const [verifyCode, setVerifyCode] = useState('');
|
||||
const [verifyLoading, setVerifyLoading] = useState(false);
|
||||
|
||||
// 表单块导航相关状态
|
||||
const formSectionRefs = useRef({
|
||||
basicInfo: null,
|
||||
apiConfig: null,
|
||||
modelConfig: null,
|
||||
advancedSettings: null,
|
||||
channelExtraSettings: null,
|
||||
});
|
||||
const [currentSectionIndex, setCurrentSectionIndex] = useState(0);
|
||||
const formSections = ['basicInfo', 'apiConfig', 'modelConfig', 'advancedSettings', 'channelExtraSettings'];
|
||||
const formContainerRef = useRef(null);
|
||||
|
||||
// 2FA状态更新辅助函数
|
||||
const updateTwoFAState = (updates) => {
|
||||
setTwoFAState((prev) => ({ ...prev, ...updates }));
|
||||
};
|
||||
// 使用通用安全验证 Hook
|
||||
const {
|
||||
isModalVisible,
|
||||
@@ -241,6 +268,44 @@ const EditChannelModal = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 重置2FA验证状态
|
||||
const reset2FAVerifyState = () => {
|
||||
setShow2FAVerifyModal(false);
|
||||
setVerifyCode('');
|
||||
setVerifyLoading(false);
|
||||
};
|
||||
|
||||
// 表单导航功能
|
||||
const scrollToSection = (sectionKey) => {
|
||||
const sectionElement = formSectionRefs.current[sectionKey];
|
||||
if (sectionElement) {
|
||||
sectionElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToSection = (direction) => {
|
||||
const availableSections = formSections.filter(section => {
|
||||
if (section === 'apiConfig') {
|
||||
return showApiConfigCard;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
let newIndex;
|
||||
if (direction === 'up') {
|
||||
newIndex = currentSectionIndex > 0 ? currentSectionIndex - 1 : availableSections.length - 1;
|
||||
} else {
|
||||
newIndex = currentSectionIndex < availableSections.length - 1 ? currentSectionIndex + 1 : 0;
|
||||
}
|
||||
|
||||
setCurrentSectionIndex(newIndex);
|
||||
scrollToSection(availableSections[newIndex]);
|
||||
};
|
||||
|
||||
// 渠道额外设置状态
|
||||
const [channelSettings, setChannelSettings] = useState({
|
||||
force_format: false,
|
||||
@@ -453,17 +518,27 @@ const EditChannelModal = (props) => {
|
||||
data.vertex_key_type = parsedSettings.vertex_key_type || 'json';
|
||||
// 读取企业账户设置
|
||||
data.is_enterprise_account = parsedSettings.openrouter_enterprise === true;
|
||||
// 读取字段透传控制设置
|
||||
data.allow_service_tier = parsedSettings.allow_service_tier || false;
|
||||
data.disable_store = parsedSettings.disable_store || false;
|
||||
data.allow_safety_identifier = parsedSettings.allow_safety_identifier || false;
|
||||
} catch (error) {
|
||||
console.error('解析其他设置失败:', error);
|
||||
data.azure_responses_version = '';
|
||||
data.region = '';
|
||||
data.vertex_key_type = 'json';
|
||||
data.is_enterprise_account = false;
|
||||
data.allow_service_tier = false;
|
||||
data.disable_store = false;
|
||||
data.allow_safety_identifier = false;
|
||||
}
|
||||
} else {
|
||||
// 兼容历史数据:老渠道没有 settings 时,默认按 json 展示
|
||||
data.vertex_key_type = 'json';
|
||||
data.is_enterprise_account = false;
|
||||
data.allow_service_tier = false;
|
||||
data.disable_store = false;
|
||||
data.allow_safety_identifier = false;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -715,6 +790,8 @@ const EditChannelModal = (props) => {
|
||||
fetchModelGroups();
|
||||
// 重置手动输入模式状态
|
||||
setUseManualInput(false);
|
||||
// 重置导航状态
|
||||
setCurrentSectionIndex(0);
|
||||
} else {
|
||||
// 统一的模态框关闭重置逻辑
|
||||
resetModalState();
|
||||
@@ -900,21 +977,33 @@ const EditChannelModal = (props) => {
|
||||
};
|
||||
localInputs.setting = JSON.stringify(channelExtraSettings);
|
||||
|
||||
// 处理type === 20的企业账户设置
|
||||
if (localInputs.type === 20) {
|
||||
let settings = {};
|
||||
if (localInputs.settings) {
|
||||
try {
|
||||
settings = JSON.parse(localInputs.settings);
|
||||
} catch (error) {
|
||||
console.error('解析settings失败:', error);
|
||||
}
|
||||
// 处理 settings 字段(包括企业账户设置和字段透传控制)
|
||||
let settings = {};
|
||||
if (localInputs.settings) {
|
||||
try {
|
||||
settings = JSON.parse(localInputs.settings);
|
||||
} catch (error) {
|
||||
console.error('解析settings失败:', error);
|
||||
}
|
||||
// 设置企业账户标识,无论是true还是false都要传到后端
|
||||
settings.openrouter_enterprise = localInputs.is_enterprise_account === true;
|
||||
localInputs.settings = JSON.stringify(settings);
|
||||
}
|
||||
|
||||
// type === 20: 设置企业账户标识,无论是true还是false都要传到后端
|
||||
if (localInputs.type === 20) {
|
||||
settings.openrouter_enterprise = localInputs.is_enterprise_account === true;
|
||||
}
|
||||
|
||||
// type === 1 (OpenAI) 或 type === 14 (Claude): 设置字段透传控制(显式保存布尔值)
|
||||
if (localInputs.type === 1 || localInputs.type === 14) {
|
||||
settings.allow_service_tier = localInputs.allow_service_tier === true;
|
||||
// 仅 OpenAI 渠道需要 store 和 safety_identifier
|
||||
if (localInputs.type === 1) {
|
||||
settings.disable_store = localInputs.disable_store === true;
|
||||
settings.allow_safety_identifier = localInputs.allow_safety_identifier === true;
|
||||
}
|
||||
}
|
||||
|
||||
localInputs.settings = JSON.stringify(settings);
|
||||
|
||||
// 清理不需要发送到后端的字段
|
||||
delete localInputs.force_format;
|
||||
delete localInputs.thinking_to_content;
|
||||
@@ -925,6 +1014,10 @@ const EditChannelModal = (props) => {
|
||||
delete localInputs.is_enterprise_account;
|
||||
// 顶层的 vertex_key_type 不应发送给后端
|
||||
delete localInputs.vertex_key_type;
|
||||
// 清理字段透传控制的临时字段
|
||||
delete localInputs.allow_service_tier;
|
||||
delete localInputs.disable_store;
|
||||
delete localInputs.allow_safety_identifier;
|
||||
|
||||
let res;
|
||||
localInputs.auto_ban = localInputs.auto_ban ? 1 : 0;
|
||||
@@ -1240,7 +1333,41 @@ const EditChannelModal = (props) => {
|
||||
visible={props.visible}
|
||||
width={isMobile ? '100%' : 600}
|
||||
footer={
|
||||
<div className='flex justify-end bg-white'>
|
||||
<div className='flex justify-between items-center bg-white'>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
icon={<IconChevronUp />}
|
||||
onClick={() => navigateToSection('up')}
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
title={t('上一个表单块')}
|
||||
/>
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
icon={<IconChevronDown />}
|
||||
onClick={() => navigateToSection('down')}
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
title={t('下一个表单块')}
|
||||
/>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
theme='solid'
|
||||
@@ -1271,10 +1398,14 @@ const EditChannelModal = (props) => {
|
||||
>
|
||||
{() => (
|
||||
<Spin spinning={loading}>
|
||||
<div className='p-2'>
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Basic Info */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<div
|
||||
className='p-2'
|
||||
ref={formContainerRef}
|
||||
>
|
||||
<div ref={el => formSectionRefs.current.basicInfo = el}>
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Basic Info */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<Avatar
|
||||
size='small'
|
||||
color='blue'
|
||||
@@ -1748,13 +1879,15 @@ const EditChannelModal = (props) => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* API Configuration Card */}
|
||||
{showApiConfigCard && (
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: API Config */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<div ref={el => formSectionRefs.current.apiConfig = el}>
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: API Config */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<Avatar
|
||||
size='small'
|
||||
color='green'
|
||||
@@ -1965,13 +2098,15 @@ const EditChannelModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Model Configuration Card */}
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Model Config */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<div ref={el => formSectionRefs.current.modelConfig = el}>
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Model Config */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<Avatar
|
||||
size='small'
|
||||
color='purple'
|
||||
@@ -2166,12 +2301,14 @@ const EditChannelModal = (props) => {
|
||||
formApi={formApiRef.current}
|
||||
extraText={t('键为请求中的模型名称,值为要替换的模型名称')}
|
||||
/>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Advanced Settings Card */}
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Advanced Settings */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<div ref={el => formSectionRefs.current.advancedSettings = el}>
|
||||
<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'
|
||||
@@ -2384,12 +2521,84 @@ const EditChannelModal = (props) => {
|
||||
'键为原状态码,值为要复写的状态码,仅影响本地判断',
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 字段透传控制 - OpenAI 渠道 */}
|
||||
{inputs.type === 1 && (
|
||||
<>
|
||||
<div className='mt-4 mb-2 text-sm font-medium text-gray-700'>
|
||||
{t('字段透传控制')}
|
||||
</div>
|
||||
|
||||
<Form.Switch
|
||||
field='allow_service_tier'
|
||||
label={t('允许 service_tier 透传')}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
onChange={(value) =>
|
||||
handleChannelOtherSettingsChange('allow_service_tier', value)
|
||||
}
|
||||
extraText={t(
|
||||
'service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用',
|
||||
)}
|
||||
/>
|
||||
|
||||
<Form.Switch
|
||||
field='disable_store'
|
||||
label={t('禁用 store 透传')}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
onChange={(value) =>
|
||||
handleChannelOtherSettingsChange('disable_store', value)
|
||||
}
|
||||
extraText={t(
|
||||
'store 字段用于授权 OpenAI 存储请求数据以评估和优化产品。默认关闭,开启后可能导致 Codex 无法正常使用',
|
||||
)}
|
||||
/>
|
||||
|
||||
<Form.Switch
|
||||
field='allow_safety_identifier'
|
||||
label={t('允许 safety_identifier 透传')}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
onChange={(value) =>
|
||||
handleChannelOtherSettingsChange('allow_safety_identifier', value)
|
||||
}
|
||||
extraText={t(
|
||||
'safety_identifier 字段用于帮助 OpenAI 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 字段透传控制 - Claude 渠道 */}
|
||||
{(inputs.type === 14) && (
|
||||
<>
|
||||
<div className='mt-4 mb-2 text-sm font-medium text-gray-700'>
|
||||
{t('字段透传控制')}
|
||||
</div>
|
||||
|
||||
<Form.Switch
|
||||
field='allow_service_tier'
|
||||
label={t('允许 service_tier 透传')}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
onChange={(value) =>
|
||||
handleChannelOtherSettingsChange('allow_service_tier', value)
|
||||
}
|
||||
extraText={t(
|
||||
'service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Channel Extra Settings Card */}
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Channel Extra Settings */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<div ref={el => formSectionRefs.current.channelExtraSettings = el}>
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Channel Extra Settings */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<Avatar
|
||||
size='small'
|
||||
color='violet'
|
||||
@@ -2487,7 +2696,8 @@ const EditChannelModal = (props) => {
|
||||
'如果用户请求中包含系统提示词,则使用此设置拼接到用户的系统提示词前面',
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
)}
|
||||
|
||||
@@ -164,6 +164,11 @@ export const CHANNEL_OPTIONS = [
|
||||
color: 'blue',
|
||||
label: 'SubModel',
|
||||
},
|
||||
{
|
||||
value: 54,
|
||||
color: 'blue',
|
||||
label: '豆包视频',
|
||||
},
|
||||
];
|
||||
|
||||
export const MODEL_TABLE_PAGE_SIZE = 10;
|
||||
|
||||
@@ -337,6 +337,8 @@ export function getChannelIcon(channelType) {
|
||||
return <Kling.Color size={iconSize} />;
|
||||
case 51: // 即梦 Jimeng
|
||||
return <Jimeng.Color size={iconSize} />;
|
||||
case 54: // 豆包视频 Doubao Video
|
||||
return <Doubao.Color size={iconSize} />;
|
||||
case 8: // 自定义渠道
|
||||
case 22: // 知识库:FastGPT
|
||||
return <FastGPT.Color size={iconSize} />;
|
||||
|
||||
@@ -1313,6 +1313,8 @@
|
||||
"请输入Webhook地址,例如: https://example.com/webhook": "Please enter the Webhook URL, e.g.: https://example.com/webhook",
|
||||
"邮件通知": "Email notification",
|
||||
"Webhook通知": "Webhook notification",
|
||||
"Bark通知": "Bark notification",
|
||||
"Gotify通知": "Gotify notification",
|
||||
"接口凭证(可选)": "Interface credentials (optional)",
|
||||
"密钥将以 Bearer 方式添加到请求头中,用于验证webhook请求的合法性": "The secret will be added to the request header as a Bearer token to verify the legitimacy of the webhook request",
|
||||
"Authorization: Bearer your-secret-key": "Authorization: Bearer your-secret-key",
|
||||
@@ -1323,6 +1325,36 @@
|
||||
"通知邮箱": "Notification email",
|
||||
"设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱": "Set the email address for receiving quota warning notifications, if not set, the email address bound to the account will be used",
|
||||
"留空则使用账号绑定的邮箱": "If left blank, the email address bound to the account will be used",
|
||||
"Bark推送URL": "Bark Push URL",
|
||||
"请输入Bark推送URL,例如: https://api.day.app/yourkey/{{title}}/{{content}}": "Please enter Bark push URL, e.g.: https://api.day.app/yourkey/{{title}}/{{content}}",
|
||||
"支持HTTP和HTTPS,模板变量: {{title}} (通知标题), {{content}} (通知内容)": "Supports HTTP and HTTPS, template variables: {{title}} (notification title), {{content}} (notification content)",
|
||||
"请输入Bark推送URL": "Please enter Bark push URL",
|
||||
"Bark推送URL必须以http://或https://开头": "Bark push URL must start with http:// or https://",
|
||||
"模板示例": "Template example",
|
||||
"更多参数请参考": "For more parameters, please refer to",
|
||||
"Gotify服务器地址": "Gotify server address",
|
||||
"请输入Gotify服务器地址,例如: https://gotify.example.com": "Please enter Gotify server address, e.g.: https://gotify.example.com",
|
||||
"支持HTTP和HTTPS,填写Gotify服务器的完整URL地址": "Supports HTTP and HTTPS, enter the complete URL of the Gotify server",
|
||||
"请输入Gotify服务器地址": "Please enter Gotify server address",
|
||||
"Gotify服务器地址必须以http://或https://开头": "Gotify server address must start with http:// or https://",
|
||||
"Gotify应用令牌": "Gotify application token",
|
||||
"请输入Gotify应用令牌": "Please enter Gotify application token",
|
||||
"在Gotify服务器创建应用后获得的令牌,用于发送通知": "Token obtained after creating an application on the Gotify server, used to send notifications",
|
||||
"消息优先级": "Message priority",
|
||||
"请选择消息优先级": "Please select message priority",
|
||||
"0 - 最低": "0 - Lowest",
|
||||
"2 - 低": "2 - Low",
|
||||
"5 - 正常(默认)": "5 - Normal (default)",
|
||||
"8 - 高": "8 - High",
|
||||
"10 - 最高": "10 - Highest",
|
||||
"消息优先级,范围0-10,默认为5": "Message priority, range 0-10, default is 5",
|
||||
"配置说明": "Configuration instructions",
|
||||
"在Gotify服务器的应用管理中创建新应用": "Create a new application in the Gotify server's application management",
|
||||
"复制应用的令牌(Token)并填写到上方的应用令牌字段": "Copy the application token and fill it in the application token field above",
|
||||
"填写Gotify服务器的完整URL地址": "Fill in the complete URL address of the Gotify server",
|
||||
"更多信息请参考": "For more information, please refer to",
|
||||
"通知内容": "Notification content",
|
||||
"官方文档": "Official documentation",
|
||||
"API地址": "Base URL",
|
||||
"对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "For official channels, the new-api has a built-in address. Unless it is a third-party proxy site or a special Azure access address, there is no need to fill it in",
|
||||
"渠道额外设置": "Channel extra settings",
|
||||
@@ -2191,6 +2223,13 @@
|
||||
"输入 Origin 后回车,如:https://example.com": "Enter Origin and press Enter, e.g.: https://example.com",
|
||||
"保存 Passkey 设置": "Save Passkey Settings",
|
||||
"黑名单": "Blacklist",
|
||||
"字段透传控制": "Field Pass-through Control",
|
||||
"允许 service_tier 透传": "Allow service_tier Pass-through",
|
||||
"禁用 store 透传": "Disable store Pass-through",
|
||||
"允许 safety_identifier 透传": "Allow safety_identifier Pass-through",
|
||||
"service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用": "The service_tier field is used to specify service level. Allowing pass-through may result in higher billing than expected. Disabled by default to avoid extra charges",
|
||||
"store 字段用于授权 OpenAI 存储请求数据以评估和优化产品。默认关闭,开启后可能导致 Codex 无法正常使用": "The store field authorizes OpenAI to store request data for product evaluation and optimization. Disabled by default. Enabling may cause Codex to malfunction",
|
||||
"safety_identifier 字段用于帮助 OpenAI 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私": "The safety_identifier field helps OpenAI identify application users who may violate usage policies. Disabled by default to protect user privacy",
|
||||
"common": {
|
||||
"changeLanguage": "Change Language"
|
||||
}
|
||||
|
||||
@@ -1308,6 +1308,8 @@
|
||||
"请输入Webhook地址,例如: https://example.com/webhook": "Veuillez saisir l'URL du Webhook, par exemple : https://example.com/webhook",
|
||||
"邮件通知": "Notification par e-mail",
|
||||
"Webhook通知": "Notification par Webhook",
|
||||
"Bark通知": "Notification Bark",
|
||||
"Gotify通知": "Notification Gotify",
|
||||
"接口凭证(可选)": "Informations d'identification de l'interface (facultatif)",
|
||||
"密钥将以 Bearer 方式添加到请求头中,用于验证webhook请求的合法性": "Le secret sera ajouté à l'en-tête de la requête en tant que jeton Bearer pour vérifier la légitimité de la requête webhook",
|
||||
"Authorization: Bearer your-secret-key": "Autorisation : Bearer votre-clé-secrète",
|
||||
@@ -1318,6 +1320,36 @@
|
||||
"通知邮箱": "E-mail de notification",
|
||||
"设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱": "Définissez l'adresse e-mail pour recevoir les notifications d'avertissement de quota, si elle n'est pas définie, l'adresse e-mail liée au compte sera utilisée",
|
||||
"留空则使用账号绑定的邮箱": "Si ce champ est laissé vide, l'adresse e-mail liée au compte sera utilisée",
|
||||
"Bark推送URL": "URL de notification Bark",
|
||||
"请输入Bark推送URL,例如: https://api.day.app/yourkey/{{title}}/{{content}}": "Veuillez saisir l'URL de notification Bark, par exemple : https://api.day.app/yourkey/{{title}}/{{content}}",
|
||||
"支持HTTP和HTTPS,模板变量: {{title}} (通知标题), {{content}} (通知内容)": "Prend en charge HTTP et HTTPS, variables de modèle : {{title}} (titre de la notification), {{content}} (contenu de la notification)",
|
||||
"请输入Bark推送URL": "Veuillez saisir l'URL de notification Bark",
|
||||
"Bark推送URL必须以http://或https://开头": "L'URL de notification Bark doit commencer par http:// ou https://",
|
||||
"模板示例": "Exemple de modèle",
|
||||
"更多参数请参考": "Pour plus de paramètres, veuillez vous référer à",
|
||||
"Gotify服务器地址": "Adresse du serveur Gotify",
|
||||
"请输入Gotify服务器地址,例如: https://gotify.example.com": "Veuillez saisir l'adresse du serveur Gotify, par exemple : https://gotify.example.com",
|
||||
"支持HTTP和HTTPS,填写Gotify服务器的完整URL地址": "Prend en charge HTTP et HTTPS, saisissez l'URL complète du serveur Gotify",
|
||||
"请输入Gotify服务器地址": "Veuillez saisir l'adresse du serveur Gotify",
|
||||
"Gotify服务器地址必须以http://或https://开头": "L'adresse du serveur Gotify doit commencer par http:// ou https://",
|
||||
"Gotify应用令牌": "Jeton d'application Gotify",
|
||||
"请输入Gotify应用令牌": "Veuillez saisir le jeton d'application Gotify",
|
||||
"在Gotify服务器创建应用后获得的令牌,用于发送通知": "Jeton obtenu après la création d'une application sur le serveur Gotify, utilisé pour envoyer des notifications",
|
||||
"消息优先级": "Priorité du message",
|
||||
"请选择消息优先级": "Veuillez sélectionner la priorité du message",
|
||||
"0 - 最低": "0 - La plus basse",
|
||||
"2 - 低": "2 - Basse",
|
||||
"5 - 正常(默认)": "5 - Normale (par défaut)",
|
||||
"8 - 高": "8 - Haute",
|
||||
"10 - 最高": "10 - La plus haute",
|
||||
"消息优先级,范围0-10,默认为5": "Priorité du message, plage 0-10, par défaut 5",
|
||||
"配置说明": "Instructions de configuration",
|
||||
"在Gotify服务器的应用管理中创建新应用": "Créer une nouvelle application dans la gestion des applications du serveur Gotify",
|
||||
"复制应用的令牌(Token)并填写到上方的应用令牌字段": "Copier le jeton de l'application et le remplir dans le champ de jeton d'application ci-dessus",
|
||||
"填写Gotify服务器的完整URL地址": "Remplir l'adresse URL complète du serveur Gotify",
|
||||
"更多信息请参考": "Pour plus d'informations, veuillez vous référer à",
|
||||
"通知内容": "Contenu de la notification",
|
||||
"官方文档": "Documentation officielle",
|
||||
"API地址": "URL de base",
|
||||
"对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "Pour les canaux officiels, le new-api a une adresse intégrée. Sauf s'il s'agit d'un site proxy tiers ou d'une adresse d'accès Azure spéciale, il n'est pas nécessaire de la remplir",
|
||||
"渠道额外设置": "Paramètres supplémentaires du canal",
|
||||
@@ -2134,6 +2166,13 @@
|
||||
"关闭侧边栏": "Fermer la barre latérale",
|
||||
"定价": "Tarification",
|
||||
"语言": "Langue",
|
||||
"字段透传控制": "Contrôle du passage des champs",
|
||||
"允许 service_tier 透传": "Autoriser le passage de service_tier",
|
||||
"禁用 store 透传": "Désactiver le passage de store",
|
||||
"允许 safety_identifier 透传": "Autoriser le passage de safety_identifier",
|
||||
"service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用": "Le champ service_tier est utilisé pour spécifier le niveau de service. Permettre le passage peut entraîner une facturation plus élevée que prévu. Désactivé par défaut pour éviter des frais supplémentaires",
|
||||
"store 字段用于授权 OpenAI 存储请求数据以评估和优化产品。默认关闭,开启后可能导致 Codex 无法正常使用": "Le champ store autorise OpenAI à stocker les données de requête pour l'évaluation et l'optimisation du produit. Désactivé par défaut. L'activation peut causer un dysfonctionnement de Codex",
|
||||
"safety_identifier 字段用于帮助 OpenAI 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私": "Le champ safety_identifier aide OpenAI à identifier les utilisateurs d'applications susceptibles de violer les politiques d'utilisation. Désactivé par défaut pour protéger la confidentialité des utilisateurs",
|
||||
"common": {
|
||||
"changeLanguage": "Changer de langue"
|
||||
},
|
||||
|
||||
@@ -18,7 +18,26 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Banner, Button, Form, Space, Spin } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Banner,
|
||||
Button,
|
||||
Form,
|
||||
Space,
|
||||
Spin,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Table,
|
||||
Modal,
|
||||
Input,
|
||||
Divider,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconPlus,
|
||||
IconEdit,
|
||||
IconDelete,
|
||||
IconSearch,
|
||||
IconSaveStroked,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import {
|
||||
compareObjects,
|
||||
API,
|
||||
@@ -37,6 +56,52 @@ export default function SettingsChats(props) {
|
||||
});
|
||||
const refForm = useRef();
|
||||
const [inputsRow, setInputsRow] = useState(inputs);
|
||||
const [editMode, setEditMode] = useState('visual');
|
||||
const [chatConfigs, setChatConfigs] = useState([]);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [editingConfig, setEditingConfig] = useState(null);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const modalFormRef = useRef();
|
||||
|
||||
const jsonToConfigs = (jsonString) => {
|
||||
try {
|
||||
const configs = JSON.parse(jsonString);
|
||||
return Array.isArray(configs)
|
||||
? configs.map((config, index) => ({
|
||||
id: index,
|
||||
name: Object.keys(config)[0] || '',
|
||||
url: Object.values(config)[0] || '',
|
||||
}))
|
||||
: [];
|
||||
} catch (error) {
|
||||
console.error('JSON parse error:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const configsToJson = (configs) => {
|
||||
const jsonArray = configs.map((config) => ({
|
||||
[config.name]: config.url,
|
||||
}));
|
||||
return JSON.stringify(jsonArray, null, 2);
|
||||
};
|
||||
|
||||
const syncJsonToConfigs = () => {
|
||||
const configs = jsonToConfigs(inputs.Chats);
|
||||
setChatConfigs(configs);
|
||||
};
|
||||
|
||||
const syncConfigsToJson = (configs) => {
|
||||
const jsonString = configsToJson(configs);
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
Chats: jsonString,
|
||||
}));
|
||||
if (refForm.current && editMode === 'json') {
|
||||
refForm.current.setValues({ Chats: jsonString });
|
||||
}
|
||||
};
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
@@ -103,16 +168,184 @@ export default function SettingsChats(props) {
|
||||
}
|
||||
setInputs(currentInputs);
|
||||
setInputsRow(structuredClone(currentInputs));
|
||||
refForm.current.setValues(currentInputs);
|
||||
if (refForm.current) {
|
||||
refForm.current.setValues(currentInputs);
|
||||
}
|
||||
|
||||
// 同步到可视化配置
|
||||
const configs = jsonToConfigs(currentInputs.Chats || '[]');
|
||||
setChatConfigs(configs);
|
||||
}, [props.options]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editMode === 'visual') {
|
||||
syncJsonToConfigs();
|
||||
}
|
||||
}, [inputs.Chats, editMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (refForm.current && editMode === 'json') {
|
||||
refForm.current.setValues(inputs);
|
||||
}
|
||||
}, [editMode, inputs]);
|
||||
|
||||
const handleAddConfig = () => {
|
||||
setEditingConfig({ name: '', url: '' });
|
||||
setIsEdit(false);
|
||||
setModalVisible(true);
|
||||
setTimeout(() => {
|
||||
if (modalFormRef.current) {
|
||||
modalFormRef.current.setValues({ name: '', url: '' });
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleEditConfig = (config) => {
|
||||
setEditingConfig({ ...config });
|
||||
setIsEdit(true);
|
||||
setModalVisible(true);
|
||||
setTimeout(() => {
|
||||
if (modalFormRef.current) {
|
||||
modalFormRef.current.setValues(config);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleDeleteConfig = (id) => {
|
||||
const newConfigs = chatConfigs.filter((config) => config.id !== id);
|
||||
setChatConfigs(newConfigs);
|
||||
syncConfigsToJson(newConfigs);
|
||||
showSuccess(t('删除成功'));
|
||||
};
|
||||
|
||||
const handleModalOk = () => {
|
||||
if (modalFormRef.current) {
|
||||
modalFormRef.current
|
||||
.validate()
|
||||
.then((values) => {
|
||||
// 检查名称是否重复
|
||||
const isDuplicate = chatConfigs.some(
|
||||
(config) =>
|
||||
config.name === values.name &&
|
||||
(!isEdit || config.id !== editingConfig.id)
|
||||
);
|
||||
|
||||
if (isDuplicate) {
|
||||
showError(t('聊天应用名称已存在,请使用其他名称'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEdit) {
|
||||
const newConfigs = chatConfigs.map((config) =>
|
||||
config.id === editingConfig.id
|
||||
? { ...editingConfig, name: values.name, url: values.url }
|
||||
: config,
|
||||
);
|
||||
setChatConfigs(newConfigs);
|
||||
syncConfigsToJson(newConfigs);
|
||||
} else {
|
||||
const maxId =
|
||||
chatConfigs.length > 0
|
||||
? Math.max(...chatConfigs.map((c) => c.id))
|
||||
: -1;
|
||||
const newConfig = {
|
||||
id: maxId + 1,
|
||||
name: values.name,
|
||||
url: values.url,
|
||||
};
|
||||
const newConfigs = [...chatConfigs, newConfig];
|
||||
setChatConfigs(newConfigs);
|
||||
syncConfigsToJson(newConfigs);
|
||||
}
|
||||
setModalVisible(false);
|
||||
setEditingConfig(null);
|
||||
showSuccess(isEdit ? t('编辑成功') : t('添加成功'));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Modal form validation error:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
setModalVisible(false);
|
||||
setEditingConfig(null);
|
||||
};
|
||||
|
||||
const filteredConfigs = chatConfigs.filter(
|
||||
(config) =>
|
||||
!searchText ||
|
||||
config.name.toLowerCase().includes(searchText.toLowerCase()),
|
||||
);
|
||||
|
||||
const highlightKeywords = (text) => {
|
||||
if (!text) return text;
|
||||
|
||||
const parts = text.split(/(\{address\}|\{key\})/g);
|
||||
return parts.map((part, index) => {
|
||||
if (part === '{address}') {
|
||||
return (
|
||||
<span key={index} style={{ color: '#0077cc', fontWeight: 600 }}>
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
} else if (part === '{key}') {
|
||||
return (
|
||||
<span key={index} style={{ color: '#ff6b35', fontWeight: 600 }}>
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return part;
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('聊天应用名称'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text) => text || t('未命名'),
|
||||
},
|
||||
{
|
||||
title: t('URL链接'),
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
render: (text) => (
|
||||
<div style={{ maxWidth: 300, wordBreak: 'break-all' }}>
|
||||
{highlightKeywords(text)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<IconEdit />}
|
||||
size='small'
|
||||
onClick={() => handleEditConfig(record)}
|
||||
>
|
||||
{t('编辑')}
|
||||
</Button>
|
||||
<Button
|
||||
type='danger'
|
||||
icon={<IconDelete />}
|
||||
size='small'
|
||||
onClick={() => handleDeleteConfig(record.id)}
|
||||
>
|
||||
{t('删除')}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
values={inputs}
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Space vertical style={{ width: '100%' }}>
|
||||
<Form.Section text={t('聊天设置')}>
|
||||
<Banner
|
||||
type='info'
|
||||
@@ -120,34 +353,155 @@ export default function SettingsChats(props) {
|
||||
'链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1',
|
||||
)}
|
||||
/>
|
||||
<Form.TextArea
|
||||
label={t('聊天配置')}
|
||||
extraText={''}
|
||||
placeholder={t('为一个 JSON 文本')}
|
||||
field={'Chats'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: t('不是合法的 JSON 字符串'),
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
Chats: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span style={{ marginRight: 16, fontWeight: 600 }}>
|
||||
{t('编辑模式')}:
|
||||
</span>
|
||||
<RadioGroup
|
||||
type='button'
|
||||
value={editMode}
|
||||
onChange={(e) => {
|
||||
const newMode = e.target.value;
|
||||
setEditMode(newMode);
|
||||
|
||||
// 确保模式切换时数据正确同步
|
||||
setTimeout(() => {
|
||||
if (newMode === 'json' && refForm.current) {
|
||||
refForm.current.setValues(inputs);
|
||||
}
|
||||
}, 100);
|
||||
}}
|
||||
>
|
||||
<Radio value='visual'>{t('可视化编辑')}</Radio>
|
||||
<Radio value='json'>{t('JSON编辑')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{editMode === 'visual' ? (
|
||||
<div>
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<IconPlus />}
|
||||
onClick={handleAddConfig}
|
||||
>
|
||||
{t('添加聊天配置')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
theme='solid'
|
||||
icon={<IconSaveStroked />}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{t('保存聊天设置')}
|
||||
</Button>
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索聊天应用名称')}
|
||||
value={searchText}
|
||||
onChange={(value) => setSearchText(value)}
|
||||
style={{ width: 250 }}
|
||||
showClear
|
||||
/>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredConfigs}
|
||||
rowKey='id'
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: false,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) =>
|
||||
t('共 {{total}} 项,当前显示 {{start}}-{{end}} 项', {
|
||||
total,
|
||||
start: range[0],
|
||||
end: range[1],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
values={inputs}
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
>
|
||||
<Form.TextArea
|
||||
label={t('聊天配置')}
|
||||
extraText={''}
|
||||
placeholder={t('为一个 JSON 文本')}
|
||||
field={'Chats'}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return verifyJSON(value);
|
||||
},
|
||||
message: t('不是合法的 JSON 字符串'),
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
Chats: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Form.Section>
|
||||
</Form>
|
||||
<Space>
|
||||
<Button onClick={onSubmit}>{t('保存聊天设置')}</Button>
|
||||
|
||||
{editMode === 'json' && (
|
||||
<Space>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<IconSaveStroked />}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{t('保存聊天设置')}
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
<Modal
|
||||
title={isEdit ? t('编辑聊天配置') : t('添加聊天配置')}
|
||||
visible={modalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
width={600}
|
||||
>
|
||||
<Form getFormApi={(api) => (modalFormRef.current = api)}>
|
||||
<Form.Input
|
||||
field='name'
|
||||
label={t('聊天应用名称')}
|
||||
placeholder={t('请输入聊天应用名称')}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入聊天应用名称') },
|
||||
{ min: 1, message: t('名称不能为空') },
|
||||
]}
|
||||
/>
|
||||
<Form.Input
|
||||
field='url'
|
||||
label={t('URL链接')}
|
||||
placeholder={t('请输入完整的URL链接')}
|
||||
rules={[{ required: true, message: t('请输入URL链接') }]}
|
||||
/>
|
||||
<Banner
|
||||
type='info'
|
||||
description={t(
|
||||
'提示:链接中的{key}将被替换为API密钥,{address}将被替换为服务器地址',
|
||||
)}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user