import { useState, useEffect } from "react"; import { Activity, Clock, TrendingUp, Server, ListOrdered, Save, Loader2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { useProxyStatus } from "@/hooks/useProxyStatus"; import { toast } from "sonner"; import { useFailoverQueue } from "@/lib/query/failover"; import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge"; import { useProviderHealth } from "@/lib/query/failover"; import { useProxyTakeoverStatus, useSetProxyTakeoverForApp, useGlobalProxyConfig, useUpdateGlobalProxyConfig, } from "@/lib/query/proxy"; import type { ProxyStatus } from "@/types/proxy"; import { useTranslation } from "react-i18next"; export function ProxyPanel() { const { t } = useTranslation(); const { status, isRunning } = useProxyStatus(); // 获取应用接管状态 const { data: takeoverStatus } = useProxyTakeoverStatus(); const setTakeoverForApp = useSetProxyTakeoverForApp(); // 获取全局代理配置 const { data: globalConfig } = useGlobalProxyConfig(); const updateGlobalConfig = useUpdateGlobalProxyConfig(); // 监听地址/端口的本地状态(端口用字符串以支持完全清空) const [listenAddress, setListenAddress] = useState("127.0.0.1"); const [listenPort, setListenPort] = useState("15721"); // 同步全局配置到本地状态 useEffect(() => { if (globalConfig) { setListenAddress(globalConfig.listenAddress); setListenPort(String(globalConfig.listenPort)); } }, [globalConfig]); // 获取所有三个应用类型的故障转移队列(不包含当前供应商) // 当前供应商始终优先,队列仅用于失败后的备用顺序 const { data: claudeQueue = [] } = useFailoverQueue("claude"); const { data: codexQueue = [] } = useFailoverQueue("codex"); const { data: geminiQueue = [] } = useFailoverQueue("gemini"); const handleTakeoverChange = async (appType: string, enabled: boolean) => { try { await setTakeoverForApp.mutateAsync({ appType, enabled }); toast.success( enabled ? t("proxy.takeover.enabled", { app: appType, defaultValue: `${appType} 接管已启用`, }) : t("proxy.takeover.disabled", { app: appType, defaultValue: `${appType} 接管已关闭`, }), { closeButton: true }, ); } catch (error) { toast.error( t("proxy.takeover.failed", { defaultValue: "切换接管状态失败", }), ); } }; const handleLoggingChange = async (enabled: boolean) => { if (!globalConfig) return; try { await updateGlobalConfig.mutateAsync({ ...globalConfig, enableLogging: enabled, }); toast.success( enabled ? t("proxy.logging.enabled", { defaultValue: "日志记录已启用" }) : t("proxy.logging.disabled", { defaultValue: "日志记录已关闭" }), { closeButton: true }, ); } catch (error) { toast.error( t("proxy.logging.failed", { defaultValue: "切换日志状态失败" }), ); } }; const handleSaveBasicConfig = async () => { if (!globalConfig) return; // 校验地址格式(简单的 IP 地址或 localhost 校验) const addressTrimmed = listenAddress.trim(); const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; // 规范化 localhost 为 127.0.0.1 const normalizedAddress = addressTrimmed === "localhost" ? "127.0.0.1" : addressTrimmed; const isValidAddress = normalizedAddress === "0.0.0.0" || (ipv4Regex.test(normalizedAddress) && normalizedAddress.split(".").every((n) => { const num = parseInt(n); return num >= 0 && num <= 255; })); if (!isValidAddress) { toast.error( t("proxy.settings.invalidAddress", { defaultValue: "地址无效,请输入有效的 IP 地址(如 127.0.0.1)或 localhost", }), ); return; } // 严格校验端口:必须是纯数字 const portTrimmed = listenPort.trim(); if (!/^\d+$/.test(portTrimmed)) { toast.error( t("proxy.settings.invalidPort", { defaultValue: "端口无效,请输入 1024-65535 之间的数字", }), ); return; } const port = parseInt(portTrimmed); if (isNaN(port) || port < 1024 || port > 65535) { toast.error( t("proxy.settings.invalidPort", { defaultValue: "端口无效,请输入 1024-65535 之间的数字", }), ); return; } try { await updateGlobalConfig.mutateAsync({ ...globalConfig, listenAddress: normalizedAddress, listenPort: port, }); // 同步更新本地状态为规范化后的值 setListenAddress(normalizedAddress); toast.success( t("proxy.settings.configSaved", { defaultValue: "代理配置已保存" }), { closeButton: true }, ); } catch (error) { toast.error( t("proxy.settings.configSaveFailed", { defaultValue: "保存配置失败" }), ); } }; const formatUptime = (seconds: number): string => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}h ${minutes}m ${secs}s`; } else if (minutes > 0) { return `${minutes}m ${secs}s`; } else { return `${secs}s`; } }; // 格式化地址用于 URL(IPv6 需要方括号) const formatAddressForUrl = (address: string, port: number): string => { const isIPv6 = address.includes(":"); const host = isIPv6 ? `[${address}]` : address; return `http://${host}:${port}`; }; return ( <>
{isRunning && status ? (

{t("proxy.panel.serviceAddress", { defaultValue: "服务地址", })}

{formatAddressForUrl(status.address, status.port)}

{t("proxy.settings.restartRequired", { defaultValue: "修改监听地址/端口需要先停止代理服务", })}

{t("provider.inUse")}

{status.active_targets && status.active_targets.length > 0 ? (
{status.active_targets.map((target) => (
{target.app_type} {target.provider_name}
))}
) : status.current_provider ? (

{t("proxy.panel.currentProvider", { defaultValue: "当前 Provider:", })}{" "} {status.current_provider}

) : (

{t("proxy.panel.waitingFirstRequest", { defaultValue: "当前 Provider:等待首次请求…", })}

)}
{/* 应用接管开关 */}

{t("proxyConfig.appTakeover", { defaultValue: "应用接管", })}

{(["claude", "codex", "gemini"] as const).map((appType) => { const isEnabled = takeoverStatus?.[ appType as keyof typeof takeoverStatus ] ?? false; return (
{appType} handleTakeoverChange(appType, checked) } disabled={setTakeoverForApp.isPending} />
); })}
{/* 日志记录开关 */}

{t("proxy.settings.fields.enableLogging.description", { defaultValue: "记录所有代理请求,便于排查问题", })}

{/* 供应商队列 - 按应用类型分组展示 */} {(claudeQueue.length > 0 || codexQueue.length > 0 || geminiQueue.length > 0) && (

{t("proxy.failoverQueue.title")}

{/* Claude 队列 */} {claudeQueue.length > 0 && ( ({ id: item.providerId, name: item.providerName, }))} status={status} /> )} {/* Codex 队列 */} {codexQueue.length > 0 && ( ({ id: item.providerId, name: item.providerName, }))} status={status} /> )} {/* Gemini 队列 */} {geminiQueue.length > 0 && ( ({ id: item.providerId, name: item.providerName, }))} status={status} /> )}
)}
} label={t("proxy.panel.stats.activeConnections", { defaultValue: "活跃连接", })} value={status.active_connections} /> } label={t("proxy.panel.stats.totalRequests", { defaultValue: "总请求数", })} value={status.total_requests} /> } label={t("proxy.panel.stats.successRate", { defaultValue: "成功率", })} value={`${status.success_rate.toFixed(1)}%`} variant={status.success_rate > 90 ? "success" : "warning"} /> } label={t("proxy.panel.stats.uptime", { defaultValue: "运行时间", })} value={formatUptime(status.uptime_seconds)} />
) : (
{/* 空白区域避免冲突 */}
{/* 基础设置 - 监听地址/端口 */}

{t("proxy.settings.basic.title", { defaultValue: "基础设置", })}

{t("proxy.settings.basic.description", { defaultValue: "配置代理服务监听的地址与端口。", })}

setListenAddress(e.target.value)} placeholder={t( "proxy.settings.fields.listenAddress.placeholder", { defaultValue: "127.0.0.1", }, )} />

