Commit 7399e472 by CaIon

feat: add slide-in animations and update translations for new UI elements

# Conflicts:
#	web/src/components/table/channels/modals/EditChannelModal.jsx
parent a5e20269
......@@ -47,6 +47,8 @@ import {
Highlight,
Input,
Tooltip,
Collapse,
Dropdown,
} from '@douyinfe/semi-ui';
import {
getChannelModels,
......@@ -80,7 +82,6 @@ import {
IconGlobe,
IconBolt,
IconSearch,
IconChevronUp,
IconChevronDown,
} from '@douyinfe/semi-icons';
......@@ -124,8 +125,6 @@ const PARAM_OVERRIDE_OPERATIONS_TEMPLATE = {
],
};
const DEPRECATED_DOUBAO_CODING_PLAN_BASE_URL = 'doubao-coding-plan';
// 支持并且已适配通过接口获取模型列表的渠道类型
const MODEL_FETCHABLE_TYPES = new Set([
1, 4, 14, 34, 17, 26, 27, 24, 47, 25, 20, 23, 31, 40, 42, 48, 43,
......@@ -397,39 +396,13 @@ const EditChannelModal = (props) => {
[],
);
// 表单块导航相关状态
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 [advancedSettingsOpen, setAdvancedSettingsOpen] = useState(false);
const formContainerRef = useRef(null);
const doubaoApiClickCountRef = useRef(0);
const initialBaseUrlRef = useRef('');
const initialModelsRef = useRef([]);
const initialModelMappingRef = useRef('');
const initialStatusCodeMappingRef = useRef('');
const doubaoCodingPlanDeprecationMessage =
'Doubao Coding Plan 不再允许新增。根据火山方舟文档,Coding 套餐额度仅适用于 AI Coding 产品内调用,不适用于单独 API 调用;在非 AI Coding 产品中使用对应的 Base URL 和 API Key 可能被视为违规,并可能导致订阅停用或账号封禁。';
const canKeepDeprecatedDoubaoCodingPlan =
initialBaseUrlRef.current === DEPRECATED_DOUBAO_CODING_PLAN_BASE_URL;
const doubaoCodingPlanOptionLabel = (
<Tooltip content={doubaoCodingPlanDeprecationMessage} position='left'>
<span className='inline-flex items-center gap-2'>
<span>Doubao Coding Plan</span>
</span>
</Tooltip>
);
// 2FA状态更新辅助函数
const updateTwoFAState = (updates) => {
......@@ -481,43 +454,6 @@ const EditChannelModal = (props) => {
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 handleApiConfigSecretClick = () => {
if (inputs.type !== 45) return;
const next = doubaoApiClickCountRef.current + 1;
......@@ -960,7 +896,6 @@ const EditChannelModal = (props) => {
data.base_url = 'https://ark.cn-beijing.volces.com';
}
initialBaseUrlRef.current = data.base_url || '';
setInputs(data);
if (formApiRef.current) {
formApiRef.current.setValues(data);
......@@ -1006,7 +941,27 @@ const EditChannelModal = (props) => {
const managedByIonet = !!parsedIonet;
setIsIonetChannel(managedByIonet);
setIonetMetadata(parsedIonet);
// console.log(data);
// Smart expand: auto-open advanced settings if any advanced field has a value
const hasAdvancedValues =
(data.model_mapping && data.model_mapping.trim()) ||
(data.param_override && data.param_override.trim()) ||
(data.status_code_mapping && data.status_code_mapping.trim()) ||
(data.header_override && data.header_override.trim()) ||
(data.tag && data.tag.trim()) ||
(data.remark && data.remark.trim()) ||
(data.priority && data.priority !== 0) ||
(data.weight && data.weight !== 0) ||
(data.proxy && data.proxy.trim()) ||
(data.system_prompt && data.system_prompt.trim()) ||
data.thinking_to_content ||
data.pass_through_body_enabled ||
data.force_format ||
data.claude_beta_query ||
data.system_prompt_override;
if (hasAdvancedValues) {
setAdvancedSettingsOpen(true);
}
} else {
showError(message);
}
......@@ -1275,7 +1230,6 @@ const EditChannelModal = (props) => {
fetchModels().then();
fetchGroups().then();
if (!isEdit) {
initialBaseUrlRef.current = '';
setInputs(originInputs);
if (formApiRef.current) {
formApiRef.current.setValues(originInputs);
......@@ -1303,8 +1257,8 @@ const EditChannelModal = (props) => {
fetchModelGroups();
// 重置手动输入模式状态
setUseManualInput(false);
// 重置导航状态
setCurrentSectionIndex(0);
// 重置高级设置折叠状态
setAdvancedSettingsOpen(false);
} else {
// 统一的模态框关闭重置逻辑
resetModalState();
......@@ -1349,6 +1303,8 @@ const EditChannelModal = (props) => {
setDoubaoApiEditUnlocked(false);
doubaoApiClickCountRef.current = 0;
setModelSearchValue('');
// 重置高级设置折叠状态
setAdvancedSettingsOpen(false);
// 清空表单中的key_mode字段
if (formApiRef.current) {
formApiRef.current.setValue('key_mode', undefined);
......@@ -2118,58 +2074,22 @@ const EditChannelModal = (props) => {
visible={props.visible}
width={isMobile ? '100%' : 600}
footer={
<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'
onClick={() => formApiRef.current?.submitForm()}
icon={<IconSave />}
>
{t('提交')}
</Button>
<Button
theme='light'
type='primary'
onClick={handleCancel}
icon={<IconClose />}
>
{t('取消')}
</Button>
</Space>
<div className='flex justify-end items-center gap-2'>
<Button
theme='solid'
onClick={() => formApiRef.current?.submitForm()}
icon={<IconSave />}
>
{t('提交')}
</Button>
<Button
theme='light'
type='primary'
onClick={handleCancel}
icon={<IconClose />}
>
{t('取消')}
</Button>
</div>
}
closeIcon={null}
......@@ -2181,29 +2101,350 @@ const EditChannelModal = (props) => {
getFormApi={(api) => (formApiRef.current = api)}
onSubmit={submit}
>
{() => (
{() => {
const advancedSettingsContent = (
<div className='space-y-4'>
{/* Upstream Model Management Section */}
{MODEL_FETCHABLE_CHANNEL_TYPES.has(inputs.type) && (
<div className='pb-3 border-b border-gray-100'>
<Text className='text-sm font-medium text-gray-500 mb-3 block'>
{t('上游模型管理')}
</Text>
<Form.Switch
field='upstream_model_update_check_enabled'
label={t('是否检测上游模型更新')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelOtherSettingsChange(
'upstream_model_update_check_enabled',
value,
)
}
extraText={t(
'开启后由后端定时任务检测该渠道上游模型变化',
)}
/>
<Form.Switch
field='upstream_model_update_auto_sync_enabled'
label={t('是否自动同步上游模型更新')}
checkedText={t('开')}
uncheckedText={t('关')}
disabled={!inputs.upstream_model_update_check_enabled}
onChange={(value) =>
handleChannelOtherSettingsChange('upstream_model_update_auto_sync_enabled', value)
}
extraText={t('开启后检测到新增模型会自动加入当前渠道模型列表')}
/>
<Form.Input
field='upstream_model_update_ignored_models'
label={t('已忽略模型')}
placeholder={t('例如:gpt-4.1-nano,gpt-4o-mini')}
onChange={(value) =>
handleInputChange(
'upstream_model_update_ignored_models',
value,
)
}
showClear
/>
<div className='text-xs text-gray-500 mb-2'>
{t('上次检测时间')}:&nbsp;
{formatUnixTime(
inputs.upstream_model_update_last_check_time,
)}
</div>
<div className='text-xs text-gray-500 mb-3'>
{t('上次检测到可加入模型')}:&nbsp;
{upstreamDetectedModels.length === 0 ? (
t('暂无')
) : (
<>
<Tooltip
position='topLeft'
content={
<div className='max-w-[640px] break-all text-xs leading-5'>
{upstreamDetectedModels.join(', ')}
</div>
}
>
<span className='cursor-help break-all'>
{upstreamDetectedModelsPreview.join(', ')}
</span>
</Tooltip>
<span className='ml-1 text-gray-400'>
{upstreamDetectedModelsOmittedCount > 0
? t('(共 {{total}} 个,省略 {{omit}} 个)', {
total: upstreamDetectedModels.length,
omit: upstreamDetectedModelsOmittedCount,
})
: t('(共 {{total}} 个)', {
total: upstreamDetectedModels.length,
})}
</span>
</>
)}
</div>
</div>
)}
{/* Request Config Section */}
<div className='py-3 border-b border-gray-100'>
<Text className='text-sm font-medium text-gray-500 mb-3 block'>
{t('请求配置')}
</Text>
<div className='mb-4'>
<div className='flex items-center justify-between gap-2 mb-1'>
<Text className='text-sm font-medium'>{t('参数覆盖')}</Text>
<Space>
<Button
size='small'
type='primary'
icon={<IconCode size={14} />}
onClick={() => setParamOverrideEditorVisible(true)}
>
{t('可视化编辑')}
</Button>
<Dropdown
trigger='click'
position='bottomRight'
menu={[
{ node: 'item', name: t('填充新模板'), onClick: () => applyParamOverrideTemplate('operations', 'fill') },
{ node: 'item', name: t('填充旧模板'), onClick: () => applyParamOverrideTemplate('legacy', 'fill') },
{ node: 'item', name: t('清空'), onClick: clearParamOverride },
]}
>
<Button size='small' type='tertiary'>
{t('更多')} <IconChevronDown size={12} />
</Button>
</Dropdown>
</Space>
</div>
<Text type='tertiary' size='small'>
{t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数')}
</Text>
<div
className='mt-2 rounded-xl p-3'
style={{
backgroundColor: 'var(--semi-color-fill-0)',
border: '1px solid var(--semi-color-fill-2)',
}}
>
<div className='flex items-center justify-between mb-2'>
<Tag color={paramOverrideMeta.tagColor}>
{paramOverrideMeta.tagLabel}
</Tag>
<Button
size='small'
icon={<IconCopy />}
type='tertiary'
onClick={copyParamOverrideJson}
>
{t('复制')}
</Button>
</div>
<pre className='mb-0 text-xs leading-5 whitespace-pre-wrap break-all max-h-56 overflow-auto'>
{paramOverrideMeta.preview}
</pre>
</div>
</div>
<Form.TextArea
field='header_override'
label={t('请求头覆盖')}
placeholder={
t('此项可选,用于覆盖请求头参数') +
'\n' +
t('格式示例:') +
'\n{\n "User-Agent": "Mozilla/5.0 ...",\n "Authorization": "Bearer {api_key}"\n}'
}
autosize
onChange={(value) =>
handleInputChange('header_override', value)
}
extraText={
<div className='flex flex-col gap-1'>
<div className='flex gap-2 flex-wrap items-center'>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange(
'header_override',
JSON.stringify({ '*': true, 're:^X-Trace-.*$': true, 'X-Foo': '{client_header:X-Foo}', Authorization: 'Bearer {api_key}', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0' }, null, 2),
)
}
>
{t('填入模板')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange('header_override', JSON.stringify({ '*': true }, null, 2))
}
>
{t('填入透传模版')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() => formatJsonField('header_override')}
>
{t('格式化')}
</Text>
</div>
<div>
<Text type='tertiary' size='small'>
{t('支持变量:')}
</Text>
<div className='text-xs text-tertiary ml-2'>
<div>
{t('渠道密钥')}: {'{api_key}'}
</div>
</div>
</div>
</div>
}
showClear
/>
<JSONEditor
key={`status_code_mapping-${isEdit ? channelId : 'new'}`}
field='status_code_mapping'
label={t('状态码复写')}
placeholder={
t('此项可选,用于复写返回的状态码,仅影响本地判断,不修改返回到上游的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:') +
'\n' +
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)
}
value={inputs.status_code_mapping || ''}
onChange={(value) =>
handleInputChange('status_code_mapping', value)
}
template={STATUS_CODE_MAPPING_EXAMPLE}
templateLabel={t('填入模板')}
editorType='keyValue'
formApi={formApiRef.current}
extraText={t('键为原状态码,值为要复写的状态码,仅影响本地判断')}
/>
</div>
{/* Channel Behavior Section */}
<div className='py-3 border-b border-gray-100'>
<Text className='text-sm font-medium text-gray-500 mb-3 block'>
{t('渠道行为')}
</Text>
<Form.Input
field='tag'
label={t('渠道标签')}
placeholder={t('渠道标签')}
showClear
onChange={(value) => handleInputChange('tag', value)}
/>
<Form.TextArea
field='remark'
label={t('备注')}
placeholder={t('请输入备注(仅管理员可见)')}
maxLength={255}
showClear
onChange={(value) => handleInputChange('remark', value)}
/>
<Row gutter={12}>
<Col span={12}>
<Form.InputNumber
field='priority'
label={t('渠道优先级')}
placeholder={t('渠道优先级')}
min={0}
onNumberChange={(value) => handleInputChange('priority', value)}
style={{ width: '100%' }}
/>
</Col>
<Col span={12}>
<Form.InputNumber
field='weight'
label={t('渠道权重')}
placeholder={t('渠道权重')}
min={0}
onNumberChange={(value) => handleInputChange('weight', value)}
style={{ width: '100%' }}
/>
</Col>
</Row>
{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 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私')} />
<Form.Switch field='allow_include_obfuscation' label={t('允许 stream_options.include_obfuscation 透传')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelOtherSettingsChange('allow_include_obfuscation', value)} extraText={t('include_obfuscation 用于控制 Responses 流混淆字段。默认关闭以避免客户端关闭该安全保护')} />
</>
)}
{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 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用')} />
<Form.Switch field='allow_inference_geo' label={t('允许 inference_geo 透传')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelOtherSettingsChange('allow_inference_geo', value)} extraText={t('inference_geo 字段用于控制 Claude 数据驻留推理区域。默认关闭以避免未经授权透传地域信息')} />
</>
)}
</div>
{/* Extra Settings Section */}
<div className='pt-3'>
<Text className='text-sm font-medium text-gray-500 mb-3 block'>
{t('额外设置')}
</Text>
{inputs.type === 14 && (
<Form.Switch field='claude_beta_query' label={t('Claude 强制 beta=true')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelOtherSettingsChange('claude_beta_query', value)} extraText={t('开启后,该渠道请求 Claude 时将强制追加 ?beta=true(无需客户端手动传参)')} />
)}
{inputs.type === 1 && (
<Form.Switch field='force_format' label={t('强制格式化')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelSettingsChange('force_format', value)} extraText={t('强制将响应格式化为 OpenAI 标准格式(只适用于OpenAI渠道类型)')} />
)}
<Form.Switch field='thinking_to_content' label={t('思考内容转换')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelSettingsChange('thinking_to_content', value)} extraText={t('将 reasoning_content 转换为 <think> 标签拼接到内容中')} />
<Form.Switch field='pass_through_body_enabled' label={t('透传请求体')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelSettingsChange('pass_through_body_enabled', value)} extraText={t('启用请求体透传功能')} />
<Form.Input field='proxy' label={t('代理地址')} placeholder={t('例如: socks5://user:pass@host:port')} onChange={(value) => handleChannelSettingsChange('proxy', value)} showClear extraText={t('用于配置网络代理,支持 socks5 协议')} />
<Form.TextArea field='system_prompt' label={t('系统提示词')} placeholder={t('输入系统提示词,用户的系统提示词将优先于此设置')} onChange={(value) => handleChannelSettingsChange('system_prompt', value)} autosize showClear extraText={t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')} />
<Form.Switch field='system_prompt_override' label={t('系统提示词拼接')} checkedText={t('开')} uncheckedText={t('关')} onChange={(value) => handleChannelSettingsChange('system_prompt_override', value)} extraText={t('如果用户请求中包含系统提示词,则使用此设置拼接到用户的系统提示词前面')} />
</div>
</div>
);
return (
<>
<Spin spinning={loading}>
<div className='p-2 space-y-3' 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'
className='mr-2 shadow-md'
>
<IconServer size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('基本信息')}
</Text>
<div className='text-xs text-gray-600'>
{t('渠道的基本配置信息')}
</div>
{/* Core Configuration Card - Always Visible */}
<Card className='!rounded-2xl shadow-sm border-0'>
{/* Header */}
<div className='flex items-center mb-4'>
<Avatar
size='small'
color='blue'
className='mr-2 shadow-md'
>
<IconServer size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('核心配置')}
</Text>
<div className='text-xs text-gray-600'>
{t('创建渠道所需的基本信息')}
</div>
</div>
</div>
{isIonetChannel && (
<Banner
......@@ -2853,34 +3094,10 @@ const EditChannelModal = (props) => {
}
/>
)}
</Card>
</div>
{/* API Configuration Card */}
{showApiConfigCard && (
<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'
onClick={handleApiConfigSecretClick}
>
<Avatar
size='small'
color='green'
className='mr-2 shadow-md'
>
<IconGlobe size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('API 配置')}
</Text>
<div className='text-xs text-gray-600'>
{t('API 地址和相关配置')}
</div>
</div>
</div>
{/* API Configuration Section */}
{showApiConfigCard && (
<div onClick={handleApiConfigSecretClick}>
{inputs.type === 40 && (
<Banner
......@@ -3078,9 +3295,8 @@ const EditChannelModal = (props) => {
'https://ark.ap-southeast.bytepluses.com',
},
{
value: DEPRECATED_DOUBAO_CODING_PLAN_BASE_URL,
label: doubaoCodingPlanOptionLabel,
disabled: !canKeepDeprecatedDoubaoCodingPlan,
value: 'doubao-coding-plan',
label: 'Doubao Coding Plan',
},
]}
defaultValue='https://ark.cn-beijing.volces.com'
......@@ -3088,33 +3304,11 @@ const EditChannelModal = (props) => {
/>
</div>
)}
</Card>
</div>
)}
{/* Model Configuration Card */}
<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'
className='mr-2 shadow-md'
>
<IconCode size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('模型配置')}
</Text>
<div className='text-xs text-gray-600'>
{t('模型选择和映射设置')}
</div>
</div>
</div>
)}
<Form.Select
{/* Model Selection - Part of Core Config */}
<Form.Select
field='models'
label={t('模型')}
placeholder={t('请选择该渠道所支持的模型')}
......@@ -3163,7 +3357,7 @@ const EditChannelModal = (props) => {
};
}}
extraText={
<Space wrap>
<Space>
<Button
size='small'
type='primary'
......@@ -3173,15 +3367,6 @@ const EditChannelModal = (props) => {
>
{t('填入相关模型')}
</Button>
<Button
size='small'
type='secondary'
onClick={() =>
handleInputChange('models', fullModels)
}
>
{t('填入所有模型')}
</Button>
{MODEL_FETCHABLE_CHANNEL_TYPES.has(inputs.type) && (
<Button
size='small'
......@@ -3191,767 +3376,267 @@ const EditChannelModal = (props) => {
{t('获取模型列表')}
</Button>
)}
{inputs.type === 4 && isEdit && (
<Button
size='small'
type='primary'
theme='light'
onClick={() => setOllamaModalVisible(true)}
>
{t('Ollama 模型管理')}
</Button>
)}
<Button
size='small'
type='warning'
onClick={() => handleInputChange('models', [])}
>
{t('清除所有模型')}
</Button>
<Button
size='small'
type='tertiary'
onClick={() => {
if (inputs.models.length === 0) {
showInfo(t('没有模型可以复制'));
return;
}
try {
copy(inputs.models.join(','));
showSuccess(t('模型列表已复制到剪贴板'));
} catch (error) {
showError(t('复制失败'));
}
}}
<Dropdown
trigger='click'
position='bottomRight'
menu={[
{ node: 'item', name: t('填入所有模型'), onClick: () => handleInputChange('models', fullModels) },
...(inputs.type === 4 && isEdit ? [{ node: 'item', name: t('Ollama 模型管理'), onClick: () => setOllamaModalVisible(true) }] : []),
{ node: 'divider' },
{ node: 'item', name: t('复制所有模型'), onClick: () => {
if (inputs.models.length === 0) { showInfo(t('没有模型可以复制')); return; }
try { copy(inputs.models.join(',')); showSuccess(t('模型列表已复制到剪贴板')); } catch (error) { showError(t('复制失败')); }
}},
{ node: 'item', name: t('清除所有模型'), type: 'danger', onClick: () => handleInputChange('models', []) },
...((modelGroups && modelGroups.length > 0) ? [
{ node: 'divider' },
...modelGroups.map((group) => ({
node: 'item',
name: group.name,
onClick: () => {
let items = [];
try {
if (Array.isArray(group.items)) { items = group.items; }
else if (typeof group.items === 'string') {
const parsed = JSON.parse(group.items || '[]');
if (Array.isArray(parsed)) items = parsed;
}
} catch {}
const current = formApiRef.current?.getValue('models') || inputs.models || [];
const merged = Array.from(new Set([...current, ...items].map((m) => (m || '').trim()).filter(Boolean)));
handleInputChange('models', merged);
},
})),
] : []),
]}
>
{t('复制所有模型')}
</Button>
{modelGroups &&
modelGroups.length > 0 &&
modelGroups.map((group) => (
<Button
key={group.id}
size='small'
type='primary'
onClick={() => {
let items = [];
try {
if (Array.isArray(group.items)) {
items = group.items;
} else if (
typeof group.items === 'string'
) {
const parsed = JSON.parse(
group.items || '[]',
);
if (Array.isArray(parsed)) items = parsed;
}
} catch {}
const current =
formApiRef.current?.getValue('models') ||
inputs.models ||
[];
const merged = Array.from(
new Set(
[...current, ...items]
.map((m) => (m || '').trim())
.filter(Boolean),
),
);
handleInputChange('models', merged);
}}
>
{group.name}
</Button>
))}
<Button size='small' type='tertiary'>
{t('更多')} <IconChevronDown size={12} />
</Button>
</Dropdown>
</Space>
}
/>
<Form.Input
field='custom_model'
label={t('自定义模型名称')}
placeholder={t('输入自定义模型名称')}
onChange={(value) => setCustomModel(value.trim())}
value={customModel}
suffix={
<Button
size='small'
type='primary'
onClick={addCustomModels}
>
{t('填入')}
</Button>
}
/>
{MODEL_FETCHABLE_CHANNEL_TYPES.has(inputs.type) && (
<>
<Form.Switch
field='upstream_model_update_check_enabled'
label={t('是否检测上游模型更新')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelOtherSettingsChange(
'upstream_model_update_check_enabled',
value,
)
}
extraText={t(
'开启后由后端定时任务检测该渠道上游模型变化',
)}
/>
<div className='text-xs text-gray-500 mb-2'>
{t('上次检测时间')}:&nbsp;
{formatUnixTime(
inputs.upstream_model_update_last_check_time,
)}
</div>
<Form.Input
field='upstream_model_update_ignored_models'
label={t('已忽略模型')}
placeholder={t(
'例如:gpt-4.1-nano,regex:^claude-.*$,regex:^sora-.*$',
)}
extraText={t(
'支持精确匹配;使用 regex: 开头可按正则匹配。',
)}
onChange={(value) =>
handleInputChange(
'upstream_model_update_ignored_models',
value,
)
}
showClear
/>
</>
{/* Custom Model Name - Core Config */}
<Form.Input
field='custom_model'
label={t('自定义模型名称')}
placeholder={t('输入自定义模型名称')}
onChange={(value) => setCustomModel(value.trim())}
value={customModel}
suffix={
<Button
size='small'
type='primary'
onClick={addCustomModels}
>
{t('填入')}
</Button>
}
/>
{/* Groups - Core Config */}
<Form.Select
field='groups'
label={t('分组')}
placeholder={t('请选择可以使用该渠道的分组')}
multiple
allowAdditions
additionLabel={t(
'请在系统设置页面编辑分组倍率以添加新的分组:',
)}
<Form.Input
field='test_model'
label={t('默认测试模型')}
placeholder={t('不填则为模型列表第一个')}
onChange={(value) =>
handleInputChange('test_model', value)
optionList={groupOptions}
style={{ width: '100%' }}
position='top'
onChange={(value) => handleInputChange('groups', value)}
/>
{/* Model Mapping - Core Config */}
<JSONEditor
key={`model_mapping-${isEdit ? channelId : 'new'}`}
field='model_mapping'
label={t('模型重定向')}
placeholder={
t(
'此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:',
) +
`\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`
}
value={inputs.model_mapping || ''}
onChange={(value) =>
handleInputChange('model_mapping', value)
}
template={MODEL_MAPPING_EXAMPLE}
templateLabel={t('填入模板')}
editorType='keyValue'
formApi={formApiRef.current}
renderStringValueSuffix={({ pairKey, value }) => {
if (!MODEL_FETCHABLE_CHANNEL_TYPES.has(inputs.type)) {
return null;
}
showClear
/>
<JSONEditor
key={`model_mapping-${isEdit ? channelId : 'new'}`}
field='model_mapping'
label={t('模型重定向')}
placeholder={
t(
'此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:',
) +
`\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`
}
value={inputs.model_mapping || ''}
onChange={(value) =>
handleInputChange('model_mapping', value)
}
template={MODEL_MAPPING_EXAMPLE}
templateLabel={t('填入模板')}
editorType='keyValue'
formApi={formApiRef.current}
renderStringValueSuffix={({ pairKey, value }) => {
if (!MODEL_FETCHABLE_CHANNEL_TYPES.has(inputs.type)) {
return null;
}
const disabled = !String(pairKey ?? '').trim();
return (
<Tooltip content={t('选择模型')}>
<Button
type='tertiary'
theme='borderless'
size='small'
icon={<IconSearch size={14} />}
disabled={disabled}
onClick={(e) => {
e.stopPropagation();
openModelMappingValueModal({ pairKey, value });
}}
/>
</Tooltip>
);
}}
extraText={t(
'键为请求中的模型名称,值为要替换的模型名称',
)}
/>
</Card>
</div>
{/* Advanced Settings Card */}
<div
ref={(el) => (formSectionRefs.current.advancedSettings = el)}
const disabled = !String(pairKey ?? '').trim();
return (
<Tooltip content={t('选择模型')}>
<Button
type='tertiary'
theme='borderless'
size='small'
icon={<IconSearch size={14} />}
disabled={disabled}
onClick={(e) => {
e.stopPropagation();
openModelMappingValueModal({ pairKey, value });
}}
/>
</Tooltip>
);
}}
extraText={t(
'键为请求中的模型名称,值为要替换的模型名称',
)}
/>
{/* Auto Ban - Core Config */}
<Form.Switch
field='auto_ban'
label={t('是否自动禁用')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) => setAutoBan(value)}
extraText={t(
'仅当自动禁用开启时有效,关闭后不会自动禁用该渠道',
)}
initValue={autoBan}
/>
{/* Test Model - Core Config */}
<Form.Input
field='test_model'
label={t('默认测试模型')}
placeholder={t('不填则为模型列表第一个')}
onChange={(value) =>
handleInputChange('test_model', value)
}
showClear
/>
</Card>
{/* Advanced Settings Toggle / Collapse */}
{isMobile ? (
<Collapse
activeKey={advancedSettingsOpen ? ['advanced'] : []}
onChange={(keys) => setAdvancedSettingsOpen(keys.includes('advanced'))}
>
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
{/* Header: Advanced Settings */}
<div className='flex items-center mb-2'>
<Avatar
size='small'
color='orange'
className='mr-2 shadow-md'
>
<Collapse.Panel
header={
<div className='flex items-center gap-2'>
<IconSetting size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('高级设置')}
</Text>
<div className='text-xs text-gray-600'>
{t('渠道的高级配置选项')}
</div>
<Text className='font-medium'>{t('高级设置')}</Text>
</div>
}
itemKey='advanced'
>
{advancedSettingsContent}
</Collapse.Panel>
</Collapse>
) : (
/* Desktop: toggle button to open side panel */
<div
className='flex items-center justify-between p-3 rounded-xl cursor-pointer transition-colors hover:bg-gray-50'
style={{
backgroundColor: advancedSettingsOpen ? 'var(--semi-color-primary-light-default)' : 'var(--semi-color-fill-0)',
border: '1px solid var(--semi-color-fill-2)',
}}
onClick={() => setAdvancedSettingsOpen(!advancedSettingsOpen)}
>
<div className='flex items-center gap-2'>
<IconSetting size={16} />
<Text className='font-medium'>{t('高级设置')}</Text>
</div>
<Form.Select
field='groups'
label={t('分组')}
placeholder={t('请选择可以使用该渠道的分组')}
multiple
allowAdditions
additionLabel={t(
'请在系统设置页面编辑分组倍率以添加新的分组:',
)}
optionList={groupOptions}
style={{ width: '100%' }}
onChange={(value) => handleInputChange('groups', value)}
/>
<Form.Input
field='tag'
label={t('渠道标签')}
placeholder={t('渠道标签')}
showClear
onChange={(value) => handleInputChange('tag', value)}
/>
<Form.TextArea
field='remark'
label={t('备注')}
placeholder={t('请输入备注(仅管理员可见)')}
maxLength={255}
showClear
onChange={(value) => handleInputChange('remark', value)}
/>
<Row gutter={12}>
<Col span={12}>
<Form.InputNumber
field='priority'
label={t('渠道优先级')}
placeholder={t('渠道优先级')}
min={0}
onNumberChange={(value) =>
handleInputChange('priority', value)
}
style={{ width: '100%' }}
/>
</Col>
<Col span={12}>
<Form.InputNumber
field='weight'
label={t('渠道权重')}
placeholder={t('渠道权重')}
min={0}
onNumberChange={(value) =>
handleInputChange('weight', value)
}
style={{ width: '100%' }}
/>
</Col>
</Row>
<Form.Switch
field='auto_ban'
label={t('是否自动禁用')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) => setAutoBan(value)}
extraText={t(
'仅当自动禁用开启时有效,关闭后不会自动禁用该渠道',
)}
initValue={autoBan}
/>
<Form.Switch
field='upstream_model_update_auto_sync_enabled'
label={t('是否自动同步上游模型更新')}
checkedText={t('开')}
uncheckedText={t('关')}
disabled={!inputs.upstream_model_update_check_enabled}
onChange={(value) =>
handleChannelOtherSettingsChange(
'upstream_model_update_auto_sync_enabled',
value,
)
}
extraText={t(
'开启后检测到新增模型会自动加入当前渠道模型列表',
)}
/>
<div className='text-xs text-gray-500 mb-3'>
{t('上次检测到可加入模型')}:&nbsp;
{upstreamDetectedModels.length === 0 ? (
t('暂无')
) : (
<>
<Tooltip
position='topLeft'
content={
<div className='max-w-[640px] break-all text-xs leading-5'>
{upstreamDetectedModels.join(', ')}
</div>
}
>
<span className='cursor-help break-all'>
{upstreamDetectedModelsPreview.join(', ')}
</span>
</Tooltip>
<span className='ml-1 text-gray-400'>
{upstreamDetectedModelsOmittedCount > 0
? t('(共 {{total}} 个,省略 {{omit}} 个)', {
total: upstreamDetectedModels.length,
omit: upstreamDetectedModelsOmittedCount,
})
: t('(共 {{total}} 个)', {
total: upstreamDetectedModels.length,
})}
</span>
</>
)}
</div>
<div className='mb-4'>
<div className='flex items-center justify-between gap-2 mb-1'>
<Text className='text-sm font-medium'>{t('参数覆盖')}</Text>
<Space wrap>
<Button
size='small'
type='primary'
icon={<IconCode size={14} />}
onClick={() => setParamOverrideEditorVisible(true)}
>
{t('可视化编辑')}
</Button>
<Button
size='small'
onClick={() =>
applyParamOverrideTemplate('operations', 'fill')
}
>
{t('填充新模板')}
</Button>
<Button
size='small'
onClick={() =>
applyParamOverrideTemplate('legacy', 'fill')
}
>
{t('填充旧模板')}
</Button>
<Button
size='small'
type='tertiary'
onClick={clearParamOverride}
>
{t('清空')}
</Button>
</Space>
</div>
<Text type='tertiary' size='small'>
{t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数')}
<div className='flex items-center gap-1 text-sm' style={{ color: 'var(--semi-color-primary)' }}>
<Text size='small' style={{ color: 'var(--semi-color-primary)' }}>
{advancedSettingsOpen ? t('收起') : isEdit ? t('向左展开') : t('向右展开')}
</Text>
<div
className='mt-2 rounded-xl p-3'
style={{
backgroundColor: 'var(--semi-color-fill-0)',
border: '1px solid var(--semi-color-fill-2)',
}}
>
<div className='flex items-center justify-between mb-2'>
<Tag color={paramOverrideMeta.tagColor}>
{paramOverrideMeta.tagLabel}
</Tag>
<Space spacing={8}>
<Button
size='small'
icon={<IconCopy />}
type='tertiary'
onClick={copyParamOverrideJson}
>
{t('复制')}
</Button>
<Button
size='small'
type='tertiary'
onClick={() => setParamOverrideEditorVisible(true)}
>
{t('编辑')}
</Button>
</Space>
</div>
<pre className='mb-0 text-xs leading-5 whitespace-pre-wrap break-all max-h-56 overflow-auto'>
{paramOverrideMeta.preview}
</pre>
</div>
<IconChevronDown
size={14}
style={{
transform: advancedSettingsOpen
? 'rotate(180deg)'
: isEdit ? 'rotate(90deg)' : 'rotate(-90deg)',
transition: 'transform 0.2s',
}}
/>
</div>
</div>
)}
</div>
</Spin>
<Form.TextArea
field='header_override'
label={t('请求头覆盖')}
placeholder={
t('此项可选,用于覆盖请求头参数') +
'\n' +
t('格式示例:') +
'\n{\n "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",\n "Authorization": "Bearer {api_key}"\n}'
}
autosize
onChange={(value) =>
handleInputChange('header_override', value)
}
extraText={
<div className='flex flex-col gap-1'>
<div className='flex gap-2 flex-wrap items-center'>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange(
'header_override',
JSON.stringify(
{
'*': true,
're:^X-Trace-.*$': true,
'X-Foo': '{client_header:X-Foo}',
Authorization: 'Bearer {api_key}',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
},
null,
2,
),
)
}
>
{t('填入模板')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() =>
handleInputChange(
'header_override',
JSON.stringify(
{
'*': true,
},
null,
2,
),
)
}
>
{t('填入透传模版')}
</Text>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() => formatJsonField('header_override')}
>
{t('格式化')}
</Text>
</div>
<div>
<Text type='tertiary' size='small'>
{t('支持变量:')}
</Text>
<div className='text-xs text-tertiary ml-2'>
<div>
{t('渠道密钥')}: {'{api_key}'}
</div>
</div>
</div>
{/* Desktop: Advanced Settings Side Panel - rendered inside Form tree */}
{!isMobile && advancedSettingsOpen && (
<div
className='fixed top-0 h-full overflow-y-auto z-[999] semi-sidesheet-inner'
style={{
width: 600,
[isEdit ? 'right' : 'left']: 600,
backgroundColor: 'var(--semi-color-bg-0)',
borderLeft: isEdit ? 'none' : '1px solid var(--semi-color-border)',
borderRight: isEdit ? '1px solid var(--semi-color-border)' : 'none',
animation: `slideIn${isEdit ? 'Left' : 'Right'} 0.3s ease-out`,
}}
>
<div className='semi-sidesheet-header'>
<div className='semi-sidesheet-title'>
<Space>
<Tag color='cyan' shape='circle'>
{t('高级')}
</Tag>
<Title heading={4} className='m-0'>
{t('高级设置')}
</Title>
</Space>
</div>
<Button
className='semi-sidesheet-close'
type='tertiary'
theme='borderless'
icon={<IconClose />}
size='small'
onClick={() => setAdvancedSettingsOpen(false)}
/>
</div>
<div className='semi-sidesheet-body' style={{ padding: 0 }}>
<div className='p-2 space-y-3'>
<Card className='!rounded-2xl shadow-sm border-0'>
<div className='flex items-center mb-4'>
<Avatar
size='small'
color='orange'
className='mr-2 shadow-md'
>
<IconSetting size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('高级设置')}
</Text>
<div className='text-xs text-gray-600'>
{t('渠道的高级配置选项')}
</div>
}
showClear
/>
<JSONEditor
key={`status_code_mapping-${isEdit ? channelId : 'new'}`}
field='status_code_mapping'
label={t('状态码复写')}
placeholder={
t(
'此项可选,用于复写返回的状态码,仅影响本地判断,不修改返回到上游的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:',
) +
'\n' +
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)
}
value={inputs.status_code_mapping || ''}
onChange={(value) =>
handleInputChange('status_code_mapping', value)
}
template={STATUS_CODE_MAPPING_EXAMPLE}
templateLabel={t('填入模板')}
editorType='keyValue'
formApi={formApiRef.current}
extraText={t(
'键为原状态码,值为要复写的状态码,仅影响本地判断',
)}
/>
{/* 字段透传控制 - 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 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私',
)}
/>
<Form.Switch
field='allow_include_obfuscation'
label={t(
'允许 stream_options.include_obfuscation 透传',
)}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelOtherSettingsChange(
'allow_include_obfuscation',
value,
)
}
extraText={t(
'include_obfuscation 用于控制 Responses 流混淆字段。默认关闭以避免客户端关闭该安全保护',
)}
/>
</>
)}
{/* 字段透传控制 - 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 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用',
)}
/>
<Form.Switch
field='allow_inference_geo'
label={t('允许 inference_geo 透传')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelOtherSettingsChange(
'allow_inference_geo',
value,
)
}
extraText={t(
'inference_geo 字段用于控制 Claude 数据驻留推理区域。默认关闭以避免未经授权透传地域信息',
)}
/>
</>
)}
</Card>
</div>
{/* Channel Extra Settings Card */}
<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'
className='mr-2 shadow-md'
>
<IconBolt size={16} />
</Avatar>
<div>
<Text className='text-lg font-medium'>
{t('渠道额外设置')}
</Text>
</div>
</div>
{inputs.type === 14 && (
<Form.Switch
field='claude_beta_query'
label={t('Claude 强制 beta=true')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelOtherSettingsChange(
'claude_beta_query',
value,
)
}
extraText={t(
'开启后,该渠道请求 Claude 时将强制追加 ?beta=true(无需客户端手动传参)',
)}
/>
)}
{inputs.type === 1 && (
<Form.Switch
field='force_format'
label={t('强制格式化')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelSettingsChange('force_format', value)
}
extraText={t(
'强制将响应格式化为 OpenAI 标准格式(只适用于OpenAI渠道类型)',
)}
/>
)}
<Form.Switch
field='thinking_to_content'
label={t('思考内容转换')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelSettingsChange(
'thinking_to_content',
value,
)
}
extraText={t(
'将 reasoning_content 转换为 <think> 标签拼接到内容中',
)}
/>
<Form.Switch
field='pass_through_body_enabled'
label={t('透传请求体')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelSettingsChange(
'pass_through_body_enabled',
value,
)
}
extraText={t('启用请求体透传功能')}
/>
<Form.Input
field='proxy'
label={t('代理地址')}
placeholder={t('例如: socks5://user:pass@host:port')}
onChange={(value) =>
handleChannelSettingsChange('proxy', value)
}
showClear
extraText={t('用于配置网络代理,支持 socks5 协议')}
/>
<Form.TextArea
field='system_prompt'
label={t('系统提示词')}
placeholder={t(
'输入系统提示词,用户的系统提示词将优先于此设置',
)}
onChange={(value) =>
handleChannelSettingsChange('system_prompt', value)
}
autosize
showClear
extraText={t(
'用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置',
)}
/>
<Form.Switch
field='system_prompt_override'
label={t('系统提示词拼接')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelSettingsChange(
'system_prompt_override',
value,
)
}
extraText={t(
'如果用户请求中包含系统提示词,则使用此设置拼接到用户的系统提示词前面',
)}
/>
</Card>
{advancedSettingsContent}
</Card>
</div>
</div>
</div>
</Spin>
)}
)}
</>
);
}}
</Form>
<ImagePreview
src={modalImageUrl}
visible={isModalOpenurl}
......
......@@ -1507,6 +1507,8 @@
"收益统计": "Income statistics",
"收起": "Collapse",
"收起侧边栏": "Collapse sidebar",
"向左展开": "Expand left",
"向右展开": "Expand right",
"收起内容": "Collapse content",
"放大": "Upscalers",
"放大编辑": "Expand editor",
......@@ -3220,6 +3222,12 @@
"高级设置": "Advanced Settings",
"高级选项": "Advanced Options",
"高级配置": "Advanced Configuration",
"核心配置": "Core Configuration",
"创建渠道所需的基本信息": "Basic information needed to create a channel",
"请求配置": "Request Configuration",
"渠道行为": "Channel Behavior",
"额外设置": "Extra Settings",
"上游模型管理": "Upstream Model Management",
"黑名单": "Blacklist",
"默认": "Default",
"默认 API 版本": "Default API Version",
......
......@@ -1495,6 +1495,8 @@
"收益统计": "Statistiques sur les revenus",
"收起": "Réduire",
"收起侧边栏": "Réduire la barre latérale",
"向左展开": "Développer à gauche",
"向右展开": "Développer à droite",
"收起内容": "Réduire le contenu",
"放大": "Upscalers",
"放大编辑": "Développer l'éditeur",
......@@ -3164,6 +3166,12 @@
"高级设置": "Paramètres avancés",
"高级选项": "Options avancées",
"高级配置": "Advanced Configuration",
"核心配置": "Configuration principale",
"创建渠道所需的基本信息": "Informations de base pour créer un canal",
"请求配置": "Configuration des requêtes",
"渠道行为": "Comportement du canal",
"额外设置": "Paramètres supplémentaires",
"上游模型管理": "Gestion des modèles amont",
"黑名单": "Liste noire",
"默认": "Par défaut",
"默认 API 版本": "Version de l'API par défaut",
......
......@@ -1478,6 +1478,8 @@
"收益统计": "収益統計",
"收起": "折りたたみ",
"收起侧边栏": "サイドバー折りたたみ",
"向左展开": "左に展開",
"向右展开": "右に展開",
"收起内容": "コンテンツ折りたたみ",
"放大": "アップスケール",
"放大编辑": "エディタで開く",
......@@ -3145,6 +3147,12 @@
"高级设置": "詳細設定",
"高级选项": "高度なオプション",
"高级配置": "Advanced Configuration",
"核心配置": "基本設定",
"创建渠道所需的基本信息": "チャネル作成に必要な基本情報",
"请求配置": "リクエスト設定",
"渠道行为": "チャネル動作",
"额外设置": "追加設定",
"上游模型管理": "上流モデル管理",
"黑名单": "ブラックリスト",
"默认": "デフォルト",
"默认 API 版本": "デフォルトAPIバージョン",
......
......@@ -1507,6 +1507,8 @@
"收益统计": "Статистика доходов",
"收起": "Свернуть",
"收起侧边栏": "Свернуть боковую панель",
"向左展开": "Развернуть влево",
"向右展开": "Развернуть вправо",
"收起内容": "Свернуть содержимое",
"放大": "Увеличить",
"放大编辑": "Увеличить и редактировать",
......@@ -3178,6 +3180,12 @@
"高级设置": "Расширенные настройки",
"高级选项": "Расширенные параметры",
"高级配置": "Advanced Configuration",
"核心配置": "Основные настройки",
"创建渠道所需的基本信息": "Основная информация для создания канала",
"请求配置": "Настройки запросов",
"渠道行为": "Поведение канала",
"额外设置": "Дополнительные настройки",
"上游模型管理": "Управление моделями апстрима",
"黑名单": "Черный список",
"默认": "По умолчанию",
"默认 API 版本": "Версия API по умолчанию",
......
......@@ -1479,6 +1479,8 @@
"收益统计": "Thống kê thu nhập",
"收起": "Thu gọn",
"收起侧边栏": "Thu gọn thanh bên",
"向左展开": "Mở rộng sang trái",
"向右展开": "Mở rộng sang phải",
"收起内容": "Thu gọn nội dung",
"放大": "Upscalers",
"放大编辑": "Mở rộng trình chỉnh sửa",
......@@ -3716,6 +3718,12 @@
"高级设置": "Cài đặt nâng cao",
"高级选项": "Tùy chọn nâng cao",
"高级配置": "Advanced Configuration",
"核心配置": "Cấu hình cốt lõi",
"创建渠道所需的基本信息": "Thông tin cơ bản cần thiết để tạo kênh",
"请求配置": "Cấu hình yêu cầu",
"渠道行为": "Hành vi kênh",
"额外设置": "Cài đặt bổ sung",
"上游模型管理": "Quản lý mô hình thượng nguồn",
"黑名单": "Danh sách đen",
"默认": "Mặc định",
"默认 API 版本": "Phiên bản API mặc định",
......
......@@ -1190,6 +1190,8 @@
"收益统计": "收益統計",
"收起": "收起",
"收起侧边栏": "收起側邊欄",
"向左展开": "向左展開",
"向右展开": "向右展開",
"收起内容": "收起內容",
"放大": "放大",
"放大编辑": "放大編輯",
......@@ -2653,6 +2655,12 @@
"验证配置错误": "驗證設定錯誤",
"高级设置": "進階設定",
"高级配置": "進階設定",
"核心配置": "核心設定",
"创建渠道所需的基本信息": "建立頻道所需的基本資訊",
"请求配置": "請求設定",
"渠道行为": "頻道行為",
"额外设置": "額外設定",
"上游模型管理": "上游模型管理",
"黑名单": "黑名單",
"默认": "預設",
"默认 API 版本": "預設 API 版本",
......
......@@ -971,3 +971,26 @@ html.dark .with-pastel-balls::before {
.semi-datepicker-range-input {
border-radius: 10px !important;
}
@keyframes slideInLeft {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Channel advanced settings side panel animations */
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment