From 298d19af89b0aee3acde3bdfa91824bd5eb8ff35 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Wed, 24 Dec 2025 23:47:07 +0800 Subject: [PATCH] refactor(ui): redesign proxy panel with inline config controls Replace ProxySettingsDialog with inline controls in ProxyPanel. Add per-app takeover switches and global address/port settings. Simplify AutoFailoverConfigPanel by removing timeout settings. --- .../proxy/AutoFailoverConfigPanel.tsx | 340 ++++++----- src/components/proxy/ProxyPanel.tsx | 307 ++++++++-- src/components/proxy/ProxySettingsDialog.tsx | 553 ------------------ src/components/proxy/index.ts | 1 - 4 files changed, 469 insertions(+), 732 deletions(-) delete mode 100644 src/components/proxy/ProxySettingsDialog.tsx diff --git a/src/components/proxy/AutoFailoverConfigPanel.tsx b/src/components/proxy/AutoFailoverConfigPanel.tsx index 182dbce5c..9daeaed0d 100644 --- a/src/components/proxy/AutoFailoverConfigPanel.tsx +++ b/src/components/proxy/AutoFailoverConfigPanel.tsx @@ -6,51 +6,67 @@ 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 { - useCircuitBreakerConfig, - useUpdateCircuitBreakerConfig, -} from "@/lib/query/failover"; +import { useAppProxyConfig, useUpdateAppProxyConfig } from "@/lib/query/proxy"; export interface AutoFailoverConfigPanelProps { - enabled?: boolean; - onEnabledChange?: (enabled: boolean) => void; + appType: string; + disabled?: boolean; } export function AutoFailoverConfigPanel({ - enabled = true, - onEnabledChange: _onEnabledChange, -}: AutoFailoverConfigPanelProps = {}) { - // Note: onEnabledChange is currently unused but kept in the interface - // for potential future use by parent components - void _onEnabledChange; + appType, + disabled = false, +}: AutoFailoverConfigPanelProps) { const { t } = useTranslation(); - const { data: config, isLoading, error } = useCircuitBreakerConfig(); - const updateConfig = useUpdateCircuitBreakerConfig(); + const { data: config, isLoading, error } = useAppProxyConfig(appType); + const updateConfig = useUpdateAppProxyConfig(); const [formData, setFormData] = useState({ - failureThreshold: 5, - successThreshold: 2, - timeoutSeconds: 60, - errorRateThreshold: 0.5, - minRequests: 10, + 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({ - ...config, + 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({ - failureThreshold: formData.failureThreshold, - successThreshold: formData.successThreshold, - timeoutSeconds: formData.timeoutSeconds, - errorRateThreshold: formData.errorRateThreshold, - minRequests: formData.minRequests, + 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", "自动故障转移配置已保存"), @@ -66,7 +82,16 @@ export function AutoFailoverConfigPanel({ const handleReset = () => { if (config) { setFormData({ - ...config, + 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, }); } }; @@ -79,16 +104,10 @@ export function AutoFailoverConfigPanel({ ); } + const isDisabled = disabled || updateConfig.isPending; + return (
- {/* Header Switch moved to parent accordion logic or kept here absolutely positioned if styling permits. - Since we need it in the accordion header, and this component is inside the content, we can use a portal or - absolute positioning trick similar to ProxyPanel, OR cleaner, just duplicate the switch logic in SettingsPage - and pass it down. But for now, let's use the absolute positioning trick to "lift" it visually. - Better yet, let's just render the content directly without the wrapping Card header/collapse logic - since the user requested "click to expand is detailed info, no need to fold again" (implying the accordion handles folding). - */} -
{error && ( @@ -114,22 +133,48 @@ export function AutoFailoverConfigPanel({
-
+ +
+ + + setFormData({ + ...formData, + circuitFailureThreshold: parseInt(e.target.value) || 5, + }) + } + disabled={isDisabled} />

{t( @@ -138,59 +183,123 @@ export function AutoFailoverConfigPanel({ )}

+
+
+ {/* 超时配置 */} +
+

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

+ +
-
+ +
+ + + setFormData({ + ...formData, + streamingIdleTimeout: parseInt(e.target.value) || 60, + }) + } + disabled={isDisabled} + /> +

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

+
+ +
+ + + setFormData({ + ...formData, + nonStreamingTimeout: parseInt(e.target.value) || 300, + }) + } + disabled={isDisabled} + /> +

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

- {/* 熔断器高级配置 */} + {/* 熔断器配置 */}

- {t("proxy.autoFailover.circuitBreakerSettings", "熔断器高级设置")} + {t("proxy.autoFailover.circuitBreakerSettings", "熔断器配置")}

-
+
-
-
+ +
+ setFormData({ ...formData, - errorRateThreshold: (parseInt(e.target.value) || 50) / 100, + circuitErrorRateThreshold: + (parseInt(e.target.value) || 50) / 100, }) } - disabled={!enabled} + disabled={isDisabled} />

{t( @@ -228,22 +364,22 @@ export function AutoFailoverConfigPanel({

-
); diff --git a/src/components/proxy/ProxyPanel.tsx b/src/components/proxy/ProxyPanel.tsx index 66a1c829e..537b8ff68 100644 --- a/src/components/proxy/ProxyPanel.tsx +++ b/src/components/proxy/ProxyPanel.tsx @@ -1,26 +1,54 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Activity, Clock, TrendingUp, Server, ListOrdered, - Settings, + 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 { ProxySettingsDialog } from "./ProxySettingsDialog"; 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 [showSettings, setShowSettings] = useState(false); + + // 获取应用接管状态 + 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(5000); + + // 同步全局配置到本地状态 + useEffect(() => { + if (globalConfig) { + setListenAddress(globalConfig.listenAddress); + setListenPort(globalConfig.listenPort); + } + }, [globalConfig]); // 获取所有三个应用类型的故障转移队列(不包含当前供应商) // 当前供应商始终优先,队列仅用于失败后的备用顺序 @@ -28,6 +56,69 @@ export function ProxyPanel() { 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; + try { + await updateGlobalConfig.mutateAsync({ + ...globalConfig, + listenAddress, + listenPort, + }); + 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); @@ -49,22 +140,11 @@ export function ProxyPanel() {
-
-

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

- -
+

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

http://{status.address}:{status.port} @@ -87,6 +167,11 @@ export function ProxyPanel() { {t("common.copy")}
+

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

@@ -130,6 +215,63 @@ export function ProxyPanel() { )}
+ {/* 应用接管开关 */} +
+

+ {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 || @@ -217,36 +359,109 @@ export function ProxyPanel() {
) : ( -
-
- +
+ {/* 空白区域避免冲突 */} +
+ + {/* 基础设置 - 监听地址/端口 */} +
+
+

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

+

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

+
+ +
+
+ + setListenAddress(e.target.value)} + placeholder="127.0.0.1" + /> +

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

+
+ +
+ + + setListenPort(parseInt(e.target.value) || 5000) + } + placeholder="5000" + /> +

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

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

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

+

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

-

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

-

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

-
)} - - ); } diff --git a/src/components/proxy/ProxySettingsDialog.tsx b/src/components/proxy/ProxySettingsDialog.tsx deleted file mode 100644 index b697312c0..000000000 --- a/src/components/proxy/ProxySettingsDialog.tsx +++ /dev/null @@ -1,553 +0,0 @@ -/** - * 代理服务设置对话框 - */ - -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormDescription, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Switch } from "@/components/ui/switch"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { useProxyConfig } from "@/hooks/useProxyConfig"; -import { useEffect, useMemo } from "react"; -import { AlertCircle } from "lucide-react"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { FullScreenPanel } from "@/components/common/FullScreenPanel"; -import { useTranslation } from "react-i18next"; -import type { TFunction } from "i18next"; -import type { ProxyConfig } from "@/types/proxy"; - -// 表单数据类型(仅包含可编辑字段) -type ProxyConfigForm = Pick< - ProxyConfig, - | "listen_address" - | "listen_port" - | "max_retries" - | "request_timeout" - | "enable_logging" - | "streaming_first_byte_timeout" - | "streaming_idle_timeout" - | "non_streaming_timeout" ->; - -const createProxyConfigSchema = (t: TFunction) => { - // 流式首字节超时:0 或 1-180 秒 - const streamingFirstByteSchema = z - .number() - .min(0) - .refine((val) => val === 0 || (val >= 1 && val <= 180), { - message: t("proxy.settings.validation.streamingFirstByteRange", { - defaultValue: "请输入 0 或 1-180 之间的数值", - }), - }); - - // 流式静默期超时:0 或 60-600 秒 - const streamingIdleSchema = z - .number() - .min(0) - .refine((val) => val === 0 || (val >= 60 && val <= 600), { - message: t("proxy.settings.validation.streamingIdleRange", { - defaultValue: "请输入 0 或 60-600 之间的数值", - }), - }); - - // 非流式总超时:0 或 60-1800 秒 - const nonStreamingSchema = z - .number() - .min(0) - .refine((val) => val === 0 || (val >= 60 && val <= 1800), { - message: t("proxy.settings.validation.nonStreamingRange", { - defaultValue: "请输入 0 或 60-1800 之间的数值", - }), - }); - - // 旧版请求超时(兼容) - const requestTimeoutSchema = z.number().min(0); - - return z.object({ - listen_address: z.string().regex( - /^(\d{1,3}\.){3}\d{1,3}$/, - t("proxy.settings.validation.addressInvalid", { - defaultValue: "请输入有效的IP地址", - }), - ), - listen_port: z - .number() - .min( - 1024, - t("proxy.settings.validation.portMin", { - defaultValue: "端口必须大于1024", - }), - ) - .max( - 65535, - t("proxy.settings.validation.portMax", { - defaultValue: "端口必须小于65535", - }), - ), - max_retries: z - .number() - .min( - 0, - t("proxy.settings.validation.retryMin", { - defaultValue: "重试次数不能为负", - }), - ) - .max( - 10, - t("proxy.settings.validation.retryMax", { - defaultValue: "重试次数不能超过10", - }), - ), - request_timeout: requestTimeoutSchema, - enable_logging: z.boolean(), - streaming_first_byte_timeout: streamingFirstByteSchema, - streaming_idle_timeout: streamingIdleSchema, - non_streaming_timeout: nonStreamingSchema, - }); -}; - -interface ProxySettingsDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export function ProxySettingsDialog({ - open, - onOpenChange, -}: ProxySettingsDialogProps) { - const { config, isLoading, updateConfig, isUpdating } = useProxyConfig(); - const { t } = useTranslation(); - const schema = useMemo(() => createProxyConfigSchema(t), [t]); - - const closePanel = () => onOpenChange(false); - - const form = useForm({ - resolver: zodResolver(schema), - defaultValues: { - listen_address: "127.0.0.1", - listen_port: 5000, - max_retries: 3, - request_timeout: 300, - enable_logging: true, - streaming_first_byte_timeout: 30, - streaming_idle_timeout: 60, - non_streaming_timeout: 600, - }, - }); - - // 当配置加载完成后更新表单 - useEffect(() => { - if (config) { - form.reset({ - listen_address: config.listen_address, - listen_port: config.listen_port, - max_retries: config.max_retries, - request_timeout: config.request_timeout, - enable_logging: config.enable_logging, - streaming_first_byte_timeout: config.streaming_first_byte_timeout ?? 30, - streaming_idle_timeout: config.streaming_idle_timeout ?? 60, - non_streaming_timeout: config.non_streaming_timeout ?? 300, - }); - } - }, [config, form]); - - const onSubmit = async (data: ProxyConfigForm) => { - try { - await updateConfig(data); - closePanel(); - } catch (error) { - console.error("Save config failed:", error); - } - }; - - const formId = "proxy-settings-form"; - - return ( - - - - - } - > -
-

