feat(i18n): add internationalization support for visual config editor

This commit is contained in:
LTbinglingfeng
2026-02-06 03:34:38 +08:00
parent f53d333198
commit e6e62e2992
3 changed files with 390 additions and 116 deletions

View File

@@ -1,4 +1,5 @@
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
@@ -198,6 +199,7 @@ function ApiKeysCardEditor({
disabled?: boolean; disabled?: boolean;
onChange: (nextValue: string) => void; onChange: (nextValue: string) => void;
}) { }) {
const { t } = useTranslation();
const apiKeys = useMemo( const apiKeys = useMemo(
() => () =>
value value
@@ -244,11 +246,11 @@ function ApiKeysCardEditor({
const handleSave = () => { const handleSave = () => {
const trimmed = inputValue.trim(); const trimmed = inputValue.trim();
if (!trimmed) { if (!trimmed) {
setFormError('请输入 API 密钥'); setFormError(t('config_management.visual.api_keys.error_empty'));
return; return;
} }
if (!isValidApiKeyCharset(trimmed)) { if (!isValidApiKeyCharset(trimmed)) {
setFormError('API 密钥包含无效字符'); setFormError(t('config_management.visual.api_keys.error_invalid'));
return; return;
} }
@@ -263,9 +265,9 @@ function ApiKeysCardEditor({
return ( return (
<div className="form-group" style={{ marginBottom: 0 }}> <div className="form-group" style={{ marginBottom: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
<label style={{ margin: 0 }}>API (api-keys)</label> <label style={{ margin: 0 }}>{t('config_management.visual.api_keys.label')}</label>
<Button size="sm" onClick={openAddModal} disabled={disabled}> <Button size="sm" onClick={openAddModal} disabled={disabled}>
API {t('config_management.visual.api_keys.add')}
</Button> </Button>
</div> </div>
@@ -279,7 +281,7 @@ function ApiKeysCardEditor({
textAlign: 'center', textAlign: 'center',
}} }}
> >
API {t('config_management.visual.api_keys.empty')}
</div> </div>
) : ( ) : (
<div className="item-list" style={{ marginTop: 4 }}> <div className="item-list" style={{ marginTop: 4 }}>
@@ -292,10 +294,10 @@ function ApiKeysCardEditor({
</div> </div>
<div className="item-actions"> <div className="item-actions">
<Button variant="secondary" size="sm" onClick={() => openEditModal(index)} disabled={disabled}> <Button variant="secondary" size="sm" onClick={() => openEditModal(index)} disabled={disabled}>
{t('config_management.visual.common.edit')}
</Button> </Button>
<Button variant="danger" size="sm" onClick={() => handleDelete(index)} disabled={disabled}> <Button variant="danger" size="sm" onClick={() => handleDelete(index)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -303,31 +305,31 @@ function ApiKeysCardEditor({
</div> </div>
)} )}
<div className="hint"> API API </div> <div className="hint">{t('config_management.visual.api_keys.hint')}</div>
<Modal <Modal
open={modalOpen} open={modalOpen}
onClose={closeModal} onClose={closeModal}
title={editingIndex !== null ? '编辑 API 密钥' : '添加 API 密钥'} title={editingIndex !== null ? t('config_management.visual.api_keys.edit_title') : t('config_management.visual.api_keys.add_title')}
footer={ footer={
<> <>
<Button variant="secondary" onClick={closeModal} disabled={disabled}> <Button variant="secondary" onClick={closeModal} disabled={disabled}>
{t('config_management.visual.common.cancel')}
</Button> </Button>
<Button onClick={handleSave} disabled={disabled}> <Button onClick={handleSave} disabled={disabled}>
{editingIndex !== null ? '更新' : '添加'} {editingIndex !== null ? t('config_management.visual.common.update') : t('config_management.visual.common.add')}
</Button> </Button>
</> </>
} }
> >
<Input <Input
label="API 密钥" label={t('config_management.visual.api_keys.input_label')}
placeholder="粘贴你的 API 密钥" placeholder={t('config_management.visual.api_keys.input_placeholder')}
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
disabled={disabled} disabled={disabled}
error={formError || undefined} error={formError || undefined}
hint="此处仅修改本地配置文件内容,不会自动同步到 API 密钥管理接口" hint={t('config_management.visual.api_keys.input_hint')}
/> />
</Modal> </Modal>
</div> </div>
@@ -345,6 +347,7 @@ function StringListEditor({
placeholder?: string; placeholder?: string;
onChange: (next: string[]) => void; onChange: (next: string[]) => void;
}) { }) {
const { t } = useTranslation();
const items = value.length ? value : []; const items = value.length ? value : [];
const updateItem = (index: number, nextValue: string) => const updateItem = (index: number, nextValue: string) =>
@@ -365,13 +368,13 @@ function StringListEditor({
style={{ flex: 1 }} style={{ flex: 1 }}
/> />
<Button variant="ghost" size="sm" onClick={() => removeItem(index)} disabled={disabled}> <Button variant="ghost" size="sm" onClick={() => removeItem(index)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
))} ))}
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="secondary" size="sm" onClick={addItem} disabled={disabled}> <Button variant="secondary" size="sm" onClick={addItem} disabled={disabled}>
{t('config_management.visual.common.add')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -389,6 +392,7 @@ function PayloadRulesEditor({
protocolFirst?: boolean; protocolFirst?: boolean;
onChange: (next: PayloadRule[]) => void; onChange: (next: PayloadRule[]) => void;
}) { }) {
const { t } = useTranslation();
const rules = value.length ? value : []; const rules = value.length ? value : [];
const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]); const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]);
@@ -441,15 +445,15 @@ function PayloadRulesEditor({
const getValuePlaceholder = (valueType: PayloadParamValueType) => { const getValuePlaceholder = (valueType: PayloadParamValueType) => {
switch (valueType) { switch (valueType) {
case 'string': case 'string':
return '字符串值'; return t('config_management.visual.payload_rules.value_string');
case 'number': case 'number':
return '数字值 (如 0.7)'; return t('config_management.visual.payload_rules.value_number');
case 'boolean': case 'boolean':
return 'true 或 false'; return t('config_management.visual.payload_rules.value_boolean');
case 'json': case 'json':
return 'JSON 值'; return t('config_management.visual.payload_rules.value_json');
default: default:
return '值'; return t('config_management.visual.payload_rules.value_default');
} }
}; };
@@ -468,14 +472,14 @@ function PayloadRulesEditor({
}} }}
> >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
<div style={{ fontWeight: 700, color: 'var(--text-primary)' }}> {ruleIndex + 1}</div> <div style={{ fontWeight: 700, color: 'var(--text-primary)' }}>{t('config_management.visual.payload_rules.rule')} {ruleIndex + 1}</div>
<Button variant="ghost" size="sm" onClick={() => removeRule(ruleIndex)} disabled={disabled}> <Button variant="ghost" size="sm" onClick={() => removeRule(ruleIndex)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}></div> <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>{t('config_management.visual.payload_rules.models')}</div>
{(rule.models.length ? rule.models : []).map((model, modelIndex) => ( {(rule.models.length ? rule.models : []).map((model, modelIndex) => (
<div <div
key={model.id} key={model.id}
@@ -491,7 +495,7 @@ function PayloadRulesEditor({
value={model.protocol ?? ''} value={model.protocol ?? ''}
options={VISUAL_CONFIG_PROTOCOL_OPTIONS} options={VISUAL_CONFIG_PROTOCOL_OPTIONS}
disabled={disabled} disabled={disabled}
ariaLabel="供应商类型" ariaLabel={t('config_management.visual.payload_rules.provider_type')}
onChange={(nextValue) => onChange={(nextValue) =>
updateModel(ruleIndex, modelIndex, { updateModel(ruleIndex, modelIndex, {
protocol: (nextValue || undefined) as PayloadModelEntry['protocol'], protocol: (nextValue || undefined) as PayloadModelEntry['protocol'],
@@ -500,7 +504,7 @@ function PayloadRulesEditor({
/> />
<input <input
className="input" className="input"
placeholder="模型名称" placeholder={t('config_management.visual.payload_rules.model_name')}
value={model.name} value={model.name}
onChange={(e) => updateModel(ruleIndex, modelIndex, { name: e.target.value })} onChange={(e) => updateModel(ruleIndex, modelIndex, { name: e.target.value })}
disabled={disabled} disabled={disabled}
@@ -510,7 +514,7 @@ function PayloadRulesEditor({
<> <>
<input <input
className="input" className="input"
placeholder="模型名称" placeholder={t('config_management.visual.payload_rules.model_name')}
value={model.name} value={model.name}
onChange={(e) => updateModel(ruleIndex, modelIndex, { name: e.target.value })} onChange={(e) => updateModel(ruleIndex, modelIndex, { name: e.target.value })}
disabled={disabled} disabled={disabled}
@@ -519,7 +523,7 @@ function PayloadRulesEditor({
value={model.protocol ?? ''} value={model.protocol ?? ''}
options={VISUAL_CONFIG_PROTOCOL_OPTIONS} options={VISUAL_CONFIG_PROTOCOL_OPTIONS}
disabled={disabled} disabled={disabled}
ariaLabel="供应商类型" ariaLabel={t('config_management.visual.payload_rules.provider_type')}
onChange={(nextValue) => onChange={(nextValue) =>
updateModel(ruleIndex, modelIndex, { updateModel(ruleIndex, modelIndex, {
protocol: (nextValue || undefined) as PayloadModelEntry['protocol'], protocol: (nextValue || undefined) as PayloadModelEntry['protocol'],
@@ -529,24 +533,24 @@ function PayloadRulesEditor({
</> </>
)} )}
<Button variant="ghost" size="sm" onClick={() => removeModel(ruleIndex, modelIndex)} disabled={disabled}> <Button variant="ghost" size="sm" onClick={() => removeModel(ruleIndex, modelIndex)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
))} ))}
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="secondary" size="sm" onClick={() => addModel(ruleIndex)} disabled={disabled}> <Button variant="secondary" size="sm" onClick={() => addModel(ruleIndex)} disabled={disabled}>
{t('config_management.visual.payload_rules.add_model')}
</Button> </Button>
</div> </div>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}></div> <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>{t('config_management.visual.payload_rules.params')}</div>
{(rule.params.length ? rule.params : []).map((param, paramIndex) => ( {(rule.params.length ? rule.params : []).map((param, paramIndex) => (
<div key={param.id} style={{ display: 'grid', gridTemplateColumns: '1fr 140px 1fr auto', gap: 8 }}> <div key={param.id} style={{ display: 'grid', gridTemplateColumns: '1fr 140px 1fr auto', gap: 8 }}>
<input <input
className="input" className="input"
placeholder="JSON 路径 (如 temperature)" placeholder={t('config_management.visual.payload_rules.json_path')}
value={param.path} value={param.path}
onChange={(e) => updateParam(ruleIndex, paramIndex, { path: e.target.value })} onChange={(e) => updateParam(ruleIndex, paramIndex, { path: e.target.value })}
disabled={disabled} disabled={disabled}
@@ -555,7 +559,7 @@ function PayloadRulesEditor({
value={param.valueType} value={param.valueType}
options={VISUAL_CONFIG_PAYLOAD_VALUE_TYPE_OPTIONS} options={VISUAL_CONFIG_PAYLOAD_VALUE_TYPE_OPTIONS}
disabled={disabled} disabled={disabled}
ariaLabel="参数类型" ariaLabel={t('config_management.visual.payload_rules.param_type')}
onChange={(nextValue) => onChange={(nextValue) =>
updateParam(ruleIndex, paramIndex, { valueType: nextValue as PayloadParamValueType }) updateParam(ruleIndex, paramIndex, { valueType: nextValue as PayloadParamValueType })
} }
@@ -568,13 +572,13 @@ function PayloadRulesEditor({
disabled={disabled} disabled={disabled}
/> />
<Button variant="ghost" size="sm" onClick={() => removeParam(ruleIndex, paramIndex)} disabled={disabled}> <Button variant="ghost" size="sm" onClick={() => removeParam(ruleIndex, paramIndex)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
))} ))}
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="secondary" size="sm" onClick={() => addParam(ruleIndex)} disabled={disabled}> <Button variant="secondary" size="sm" onClick={() => addParam(ruleIndex)} disabled={disabled}>
{t('config_management.visual.payload_rules.add_param')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -591,13 +595,13 @@ function PayloadRulesEditor({
textAlign: 'center', textAlign: 'center',
}} }}
> >
{t('config_management.visual.payload_rules.no_rules')}
</div> </div>
)} )}
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="secondary" size="sm" onClick={addRule} disabled={disabled}> <Button variant="secondary" size="sm" onClick={addRule} disabled={disabled}>
{t('config_management.visual.payload_rules.add_rule')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -613,6 +617,7 @@ function PayloadFilterRulesEditor({
disabled?: boolean; disabled?: boolean;
onChange: (next: PayloadFilterRule[]) => void; onChange: (next: PayloadFilterRule[]) => void;
}) { }) {
const { t } = useTranslation();
const rules = value.length ? value : []; const rules = value.length ? value : [];
const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]); const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]);
@@ -654,19 +659,19 @@ function PayloadFilterRulesEditor({
}} }}
> >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
<div style={{ fontWeight: 700, color: 'var(--text-primary)' }}> {ruleIndex + 1}</div> <div style={{ fontWeight: 700, color: 'var(--text-primary)' }}>{t('config_management.visual.payload_rules.rule')} {ruleIndex + 1}</div>
<Button variant="ghost" size="sm" onClick={() => removeRule(ruleIndex)} disabled={disabled}> <Button variant="ghost" size="sm" onClick={() => removeRule(ruleIndex)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}></div> <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>{t('config_management.visual.payload_rules.models')}</div>
{rule.models.map((model, modelIndex) => ( {rule.models.map((model, modelIndex) => (
<div key={model.id} style={{ display: 'grid', gridTemplateColumns: '1fr 160px auto', gap: 8 }}> <div key={model.id} style={{ display: 'grid', gridTemplateColumns: '1fr 160px auto', gap: 8 }}>
<input <input
className="input" className="input"
placeholder="模型名称" placeholder={t('config_management.visual.payload_rules.model_name')}
value={model.name} value={model.name}
onChange={(e) => updateModel(ruleIndex, modelIndex, { name: e.target.value })} onChange={(e) => updateModel(ruleIndex, modelIndex, { name: e.target.value })}
disabled={disabled} disabled={disabled}
@@ -675,7 +680,7 @@ function PayloadFilterRulesEditor({
value={model.protocol ?? ''} value={model.protocol ?? ''}
options={VISUAL_CONFIG_PROTOCOL_OPTIONS} options={VISUAL_CONFIG_PROTOCOL_OPTIONS}
disabled={disabled} disabled={disabled}
ariaLabel="供应商类型" ariaLabel={t('config_management.visual.payload_rules.provider_type')}
onChange={(nextValue) => onChange={(nextValue) =>
updateModel(ruleIndex, modelIndex, { updateModel(ruleIndex, modelIndex, {
protocol: (nextValue || undefined) as PayloadModelEntry['protocol'], protocol: (nextValue || undefined) as PayloadModelEntry['protocol'],
@@ -683,23 +688,23 @@ function PayloadFilterRulesEditor({
} }
/> />
<Button variant="ghost" size="sm" onClick={() => removeModel(ruleIndex, modelIndex)} disabled={disabled}> <Button variant="ghost" size="sm" onClick={() => removeModel(ruleIndex, modelIndex)} disabled={disabled}>
{t('config_management.visual.common.delete')}
</Button> </Button>
</div> </div>
))} ))}
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="secondary" size="sm" onClick={() => addModel(ruleIndex)} disabled={disabled}> <Button variant="secondary" size="sm" onClick={() => addModel(ruleIndex)} disabled={disabled}>
{t('config_management.visual.payload_rules.add_model')}
</Button> </Button>
</div> </div>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}></div> <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' }}>{t('config_management.visual.payload_rules.remove_params')}</div>
<StringListEditor <StringListEditor
value={rule.params} value={rule.params}
disabled={disabled} disabled={disabled}
placeholder="JSON 路径 (gjson/sjson),如 generationConfig.thinkingConfig.thinkingBudget" placeholder={t('config_management.visual.payload_rules.json_path_filter')}
onChange={(params) => updateRule(ruleIndex, { params })} onChange={(params) => updateRule(ruleIndex, { params })}
/> />
</div> </div>
@@ -708,7 +713,7 @@ function PayloadFilterRulesEditor({
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="secondary" size="sm" onClick={addRule} disabled={disabled}> <Button variant="secondary" size="sm" onClick={addRule} disabled={disabled}>
{t('config_management.visual.payload_rules.add_rule')}
</Button> </Button>
</div> </div>
</div> </div>
@@ -716,23 +721,24 @@ function PayloadFilterRulesEditor({
} }
export function VisualConfigEditor({ values, disabled = false, onChange }: VisualConfigEditorProps) { export function VisualConfigEditor({ values, disabled = false, onChange }: VisualConfigEditorProps) {
const { t } = useTranslation();
const isKeepaliveDisabled = values.streaming.keepaliveSeconds === '' || values.streaming.keepaliveSeconds === '0'; const isKeepaliveDisabled = values.streaming.keepaliveSeconds === '' || values.streaming.keepaliveSeconds === '0';
const isNonstreamKeepaliveDisabled = const isNonstreamKeepaliveDisabled =
values.streaming.nonstreamKeepaliveInterval === '' || values.streaming.nonstreamKeepaliveInterval === '0'; values.streaming.nonstreamKeepaliveInterval === '' || values.streaming.nonstreamKeepaliveInterval === '0';
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<ConfigSection title="服务器配置" description="基础服务器设置"> <ConfigSection title={t('config_management.visual.sections.server.title')} description={t('config_management.visual.sections.server.description')}>
<SectionGrid> <SectionGrid>
<Input <Input
label="主机地址" label={t('config_management.visual.sections.server.host')}
placeholder="0.0.0.0" placeholder="0.0.0.0"
value={values.host} value={values.host}
onChange={(e) => onChange({ host: e.target.value })} onChange={(e) => onChange({ host: e.target.value })}
disabled={disabled} disabled={disabled}
/> />
<Input <Input
label="端口" label={t('config_management.visual.sections.server.port')}
type="number" type="number"
placeholder="8317" placeholder="8317"
value={values.port} value={values.port}
@@ -742,11 +748,11 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</SectionGrid> </SectionGrid>
</ConfigSection> </ConfigSection>
<ConfigSection title="TLS/SSL 配置" description="HTTPS 安全连接设置"> <ConfigSection title={t('config_management.visual.sections.tls.title')} description={t('config_management.visual.sections.tls.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<ToggleRow <ToggleRow
title="启用 TLS" title={t('config_management.visual.sections.tls.enable')}
description="启用 HTTPS 安全连接" description={t('config_management.visual.sections.tls.enable_desc')}
checked={values.tlsEnable} checked={values.tlsEnable}
disabled={disabled} disabled={disabled}
onChange={(tlsEnable) => onChange({ tlsEnable })} onChange={(tlsEnable) => onChange({ tlsEnable })}
@@ -756,14 +762,14 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
<Divider /> <Divider />
<SectionGrid> <SectionGrid>
<Input <Input
label="证书文件路径" label={t('config_management.visual.sections.tls.cert')}
placeholder="/path/to/cert.pem" placeholder="/path/to/cert.pem"
value={values.tlsCert} value={values.tlsCert}
onChange={(e) => onChange({ tlsCert: e.target.value })} onChange={(e) => onChange({ tlsCert: e.target.value })}
disabled={disabled} disabled={disabled}
/> />
<Input <Input
label="私钥文件路径" label={t('config_management.visual.sections.tls.key')}
placeholder="/path/to/key.pem" placeholder="/path/to/key.pem"
value={values.tlsKey} value={values.tlsKey}
onChange={(e) => onChange({ tlsKey: e.target.value })} onChange={(e) => onChange({ tlsKey: e.target.value })}
@@ -775,33 +781,33 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="远程管理" description="远程访问和控制面板设置"> <ConfigSection title={t('config_management.visual.sections.remote.title')} description={t('config_management.visual.sections.remote.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<ToggleRow <ToggleRow
title="允许远程访问" title={t('config_management.visual.sections.remote.allow_remote')}
description="允许从其他主机访问管理接口" description={t('config_management.visual.sections.remote.allow_remote_desc')}
checked={values.rmAllowRemote} checked={values.rmAllowRemote}
disabled={disabled} disabled={disabled}
onChange={(rmAllowRemote) => onChange({ rmAllowRemote })} onChange={(rmAllowRemote) => onChange({ rmAllowRemote })}
/> />
<ToggleRow <ToggleRow
title="禁用控制面板" title={t('config_management.visual.sections.remote.disable_panel')}
description="禁用内置的 Web 控制面板" description={t('config_management.visual.sections.remote.disable_panel_desc')}
checked={values.rmDisableControlPanel} checked={values.rmDisableControlPanel}
disabled={disabled} disabled={disabled}
onChange={(rmDisableControlPanel) => onChange({ rmDisableControlPanel })} onChange={(rmDisableControlPanel) => onChange({ rmDisableControlPanel })}
/> />
<SectionGrid> <SectionGrid>
<Input <Input
label="管理密钥" label={t('config_management.visual.sections.remote.secret_key')}
type="password" type="password"
placeholder="设置管理密钥" placeholder={t('config_management.visual.sections.remote.secret_key_placeholder')}
value={values.rmSecretKey} value={values.rmSecretKey}
onChange={(e) => onChange({ rmSecretKey: e.target.value })} onChange={(e) => onChange({ rmSecretKey: e.target.value })}
disabled={disabled} disabled={disabled}
/> />
<Input <Input
label="面板仓库" label={t('config_management.visual.sections.remote.panel_repo')}
placeholder="https://github.com/router-for-me/Cli-Proxy-API-Management-Center" placeholder="https://github.com/router-for-me/Cli-Proxy-API-Management-Center"
value={values.rmPanelRepo} value={values.rmPanelRepo}
onChange={(e) => onChange({ rmPanelRepo: e.target.value })} onChange={(e) => onChange({ rmPanelRepo: e.target.value })}
@@ -811,15 +817,15 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="认证配置" description="API 密钥与认证文件目录设置"> <ConfigSection title={t('config_management.visual.sections.auth.title')} description={t('config_management.visual.sections.auth.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<Input <Input
label="认证文件目录 (auth-dir)" label={t('config_management.visual.sections.auth.auth_dir')}
placeholder="~/.cli-proxy-api" placeholder="~/.cli-proxy-api"
value={values.authDir} value={values.authDir}
onChange={(e) => onChange({ authDir: e.target.value })} onChange={(e) => onChange({ authDir: e.target.value })}
disabled={disabled} disabled={disabled}
hint="存放认证文件的目录路径(支持 ~" hint={t('config_management.visual.sections.auth.auth_dir_hint')}
/> />
<ApiKeysCardEditor <ApiKeysCardEditor
value={values.apiKeysText} value={values.apiKeysText}
@@ -829,33 +835,33 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="系统配置" description="调试、日志、统计与性能调试设置"> <ConfigSection title={t('config_management.visual.sections.system.title')} description={t('config_management.visual.sections.system.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<SectionGrid> <SectionGrid>
<ToggleRow <ToggleRow
title="调试模式" title={t('config_management.visual.sections.system.debug')}
description="启用详细的调试日志" description={t('config_management.visual.sections.system.debug_desc')}
checked={values.debug} checked={values.debug}
disabled={disabled} disabled={disabled}
onChange={(debug) => onChange({ debug })} onChange={(debug) => onChange({ debug })}
/> />
<ToggleRow <ToggleRow
title="商业模式" title={t('config_management.visual.sections.system.commercial_mode')}
description="禁用高开销中间件以减少高并发内存" description={t('config_management.visual.sections.system.commercial_mode_desc')}
checked={values.commercialMode} checked={values.commercialMode}
disabled={disabled} disabled={disabled}
onChange={(commercialMode) => onChange({ commercialMode })} onChange={(commercialMode) => onChange({ commercialMode })}
/> />
<ToggleRow <ToggleRow
title="写入日志文件" title={t('config_management.visual.sections.system.logging_to_file')}
description="将日志保存到滚动文件" description={t('config_management.visual.sections.system.logging_to_file_desc')}
checked={values.loggingToFile} checked={values.loggingToFile}
disabled={disabled} disabled={disabled}
onChange={(loggingToFile) => onChange({ loggingToFile })} onChange={(loggingToFile) => onChange({ loggingToFile })}
/> />
<ToggleRow <ToggleRow
title="使用统计" title={t('config_management.visual.sections.system.usage_statistics')}
description="收集使用统计信息" description={t('config_management.visual.sections.system.usage_statistics_desc')}
checked={values.usageStatisticsEnabled} checked={values.usageStatisticsEnabled}
disabled={disabled} disabled={disabled}
onChange={(usageStatisticsEnabled) => onChange({ usageStatisticsEnabled })} onChange={(usageStatisticsEnabled) => onChange({ usageStatisticsEnabled })}
@@ -864,7 +870,7 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
<SectionGrid> <SectionGrid>
<Input <Input
label="日志文件大小限制 (MB)" label={t('config_management.visual.sections.system.logs_max_size')}
type="number" type="number"
placeholder="0" placeholder="0"
value={values.logsMaxTotalSizeMb} value={values.logsMaxTotalSizeMb}
@@ -872,30 +878,30 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
disabled={disabled} disabled={disabled}
/> />
<Input <Input
label="使用记录保留天数" label={t('config_management.visual.sections.system.usage_retention_days')}
type="number" type="number"
placeholder="30" placeholder="30"
value={values.usageRecordsRetentionDays} value={values.usageRecordsRetentionDays}
onChange={(e) => onChange({ usageRecordsRetentionDays: e.target.value })} onChange={(e) => onChange({ usageRecordsRetentionDays: e.target.value })}
disabled={disabled} disabled={disabled}
hint="0 为无限制(不清理)" hint={t('config_management.visual.sections.system.usage_retention_hint')}
/> />
</SectionGrid> </SectionGrid>
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="网络配置" description="代理、重试和路由设置"> <ConfigSection title={t('config_management.visual.sections.network.title')} description={t('config_management.visual.sections.network.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<SectionGrid> <SectionGrid>
<Input <Input
label="代理 URL" label={t('config_management.visual.sections.network.proxy_url')}
placeholder="socks5://user:pass@127.0.0.1:1080/" placeholder="socks5://user:pass@127.0.0.1:1080/"
value={values.proxyUrl} value={values.proxyUrl}
onChange={(e) => onChange({ proxyUrl: e.target.value })} onChange={(e) => onChange({ proxyUrl: e.target.value })}
disabled={disabled} disabled={disabled}
/> />
<Input <Input
label="请求重试次数" label={t('config_management.visual.sections.network.request_retry')}
type="number" type="number"
placeholder="3" placeholder="3"
value={values.requestRetry} value={values.requestRetry}
@@ -903,7 +909,7 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
disabled={disabled} disabled={disabled}
/> />
<Input <Input
label="最大重试间隔 (秒)" label={t('config_management.visual.sections.network.max_retry_interval')}
type="number" type="number"
placeholder="30" placeholder="30"
value={values.maxRetryInterval} value={values.maxRetryInterval}
@@ -911,33 +917,33 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
disabled={disabled} disabled={disabled}
/> />
<div className="form-group"> <div className="form-group">
<label></label> <label>{t('config_management.visual.sections.network.routing_strategy')}</label>
<ToastSelect <ToastSelect
value={values.routingStrategy} value={values.routingStrategy}
options={[ options={[
{ value: 'round-robin', label: '轮询 (Round Robin)' }, { value: 'round-robin', label: t('config_management.visual.sections.network.strategy_round_robin') },
{ value: 'fill-first', label: '填充优先 (Fill First)' }, { value: 'fill-first', label: t('config_management.visual.sections.network.strategy_fill_first') },
]} ]}
disabled={disabled} disabled={disabled}
ariaLabel="路由策略" ariaLabel={t('config_management.visual.sections.network.routing_strategy')}
onChange={(nextValue) => onChange={(nextValue) =>
onChange({ routingStrategy: nextValue as VisualConfigValues['routingStrategy'] }) onChange({ routingStrategy: nextValue as VisualConfigValues['routingStrategy'] })
} }
/> />
<div className="hint"></div> <div className="hint">{t('config_management.visual.sections.network.routing_strategy_hint')}</div>
</div> </div>
</SectionGrid> </SectionGrid>
<ToggleRow <ToggleRow
title="强制模型前缀" title={t('config_management.visual.sections.network.force_model_prefix')}
description="未带前缀的模型请求只使用无前缀凭据" description={t('config_management.visual.sections.network.force_model_prefix_desc')}
checked={values.forceModelPrefix} checked={values.forceModelPrefix}
disabled={disabled} disabled={disabled}
onChange={(forceModelPrefix) => onChange({ forceModelPrefix })} onChange={(forceModelPrefix) => onChange({ forceModelPrefix })}
/> />
<ToggleRow <ToggleRow
title="WebSocket 认证" title={t('config_management.visual.sections.network.ws_auth')}
description="启用 WebSocket 连接认证 (/v1/ws)" description={t('config_management.visual.sections.network.ws_auth_desc')}
checked={values.wsAuth} checked={values.wsAuth}
disabled={disabled} disabled={disabled}
onChange={(wsAuth) => onChange({ wsAuth })} onChange={(wsAuth) => onChange({ wsAuth })}
@@ -945,18 +951,18 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="配额回退" description="配额耗尽时的回退策略"> <ConfigSection title={t('config_management.visual.sections.quota.title')} description={t('config_management.visual.sections.quota.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<ToggleRow <ToggleRow
title="切换项目" title={t('config_management.visual.sections.quota.switch_project')}
description="配额耗尽时自动切换到其他项目" description={t('config_management.visual.sections.quota.switch_project_desc')}
checked={values.quotaSwitchProject} checked={values.quotaSwitchProject}
disabled={disabled} disabled={disabled}
onChange={(quotaSwitchProject) => onChange({ quotaSwitchProject })} onChange={(quotaSwitchProject) => onChange({ quotaSwitchProject })}
/> />
<ToggleRow <ToggleRow
title="切换预览模型" title={t('config_management.visual.sections.quota.switch_preview_model')}
description="配额耗尽时切换到预览版本模型" description={t('config_management.visual.sections.quota.switch_preview_model_desc')}
checked={values.quotaSwitchPreviewModel} checked={values.quotaSwitchPreviewModel}
disabled={disabled} disabled={disabled}
onChange={(quotaSwitchPreviewModel) => onChange({ quotaSwitchPreviewModel })} onChange={(quotaSwitchPreviewModel) => onChange({ quotaSwitchPreviewModel })}
@@ -964,11 +970,11 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="流式传输配置" description="Keepalive 与 bootstrap 重试设置"> <ConfigSection title={t('config_management.visual.sections.streaming.title')} description={t('config_management.visual.sections.streaming.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<SectionGrid> <SectionGrid>
<div className="form-group"> <div className="form-group">
<label>Keepalive </label> <label>{t('config_management.visual.sections.streaming.keepalive_seconds')}</label>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<input <input
className="input" className="input"
@@ -995,26 +1001,26 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
border: '1px solid var(--border-color)', border: '1px solid var(--border-color)',
}} }}
> >
{t('config_management.visual.sections.streaming.disabled')}
</span> </span>
)} )}
</div> </div>
<div className="hint"> 0 keepalive</div> <div className="hint">{t('config_management.visual.sections.streaming.keepalive_hint')}</div>
</div> </div>
<Input <Input
label="Bootstrap 重试次数" label={t('config_management.visual.sections.streaming.bootstrap_retries')}
type="number" type="number"
placeholder="1" placeholder="1"
value={values.streaming.bootstrapRetries} value={values.streaming.bootstrapRetries}
onChange={(e) => onChange({ streaming: { ...values.streaming, bootstrapRetries: e.target.value } })} onChange={(e) => onChange({ streaming: { ...values.streaming, bootstrapRetries: e.target.value } })}
disabled={disabled} disabled={disabled}
hint="流式传输启动时(首包前)的重试次数" hint={t('config_management.visual.sections.streaming.bootstrap_hint')}
/> />
</SectionGrid> </SectionGrid>
<SectionGrid> <SectionGrid>
<div className="form-group"> <div className="form-group">
<label> Keepalive ()</label> <label>{t('config_management.visual.sections.streaming.nonstream_keepalive')}</label>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<input <input
className="input" className="input"
@@ -1043,24 +1049,24 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
border: '1px solid var(--border-color)', border: '1px solid var(--border-color)',
}} }}
> >
{t('config_management.visual.sections.streaming.disabled')}
</span> </span>
)} )}
</div> </div>
<div className="hint"> <div className="hint">
N 0 {t('config_management.visual.sections.streaming.nonstream_keepalive_hint')}
</div> </div>
</div> </div>
</SectionGrid> </SectionGrid>
</div> </div>
</ConfigSection> </ConfigSection>
<ConfigSection title="Payload 配置" description="默认值、覆盖规则与过滤规则"> <ConfigSection title={t('config_management.visual.sections.payload.title')} description={t('config_management.visual.sections.payload.description')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div> <div>
<div style={{ fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}></div> <div style={{ fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}>{t('config_management.visual.sections.payload.default_rules')}</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}> <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}>
使 {t('config_management.visual.sections.payload.default_rules_desc')}
</div> </div>
<PayloadRulesEditor <PayloadRulesEditor
value={values.payloadDefaultRules} value={values.payloadDefaultRules}
@@ -1070,9 +1076,9 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
<div> <div>
<div style={{ fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}></div> <div style={{ fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}>{t('config_management.visual.sections.payload.override_rules')}</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}> <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}>
{t('config_management.visual.sections.payload.override_rules_desc')}
</div> </div>
<PayloadRulesEditor <PayloadRulesEditor
value={values.payloadOverrideRules} value={values.payloadOverrideRules}
@@ -1083,9 +1089,9 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
</div> </div>
<div> <div>
<div style={{ fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}></div> <div style={{ fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}>{t('config_management.visual.sections.payload.filter_rules')}</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}> <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}>
JSON Path /Request Sanitization {t('config_management.visual.sections.payload.filter_rules_desc')}
</div> </div>
<PayloadFilterRulesEditor <PayloadFilterRulesEditor
value={values.payloadFilterRules} value={values.payloadFilterRules}

View File

@@ -816,7 +816,7 @@
"editor_title": "Configuration File", "editor_title": "Configuration File",
"reload": "Reload", "reload": "Reload",
"save": "Save", "save": "Save",
"description": "View and edit the server-side config.yaml file. Validate the syntax before saving.", "description": "Edit config.yaml via visual editor or source file",
"status_idle": "Waiting for action", "status_idle": "Waiting for action",
"status_loading": "Loading configuration...", "status_loading": "Loading configuration...",
"status_loaded": "Configuration loaded", "status_loaded": "Configuration loaded",
@@ -836,7 +836,141 @@
"search_next": "Next", "search_next": "Next",
"tabs": { "tabs": {
"visual": "Visual Editor", "visual": "Visual Editor",
"source": "Source Editor" "source": "Source File Editor"
},
"visual": {
"sections": {
"server": {
"title": "Server Configuration",
"description": "Basic server settings",
"host": "Host Address",
"port": "Port"
},
"tls": {
"title": "TLS/SSL Configuration",
"description": "HTTPS secure connection settings",
"enable": "Enable TLS",
"enable_desc": "Enable HTTPS secure connection",
"cert": "Certificate File Path",
"key": "Private Key File Path"
},
"remote": {
"title": "Remote Management",
"description": "Remote access and control panel settings",
"allow_remote": "Allow Remote Access",
"allow_remote_desc": "Allow management access from other hosts",
"disable_panel": "Disable Control Panel",
"disable_panel_desc": "Disable the built-in web control panel",
"secret_key": "Management Key",
"secret_key_placeholder": "Set management key",
"panel_repo": "Panel Repository"
},
"auth": {
"title": "Authentication Configuration",
"description": "API keys and authentication directory settings",
"auth_dir": "Auth Directory (auth-dir)",
"auth_dir_hint": "Directory path for authentication files (supports ~)"
},
"system": {
"title": "System Configuration",
"description": "Debug, logging, statistics, and performance settings",
"debug": "Debug Mode",
"debug_desc": "Enable verbose debug logging",
"commercial_mode": "Commercial Mode",
"commercial_mode_desc": "Disable high-overhead middleware to reduce memory under high concurrency",
"logging_to_file": "Log to File",
"logging_to_file_desc": "Save logs to rotating files",
"usage_statistics": "Usage Statistics",
"usage_statistics_desc": "Collect usage statistics",
"logs_max_size": "Log File Size Limit (MB)",
"usage_retention_days": "Usage Records Retention Days",
"usage_retention_hint": "0 means no limit (no cleanup)"
},
"network": {
"title": "Network Configuration",
"description": "Proxy, retry, and routing settings",
"proxy_url": "Proxy URL",
"request_retry": "Request Retry Count",
"max_retry_interval": "Max Retry Interval (seconds)",
"routing_strategy": "Routing Strategy",
"routing_strategy_hint": "Select credential selection strategy",
"strategy_round_robin": "Round Robin",
"strategy_fill_first": "Fill First",
"force_model_prefix": "Force Model Prefix",
"force_model_prefix_desc": "Unprefixed model requests only use credentials without prefix",
"ws_auth": "WebSocket Authentication",
"ws_auth_desc": "Enable WebSocket authentication (/v1/ws)"
},
"quota": {
"title": "Quota Fallback",
"description": "Fallback strategy when quota is exceeded",
"switch_project": "Switch Project",
"switch_project_desc": "Automatically switch to another project when quota is exceeded",
"switch_preview_model": "Switch to Preview Model",
"switch_preview_model_desc": "Switch to preview model version when quota is exceeded"
},
"streaming": {
"title": "Streaming Configuration",
"description": "Keepalive and bootstrap retry settings",
"keepalive_seconds": "Keepalive Seconds",
"keepalive_hint": "Set to 0 or leave empty to disable keepalive",
"bootstrap_retries": "Bootstrap Retries",
"bootstrap_hint": "Number of retries during stream startup (before first byte)",
"nonstream_keepalive": "Non-stream Keepalive Interval (seconds)",
"nonstream_keepalive_hint": "Send blank lines every N seconds for non-streaming responses to prevent idle timeout, set to 0 or leave empty to disable",
"disabled": "Disabled"
},
"payload": {
"title": "Payload Configuration",
"description": "Default values, override rules, and filter rules",
"default_rules": "Default Rules",
"default_rules_desc": "Use these default values when parameters are not specified in the request",
"override_rules": "Override Rules",
"override_rules_desc": "Force override parameter values in the request",
"filter_rules": "Filter Rules",
"filter_rules_desc": "Pre-filter upstream request body via JSON Path, automatically remove non-compliant/redundant parameters (Request Sanitization)"
}
},
"api_keys": {
"label": "API Keys List (api-keys)",
"add": "Add API Key",
"empty": "No API keys",
"hint": "Each entry represents an API key (consistent with 'API Key Management' page style)",
"edit_title": "Edit API Key",
"add_title": "Add API Key",
"input_label": "API Key",
"input_placeholder": "Paste your API key",
"input_hint": "This only modifies the local config file content, it will not sync to the API Key Management interface",
"error_empty": "Please enter an API key",
"error_invalid": "API key contains invalid characters"
},
"payload_rules": {
"rule": "Rule",
"models": "Applicable Models",
"model_name": "Model Name",
"provider_type": "Provider Type",
"add_model": "Add Model",
"params": "Parameter Settings",
"remove_params": "Remove Parameters",
"json_path": "JSON Path (e.g., temperature)",
"json_path_filter": "JSON Path (gjson/sjson), e.g., generationConfig.thinkingConfig.thinkingBudget",
"param_type": "Parameter Type",
"add_param": "Add Parameter",
"no_rules": "No rules",
"add_rule": "Add Rule",
"value_string": "String value",
"value_number": "Number value (e.g., 0.7)",
"value_boolean": "true or false",
"value_json": "JSON value",
"value_default": "Value"
},
"common": {
"edit": "Edit",
"delete": "Delete",
"cancel": "Cancel",
"update": "Update",
"add": "Add"
}
} }
}, },
"quota_management": { "quota_management": {

View File

@@ -816,7 +816,7 @@
"editor_title": "配置文件", "editor_title": "配置文件",
"reload": "重新加载", "reload": "重新加载",
"save": "保存", "save": "保存",
"description": "查看并编辑服务器上的 config.yaml 配置文件。保存前请确认语法正确。", "description": "通过可视化或者源文件方式编辑 config.yaml 配置文件",
"status_idle": "等待操作", "status_idle": "等待操作",
"status_loading": "加载配置中...", "status_loading": "加载配置中...",
"status_loaded": "配置已加载", "status_loaded": "配置已加载",
@@ -836,7 +836,141 @@
"search_next": "下一个", "search_next": "下一个",
"tabs": { "tabs": {
"visual": "可视化编辑", "visual": "可视化编辑",
"source": "源代码编辑" "source": "源文件编辑"
},
"visual": {
"sections": {
"server": {
"title": "服务器配置",
"description": "基础服务器设置",
"host": "主机地址",
"port": "端口"
},
"tls": {
"title": "TLS/SSL 配置",
"description": "HTTPS 安全连接设置",
"enable": "启用 TLS",
"enable_desc": "启用 HTTPS 安全连接",
"cert": "证书文件路径",
"key": "私钥文件路径"
},
"remote": {
"title": "远程管理",
"description": "远程访问和控制面板设置",
"allow_remote": "允许远程访问",
"allow_remote_desc": "允许从其他主机访问管理接口",
"disable_panel": "禁用控制面板",
"disable_panel_desc": "禁用内置的 Web 控制面板",
"secret_key": "管理密钥",
"secret_key_placeholder": "设置管理密钥",
"panel_repo": "面板仓库"
},
"auth": {
"title": "认证配置",
"description": "API 密钥与认证文件目录设置",
"auth_dir": "认证文件目录 (auth-dir)",
"auth_dir_hint": "存放认证文件的目录路径(支持 ~"
},
"system": {
"title": "系统配置",
"description": "调试、日志、统计与性能调试设置",
"debug": "调试模式",
"debug_desc": "启用详细的调试日志",
"commercial_mode": "商业模式",
"commercial_mode_desc": "禁用高开销中间件以减少高并发内存",
"logging_to_file": "写入日志文件",
"logging_to_file_desc": "将日志保存到滚动文件",
"usage_statistics": "使用统计",
"usage_statistics_desc": "收集使用统计信息",
"logs_max_size": "日志文件大小限制 (MB)",
"usage_retention_days": "使用记录保留天数",
"usage_retention_hint": "0 为无限制(不清理)"
},
"network": {
"title": "网络配置",
"description": "代理、重试和路由设置",
"proxy_url": "代理 URL",
"request_retry": "请求重试次数",
"max_retry_interval": "最大重试间隔 (秒)",
"routing_strategy": "路由策略",
"routing_strategy_hint": "选择凭据选择策略",
"strategy_round_robin": "轮询 (Round Robin)",
"strategy_fill_first": "填充优先 (Fill First)",
"force_model_prefix": "强制模型前缀",
"force_model_prefix_desc": "未带前缀的模型请求只使用无前缀凭据",
"ws_auth": "WebSocket 认证",
"ws_auth_desc": "启用 WebSocket 连接认证 (/v1/ws)"
},
"quota": {
"title": "配额回退",
"description": "配额耗尽时的回退策略",
"switch_project": "切换项目",
"switch_project_desc": "配额耗尽时自动切换到其他项目",
"switch_preview_model": "切换预览模型",
"switch_preview_model_desc": "配额耗尽时切换到预览版本模型"
},
"streaming": {
"title": "流式传输配置",
"description": "Keepalive 与 bootstrap 重试设置",
"keepalive_seconds": "Keepalive 秒数",
"keepalive_hint": "设置为 0 或留空表示禁用 keepalive",
"bootstrap_retries": "Bootstrap 重试次数",
"bootstrap_hint": "流式传输启动时(首包前)的重试次数",
"nonstream_keepalive": "非流式 Keepalive 间隔 (秒)",
"nonstream_keepalive_hint": "非流式响应时每隔 N 秒发送空行以防止空闲超时,设置为 0 或留空表示禁用",
"disabled": "已禁用"
},
"payload": {
"title": "Payload 配置",
"description": "默认值、覆盖规则与过滤规则",
"default_rules": "默认规则",
"default_rules_desc": "当请求中未指定参数时,使用这些默认值",
"override_rules": "覆盖规则",
"override_rules_desc": "强制覆盖请求中的参数值",
"filter_rules": "过滤规则",
"filter_rules_desc": "通过 JSON Path 预过滤上游请求体,自动剔除不合规/冗余参数Request Sanitization"
}
},
"api_keys": {
"label": "API 密钥列表 (api-keys)",
"add": "添加 API 密钥",
"empty": "暂无 API 密钥",
"hint": "每个条目代表一个 API 密钥(与 「API 密钥管理」 页面样式一致)",
"edit_title": "编辑 API 密钥",
"add_title": "添加 API 密钥",
"input_label": "API 密钥",
"input_placeholder": "粘贴你的 API 密钥",
"input_hint": "此处仅修改本地配置文件内容,不会自动同步到 API 密钥管理接口",
"error_empty": "请输入 API 密钥",
"error_invalid": "API 密钥包含无效字符"
},
"payload_rules": {
"rule": "规则",
"models": "适用模型",
"model_name": "模型名称",
"provider_type": "供应商类型",
"add_model": "添加模型",
"params": "参数设置",
"remove_params": "移除参数",
"json_path": "JSON 路径 (如 temperature)",
"json_path_filter": "JSON 路径 (gjson/sjson),如 generationConfig.thinkingConfig.thinkingBudget",
"param_type": "参数类型",
"add_param": "添加参数",
"no_rules": "暂无规则",
"add_rule": "添加规则",
"value_string": "字符串值",
"value_number": "数字值 (如 0.7)",
"value_boolean": "true 或 false",
"value_json": "JSON 值",
"value_default": "值"
},
"common": {
"edit": "编辑",
"delete": "删除",
"cancel": "取消",
"update": "更新",
"add": "添加"
}
} }
}, },
"quota_management": { "quota_management": {