fix(config): align visual editor with backend config semantics

This commit is contained in:
LTbinglingfeng
2026-02-06 18:14:13 +08:00
parent 26fa1ea98e
commit f833f0dfd2
8 changed files with 53 additions and 42 deletions

13
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@codemirror/lang-yaml": "^6.1.2", "@codemirror/lang-yaml": "^6.1.2",
"@openai/codex": "^0.98.0",
"@uiw/react-codemirror": "^4.25.3", "@uiw/react-codemirror": "^4.25.3",
"axios": "^1.13.2", "axios": "^1.13.2",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
@@ -1243,6 +1244,18 @@
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@openai/codex": {
"version": "0.98.0",
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.98.0.tgz",
"integrity": "sha512-CKjrhAmzTvWn7Vbsi27iZRKBAJw9a7ZTTkWQDbLgQZP1weGbDIBk1r6wiLEp1ZmDO7w0fHPLYgnVspiOrYgcxg==",
"license": "Apache-2.0",
"bin": {
"codex": "bin/codex.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@parcel/watcher": { "node_modules/@parcel/watcher": {
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",

View File

@@ -13,6 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-yaml": "^6.1.2", "@codemirror/lang-yaml": "^6.1.2",
"@openai/codex": "^0.98.0",
"@uiw/react-codemirror": "^4.25.3", "@uiw/react-codemirror": "^4.25.3",
"axios": "^1.13.2", "axios": "^1.13.2",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",

View File

@@ -891,15 +891,6 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
onChange={(e) => onChange({ logsMaxTotalSizeMb: e.target.value })} onChange={(e) => onChange({ logsMaxTotalSizeMb: e.target.value })}
disabled={disabled} disabled={disabled}
/> />
<Input
label={t('config_management.visual.sections.system.usage_retention_days')}
type="number"
placeholder="30"
value={values.usageRecordsRetentionDays}
onChange={(e) => onChange({ usageRecordsRetentionDays: e.target.value })}
disabled={disabled}
hint={t('config_management.visual.sections.system.usage_retention_hint')}
/>
</SectionGrid> </SectionGrid>
</div> </div>
</ConfigSection> </ConfigSection>

View File

@@ -102,6 +102,27 @@ function deepClone<T>(value: T): T {
return JSON.parse(JSON.stringify(value)) as T; return JSON.parse(JSON.stringify(value)) as T;
} }
function parsePayloadParamValue(raw: unknown): { valueType: PayloadParamValueType; value: string } {
if (typeof raw === 'number') {
return { valueType: 'number', value: String(raw) };
}
if (typeof raw === 'boolean') {
return { valueType: 'boolean', value: String(raw) };
}
if (raw === null || typeof raw === 'object') {
try {
const json = JSON.stringify(raw, null, 2);
return { valueType: 'json', value: json ?? 'null' };
} catch {
return { valueType: 'json', value: String(raw) };
}
}
return { valueType: 'string', value: String(raw ?? '') };
}
function parsePayloadRules(rules: unknown): PayloadRule[] { function parsePayloadRules(rules: unknown): PayloadRule[] {
if (!Array.isArray(rules)) return []; if (!Array.isArray(rules)) return [];
@@ -115,19 +136,15 @@ function parsePayloadRules(rules: unknown): PayloadRule[] {
})) }))
: [], : [],
params: (rule as any)?.params params: (rule as any)?.params
? Object.entries((rule as any).params as Record<string, unknown>).map(([path, value], pIndex) => ({ ? Object.entries((rule as any).params as Record<string, unknown>).map(([path, value], pIndex) => {
id: `param-${index}-${pIndex}`, const parsedValue = parsePayloadParamValue(value);
path, return {
valueType: id: `param-${index}-${pIndex}`,
typeof value === 'number' path,
? 'number' valueType: parsedValue.valueType,
: typeof value === 'boolean' value: parsedValue.value,
? 'boolean' };
: typeof value === 'object' })
? 'json'
: 'string',
value: String(value),
}))
: [], : [],
})); }));
} }
@@ -220,7 +237,7 @@ export function useVisualConfig() {
const newValues: VisualConfigValues = { const newValues: VisualConfigValues = {
host: parsed.host || '', host: parsed.host || '',
port: String(parsed.port || ''), port: String(parsed.port ?? ''),
tlsEnable: Boolean(parsed.tls?.enable), tlsEnable: Boolean(parsed.tls?.enable),
tlsCert: parsed.tls?.cert || '', tlsCert: parsed.tls?.cert || '',
@@ -240,14 +257,13 @@ export function useVisualConfig() {
debug: Boolean(parsed.debug), debug: Boolean(parsed.debug),
commercialMode: Boolean(parsed['commercial-mode']), commercialMode: Boolean(parsed['commercial-mode']),
loggingToFile: Boolean(parsed['logging-to-file']), loggingToFile: Boolean(parsed['logging-to-file']),
logsMaxTotalSizeMb: String(parsed['logs-max-total-size-mb'] || ''), logsMaxTotalSizeMb: String(parsed['logs-max-total-size-mb'] ?? ''),
usageStatisticsEnabled: Boolean(parsed['usage-statistics-enabled']), usageStatisticsEnabled: Boolean(parsed['usage-statistics-enabled']),
usageRecordsRetentionDays: String(parsed['usage-records-retention-days'] ?? ''),
proxyUrl: parsed['proxy-url'] || '', proxyUrl: parsed['proxy-url'] || '',
forceModelPrefix: Boolean(parsed['force-model-prefix']), forceModelPrefix: Boolean(parsed['force-model-prefix']),
requestRetry: String(parsed['request-retry'] || ''), requestRetry: String(parsed['request-retry'] ?? ''),
maxRetryInterval: String(parsed['max-retry-interval'] || ''), maxRetryInterval: String(parsed['max-retry-interval'] ?? ''),
wsAuth: Boolean(parsed['ws-auth']), wsAuth: Boolean(parsed['ws-auth']),
quotaSwitchProject: Boolean(parsed['quota-exceeded']?.['switch-project'] ?? true), quotaSwitchProject: Boolean(parsed['quota-exceeded']?.['switch-project'] ?? true),
@@ -333,11 +349,6 @@ export function useVisualConfig() {
setBoolean(parsed, 'logging-to-file', values.loggingToFile); setBoolean(parsed, 'logging-to-file', values.loggingToFile);
setIntFromString(parsed, 'logs-max-total-size-mb', values.logsMaxTotalSizeMb); setIntFromString(parsed, 'logs-max-total-size-mb', values.logsMaxTotalSizeMb);
setBoolean(parsed, 'usage-statistics-enabled', values.usageStatisticsEnabled); setBoolean(parsed, 'usage-statistics-enabled', values.usageStatisticsEnabled);
setIntFromString(
parsed,
'usage-records-retention-days',
values.usageRecordsRetentionDays
);
setString(parsed, 'proxy-url', values.proxyUrl); setString(parsed, 'proxy-url', values.proxyUrl);
setBoolean(parsed, 'force-model-prefix', values.forceModelPrefix); setBoolean(parsed, 'force-model-prefix', values.forceModelPrefix);

View File

@@ -882,9 +882,7 @@
"logging_to_file_desc": "Save logs to rotating files", "logging_to_file_desc": "Save logs to rotating files",
"usage_statistics": "Usage Statistics", "usage_statistics": "Usage Statistics",
"usage_statistics_desc": "Collect usage statistics", "usage_statistics_desc": "Collect usage statistics",
"logs_max_size": "Log File Size Limit (MB)", "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": { "network": {
"title": "Network Configuration", "title": "Network Configuration",

View File

@@ -882,9 +882,7 @@
"logging_to_file_desc": "将日志保存到滚动文件", "logging_to_file_desc": "将日志保存到滚动文件",
"usage_statistics": "使用统计", "usage_statistics": "使用统计",
"usage_statistics_desc": "收集使用统计信息", "usage_statistics_desc": "收集使用统计信息",
"logs_max_size": "日志文件大小限制 (MB)", "logs_max_size": "日志文件大小限制 (MB)"
"usage_retention_days": "使用记录保留天数",
"usage_retention_hint": "0 为无限制(不清理)"
}, },
"network": { "network": {
"title": "网络配置", "title": "网络配置",

View File

@@ -80,9 +80,10 @@ export function ConfigPage() {
try { try {
const nextContent = activeTab === 'visual' ? applyVisualChangesToYaml(content) : content; const nextContent = activeTab === 'visual' ? applyVisualChangesToYaml(content) : content;
await configFileApi.saveConfigYaml(nextContent); await configFileApi.saveConfigYaml(nextContent);
const latestContent = await configFileApi.fetchConfigYaml();
setDirty(false); setDirty(false);
setContent(nextContent); setContent(latestContent);
loadVisualValuesFromYaml(nextContent); loadVisualValuesFromYaml(latestContent);
showNotification(t('config_management.save_success'), 'success'); showNotification(t('config_management.save_success'), 'success');
} catch (err: unknown) { } catch (err: unknown) {
const message = err instanceof Error ? err.message : ''; const message = err instanceof Error ? err.message : '';

View File

@@ -48,7 +48,6 @@ export type VisualConfigValues = {
loggingToFile: boolean; loggingToFile: boolean;
logsMaxTotalSizeMb: string; logsMaxTotalSizeMb: string;
usageStatisticsEnabled: boolean; usageStatisticsEnabled: boolean;
usageRecordsRetentionDays: string;
proxyUrl: string; proxyUrl: string;
forceModelPrefix: boolean; forceModelPrefix: boolean;
requestRetry: string; requestRetry: string;
@@ -85,7 +84,6 @@ export const DEFAULT_VISUAL_VALUES: VisualConfigValues = {
loggingToFile: false, loggingToFile: false,
logsMaxTotalSizeMb: '', logsMaxTotalSizeMb: '',
usageStatisticsEnabled: false, usageStatisticsEnabled: false,
usageRecordsRetentionDays: '',
proxyUrl: '', proxyUrl: '',
forceModelPrefix: false, forceModelPrefix: false,
requestRetry: '', requestRetry: '',