- {t("proxy.settings.description", { - defaultValue: - "配置本地代理服务器的监听地址、端口和运行参数,保存后立即生效。", - })} -

- - - - {t("proxy.settings.alert.autoApply", { - defaultValue: - "保存后将自动同步到正在运行的代理服务,无需手动重启。", - })} - - - -
- -
-
-

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

-

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

-
- -
- ( - - - {t("proxy.settings.fields.listenAddress.label", { - defaultValue: "监听地址", - })} - - - - - - {t("proxy.settings.fields.listenAddress.description", { - defaultValue: - "代理服务器监听的 IP 地址(推荐 127.0.0.1)", - })} - - - - )} - /> - - ( - - - {t("proxy.settings.fields.listenPort.label", { - defaultValue: "监听端口", - })} - - - - field.onChange(parseInt(e.target.value, 10) || 0) - } - placeholder={t( - "proxy.settings.fields.listenPort.placeholder", - { defaultValue: "5000" }, - )} - disabled={isLoading} - /> - - - {t("proxy.settings.fields.listenPort.description", { - defaultValue: - "代理服务器监听的端口号(1024 ~ 65535)", - })} - - - - )} - /> -
-
- -
-
-

- {t("proxy.settings.advanced.title", { - defaultValue: "高级参数", - })} -

