import { useState, useEffect } from "react"; import { X, RefreshCw, FolderOpen, Download, ExternalLink, Check, Undo2, FolderSearch, } from "lucide-react"; import { getVersion } from "@tauri-apps/api/app"; import { homeDir, join } from "@tauri-apps/api/path"; import "../lib/tauri-api"; import { relaunchApp } from "../lib/updater"; import { useUpdate } from "../contexts/UpdateContext"; import type { Settings } from "../types"; import type { AppType } from "../lib/tauri-api"; import { isLinux } from "../lib/platform"; interface SettingsModalProps { onClose: () => void; } export default function SettingsModal({ onClose }: SettingsModalProps) { const [settings, setSettings] = useState({ showInTray: true, minimizeToTrayOnClose: true, claudeConfigDir: undefined, codexConfigDir: undefined, }); const [configPath, setConfigPath] = useState(""); const [version, setVersion] = useState(""); const [isCheckingUpdate, setIsCheckingUpdate] = useState(false); const [isDownloading, setIsDownloading] = useState(false); const [showUpToDate, setShowUpToDate] = useState(false); const [resolvedClaudeDir, setResolvedClaudeDir] = useState(""); const [resolvedCodexDir, setResolvedCodexDir] = useState(""); const [isPortable, setIsPortable] = useState(false); const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } = useUpdate(); useEffect(() => { loadSettings(); loadConfigPath(); loadVersion(); loadResolvedDirs(); loadPortableFlag(); }, []); const loadVersion = async () => { try { const appVersion = await getVersion(); setVersion(appVersion); } catch (error) { console.error("获取版本信息失败:", error); // 失败时不硬编码版本号,显示为未知 setVersion("未知"); } }; const loadSettings = async () => { try { const loadedSettings = await window.api.getSettings(); const showInTray = (loadedSettings as any)?.showInTray ?? (loadedSettings as any)?.showInDock ?? true; const minimizeToTrayOnClose = (loadedSettings as any)?.minimizeToTrayOnClose ?? (loadedSettings as any)?.minimize_to_tray_on_close ?? true; setSettings({ showInTray, minimizeToTrayOnClose, claudeConfigDir: typeof (loadedSettings as any)?.claudeConfigDir === "string" ? (loadedSettings as any).claudeConfigDir : undefined, codexConfigDir: typeof (loadedSettings as any)?.codexConfigDir === "string" ? (loadedSettings as any).codexConfigDir : undefined, }); } catch (error) { console.error("加载设置失败:", error); } }; const loadConfigPath = async () => { try { const path = await window.api.getAppConfigPath(); if (path) { setConfigPath(path); } } catch (error) { console.error("获取配置路径失败:", error); } }; const loadResolvedDirs = async () => { try { const [claudeDir, codexDir] = await Promise.all([ window.api.getConfigDir("claude"), window.api.getConfigDir("codex"), ]); setResolvedClaudeDir(claudeDir || ""); setResolvedCodexDir(codexDir || ""); } catch (error) { console.error("获取配置目录失败:", error); } }; const loadPortableFlag = async () => { try { const portable = await window.api.isPortable(); setIsPortable(portable); } catch (error) { console.error("检测便携模式失败:", error); } }; const saveSettings = async () => { try { const payload: Settings = { ...settings, claudeConfigDir: settings.claudeConfigDir && settings.claudeConfigDir.trim() !== "" ? settings.claudeConfigDir.trim() : undefined, codexConfigDir: settings.codexConfigDir && settings.codexConfigDir.trim() !== "" ? settings.codexConfigDir.trim() : undefined, }; await window.api.saveSettings(payload); setSettings(payload); onClose(); } catch (error) { console.error("保存设置失败:", error); } }; const handleCheckUpdate = async () => { if (hasUpdate && updateHandle) { if (isPortable) { await window.api.checkForUpdates(); return; } // 已检测到更新:直接复用 updateHandle 下载并安装,避免重复检查 setIsDownloading(true); try { resetDismiss(); await updateHandle.downloadAndInstall(); await relaunchApp(); } catch (error) { console.error("更新失败:", error); // 更新失败时回退到打开 Releases 页面 await window.api.checkForUpdates(); } finally { setIsDownloading(false); } } else { // 尚未检测到更新:先检查 setIsCheckingUpdate(true); setShowUpToDate(false); try { const hasNewUpdate = await checkUpdate(); // 检查完成后,如果没有更新,显示"已是最新" if (!hasNewUpdate) { setShowUpToDate(true); // 3秒后恢复按钮文字 setTimeout(() => { setShowUpToDate(false); }, 3000); } } catch (error) { console.error("检查更新失败:", error); // 在开发模式下,模拟已是最新版本的响应 if (import.meta.env.DEV) { setShowUpToDate(true); setTimeout(() => { setShowUpToDate(false); }, 3000); } else { // 生产环境下如果更新插件不可用,回退到打开 Releases 页面 await window.api.checkForUpdates(); } } finally { setIsCheckingUpdate(false); } } }; const handleOpenConfigFolder = async () => { try { await window.api.openAppConfigFolder(); } catch (error) { console.error("打开配置文件夹失败:", error); } }; const handleBrowseConfigDir = async (app: AppType) => { try { const currentResolved = app === "claude" ? (settings.claudeConfigDir ?? resolvedClaudeDir) : (settings.codexConfigDir ?? resolvedCodexDir); const selected = await window.api.selectConfigDirectory(currentResolved); if (!selected) { return; } const sanitized = selected.trim(); if (sanitized === "") { return; } if (app === "claude") { setSettings((prev) => ({ ...prev, claudeConfigDir: sanitized })); setResolvedClaudeDir(sanitized); } else { setSettings((prev) => ({ ...prev, codexConfigDir: sanitized })); setResolvedCodexDir(sanitized); } } catch (error) { console.error("选择配置目录失败:", error); } }; const computeDefaultConfigDir = async (app: AppType) => { try { const home = await homeDir(); const folder = app === "claude" ? ".claude" : ".codex"; return await join(home, folder); } catch (error) { console.error("获取默认配置目录失败:", error); return ""; } }; const handleResetConfigDir = async (app: AppType) => { setSettings((prev) => ({ ...prev, ...(app === "claude" ? { claudeConfigDir: undefined } : { codexConfigDir: undefined }), })); const defaultDir = await computeDefaultConfigDir(app); if (!defaultDir) { return; } if (app === "claude") { setResolvedClaudeDir(defaultDir); } else { setResolvedCodexDir(defaultDir); } }; const handleOpenReleaseNotes = async () => { try { const targetVersion = updateInfo?.availableVersion || version; // 如果未知或为空,回退到 releases 首页 if (!targetVersion || targetVersion === "未知") { await window.api.openExternal( "https://github.com/farion1231/cc-switch/releases" ); return; } const tag = targetVersion.startsWith("v") ? targetVersion : `v${targetVersion}`; await window.api.openExternal( `https://github.com/farion1231/cc-switch/releases/tag/${tag}` ); } catch (error) { console.error("打开更新日志失败:", error); } }; return (
{ if (e.target === e.currentTarget) onClose(); }} >
{/* 标题栏 */}

设置

{/* 设置内容 */}
{/* 窗口行为设置 */}

窗口行为

{/* VS Code 自动同步设置已移除 */} {/* 配置文件位置 */}

配置文件位置

{configPath || "加载中..."}
{/* 配置目录覆盖 */}

配置目录覆盖(高级)

在 WSL 等环境使用 Claude Code 或 Codex 的时候,可手动指定 WSL 里的配置目录,供应商数据与主环境保持一致。

setSettings({ ...settings, claudeConfigDir: e.target.value, }) } placeholder="例如:/home/<你的用户名>/.claude" className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40" />
setSettings({ ...settings, codexConfigDir: e.target.value, }) } placeholder="例如:/home/<你的用户名>/.codex" className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40" />
{/* 关于 */}

关于

CC Switch

版本 {version}

{/* 底部按钮 */}
); }