{t("proxy.settings.fields.listenAddress.description", { defaultValue: "代理服务器监听的 IP 地址(推荐 127.0.0.1)", })}

setListenPort(e.target.value)} placeholder={t( "proxy.settings.fields.listenPort.placeholder", { defaultValue: "15721", }, )} />

{t("proxy.settings.fields.listenPort.description", { defaultValue: "代理服务器监听的端口号(1024 ~ 65535)", })}

{/* 代理服务已停止提示 */}

{t("proxy.panel.stoppedTitle", { defaultValue: "代理服务已停止", })}

{t("proxy.panel.stoppedDescription", { defaultValue: "使用右上角开关即可启动服务", })}

)}
); } interface StatCardProps { icon: React.ReactNode; label: string; value: string | number; variant?: "default" | "success" | "warning"; } function StatCard({ icon, label, value, variant = "default" }: StatCardProps) { const variantStyles = { default: "", success: "border-green-500/40 bg-green-500/5", warning: "border-yellow-500/40 bg-yellow-500/5", }; return (
{icon} {label}

{value}

); } interface ProviderQueueGroupProps { appType: string; appLabel: string; targets: Array<{ id: string; name: string; }>; status: ProxyStatus; } function ProviderQueueGroup({ appType, appLabel, targets, status, }: ProviderQueueGroupProps) { // 查找该应用类型的当前活跃目标 const activeTarget = status.active_targets?.find( (t) => t.app_type === appType, ); return (
{/* 应用类型标题 */}
{appLabel}
{/* 供应商列表 */}
{targets.map((target, index) => ( ))}
); } interface ProviderQueueItemProps { provider: { id: string; name: string; }; priority: number; appType: string; isCurrent: boolean; } function ProviderQueueItem({ provider, priority, appType, isCurrent, }: ProviderQueueItemProps) { const { t } = useTranslation(); const { data: health } = useProviderHealth(provider.id, appType); return (
{priority} {provider.name} {isCurrent && ( {t("provider.inUse")} )}
{/* 健康徽章 */}
); }