diff --git a/package-lock.json b/package-lock.json
index 2ce56de..a182545 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@codemirror/lang-yaml": "^6.1.2",
+ "@openai/codex": "^0.98.0",
"@uiw/react-codemirror": "^4.25.3",
"axios": "^1.13.2",
"chart.js": "^4.5.1",
@@ -1243,6 +1244,18 @@
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"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": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
diff --git a/package.json b/package.json
index b8d6a4d..70a1935 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"dependencies": {
"@codemirror/lang-yaml": "^6.1.2",
+ "@openai/codex": "^0.98.0",
"@uiw/react-codemirror": "^4.25.3",
"axios": "^1.13.2",
"chart.js": "^4.5.1",
diff --git a/src/components/config/VisualConfigEditor.tsx b/src/components/config/VisualConfigEditor.tsx
index eff48ed..d66e9f7 100644
--- a/src/components/config/VisualConfigEditor.tsx
+++ b/src/components/config/VisualConfigEditor.tsx
@@ -891,15 +891,6 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
onChange={(e) => onChange({ logsMaxTotalSizeMb: e.target.value })}
disabled={disabled}
/>
- onChange({ usageRecordsRetentionDays: e.target.value })}
- disabled={disabled}
- hint={t('config_management.visual.sections.system.usage_retention_hint')}
- />
diff --git a/src/hooks/useVisualConfig.ts b/src/hooks/useVisualConfig.ts
index 1f7ed30..47d3cce 100644
--- a/src/hooks/useVisualConfig.ts
+++ b/src/hooks/useVisualConfig.ts
@@ -102,6 +102,27 @@ function deepClone(value: T): 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[] {
if (!Array.isArray(rules)) return [];
@@ -115,19 +136,15 @@ function parsePayloadRules(rules: unknown): PayloadRule[] {
}))
: [],
params: (rule as any)?.params
- ? Object.entries((rule as any).params as Record).map(([path, value], pIndex) => ({
- id: `param-${index}-${pIndex}`,
- path,
- valueType:
- typeof value === 'number'
- ? 'number'
- : typeof value === 'boolean'
- ? 'boolean'
- : typeof value === 'object'
- ? 'json'
- : 'string',
- value: String(value),
- }))
+ ? Object.entries((rule as any).params as Record).map(([path, value], pIndex) => {
+ const parsedValue = parsePayloadParamValue(value);
+ return {
+ id: `param-${index}-${pIndex}`,
+ path,
+ valueType: parsedValue.valueType,
+ value: parsedValue.value,
+ };
+ })
: [],
}));
}
@@ -220,7 +237,7 @@ export function useVisualConfig() {
const newValues: VisualConfigValues = {
host: parsed.host || '',
- port: String(parsed.port || ''),
+ port: String(parsed.port ?? ''),
tlsEnable: Boolean(parsed.tls?.enable),
tlsCert: parsed.tls?.cert || '',
@@ -240,14 +257,13 @@ export function useVisualConfig() {
debug: Boolean(parsed.debug),
commercialMode: Boolean(parsed['commercial-mode']),
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']),
- usageRecordsRetentionDays: String(parsed['usage-records-retention-days'] ?? ''),
proxyUrl: parsed['proxy-url'] || '',
forceModelPrefix: Boolean(parsed['force-model-prefix']),
- requestRetry: String(parsed['request-retry'] || ''),
- maxRetryInterval: String(parsed['max-retry-interval'] || ''),
+ requestRetry: String(parsed['request-retry'] ?? ''),
+ maxRetryInterval: String(parsed['max-retry-interval'] ?? ''),
wsAuth: Boolean(parsed['ws-auth']),
quotaSwitchProject: Boolean(parsed['quota-exceeded']?.['switch-project'] ?? true),
@@ -333,11 +349,6 @@ export function useVisualConfig() {
setBoolean(parsed, 'logging-to-file', values.loggingToFile);
setIntFromString(parsed, 'logs-max-total-size-mb', values.logsMaxTotalSizeMb);
setBoolean(parsed, 'usage-statistics-enabled', values.usageStatisticsEnabled);
- setIntFromString(
- parsed,
- 'usage-records-retention-days',
- values.usageRecordsRetentionDays
- );
setString(parsed, 'proxy-url', values.proxyUrl);
setBoolean(parsed, 'force-model-prefix', values.forceModelPrefix);
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 6b90898..c58692d 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -882,9 +882,7 @@
"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)"
+ "logs_max_size": "Log File Size Limit (MB)"
},
"network": {
"title": "Network Configuration",
diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json
index 20f8a7f..ed769c9 100644
--- a/src/i18n/locales/zh-CN.json
+++ b/src/i18n/locales/zh-CN.json
@@ -882,9 +882,7 @@
"logging_to_file_desc": "将日志保存到滚动文件",
"usage_statistics": "使用统计",
"usage_statistics_desc": "收集使用统计信息",
- "logs_max_size": "日志文件大小限制 (MB)",
- "usage_retention_days": "使用记录保留天数",
- "usage_retention_hint": "0 为无限制(不清理)"
+ "logs_max_size": "日志文件大小限制 (MB)"
},
"network": {
"title": "网络配置",
diff --git a/src/pages/ConfigPage.tsx b/src/pages/ConfigPage.tsx
index 77e3a00..eaf9442 100644
--- a/src/pages/ConfigPage.tsx
+++ b/src/pages/ConfigPage.tsx
@@ -80,9 +80,10 @@ export function ConfigPage() {
try {
const nextContent = activeTab === 'visual' ? applyVisualChangesToYaml(content) : content;
await configFileApi.saveConfigYaml(nextContent);
+ const latestContent = await configFileApi.fetchConfigYaml();
setDirty(false);
- setContent(nextContent);
- loadVisualValuesFromYaml(nextContent);
+ setContent(latestContent);
+ loadVisualValuesFromYaml(latestContent);
showNotification(t('config_management.save_success'), 'success');
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '';
diff --git a/src/types/visualConfig.ts b/src/types/visualConfig.ts
index 86efcf9..ff4242a 100644
--- a/src/types/visualConfig.ts
+++ b/src/types/visualConfig.ts
@@ -48,7 +48,6 @@ export type VisualConfigValues = {
loggingToFile: boolean;
logsMaxTotalSizeMb: string;
usageStatisticsEnabled: boolean;
- usageRecordsRetentionDays: string;
proxyUrl: string;
forceModelPrefix: boolean;
requestRetry: string;
@@ -85,7 +84,6 @@ export const DEFAULT_VISUAL_VALUES: VisualConfigValues = {
loggingToFile: false,
logsMaxTotalSizeMb: '',
usageStatisticsEnabled: false,
- usageRecordsRetentionDays: '',
proxyUrl: '',
forceModelPrefix: false,
requestRetry: '',