import { useCallback, useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import { Loader2, Save, FolderSearch, Activity, Coins, Database, Server, ChevronDown, } from "lucide-react"; import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { toast } from "sonner"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { settingsApi } from "@/lib/api"; import { LanguageSettings } from "@/components/settings/LanguageSettings"; import { ThemeSettings } from "@/components/settings/ThemeSettings"; import { WindowSettings } from "@/components/settings/WindowSettings"; import { DirectorySettings } from "@/components/settings/DirectorySettings"; import { ImportExportSection } from "@/components/settings/ImportExportSection"; import { AboutSection } from "@/components/settings/AboutSection"; import { ProxyPanel } from "@/components/proxy"; import { PricingConfigPanel } from "@/components/usage/PricingConfigPanel"; import { ModelTestConfigPanel } from "@/components/usage/ModelTestConfigPanel"; import { AutoFailoverConfigPanel } from "@/components/proxy/AutoFailoverConfigPanel"; import { FailoverQueueManager } from "@/components/proxy/FailoverQueueManager"; import { UsageDashboard } from "@/components/usage/UsageDashboard"; import { useSettings } from "@/hooks/useSettings"; import { useImportExport } from "@/hooks/useImportExport"; import { useTranslation } from "react-i18next"; import type { SettingsFormState } from "@/hooks/useSettings"; import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; import { useProxyStatus } from "@/hooks/useProxyStatus"; interface SettingsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onImportSuccess?: () => void | Promise; defaultTab?: string; } export function SettingsPage({ open, onOpenChange, onImportSuccess, defaultTab = "general", }: SettingsDialogProps) { const { t } = useTranslation(); const { settings, isLoading, isSaving, isPortable, appConfigDir, resolvedDirs, updateSettings, updateDirectory, updateAppConfigDir, browseDirectory, browseAppConfigDir, resetDirectory, resetAppConfigDir, saveSettings, autoSaveSettings, requiresRestart, acknowledgeRestart, } = useSettings(); const { selectedFile, status: importStatus, errorMessage, backupId, isImporting, selectImportFile, importConfig, exportConfig, clearSelection, resetStatus, } = useImportExport({ onImportSuccess }); const [activeTab, setActiveTab] = useState("general"); const [showRestartPrompt, setShowRestartPrompt] = useState(false); useEffect(() => { if (open) { setActiveTab(defaultTab); resetStatus(); } }, [open, resetStatus, defaultTab]); useEffect(() => { if (requiresRestart) { setShowRestartPrompt(true); } }, [requiresRestart]); const closeAfterSave = useCallback(() => { // 保存成功后关闭:不再重置语言,避免需要“保存两次”才生效 acknowledgeRestart(); clearSelection(); resetStatus(); onOpenChange(false); }, [acknowledgeRestart, clearSelection, onOpenChange, resetStatus]); const handleSave = useCallback(async () => { try { const result = await saveSettings(undefined, { silent: false }); if (!result) return; if (result.requiresRestart) { setShowRestartPrompt(true); return; } closeAfterSave(); } catch (error) { console.error("[SettingsPage] Failed to save settings", error); } }, [closeAfterSave, saveSettings]); const handleRestartLater = useCallback(() => { setShowRestartPrompt(false); closeAfterSave(); }, [closeAfterSave]); const handleRestartNow = useCallback(async () => { setShowRestartPrompt(false); if (import.meta.env.DEV) { toast.success(t("settings.devModeRestartHint"), { closeButton: true }); closeAfterSave(); return; } try { await settingsApi.restart(); } catch (error) { console.error("[SettingsPage] Failed to restart app", error); toast.error(t("settings.restartFailed")); } finally { closeAfterSave(); } }, [closeAfterSave, t]); // 通用设置即时保存(无需手动点击) // 使用 autoSaveSettings 避免误触发系统 API(开机自启、Claude 插件等) const handleAutoSave = useCallback( async (updates: Partial) => { if (!settings) return; updateSettings(updates); try { await autoSaveSettings(updates); } catch (error) { console.error("[SettingsPage] Failed to autosave settings", error); toast.error( t("settings.saveFailedGeneric", { defaultValue: "保存失败,请重试", }), ); } }, [autoSaveSettings, settings, t, updateSettings], ); const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]); const { isRunning, startProxyServer, stopWithRestore, isPending: isProxyPending, } = useProxyStatus(); const handleToggleProxy = async (checked: boolean) => { try { if (!checked) { await stopWithRestore(); } else { await startProxyServer(); } } catch (error) { console.error("Toggle proxy failed:", error); } }; return (
{isBusy ? (
) : ( {t("settings.tabGeneral")} {t("settings.tabAdvanced")} {t("usage.title")} {t("common.about")}
{settings ? ( handleAutoSave({ language: lang })} /> ) : null} {settings ? (

{t("settings.advanced.configDir.title")}

{t("settings.advanced.configDir.description")}

{t("settings.advanced.proxy.title")}

{t("settings.advanced.proxy.description")}

{isRunning ? t("settings.advanced.proxy.running") : t("settings.advanced.proxy.stopped")}

{t("settings.advanced.failover.title")}

{t("settings.advanced.failover.description")}

{/* 代理未运行时的提示 */} {!isRunning && (

{t("proxy.failover.proxyRequired", { defaultValue: "需要先启动代理服务才能配置故障转移", })}

)} {/* 故障转移设置 - 按应用分组 */} Claude Codex Gemini

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

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

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

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

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

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

{t("settings.advanced.modelTest.title")}

{t("settings.advanced.modelTest.description")}

{t("settings.advanced.pricing.title")}

{t("settings.advanced.pricing.description")}

{t("settings.advanced.data.title")}

{t("settings.advanced.data.description")}

) : null}
)} !open && handleRestartLater()} > {t("settings.restartRequired")}

{t("settings.restartRequiredMessage")}

); }