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
This commit is contained in:
@@ -14,6 +14,7 @@ interface DirectorySettingsProps {
|
|||||||
onResetAppConfig: () => Promise<void>;
|
onResetAppConfig: () => Promise<void>;
|
||||||
claudeDir?: string;
|
claudeDir?: string;
|
||||||
codexDir?: string;
|
codexDir?: string;
|
||||||
|
geminiDir?: string;
|
||||||
onDirectoryChange: (app: AppId, value?: string) => void;
|
onDirectoryChange: (app: AppId, value?: string) => void;
|
||||||
onBrowseDirectory: (app: AppId) => Promise<void>;
|
onBrowseDirectory: (app: AppId) => Promise<void>;
|
||||||
onResetDirectory: (app: AppId) => Promise<void>;
|
onResetDirectory: (app: AppId) => Promise<void>;
|
||||||
@@ -27,6 +28,7 @@ export function DirectorySettings({
|
|||||||
onResetAppConfig,
|
onResetAppConfig,
|
||||||
claudeDir,
|
claudeDir,
|
||||||
codexDir,
|
codexDir,
|
||||||
|
geminiDir,
|
||||||
onDirectoryChange,
|
onDirectoryChange,
|
||||||
onBrowseDirectory,
|
onBrowseDirectory,
|
||||||
onResetDirectory,
|
onResetDirectory,
|
||||||
@@ -104,6 +106,17 @@ export function DirectorySettings({
|
|||||||
onBrowse={() => onBrowseDirectory("codex")}
|
onBrowse={() => onBrowseDirectory("codex")}
|
||||||
onReset={() => onResetDirectory("codex")}
|
onReset={() => onResetDirectory("codex")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DirectoryInput
|
||||||
|
label={t("settings.geminiConfigDir")}
|
||||||
|
description={undefined}
|
||||||
|
value={geminiDir}
|
||||||
|
resolvedValue={resolvedDirs.gemini}
|
||||||
|
placeholder={t("settings.browsePlaceholderGemini")}
|
||||||
|
onChange={(val) => onDirectoryChange("gemini", val)}
|
||||||
|
onBrowse={() => onBrowseDirectory("gemini")}
|
||||||
|
onReset={() => onResetDirectory("gemini")}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export function SettingsDialog({
|
|||||||
onResetAppConfig={resetAppConfigDir}
|
onResetAppConfig={resetAppConfigDir}
|
||||||
claudeDir={settings.claudeConfigDir}
|
claudeDir={settings.claudeConfigDir}
|
||||||
codexDir={settings.codexConfigDir}
|
codexDir={settings.codexConfigDir}
|
||||||
|
geminiDir={settings.geminiConfigDir}
|
||||||
onDirectoryChange={updateDirectory}
|
onDirectoryChange={updateDirectory}
|
||||||
onBrowseDirectory={browseDirectory}
|
onBrowseDirectory={browseDirectory}
|
||||||
onResetDirectory={resetDirectory}
|
onResetDirectory={resetDirectory}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { homeDir, join } from "@tauri-apps/api/path";
|
|||||||
import { settingsApi, type AppId } from "@/lib/api";
|
import { settingsApi, type AppId } from "@/lib/api";
|
||||||
import type { SettingsFormState } from "./useSettingsForm";
|
import type { SettingsFormState } from "./useSettingsForm";
|
||||||
|
|
||||||
type DirectoryKey = "appConfig" | "claude" | "codex";
|
type DirectoryKey = "appConfig" | "claude" | "codex" | "gemini";
|
||||||
|
|
||||||
export interface ResolvedDirectories {
|
export interface ResolvedDirectories {
|
||||||
appConfig: string;
|
appConfig: string;
|
||||||
claude: string;
|
claude: string;
|
||||||
codex: string;
|
codex: string;
|
||||||
|
gemini: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizeDir = (value?: string | null): string | undefined => {
|
const sanitizeDir = (value?: string | null): string | undefined => {
|
||||||
@@ -37,7 +38,8 @@ const computeDefaultConfigDir = async (
|
|||||||
): Promise<string | undefined> => {
|
): Promise<string | undefined> => {
|
||||||
try {
|
try {
|
||||||
const home = await homeDir();
|
const home = await homeDir();
|
||||||
const folder = app === "claude" ? ".claude" : ".codex";
|
const folder =
|
||||||
|
app === "claude" ? ".claude" : app === "codex" ? ".codex" : ".gemini";
|
||||||
return await join(home, folder);
|
return await join(home, folder);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -64,7 +66,11 @@ export interface UseDirectorySettingsResult {
|
|||||||
browseAppConfigDir: () => Promise<void>;
|
browseAppConfigDir: () => Promise<void>;
|
||||||
resetDirectory: (app: AppId) => Promise<void>;
|
resetDirectory: (app: AppId) => Promise<void>;
|
||||||
resetAppConfigDir: () => Promise<void>;
|
resetAppConfigDir: () => Promise<void>;
|
||||||
resetAllDirectories: (claudeDir?: string, codexDir?: string) => void;
|
resetAllDirectories: (
|
||||||
|
claudeDir?: string,
|
||||||
|
codexDir?: string,
|
||||||
|
geminiDir?: string,
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +95,7 @@ export function useDirectorySettings({
|
|||||||
appConfig: "",
|
appConfig: "",
|
||||||
claude: "",
|
claude: "",
|
||||||
codex: "",
|
codex: "",
|
||||||
|
gemini: "",
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
@@ -96,6 +103,7 @@ export function useDirectorySettings({
|
|||||||
appConfig: "",
|
appConfig: "",
|
||||||
claude: "",
|
claude: "",
|
||||||
codex: "",
|
codex: "",
|
||||||
|
gemini: "",
|
||||||
});
|
});
|
||||||
const initialAppConfigDirRef = useRef<string | undefined>(undefined);
|
const initialAppConfigDirRef = useRef<string | undefined>(undefined);
|
||||||
|
|
||||||
@@ -110,16 +118,20 @@ export function useDirectorySettings({
|
|||||||
overrideRaw,
|
overrideRaw,
|
||||||
claudeDir,
|
claudeDir,
|
||||||
codexDir,
|
codexDir,
|
||||||
|
geminiDir,
|
||||||
defaultAppConfig,
|
defaultAppConfig,
|
||||||
defaultClaudeDir,
|
defaultClaudeDir,
|
||||||
defaultCodexDir,
|
defaultCodexDir,
|
||||||
|
defaultGeminiDir,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
settingsApi.getAppConfigDirOverride(),
|
settingsApi.getAppConfigDirOverride(),
|
||||||
settingsApi.getConfigDir("claude"),
|
settingsApi.getConfigDir("claude"),
|
||||||
settingsApi.getConfigDir("codex"),
|
settingsApi.getConfigDir("codex"),
|
||||||
|
settingsApi.getConfigDir("gemini"),
|
||||||
computeDefaultAppConfigDir(),
|
computeDefaultAppConfigDir(),
|
||||||
computeDefaultConfigDir("claude"),
|
computeDefaultConfigDir("claude"),
|
||||||
computeDefaultConfigDir("codex"),
|
computeDefaultConfigDir("codex"),
|
||||||
|
computeDefaultConfigDir("gemini"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
@@ -130,6 +142,7 @@ export function useDirectorySettings({
|
|||||||
appConfig: defaultAppConfig ?? "",
|
appConfig: defaultAppConfig ?? "",
|
||||||
claude: defaultClaudeDir ?? "",
|
claude: defaultClaudeDir ?? "",
|
||||||
codex: defaultCodexDir ?? "",
|
codex: defaultCodexDir ?? "",
|
||||||
|
gemini: defaultGeminiDir ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
setAppConfigDir(normalizedOverride);
|
setAppConfigDir(normalizedOverride);
|
||||||
@@ -139,6 +152,7 @@ export function useDirectorySettings({
|
|||||||
appConfig: normalizedOverride ?? defaultsRef.current.appConfig,
|
appConfig: normalizedOverride ?? defaultsRef.current.appConfig,
|
||||||
claude: claudeDir || defaultsRef.current.claude,
|
claude: claudeDir || defaultsRef.current.claude,
|
||||||
codex: codexDir || defaultsRef.current.codex,
|
codex: codexDir || defaultsRef.current.codex,
|
||||||
|
gemini: geminiDir || defaultsRef.current.gemini,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -167,7 +181,9 @@ export function useDirectorySettings({
|
|||||||
onUpdateSettings(
|
onUpdateSettings(
|
||||||
key === "claude"
|
key === "claude"
|
||||||
? { claudeConfigDir: sanitized }
|
? { claudeConfigDir: sanitized }
|
||||||
: { codexConfigDir: sanitized },
|
: key === "codex"
|
||||||
|
? { codexConfigDir: sanitized }
|
||||||
|
: { geminiConfigDir: sanitized },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,18 +204,24 @@ export function useDirectorySettings({
|
|||||||
|
|
||||||
const updateDirectory = useCallback(
|
const updateDirectory = useCallback(
|
||||||
(app: AppId, value?: string) => {
|
(app: AppId, value?: string) => {
|
||||||
updateDirectoryState(app === "claude" ? "claude" : "codex", value);
|
updateDirectoryState(
|
||||||
|
app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini",
|
||||||
|
value,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[updateDirectoryState],
|
[updateDirectoryState],
|
||||||
);
|
);
|
||||||
|
|
||||||
const browseDirectory = useCallback(
|
const browseDirectory = useCallback(
|
||||||
async (app: AppId) => {
|
async (app: AppId) => {
|
||||||
const key: DirectoryKey = app === "claude" ? "claude" : "codex";
|
const key: DirectoryKey =
|
||||||
|
app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini";
|
||||||
const currentValue =
|
const currentValue =
|
||||||
key === "claude"
|
key === "claude"
|
||||||
? (settings?.claudeConfigDir ?? resolvedDirs.claude)
|
? (settings?.claudeConfigDir ?? resolvedDirs.claude)
|
||||||
: (settings?.codexConfigDir ?? resolvedDirs.codex);
|
: key === "codex"
|
||||||
|
? (settings?.codexConfigDir ?? resolvedDirs.codex)
|
||||||
|
: (settings?.geminiConfigDir ?? resolvedDirs.gemini);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const picked = await settingsApi.selectConfigDirectory(currentValue);
|
const picked = await settingsApi.selectConfigDirectory(currentValue);
|
||||||
@@ -240,7 +262,8 @@ export function useDirectorySettings({
|
|||||||
|
|
||||||
const resetDirectory = useCallback(
|
const resetDirectory = useCallback(
|
||||||
async (app: AppId) => {
|
async (app: AppId) => {
|
||||||
const key: DirectoryKey = app === "claude" ? "claude" : "codex";
|
const key: DirectoryKey =
|
||||||
|
app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini";
|
||||||
if (!defaultsRef.current[key]) {
|
if (!defaultsRef.current[key]) {
|
||||||
const fallback = await computeDefaultConfigDir(app);
|
const fallback = await computeDefaultConfigDir(app);
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
@@ -269,13 +292,14 @@ export function useDirectorySettings({
|
|||||||
}, [updateDirectoryState]);
|
}, [updateDirectoryState]);
|
||||||
|
|
||||||
const resetAllDirectories = useCallback(
|
const resetAllDirectories = useCallback(
|
||||||
(claudeDir?: string, codexDir?: string) => {
|
(claudeDir?: string, codexDir?: string, geminiDir?: string) => {
|
||||||
setAppConfigDir(initialAppConfigDirRef.current);
|
setAppConfigDir(initialAppConfigDirRef.current);
|
||||||
setResolvedDirs({
|
setResolvedDirs({
|
||||||
appConfig:
|
appConfig:
|
||||||
initialAppConfigDirRef.current ?? defaultsRef.current.appConfig,
|
initialAppConfigDirRef.current ?? defaultsRef.current.appConfig,
|
||||||
claude: claudeDir ?? defaultsRef.current.claude,
|
claude: claudeDir ?? defaultsRef.current.claude,
|
||||||
codex: codexDir ?? defaultsRef.current.codex,
|
codex: codexDir ?? defaultsRef.current.codex,
|
||||||
|
gemini: geminiDir ?? defaultsRef.current.gemini,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export function useSettings(): UseSettingsResult {
|
|||||||
resetAllDirectories(
|
resetAllDirectories(
|
||||||
sanitizeDir(data?.claudeConfigDir),
|
sanitizeDir(data?.claudeConfigDir),
|
||||||
sanitizeDir(data?.codexConfigDir),
|
sanitizeDir(data?.codexConfigDir),
|
||||||
|
sanitizeDir(data?.geminiConfigDir),
|
||||||
);
|
);
|
||||||
setRequiresRestart(false);
|
setRequiresRestart(false);
|
||||||
}, [
|
}, [
|
||||||
@@ -120,14 +121,17 @@ export function useSettings(): UseSettingsResult {
|
|||||||
const sanitizedAppDir = sanitizeDir(appConfigDir);
|
const sanitizedAppDir = sanitizeDir(appConfigDir);
|
||||||
const sanitizedClaudeDir = sanitizeDir(settings.claudeConfigDir);
|
const sanitizedClaudeDir = sanitizeDir(settings.claudeConfigDir);
|
||||||
const sanitizedCodexDir = sanitizeDir(settings.codexConfigDir);
|
const sanitizedCodexDir = sanitizeDir(settings.codexConfigDir);
|
||||||
|
const sanitizedGeminiDir = sanitizeDir(settings.geminiConfigDir);
|
||||||
const previousAppDir = initialAppConfigDir;
|
const previousAppDir = initialAppConfigDir;
|
||||||
const previousClaudeDir = sanitizeDir(data?.claudeConfigDir);
|
const previousClaudeDir = sanitizeDir(data?.claudeConfigDir);
|
||||||
const previousCodexDir = sanitizeDir(data?.codexConfigDir);
|
const previousCodexDir = sanitizeDir(data?.codexConfigDir);
|
||||||
|
const previousGeminiDir = sanitizeDir(data?.geminiConfigDir);
|
||||||
|
|
||||||
const payload: Settings = {
|
const payload: Settings = {
|
||||||
...settings,
|
...settings,
|
||||||
claudeConfigDir: sanitizedClaudeDir,
|
claudeConfigDir: sanitizedClaudeDir,
|
||||||
codexConfigDir: sanitizedCodexDir,
|
codexConfigDir: sanitizedCodexDir,
|
||||||
|
geminiConfigDir: sanitizedGeminiDir,
|
||||||
language: settings.language,
|
language: settings.language,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,10 +174,11 @@ export function useSettings(): UseSettingsResult {
|
|||||||
console.warn("[useSettings] Failed to refresh tray menu", error);
|
console.warn("[useSettings] Failed to refresh tray menu", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果 Claude/Codex 的目录覆盖发生变化,则立即将“当前使用的供应商”写回对应应用的 live 配置
|
// 如果 Claude/Codex/Gemini 的目录覆盖发生变化,则立即将“当前使用的供应商”写回对应应用的 live 配置
|
||||||
const claudeDirChanged = sanitizedClaudeDir !== previousClaudeDir;
|
const claudeDirChanged = sanitizedClaudeDir !== previousClaudeDir;
|
||||||
const codexDirChanged = sanitizedCodexDir !== previousCodexDir;
|
const codexDirChanged = sanitizedCodexDir !== previousCodexDir;
|
||||||
if (claudeDirChanged || codexDirChanged) {
|
const geminiDirChanged = sanitizedGeminiDir !== previousGeminiDir;
|
||||||
|
if (claudeDirChanged || codexDirChanged || geminiDirChanged) {
|
||||||
const syncResult = await syncCurrentProvidersLiveSafe();
|
const syncResult = await syncCurrentProvidersLiveSafe();
|
||||||
if (!syncResult.ok) {
|
if (!syncResult.ok) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
@@ -179,8 +179,11 @@
|
|||||||
"claudeConfigDirDescription": "Override Claude configuration directory (settings.json) and keep claude.json (MCP) alongside it.",
|
"claudeConfigDirDescription": "Override Claude configuration directory (settings.json) and keep claude.json (MCP) alongside it.",
|
||||||
"codexConfigDir": "Codex Configuration Directory",
|
"codexConfigDir": "Codex Configuration Directory",
|
||||||
"codexConfigDirDescription": "Override Codex configuration directory.",
|
"codexConfigDirDescription": "Override Codex configuration directory.",
|
||||||
|
"geminiConfigDir": "Gemini Configuration Directory",
|
||||||
|
"geminiConfigDirDescription": "Override Gemini configuration directory (.env).",
|
||||||
"browsePlaceholderClaude": "e.g., /home/<your-username>/.claude",
|
"browsePlaceholderClaude": "e.g., /home/<your-username>/.claude",
|
||||||
"browsePlaceholderCodex": "e.g., /home/<your-username>/.codex",
|
"browsePlaceholderCodex": "e.g., /home/<your-username>/.codex",
|
||||||
|
"browsePlaceholderGemini": "e.g., /home/<your-username>/.gemini",
|
||||||
"browseDirectory": "Browse Directory",
|
"browseDirectory": "Browse Directory",
|
||||||
"resetDefault": "Reset to default directory (takes effect after saving)",
|
"resetDefault": "Reset to default directory (takes effect after saving)",
|
||||||
"checkForUpdates": "Check for Updates",
|
"checkForUpdates": "Check for Updates",
|
||||||
|
|||||||
@@ -179,8 +179,11 @@
|
|||||||
"claudeConfigDirDescription": "覆盖 Claude 配置目录 (settings.json),同时会在同级存放 Claude MCP 的 claude.json。",
|
"claudeConfigDirDescription": "覆盖 Claude 配置目录 (settings.json),同时会在同级存放 Claude MCP 的 claude.json。",
|
||||||
"codexConfigDir": "Codex 配置目录",
|
"codexConfigDir": "Codex 配置目录",
|
||||||
"codexConfigDirDescription": "覆盖 Codex 配置目录。",
|
"codexConfigDirDescription": "覆盖 Codex 配置目录。",
|
||||||
|
"geminiConfigDir": "Gemini 配置目录",
|
||||||
|
"geminiConfigDirDescription": "覆盖 Gemini 配置目录 (.env)。",
|
||||||
"browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude",
|
"browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude",
|
||||||
"browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex",
|
"browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex",
|
||||||
|
"browsePlaceholderGemini": "例如:/home/<你的用户名>/.gemini",
|
||||||
"browseDirectory": "浏览目录",
|
"browseDirectory": "浏览目录",
|
||||||
"resetDefault": "恢复默认目录(需保存后生效)",
|
"resetDefault": "恢复默认目录(需保存后生效)",
|
||||||
"checkForUpdates": "检查更新",
|
"checkForUpdates": "检查更新",
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ export interface Settings {
|
|||||||
claudeConfigDir?: string;
|
claudeConfigDir?: string;
|
||||||
// 覆盖 Codex 配置目录(可选)
|
// 覆盖 Codex 配置目录(可选)
|
||||||
codexConfigDir?: string;
|
codexConfigDir?: string;
|
||||||
|
// 覆盖 Gemini 配置目录(可选)
|
||||||
|
geminiConfigDir?: string;
|
||||||
// 首选语言(可选,默认中文)
|
// 首选语言(可选,默认中文)
|
||||||
language?: "en" | "zh";
|
language?: "en" | "zh";
|
||||||
// Claude 自定义端点列表
|
// Claude 自定义端点列表
|
||||||
|
|||||||
Reference in New Issue
Block a user