-

- {t("proxy.settings.advanced.description", { - defaultValue: "控制请求的稳定性和日志记录。", - })} -

-
- -
- ( - - - {t("proxy.settings.fields.maxRetries.label", { - defaultValue: "最大重试次数", - })} - - - - field.onChange(parseInt(e.target.value, 10) || 0) - } - placeholder={t( - "proxy.settings.fields.maxRetries.placeholder", - { defaultValue: "3" }, - )} - disabled={isLoading} - /> - - - {t("proxy.settings.fields.maxRetries.description", { - defaultValue: "请求失败时的重试次数(0 ~ 10)", - })} - - - - )} - /> - - ( - - - {t("proxy.settings.fields.requestTimeout.label", { - defaultValue: "请求超时(秒)", - })} - - - - field.onChange(parseInt(e.target.value, 10) || 0) - } - placeholder={t( - "proxy.settings.fields.requestTimeout.placeholder", - { defaultValue: "0(不限)或 300" }, - )} - disabled={isLoading} - /> - - - {t("proxy.settings.fields.requestTimeout.description", { - defaultValue: - "单个请求的最大等待时间(0 表示不限制,或设置 10 ~ 600 秒)", - })} - - - - )} - /> -
- - ( - -
- - {t("proxy.settings.fields.enableLogging.label", { - defaultValue: "启用日志记录", - })} - - - {t("proxy.settings.fields.enableLogging.description", { - defaultValue: "记录所有代理请求,便于排查问题", - })} - -
- - - -
- )} - /> -
- -
-
-

