From 14ee122b27c7cfb8fa45e440ee47588e50309ea8 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Wed, 19 Nov 2025 21:14:43 +0800 Subject: [PATCH] feat(settings): add Gemini configuration directory support (#255) * style: apply code formatting across backend and frontend Apply cargo fmt and prettier formatting to improve code readability. No functional changes. Changes: - Rust: multi-line assertion formatting (gemini_config, env_checker) - Rust: simplify chained method calls (provider) - TypeScript: add trailing commas to function parameters (codexProviderPresets) * feat(settings): add Gemini configuration directory support Add custom configuration directory support for Gemini: - Add geminiConfigDir field to Settings type - Extend DirectorySettings component with Gemini input - Update useDirectorySettings hook for Gemini directory management - Add i18n translations for Gemini directory settings --- src/components/settings/DirectorySettings.tsx | 13 ++++++ src/components/settings/SettingsDialog.tsx | 1 + src/hooks/useDirectorySettings.ts | 42 +++++++++++++++---- src/hooks/useSettings.ts | 9 +++- src/i18n/locales/en.json | 3 ++ src/i18n/locales/zh.json | 3 ++ src/types.ts | 2 + 7 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/components/settings/DirectorySettings.tsx b/src/components/settings/DirectorySettings.tsx index 2e41327..17bb269 100644 --- a/src/components/settings/DirectorySettings.tsx +++ b/src/components/settings/DirectorySettings.tsx @@ -14,6 +14,7 @@ interface DirectorySettingsProps { onResetAppConfig: () => Promise; claudeDir?: string; codexDir?: string; + geminiDir?: string; onDirectoryChange: (app: AppId, value?: string) => void; onBrowseDirectory: (app: AppId) => Promise; onResetDirectory: (app: AppId) => Promise; @@ -27,6 +28,7 @@ export function DirectorySettings({ onResetAppConfig, claudeDir, codexDir, + geminiDir, onDirectoryChange, onBrowseDirectory, onResetDirectory, @@ -104,6 +106,17 @@ export function DirectorySettings({ onBrowse={() => onBrowseDirectory("codex")} onReset={() => onResetDirectory("codex")} /> + + onDirectoryChange("gemini", val)} + onBrowse={() => onBrowseDirectory("gemini")} + onReset={() => onResetDirectory("gemini")} + /> ); diff --git a/src/components/settings/SettingsDialog.tsx b/src/components/settings/SettingsDialog.tsx index ca81b34..8eb7dbf 100644 --- a/src/components/settings/SettingsDialog.tsx +++ b/src/components/settings/SettingsDialog.tsx @@ -220,6 +220,7 @@ export function SettingsDialog({ onResetAppConfig={resetAppConfigDir} claudeDir={settings.claudeConfigDir} codexDir={settings.codexConfigDir} + geminiDir={settings.geminiConfigDir} onDirectoryChange={updateDirectory} onBrowseDirectory={browseDirectory} onResetDirectory={resetDirectory} diff --git a/src/hooks/useDirectorySettings.ts b/src/hooks/useDirectorySettings.ts index 2502134..3904a57 100644 --- a/src/hooks/useDirectorySettings.ts +++ b/src/hooks/useDirectorySettings.ts @@ -5,12 +5,13 @@ import { homeDir, join } from "@tauri-apps/api/path"; import { settingsApi, type AppId } from "@/lib/api"; import type { SettingsFormState } from "./useSettingsForm"; -type DirectoryKey = "appConfig" | "claude" | "codex"; +type DirectoryKey = "appConfig" | "claude" | "codex" | "gemini"; export interface ResolvedDirectories { appConfig: string; claude: string; codex: string; + gemini: string; } const sanitizeDir = (value?: string | null): string | undefined => { @@ -37,7 +38,8 @@ const computeDefaultConfigDir = async ( ): Promise => { try { const home = await homeDir(); - const folder = app === "claude" ? ".claude" : ".codex"; + const folder = + app === "claude" ? ".claude" : app === "codex" ? ".codex" : ".gemini"; return await join(home, folder); } catch (error) { console.error( @@ -64,7 +66,11 @@ export interface UseDirectorySettingsResult { browseAppConfigDir: () => Promise; resetDirectory: (app: AppId) => Promise; resetAppConfigDir: () => Promise; - resetAllDirectories: (claudeDir?: string, codexDir?: string) => void; + resetAllDirectories: ( + claudeDir?: string, + codexDir?: string, + geminiDir?: string, + ) => void; } /** @@ -89,6 +95,7 @@ export function useDirectorySettings({ appConfig: "", claude: "", codex: "", + gemini: "", }); const [isLoading, setIsLoading] = useState(true); @@ -96,6 +103,7 @@ export function useDirectorySettings({ appConfig: "", claude: "", codex: "", + gemini: "", }); const initialAppConfigDirRef = useRef(undefined); @@ -110,16 +118,20 @@ export function useDirectorySettings({ overrideRaw, claudeDir, codexDir, + geminiDir, defaultAppConfig, defaultClaudeDir, defaultCodexDir, + defaultGeminiDir, ] = await Promise.all([ settingsApi.getAppConfigDirOverride(), settingsApi.getConfigDir("claude"), settingsApi.getConfigDir("codex"), + settingsApi.getConfigDir("gemini"), computeDefaultAppConfigDir(), computeDefaultConfigDir("claude"), computeDefaultConfigDir("codex"), + computeDefaultConfigDir("gemini"), ]); if (!active) return; @@ -130,6 +142,7 @@ export function useDirectorySettings({ appConfig: defaultAppConfig ?? "", claude: defaultClaudeDir ?? "", codex: defaultCodexDir ?? "", + gemini: defaultGeminiDir ?? "", }; setAppConfigDir(normalizedOverride); @@ -139,6 +152,7 @@ export function useDirectorySettings({ appConfig: normalizedOverride ?? defaultsRef.current.appConfig, claude: claudeDir || defaultsRef.current.claude, codex: codexDir || defaultsRef.current.codex, + gemini: geminiDir || defaultsRef.current.gemini, }); } catch (error) { console.error( @@ -167,7 +181,9 @@ export function useDirectorySettings({ onUpdateSettings( key === "claude" ? { claudeConfigDir: sanitized } - : { codexConfigDir: sanitized }, + : key === "codex" + ? { codexConfigDir: sanitized } + : { geminiConfigDir: sanitized }, ); } @@ -188,18 +204,24 @@ export function useDirectorySettings({ const updateDirectory = useCallback( (app: AppId, value?: string) => { - updateDirectoryState(app === "claude" ? "claude" : "codex", value); + updateDirectoryState( + app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini", + value, + ); }, [updateDirectoryState], ); const browseDirectory = useCallback( async (app: AppId) => { - const key: DirectoryKey = app === "claude" ? "claude" : "codex"; + const key: DirectoryKey = + app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini"; const currentValue = key === "claude" ? (settings?.claudeConfigDir ?? resolvedDirs.claude) - : (settings?.codexConfigDir ?? resolvedDirs.codex); + : key === "codex" + ? (settings?.codexConfigDir ?? resolvedDirs.codex) + : (settings?.geminiConfigDir ?? resolvedDirs.gemini); try { const picked = await settingsApi.selectConfigDirectory(currentValue); @@ -240,7 +262,8 @@ export function useDirectorySettings({ const resetDirectory = useCallback( async (app: AppId) => { - const key: DirectoryKey = app === "claude" ? "claude" : "codex"; + const key: DirectoryKey = + app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini"; if (!defaultsRef.current[key]) { const fallback = await computeDefaultConfigDir(app); if (fallback) { @@ -269,13 +292,14 @@ export function useDirectorySettings({ }, [updateDirectoryState]); const resetAllDirectories = useCallback( - (claudeDir?: string, codexDir?: string) => { + (claudeDir?: string, codexDir?: string, geminiDir?: string) => { setAppConfigDir(initialAppConfigDirRef.current); setResolvedDirs({ appConfig: initialAppConfigDirRef.current ?? defaultsRef.current.appConfig, claude: claudeDir ?? defaultsRef.current.claude, codex: codexDir ?? defaultsRef.current.codex, + gemini: geminiDir ?? defaultsRef.current.gemini, }); }, [], diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 0f29e0a..881198d 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -102,6 +102,7 @@ export function useSettings(): UseSettingsResult { resetAllDirectories( sanitizeDir(data?.claudeConfigDir), sanitizeDir(data?.codexConfigDir), + sanitizeDir(data?.geminiConfigDir), ); setRequiresRestart(false); }, [ @@ -120,14 +121,17 @@ export function useSettings(): UseSettingsResult { const sanitizedAppDir = sanitizeDir(appConfigDir); const sanitizedClaudeDir = sanitizeDir(settings.claudeConfigDir); const sanitizedCodexDir = sanitizeDir(settings.codexConfigDir); + const sanitizedGeminiDir = sanitizeDir(settings.geminiConfigDir); const previousAppDir = initialAppConfigDir; const previousClaudeDir = sanitizeDir(data?.claudeConfigDir); const previousCodexDir = sanitizeDir(data?.codexConfigDir); + const previousGeminiDir = sanitizeDir(data?.geminiConfigDir); const payload: Settings = { ...settings, claudeConfigDir: sanitizedClaudeDir, codexConfigDir: sanitizedCodexDir, + geminiConfigDir: sanitizedGeminiDir, language: settings.language, }; @@ -170,10 +174,11 @@ export function useSettings(): UseSettingsResult { console.warn("[useSettings] Failed to refresh tray menu", error); } - // 如果 Claude/Codex 的目录覆盖发生变化,则立即将“当前使用的供应商”写回对应应用的 live 配置 + // 如果 Claude/Codex/Gemini 的目录覆盖发生变化,则立即将“当前使用的供应商”写回对应应用的 live 配置 const claudeDirChanged = sanitizedClaudeDir !== previousClaudeDir; const codexDirChanged = sanitizedCodexDir !== previousCodexDir; - if (claudeDirChanged || codexDirChanged) { + const geminiDirChanged = sanitizedGeminiDir !== previousGeminiDir; + if (claudeDirChanged || codexDirChanged || geminiDirChanged) { const syncResult = await syncCurrentProvidersLiveSafe(); if (!syncResult.ok) { console.warn( diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 7be13db..f92f297 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -179,8 +179,11 @@ "claudeConfigDirDescription": "Override Claude configuration directory (settings.json) and keep claude.json (MCP) alongside it.", "codexConfigDir": "Codex Configuration Directory", "codexConfigDirDescription": "Override Codex configuration directory.", + "geminiConfigDir": "Gemini Configuration Directory", + "geminiConfigDirDescription": "Override Gemini configuration directory (.env).", "browsePlaceholderClaude": "e.g., /home//.claude", "browsePlaceholderCodex": "e.g., /home//.codex", + "browsePlaceholderGemini": "e.g., /home//.gemini", "browseDirectory": "Browse Directory", "resetDefault": "Reset to default directory (takes effect after saving)", "checkForUpdates": "Check for Updates", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 2ff4ce3..1e135a6 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -179,8 +179,11 @@ "claudeConfigDirDescription": "覆盖 Claude 配置目录 (settings.json),同时会在同级存放 Claude MCP 的 claude.json。", "codexConfigDir": "Codex 配置目录", "codexConfigDirDescription": "覆盖 Codex 配置目录。", + "geminiConfigDir": "Gemini 配置目录", + "geminiConfigDirDescription": "覆盖 Gemini 配置目录 (.env)。", "browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude", "browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex", + "browsePlaceholderGemini": "例如:/home/<你的用户名>/.gemini", "browseDirectory": "浏览目录", "resetDefault": "恢复默认目录(需保存后生效)", "checkForUpdates": "检查更新", diff --git a/src/types.ts b/src/types.ts index 6701676..b4cb57f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -97,6 +97,8 @@ export interface Settings { claudeConfigDir?: string; // 覆盖 Codex 配置目录(可选) codexConfigDir?: string; + // 覆盖 Gemini 配置目录(可选) + geminiConfigDir?: string; // 首选语言(可选,默认中文) language?: "en" | "zh"; // Claude 自定义端点列表