import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { motion } from "framer-motion"; import { Loader2, Save, FolderSearch, Database, Cloud, ScrollText, HardDriveDownload, FlaskConical, } from "lucide-react"; 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 { AppVisibilitySettings } from "@/components/settings/AppVisibilitySettings"; import { SkillStorageLocationSettings } from "@/components/settings/SkillStorageLocationSettings"; import { SkillSyncMethodSettings } from "@/components/settings/SkillSyncMethodSettings"; import { TerminalSettings } from "@/components/settings/TerminalSettings"; import { DirectorySettings } from "@/components/settings/DirectorySettings"; import { ImportExportSection } from "@/components/settings/ImportExportSection"; import { BackupListSection } from "@/components/settings/BackupListSection"; import { WebdavSyncSection } from "@/components/settings/WebdavSyncSection"; import { AboutSection } from "@/components/settings/AboutSection"; import { ProxyTabContent } from "@/components/settings/ProxyTabContent"; import { ConnectivityCheckConfigPanel } from "@/components/usage/ConnectivityCheckConfigPanel"; import { UsageDashboard } from "@/components/usage/UsageDashboard"; import { LogConfigPanel } from "@/components/settings/LogConfigPanel"; import { AuthCenterPanel } from "@/components/settings/AuthCenterPanel"; import { CodexAuthSettings } from "@/components/settings/CodexAuthSettings"; import { useInstalledSkills } from "@/hooks/useSkills"; import { useSettings } from "@/hooks/useSettings"; import { useImportExport } from "@/hooks/useImportExport"; import { useTranslation } from "react-i18next"; import type { SettingsFormState } from "@/hooks/useSettings"; 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 { data: installedSkills } = useInstalledSkills(); const [activeTab, setActiveTab] = useState("general"); const [showRestartPrompt, setShowRestartPrompt] = useState(false); const tabScrollContainerRef = useRef(null); useEffect(() => { if (open) { setActiveTab(defaultTab); resetStatus(); } }, [open, resetStatus, defaultTab]); useEffect(() => { if (requiresRestart) { setShowRestartPrompt(true); } }, [requiresRestart]); useLayoutEffect(() => { if (tabScrollContainerRef.current) { tabScrollContainerRef.current.scrollTop = 0; } }, [activeTab]); 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): Promise => { if (!settings) return false; // 乐观更新前捕获旧值:autoSaveSettings 发送的是全量表单状态,后端按 // diff 触发副作用(如统一会话开关的 live 重写与历史迁移)。保存失败 // 不回滚的话,失败的变更会滞留在表单里,被之后任意一次无关保存原样 // 重放,绕过确认弹窗。 const previousValues = Object.fromEntries( Object.keys(updates).map((key) => [ key, settings[key as keyof SettingsFormState], ]), ) as Partial; updateSettings(updates); try { await autoSaveSettings(updates); return true; } catch (error) { console.error("[SettingsPage] Failed to autosave settings", error); updateSettings(previousValues); toast.error( t("settings.saveFailedGeneric", { defaultValue: "保存失败,请重试", }), ); return false; } }, [autoSaveSettings, settings, t, updateSettings], ); const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]); return (
{isBusy ? (
) : ( {t("settings.tabGeneral")} {t("settings.tabProxy")} {t("settings.tabAuth", { defaultValue: "认证" })} {t("settings.tabAdvanced")} {t("usage.title")} {t("common.about")}
{settings ? ( handleAutoSave({ language: lang })} /> updateSettings({ skillStorageLocation: location }) } /> handleAutoSave({ skillSyncMethod: method }) } /> handleAutoSave({ preferredTerminal: terminal }) } /> ) : null} {settings ? ( ) : null} {settings ? (

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

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

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

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

{t("settings.advanced.backup.title", { defaultValue: "Backup & Restore", })}

{t("settings.advanced.backup.description", { defaultValue: "Manage automatic backups, view and restore database snapshots", })}

handleAutoSave(updates) } />

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

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

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

{t( "settings.advanced.connectivityCheck.description", )}

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

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

) : null}
handleAutoSave({ usageDashboardRefreshIntervalMs }) } />
{activeTab === "advanced" && settings && (
)}
)} !open && handleRestartLater()} > {t("settings.restartRequired")}

{t("settings.restartRequiredMessage")}

); }