diff --git a/src/components/proxy/AutoFailoverConfigPanel.tsx b/src/components/proxy/AutoFailoverConfigPanel.tsx index 30f55e13..b80130fc 100644 --- a/src/components/proxy/AutoFailoverConfigPanel.tsx +++ b/src/components/proxy/AutoFailoverConfigPanel.tsx @@ -21,52 +21,64 @@ export function AutoFailoverConfigPanel({ const { data: config, isLoading, error } = useAppProxyConfig(appType); const updateConfig = useUpdateAppProxyConfig(); + // 使用字符串状态以支持完全清空数字输入框 const [formData, setFormData] = useState({ autoFailoverEnabled: false, - maxRetries: 3, - streamingFirstByteTimeout: 30, - streamingIdleTimeout: 60, - nonStreamingTimeout: 300, - circuitFailureThreshold: 5, - circuitSuccessThreshold: 2, - circuitTimeoutSeconds: 60, - circuitErrorRateThreshold: 0.5, - circuitMinRequests: 10, + maxRetries: "3", + streamingFirstByteTimeout: "30", + streamingIdleTimeout: "60", + nonStreamingTimeout: "300", + circuitFailureThreshold: "5", + circuitSuccessThreshold: "2", + circuitTimeoutSeconds: "60", + circuitErrorRateThreshold: "50", // 存储百分比值 + circuitMinRequests: "10", }); useEffect(() => { if (config) { setFormData({ autoFailoverEnabled: config.autoFailoverEnabled, - maxRetries: config.maxRetries, - streamingFirstByteTimeout: config.streamingFirstByteTimeout, - streamingIdleTimeout: config.streamingIdleTimeout, - nonStreamingTimeout: config.nonStreamingTimeout, - circuitFailureThreshold: config.circuitFailureThreshold, - circuitSuccessThreshold: config.circuitSuccessThreshold, - circuitTimeoutSeconds: config.circuitTimeoutSeconds, - circuitErrorRateThreshold: config.circuitErrorRateThreshold, - circuitMinRequests: config.circuitMinRequests, + maxRetries: String(config.maxRetries), + streamingFirstByteTimeout: String(config.streamingFirstByteTimeout), + streamingIdleTimeout: String(config.streamingIdleTimeout), + nonStreamingTimeout: String(config.nonStreamingTimeout), + circuitFailureThreshold: String(config.circuitFailureThreshold), + circuitSuccessThreshold: String(config.circuitSuccessThreshold), + circuitTimeoutSeconds: String(config.circuitTimeoutSeconds), + circuitErrorRateThreshold: String( + Math.round(config.circuitErrorRateThreshold * 100), + ), + circuitMinRequests: String(config.circuitMinRequests), }); } }, [config]); const handleSave = async () => { if (!config) return; + // 解析数字,空值使用默认值,0 是有效值 + const parseNum = (val: string, defaultVal: number) => { + const n = parseInt(val); + return isNaN(n) ? defaultVal : n; + }; try { await updateConfig.mutateAsync({ appType, enabled: config.enabled, autoFailoverEnabled: formData.autoFailoverEnabled, - maxRetries: formData.maxRetries, - streamingFirstByteTimeout: formData.streamingFirstByteTimeout, - streamingIdleTimeout: formData.streamingIdleTimeout, - nonStreamingTimeout: formData.nonStreamingTimeout, - circuitFailureThreshold: formData.circuitFailureThreshold, - circuitSuccessThreshold: formData.circuitSuccessThreshold, - circuitTimeoutSeconds: formData.circuitTimeoutSeconds, - circuitErrorRateThreshold: formData.circuitErrorRateThreshold, - circuitMinRequests: formData.circuitMinRequests, + maxRetries: parseNum(formData.maxRetries, 3), + streamingFirstByteTimeout: parseNum( + formData.streamingFirstByteTimeout, + 30, + ), + streamingIdleTimeout: parseNum(formData.streamingIdleTimeout, 60), + nonStreamingTimeout: parseNum(formData.nonStreamingTimeout, 300), + circuitFailureThreshold: parseNum(formData.circuitFailureThreshold, 5), + circuitSuccessThreshold: parseNum(formData.circuitSuccessThreshold, 2), + circuitTimeoutSeconds: parseNum(formData.circuitTimeoutSeconds, 60), + circuitErrorRateThreshold: + parseNum(formData.circuitErrorRateThreshold, 50) / 100, + circuitMinRequests: parseNum(formData.circuitMinRequests, 10), }); toast.success( t("proxy.autoFailover.configSaved", "自动故障转移配置已保存"), @@ -83,15 +95,17 @@ export function AutoFailoverConfigPanel({ if (config) { setFormData({ autoFailoverEnabled: config.autoFailoverEnabled, - maxRetries: config.maxRetries, - streamingFirstByteTimeout: config.streamingFirstByteTimeout, - streamingIdleTimeout: config.streamingIdleTimeout, - nonStreamingTimeout: config.nonStreamingTimeout, - circuitFailureThreshold: config.circuitFailureThreshold, - circuitSuccessThreshold: config.circuitSuccessThreshold, - circuitTimeoutSeconds: config.circuitTimeoutSeconds, - circuitErrorRateThreshold: config.circuitErrorRateThreshold, - circuitMinRequests: config.circuitMinRequests, + maxRetries: String(config.maxRetries), + streamingFirstByteTimeout: String(config.streamingFirstByteTimeout), + streamingIdleTimeout: String(config.streamingIdleTimeout), + nonStreamingTimeout: String(config.nonStreamingTimeout), + circuitFailureThreshold: String(config.circuitFailureThreshold), + circuitSuccessThreshold: String(config.circuitSuccessThreshold), + circuitTimeoutSeconds: String(config.circuitTimeoutSeconds), + circuitErrorRateThreshold: String( + Math.round(config.circuitErrorRateThreshold * 100), + ), + circuitMinRequests: String(config.circuitMinRequests), }); } }; @@ -142,13 +156,9 @@ export function AutoFailoverConfigPanel({ min="0" max="10" value={formData.maxRetries} - onChange={(e) => { - const val = parseInt(e.target.value); - setFormData({ - ...formData, - maxRetries: isNaN(val) ? 0 : val, - }); - }} + onChange={(e) => + setFormData({ ...formData, maxRetries: e.target.value }) + } disabled={isDisabled} />
@@ -169,13 +179,12 @@ export function AutoFailoverConfigPanel({ min="1" max="20" value={formData.circuitFailureThreshold} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - circuitFailureThreshold: isNaN(val) ? 1 : Math.max(1, val), - }); - }} + circuitFailureThreshold: e.target.value, + }) + } disabled={isDisabled} />
@@ -208,13 +217,12 @@ export function AutoFailoverConfigPanel({ min="0" max="180" value={formData.streamingFirstByteTimeout} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - streamingFirstByteTimeout: isNaN(val) ? 0 : val, - }); - }} + streamingFirstByteTimeout: e.target.value, + }) + } disabled={isDisabled} />
@@ -235,13 +243,12 @@ export function AutoFailoverConfigPanel({ min="0" max="600" value={formData.streamingIdleTimeout} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - streamingIdleTimeout: isNaN(val) ? 0 : val, - }); - }} + streamingIdleTimeout: e.target.value, + }) + } disabled={isDisabled} />
@@ -262,13 +269,12 @@ export function AutoFailoverConfigPanel({ min="0" max="1800" value={formData.nonStreamingTimeout} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - nonStreamingTimeout: isNaN(val) ? 0 : val, - }); - }} + nonStreamingTimeout: e.target.value, + }) + } disabled={isDisabled} />
@@ -298,13 +304,12 @@ export function AutoFailoverConfigPanel({ min="1" max="10" value={formData.circuitSuccessThreshold} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - circuitSuccessThreshold: isNaN(val) ? 1 : Math.max(1, val), - }); - }} + circuitSuccessThreshold: e.target.value, + }) + } disabled={isDisabled} />
@@ -325,13 +330,12 @@ export function AutoFailoverConfigPanel({ min="10" max="300" value={formData.circuitTimeoutSeconds} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - circuitTimeoutSeconds: isNaN(val) ? 10 : Math.max(10, val), - }); - }} + circuitTimeoutSeconds: e.target.value, + }) + } disabled={isDisabled} />
@@ -352,14 +356,13 @@ export function AutoFailoverConfigPanel({ min="0" max="100" step="5" - value={Math.round(formData.circuitErrorRateThreshold * 100)} - onChange={(e) => { - const val = parseInt(e.target.value); + value={formData.circuitErrorRateThreshold} + onChange={(e) => setFormData({ ...formData, - circuitErrorRateThreshold: isNaN(val) ? 0.5 : val / 100, - }); - }} + circuitErrorRateThreshold: e.target.value, + }) + } disabled={isDisabled} />
@@ -380,13 +383,12 @@ export function AutoFailoverConfigPanel({ min="5" max="100" value={formData.circuitMinRequests} - onChange={(e) => { - const val = parseInt(e.target.value); + onChange={(e) => setFormData({ ...formData, - circuitMinRequests: isNaN(val) ? 5 : Math.max(5, val), - }); - }} + circuitMinRequests: e.target.value, + }) + } disabled={isDisabled} />
diff --git a/src/components/proxy/CircuitBreakerConfigPanel.tsx b/src/components/proxy/CircuitBreakerConfigPanel.tsx index 93f48d59..5341df33 100644 --- a/src/components/proxy/CircuitBreakerConfigPanel.tsx +++ b/src/components/proxy/CircuitBreakerConfigPanel.tsx @@ -16,24 +16,43 @@ export function CircuitBreakerConfigPanel() { const { data: config, isLoading } = useCircuitBreakerConfig(); const updateConfig = useUpdateCircuitBreakerConfig(); + // 使用字符串状态以支持完全清空输入框 const [formData, setFormData] = useState({ - failureThreshold: 5, - successThreshold: 2, - timeoutSeconds: 60, - errorRateThreshold: 0.5, - minRequests: 10, + failureThreshold: "5", + successThreshold: "2", + timeoutSeconds: "60", + errorRateThreshold: "50", // 存储百分比值 + minRequests: "10", }); // 当配置加载完成时更新表单数据 useEffect(() => { if (config) { - setFormData(config); + setFormData({ + failureThreshold: String(config.failureThreshold), + successThreshold: String(config.successThreshold), + timeoutSeconds: String(config.timeoutSeconds), + errorRateThreshold: String(Math.round(config.errorRateThreshold * 100)), + minRequests: String(config.minRequests), + }); } }, [config]); const handleSave = async () => { + // 解析数字,空值使用默认值,0 是有效值 + const parseNum = (val: string, defaultVal: number) => { + const n = parseInt(val); + return isNaN(n) ? defaultVal : n; + }; try { - await updateConfig.mutateAsync(formData); + const parsed = { + failureThreshold: parseNum(formData.failureThreshold, 5), + successThreshold: parseNum(formData.successThreshold, 2), + timeoutSeconds: parseNum(formData.timeoutSeconds, 60), + errorRateThreshold: parseNum(formData.errorRateThreshold, 50) / 100, + minRequests: parseNum(formData.minRequests, 10), + }; + await updateConfig.mutateAsync(parsed); toast.success("熔断器配置已保存", { closeButton: true }); } catch (error) { toast.error("保存失败: " + String(error)); @@ -42,7 +61,13 @@ export function CircuitBreakerConfigPanel() { const handleReset = () => { if (config) { - setFormData(config); + setFormData({ + failureThreshold: String(config.failureThreshold), + successThreshold: String(config.successThreshold), + timeoutSeconds: String(config.timeoutSeconds), + errorRateThreshold: String(Math.round(config.errorRateThreshold * 100)), + minRequests: String(config.minRequests), + }); } }; @@ -72,10 +97,7 @@ export function CircuitBreakerConfigPanel() { max="20" value={formData.failureThreshold} onChange={(e) => - setFormData({ - ...formData, - failureThreshold: parseInt(e.target.value) || 5, - }) + setFormData({ ...formData, failureThreshold: e.target.value }) } />
@@ -93,10 +115,7 @@ export function CircuitBreakerConfigPanel() { max="300" value={formData.timeoutSeconds} onChange={(e) => - setFormData({ - ...formData, - timeoutSeconds: parseInt(e.target.value) || 60, - }) + setFormData({ ...formData, timeoutSeconds: e.target.value }) } />
@@ -114,10 +133,7 @@ export function CircuitBreakerConfigPanel() { max="10" value={formData.successThreshold} onChange={(e) => - setFormData({ - ...formData, - successThreshold: parseInt(e.target.value) || 2, - }) + setFormData({ ...formData, successThreshold: e.target.value }) } />
@@ -134,12 +150,9 @@ export function CircuitBreakerConfigPanel() { min="0" max="100" step="5" - value={Math.round(formData.errorRateThreshold * 100)} + value={formData.errorRateThreshold} onChange={(e) => - setFormData({ - ...formData, - errorRateThreshold: (parseInt(e.target.value) || 50) / 100, - }) + setFormData({ ...formData, errorRateThreshold: e.target.value }) } />
@@ -157,10 +170,7 @@ export function CircuitBreakerConfigPanel() { max="100" value={formData.minRequests} onChange={(e) => - setFormData({ - ...formData, - minRequests: parseInt(e.target.value) || 10, - }) + setFormData({ ...formData, minRequests: e.target.value }) } />
diff --git a/src/components/proxy/ProxyPanel.tsx b/src/components/proxy/ProxyPanel.tsx
index be540832..1cf88a14 100644
--- a/src/components/proxy/ProxyPanel.tsx
+++ b/src/components/proxy/ProxyPanel.tsx
@@ -38,15 +38,15 @@ export function ProxyPanel() {
const { data: globalConfig } = useGlobalProxyConfig();
const updateGlobalConfig = useUpdateGlobalProxyConfig();
- // 监听地址/端口的本地状态
+ // 监听地址/端口的本地状态(端口用字符串以支持完全清空)
const [listenAddress, setListenAddress] = useState("127.0.0.1");
- const [listenPort, setListenPort] = useState(15721);
+ const [listenPort, setListenPort] = useState("15721");
// 同步全局配置到本地状态
useEffect(() => {
if (globalConfig) {
setListenAddress(globalConfig.listenAddress);
- setListenPort(globalConfig.listenPort);
+ setListenPort(String(globalConfig.listenPort));
}
}, [globalConfig]);
@@ -102,11 +102,13 @@ export function ProxyPanel() {
const handleSaveBasicConfig = async () => {
if (!globalConfig) return;
+ const port = parseInt(listenPort);
+ const validPort = isNaN(port) || port < 1024 || port > 65535 ? 15721 : port;
try {
await updateGlobalConfig.mutateAsync({
...globalConfig,
listenAddress,
- listenPort,
+ listenPort: validPort,
});
toast.success(
t("proxy.settings.configSaved", { defaultValue: "代理配置已保存" }),
@@ -414,9 +416,7 @@ export function ProxyPanel() {
id="listen-port"
type="number"
value={listenPort}
- onChange={(e) =>
- setListenPort(parseInt(e.target.value) || 15721)
- }
+ onChange={(e) => setListenPort(e.target.value)}
placeholder={t(
"proxy.settings.fields.listenPort.placeholder",
{
diff --git a/src/components/usage/ModelTestConfigPanel.tsx b/src/components/usage/ModelTestConfigPanel.tsx
index 213cf615..95a7e889 100644
--- a/src/components/usage/ModelTestConfigPanel.tsx
+++ b/src/components/usage/ModelTestConfigPanel.tsx
@@ -17,10 +17,11 @@ export function ModelTestConfigPanel() {
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState