feat: add real-time TOML validation for Codex config

- Add smol-toml dependency for client-side TOML parsing
- Create useCodexTomlValidation hook with 500ms debounce
- Display validation errors below config.toml textarea
- Trigger validation on onChange for immediate user feedback
- Backend validation remains as fallback for data integrity
This commit is contained in:
Jason
2025-10-16 23:56:30 +08:00
parent 51c68ef192
commit 54b0b3b139
4 changed files with 102 additions and 7 deletions

View File

@@ -10,3 +10,4 @@ export { useTemplateValues } from "./useTemplateValues";
export { useCommonConfigSnippet } from "./useCommonConfigSnippet";
export { useCodexCommonConfig } from "./useCodexCommonConfig";
export { useSpeedTestEndpoints } from "./useSpeedTestEndpoints";
export { useCodexTomlValidation } from "./useCodexTomlValidation";

View File

@@ -0,0 +1,75 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import TOML from 'smol-toml';
/**
* Codex config.toml 格式校验 Hook
* 使用 smol-toml 进行实时 TOML 语法校验(带 debounce
*/
export function useCodexTomlValidation() {
const [configError, setConfigError] = useState('');
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
/**
* 校验 TOML 格式
* @param tomlText - 待校验的 TOML 文本
* @returns 是否校验通过
*/
const validateToml = useCallback((tomlText: string): boolean => {
// 空字符串视为合法(允许为空)
if (!tomlText.trim()) {
setConfigError('');
return true;
}
try {
TOML.parse(tomlText);
setConfigError('');
return true;
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: 'TOML 格式错误';
setConfigError(errorMessage);
return false;
}
}, []);
/**
* 带 debounce 的校验函数500ms 延迟)
* @param tomlText - 待校验的 TOML 文本
*/
const debouncedValidate = useCallback((tomlText: string) => {
// 清除之前的定时器
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
// 设置新的定时器
debounceTimerRef.current = setTimeout(() => {
validateToml(tomlText);
}, 500);
}, [validateToml]);
/**
* 清空错误信息
*/
const clearError = useCallback(() => {
setConfigError('');
}, []);
// 清理定时器
useEffect(() => {
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
};
}, []);
return {
configError,
validateToml,
debouncedValidate,
clearError,
};
}