import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Save, Loader2, Info } from "lucide-react"; import { toast } from "sonner"; import { useAppProxyConfig, useUpdateAppProxyConfig } from "@/lib/query/proxy"; export interface AutoFailoverConfigPanelProps { appType: string; disabled?: boolean; } export function AutoFailoverConfigPanel({ appType, disabled = false, }: AutoFailoverConfigPanelProps) { const { t } = useTranslation(); 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, }); 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, }); } }, [config]); const handleSave = async () => { if (!config) return; 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, }); toast.success( t("proxy.autoFailover.configSaved", "自动故障转移配置已保存"), { closeButton: true }, ); } catch (e) { toast.error( t("proxy.autoFailover.configSaveFailed", "保存失败") + ": " + String(e), ); } }; const handleReset = () => { 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, }); } }; if (isLoading) { return (
); } const isDisabled = disabled || updateConfig.isPending; return (
{error && ( {String(error)} )} {t( "proxy.autoFailover.info", "当故障转移队列中配置了多个供应商时,系统会在请求失败时按优先级顺序依次尝试。当某个供应商连续失败达到阈值时,熔断器会打开并在一段时间内跳过该供应商。", )} {/* 重试与超时配置 */}

{t("proxy.autoFailover.retrySettings", "重试与超时设置")}

{ const val = parseInt(e.target.value); setFormData({ ...formData, maxRetries: isNaN(val) ? 0 : val, }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.maxRetriesHint", "请求失败时的重试次数(0-10)", )}

{ const val = parseInt(e.target.value); setFormData({ ...formData, circuitFailureThreshold: isNaN(val) ? 1 : Math.max(1, val), }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.failureThresholdHint", "连续失败多少次后打开熔断器(建议: 3-10)", )}

{/* 超时配置 */}

{t("proxy.autoFailover.timeoutSettings", "超时配置")}

{ const val = parseInt(e.target.value); setFormData({ ...formData, streamingFirstByteTimeout: isNaN(val) ? 0 : val, }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.streamingFirstByteHint", "等待首个数据块的最大时间", )}

{ const val = parseInt(e.target.value); setFormData({ ...formData, streamingIdleTimeout: isNaN(val) ? 0 : val, }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.streamingIdleHint", "数据块之间的最大间隔", )}

{ const val = parseInt(e.target.value); setFormData({ ...formData, nonStreamingTimeout: isNaN(val) ? 0 : val, }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.nonStreamingHint", "非流式请求的总超时时间", )}

{/* 熔断器配置 */}

{t("proxy.autoFailover.circuitBreakerSettings", "熔断器配置")}

{ const val = parseInt(e.target.value); setFormData({ ...formData, circuitSuccessThreshold: isNaN(val) ? 1 : Math.max(1, val), }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.successThresholdHint", "半开状态下成功多少次后关闭熔断器", )}

{ const val = parseInt(e.target.value); setFormData({ ...formData, circuitTimeoutSeconds: isNaN(val) ? 10 : Math.max(10, val), }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.timeoutHint", "熔断器打开后,等待多久后尝试恢复(建议: 30-120)", )}

{ const val = parseInt(e.target.value); setFormData({ ...formData, circuitErrorRateThreshold: isNaN(val) ? 0.5 : val / 100, }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.errorRateHint", "错误率超过此值时打开熔断器", )}

{ const val = parseInt(e.target.value); setFormData({ ...formData, circuitMinRequests: isNaN(val) ? 5 : Math.max(5, val), }); }} disabled={isDisabled} />

{t( "proxy.autoFailover.minRequestsHint", "计算错误率前的最小请求数", )}

{/* 操作按钮 */}
); }