- {t("proxy.settings.timeout.title", { - defaultValue: "超时设置", - })} -

-

- {t("proxy.settings.timeout.description", { - defaultValue: "配置流式和非流式请求的超时时间。", - })} -

-
- -
- ( - - - {t( - "proxy.settings.fields.streamingFirstByteTimeout.label", - { - defaultValue: "流式首字超时(秒)", - }, - )} - - - - field.onChange(parseInt(e.target.value, 10) || 0) - } - placeholder="30" - disabled={isLoading} - /> - - - {t( - "proxy.settings.fields.streamingFirstByteTimeout.description", - { - defaultValue: "等待首个数据块的最大时间", - }, - )} - - - - )} - /> - - ( - - - {t("proxy.settings.fields.streamingIdleTimeout.label", { - defaultValue: "流式静默超时(秒)", - })} - - - - field.onChange(parseInt(e.target.value, 10) || 0) - } - placeholder="60" - disabled={isLoading} - /> - - - {t( - "proxy.settings.fields.streamingIdleTimeout.description", - { - defaultValue: "数据块之间的最大间隔", - }, - )} - - - - )} - /> - - ( - - - {t("proxy.settings.fields.nonStreamingTimeout.label", { - defaultValue: "非流式超时(秒)", - })} - - - - field.onChange(parseInt(e.target.value, 10) || 0) - } - placeholder="300" - disabled={isLoading} - /> - - - {t( - "proxy.settings.fields.nonStreamingTimeout.description", - { - defaultValue: "非流式请求的总超时时间", - }, - )} - - - - )} - /> -
-
-
- -
-
- ); -} diff --git a/src/components/proxy/index.ts b/src/components/proxy/index.ts index b645228d8..0abc65408 100644 --- a/src/components/proxy/index.ts +++ b/src/components/proxy/index.ts @@ -3,4 +3,3 @@ */ export { ProxyPanel } from "./ProxyPanel"; -export { ProxySettingsDialog } from "./ProxySettingsDialog";