import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { settingsApi, type AppType } from "@/lib/api"; import { useSettingsQuery, useSaveSettingsMutation } from "@/lib/query"; import type { Settings } from "@/types"; import { useSettingsForm, type SettingsFormState, } from "./useSettingsForm"; import { useDirectorySettings, type ResolvedDirectories, } from "./useDirectorySettings"; import { useSettingsMetadata } from "./useSettingsMetadata"; type Language = "zh" | "en"; interface SaveResult { requiresRestart: boolean; } export interface UseSettingsResult { settings: SettingsFormState | null; isLoading: boolean; isSaving: boolean; isPortable: boolean; appConfigDir?: string; resolvedDirs: ResolvedDirectories; requiresRestart: boolean; updateSettings: (updates: Partial) => void; updateDirectory: (app: AppType, value?: string) => void; updateAppConfigDir: (value?: string) => void; browseDirectory: (app: AppType) => Promise; browseAppConfigDir: () => Promise; resetDirectory: (app: AppType) => Promise; resetAppConfigDir: () => Promise; saveSettings: () => Promise; resetSettings: () => void; acknowledgeRestart: () => void; } export type { SettingsFormState, ResolvedDirectories }; const sanitizeDir = (value?: string | null): string | undefined => { if (!value) return undefined; const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined; }; /** * useSettings - 组合层 * 负责: * - 组合 useSettingsForm、useDirectorySettings、useSettingsMetadata * - 保存设置逻辑 * - 重置设置逻辑 */ export function useSettings(): UseSettingsResult { const { t } = useTranslation(); const { data } = useSettingsQuery(); const saveMutation = useSaveSettingsMutation(); // 1️⃣ 表单状态管理 const { settings, isLoading: isFormLoading, initialLanguage, updateSettings, resetSettings: resetForm, syncLanguage, } = useSettingsForm(); // 2️⃣ 目录管理 const { appConfigDir, resolvedDirs, isLoading: isDirectoryLoading, initialAppConfigDir, updateDirectory, updateAppConfigDir, browseDirectory, browseAppConfigDir, resetDirectory, resetAppConfigDir, resetAllDirectories, } = useDirectorySettings({ settings, onUpdateSettings: updateSettings, }); // 3️⃣ 元数据管理 const { isPortable, requiresRestart, isLoading: isMetadataLoading, acknowledgeRestart, setRequiresRestart, } = useSettingsMetadata(); // 重置设置 const resetSettings = useCallback(() => { resetForm(data ?? null); syncLanguage(initialLanguage); resetAllDirectories( sanitizeDir(data?.claudeConfigDir), sanitizeDir(data?.codexConfigDir), ); setRequiresRestart(false); }, [ data, initialLanguage, resetForm, syncLanguage, resetAllDirectories, setRequiresRestart, ]); // 保存设置 const saveSettings = useCallback(async (): Promise => { if (!settings) return null; try { const sanitizedAppDir = sanitizeDir(appConfigDir); const sanitizedClaudeDir = sanitizeDir(settings.claudeConfigDir); const sanitizedCodexDir = sanitizeDir(settings.codexConfigDir); const previousAppDir = initialAppConfigDir; const payload: Settings = { ...settings, claudeConfigDir: sanitizedClaudeDir, codexConfigDir: sanitizedCodexDir, language: settings.language, }; await saveMutation.mutateAsync(payload); await settingsApi.setAppConfigDirOverride(sanitizedAppDir ?? null); try { if (payload.enableClaudePluginIntegration) { await settingsApi.applyClaudePluginConfig({ official: false }); } else { await settingsApi.applyClaudePluginConfig({ official: true }); } } catch (error) { console.warn( "[useSettings] Failed to sync Claude plugin config", error, ); toast.error( t("notifications.syncClaudePluginFailed", { defaultValue: "同步 Claude 插件失败", }), ); } try { if (typeof window !== "undefined") { window.localStorage.setItem("language", payload.language as Language); } } catch (error) { console.warn( "[useSettings] Failed to persist language preference", error, ); } const appDirChanged = sanitizedAppDir !== (previousAppDir ?? undefined); setRequiresRestart(appDirChanged); return { requiresRestart: appDirChanged }; } catch (error) { console.error("[useSettings] Failed to save settings", error); throw error; } }, [ appConfigDir, initialAppConfigDir, saveMutation, settings, setRequiresRestart, t, ]); const isLoading = useMemo( () => isFormLoading || isDirectoryLoading || isMetadataLoading, [isFormLoading, isDirectoryLoading, isMetadataLoading], ); return { settings, isLoading, isSaving: saveMutation.isPending, isPortable, appConfigDir, resolvedDirs, requiresRestart, updateSettings, updateDirectory, updateAppConfigDir, browseDirectory, browseAppConfigDir, resetDirectory, resetAppConfigDir, saveSettings, resetSettings, acknowledgeRestart, }; }