mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
feat(settings): add Hermes config dir override with data-driven dispatch
Adds a dedicated Hermes row to the directory-override settings so users can point CC Switch at alternate Hermes config locations (e.g. a second profile directory for work/personal split). `get_config_dir` on the Rust side already supports hermes; this just wires up the frontend row. Wiring it through `useDirectorySettings` revealed a scaling problem: every supported app required five parallel ternary chains across `computeDefaultConfigDir`, `updateDirectory`, `browseDirectory`, `resetDirectory`, and `updateDirectoryState`. Replaces those with two lookup tables (`APP_DIRECTORY_META`, `DIRECTORY_KEY_TO_SETTINGS_FIELD`) so adding the next app is two entries, not fifteen edit sites. Drive-by cleanup from the same touch: * `resetAllDirectories` takes a `ResolvedAppDirectoryOverrides` object instead of five positional optional strings. * `setResolvedDirs` returns the same reference when the sanitized value is unchanged, so no-op edits don't cascade renders. Also lands all i18n updates for this series (`hermesConfigDir` and placeholder, Memory section's enable/disable/toggleFailed copy, and the reworded `schemaMigratedV12` warning) in zh/en/ja together.
This commit is contained in:
@@ -17,6 +17,7 @@ interface DirectorySettingsProps {
|
|||||||
geminiDir?: string;
|
geminiDir?: string;
|
||||||
opencodeDir?: string;
|
opencodeDir?: string;
|
||||||
openclawDir?: string;
|
openclawDir?: string;
|
||||||
|
hermesDir?: 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>;
|
||||||
@@ -33,6 +34,7 @@ export function DirectorySettings({
|
|||||||
geminiDir,
|
geminiDir,
|
||||||
opencodeDir,
|
opencodeDir,
|
||||||
openclawDir,
|
openclawDir,
|
||||||
|
hermesDir,
|
||||||
onDirectoryChange,
|
onDirectoryChange,
|
||||||
onBrowseDirectory,
|
onBrowseDirectory,
|
||||||
onResetDirectory,
|
onResetDirectory,
|
||||||
@@ -143,6 +145,17 @@ export function DirectorySettings({
|
|||||||
onBrowse={() => onBrowseDirectory("openclaw")}
|
onBrowse={() => onBrowseDirectory("openclaw")}
|
||||||
onReset={() => onResetDirectory("openclaw")}
|
onReset={() => onResetDirectory("openclaw")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DirectoryInput
|
||||||
|
label={t("settings.hermesConfigDir")}
|
||||||
|
description={undefined}
|
||||||
|
value={hermesDir}
|
||||||
|
resolvedValue={resolvedDirs.hermes}
|
||||||
|
placeholder={t("settings.browsePlaceholderHermes")}
|
||||||
|
onChange={(val) => onDirectoryChange("hermes", val)}
|
||||||
|
onBrowse={() => onBrowseDirectory("hermes")}
|
||||||
|
onReset={() => onResetDirectory("hermes")}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ export function SettingsPage({
|
|||||||
geminiDir={settings.geminiConfigDir}
|
geminiDir={settings.geminiConfigDir}
|
||||||
opencodeDir={settings.opencodeConfigDir}
|
opencodeDir={settings.opencodeConfigDir}
|
||||||
openclawDir={settings.openclawConfigDir}
|
openclawDir={settings.openclawConfigDir}
|
||||||
|
hermesDir={settings.hermesConfigDir}
|
||||||
onDirectoryChange={updateDirectory}
|
onDirectoryChange={updateDirectory}
|
||||||
onBrowseDirectory={browseDirectory}
|
onBrowseDirectory={browseDirectory}
|
||||||
onResetDirectory={resetDirectory}
|
onResetDirectory={resetDirectory}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ 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 =
|
type AppDirectoryKey =
|
||||||
| "appConfig"
|
|
||||||
| "claude"
|
| "claude"
|
||||||
| "codex"
|
| "codex"
|
||||||
| "gemini"
|
| "gemini"
|
||||||
| "opencode"
|
| "opencode"
|
||||||
| "openclaw";
|
| "openclaw"
|
||||||
|
| "hermes";
|
||||||
|
type DirectoryKey = "appConfig" | AppDirectoryKey;
|
||||||
|
|
||||||
export interface ResolvedDirectories {
|
export interface ResolvedDirectories {
|
||||||
appConfig: string;
|
appConfig: string;
|
||||||
@@ -20,8 +21,34 @@ export interface ResolvedDirectories {
|
|||||||
gemini: string;
|
gemini: string;
|
||||||
opencode: string;
|
opencode: string;
|
||||||
openclaw: string;
|
openclaw: string;
|
||||||
|
hermes: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single source of truth for per-app directory metadata.
|
||||||
|
const APP_DIRECTORY_META: Record<
|
||||||
|
AppId,
|
||||||
|
{ key: AppDirectoryKey; defaultFolder: string }
|
||||||
|
> = {
|
||||||
|
claude: { key: "claude", defaultFolder: ".claude" },
|
||||||
|
codex: { key: "codex", defaultFolder: ".codex" },
|
||||||
|
gemini: { key: "gemini", defaultFolder: ".gemini" },
|
||||||
|
opencode: { key: "opencode", defaultFolder: ".config/opencode" },
|
||||||
|
openclaw: { key: "openclaw", defaultFolder: ".openclaw" },
|
||||||
|
hermes: { key: "hermes", defaultFolder: ".hermes" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const DIRECTORY_KEY_TO_SETTINGS_FIELD: Record<
|
||||||
|
AppDirectoryKey,
|
||||||
|
keyof SettingsFormState
|
||||||
|
> = {
|
||||||
|
claude: "claudeConfigDir",
|
||||||
|
codex: "codexConfigDir",
|
||||||
|
gemini: "geminiConfigDir",
|
||||||
|
opencode: "opencodeConfigDir",
|
||||||
|
openclaw: "openclawConfigDir",
|
||||||
|
hermes: "hermesConfigDir",
|
||||||
|
};
|
||||||
|
|
||||||
const sanitizeDir = (value?: string | null): string | undefined => {
|
const sanitizeDir = (value?: string | null): string | undefined => {
|
||||||
if (!value) return undefined;
|
if (!value) return undefined;
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
@@ -46,17 +73,7 @@ const computeDefaultConfigDir = async (
|
|||||||
): Promise<string | undefined> => {
|
): Promise<string | undefined> => {
|
||||||
try {
|
try {
|
||||||
const home = await homeDir();
|
const home = await homeDir();
|
||||||
const folder =
|
return await join(home, APP_DIRECTORY_META[app].defaultFolder);
|
||||||
app === "claude"
|
|
||||||
? ".claude"
|
|
||||||
: app === "codex"
|
|
||||||
? ".codex"
|
|
||||||
: app === "gemini"
|
|
||||||
? ".gemini"
|
|
||||||
: app === "opencode"
|
|
||||||
? ".config/opencode"
|
|
||||||
: ".openclaw";
|
|
||||||
return await join(home, folder);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"[useDirectorySettings] Failed to resolve default config dir",
|
"[useDirectorySettings] Failed to resolve default config dir",
|
||||||
@@ -82,15 +99,13 @@ 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: (
|
resetAllDirectories: (overrides?: ResolvedAppDirectoryOverrides) => void;
|
||||||
claudeDir?: string,
|
|
||||||
codexDir?: string,
|
|
||||||
geminiDir?: string,
|
|
||||||
opencodeDir?: string,
|
|
||||||
openclawDir?: string,
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResolvedAppDirectoryOverrides = Partial<
|
||||||
|
Record<AppDirectoryKey, string | undefined>
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useDirectorySettings - 目录管理
|
* useDirectorySettings - 目录管理
|
||||||
* 负责:
|
* 负责:
|
||||||
@@ -116,6 +131,7 @@ export function useDirectorySettings({
|
|||||||
gemini: "",
|
gemini: "",
|
||||||
opencode: "",
|
opencode: "",
|
||||||
openclaw: "",
|
openclaw: "",
|
||||||
|
hermes: "",
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
@@ -126,6 +142,7 @@ export function useDirectorySettings({
|
|||||||
gemini: "",
|
gemini: "",
|
||||||
opencode: "",
|
opencode: "",
|
||||||
openclaw: "",
|
openclaw: "",
|
||||||
|
hermes: "",
|
||||||
});
|
});
|
||||||
const initialAppConfigDirRef = useRef<string | undefined>(undefined);
|
const initialAppConfigDirRef = useRef<string | undefined>(undefined);
|
||||||
|
|
||||||
@@ -143,12 +160,14 @@ export function useDirectorySettings({
|
|||||||
geminiDir,
|
geminiDir,
|
||||||
opencodeDir,
|
opencodeDir,
|
||||||
openclawDir,
|
openclawDir,
|
||||||
|
hermesDir,
|
||||||
defaultAppConfig,
|
defaultAppConfig,
|
||||||
defaultClaudeDir,
|
defaultClaudeDir,
|
||||||
defaultCodexDir,
|
defaultCodexDir,
|
||||||
defaultGeminiDir,
|
defaultGeminiDir,
|
||||||
defaultOpencodeDir,
|
defaultOpencodeDir,
|
||||||
defaultOpenclawDir,
|
defaultOpenclawDir,
|
||||||
|
defaultHermesDir,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
settingsApi.getAppConfigDirOverride(),
|
settingsApi.getAppConfigDirOverride(),
|
||||||
settingsApi.getConfigDir("claude"),
|
settingsApi.getConfigDir("claude"),
|
||||||
@@ -156,12 +175,14 @@ export function useDirectorySettings({
|
|||||||
settingsApi.getConfigDir("gemini"),
|
settingsApi.getConfigDir("gemini"),
|
||||||
settingsApi.getConfigDir("opencode"),
|
settingsApi.getConfigDir("opencode"),
|
||||||
settingsApi.getConfigDir("openclaw"),
|
settingsApi.getConfigDir("openclaw"),
|
||||||
|
settingsApi.getConfigDir("hermes"),
|
||||||
computeDefaultAppConfigDir(),
|
computeDefaultAppConfigDir(),
|
||||||
computeDefaultConfigDir("claude"),
|
computeDefaultConfigDir("claude"),
|
||||||
computeDefaultConfigDir("codex"),
|
computeDefaultConfigDir("codex"),
|
||||||
computeDefaultConfigDir("gemini"),
|
computeDefaultConfigDir("gemini"),
|
||||||
computeDefaultConfigDir("opencode"),
|
computeDefaultConfigDir("opencode"),
|
||||||
computeDefaultConfigDir("openclaw"),
|
computeDefaultConfigDir("openclaw"),
|
||||||
|
computeDefaultConfigDir("hermes"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
@@ -175,6 +196,7 @@ export function useDirectorySettings({
|
|||||||
gemini: defaultGeminiDir ?? "",
|
gemini: defaultGeminiDir ?? "",
|
||||||
opencode: defaultOpencodeDir ?? "",
|
opencode: defaultOpencodeDir ?? "",
|
||||||
openclaw: defaultOpenclawDir ?? "",
|
openclaw: defaultOpenclawDir ?? "",
|
||||||
|
hermes: defaultHermesDir ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
setAppConfigDir(normalizedOverride);
|
setAppConfigDir(normalizedOverride);
|
||||||
@@ -187,6 +209,7 @@ export function useDirectorySettings({
|
|||||||
gemini: geminiDir || defaultsRef.current.gemini,
|
gemini: geminiDir || defaultsRef.current.gemini,
|
||||||
opencode: opencodeDir || defaultsRef.current.opencode,
|
opencode: opencodeDir || defaultsRef.current.opencode,
|
||||||
openclaw: openclawDir || defaultsRef.current.openclaw,
|
openclaw: openclawDir || defaultsRef.current.openclaw,
|
||||||
|
hermes: hermesDir || defaultsRef.current.hermes,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -212,23 +235,18 @@ export function useDirectorySettings({
|
|||||||
if (key === "appConfig") {
|
if (key === "appConfig") {
|
||||||
setAppConfigDir(sanitized);
|
setAppConfigDir(sanitized);
|
||||||
} else {
|
} else {
|
||||||
onUpdateSettings(
|
onUpdateSettings({
|
||||||
key === "claude"
|
[DIRECTORY_KEY_TO_SETTINGS_FIELD[key]]: sanitized,
|
||||||
? { claudeConfigDir: sanitized }
|
});
|
||||||
: key === "codex"
|
|
||||||
? { codexConfigDir: sanitized }
|
|
||||||
: key === "gemini"
|
|
||||||
? { geminiConfigDir: sanitized }
|
|
||||||
: key === "opencode"
|
|
||||||
? { opencodeConfigDir: sanitized }
|
|
||||||
: { openclawConfigDir: sanitized },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setResolvedDirs((prev) => ({
|
setResolvedDirs((prev) => {
|
||||||
...prev,
|
const next = sanitized ?? defaultsRef.current[key];
|
||||||
[key]: sanitized ?? defaultsRef.current[key],
|
// Same-ref early-return: unchanged value shouldn't cascade renders
|
||||||
}));
|
// through the settings tree.
|
||||||
|
if (prev[key] === next) return prev;
|
||||||
|
return { ...prev, [key]: next };
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[onUpdateSettings],
|
[onUpdateSettings],
|
||||||
);
|
);
|
||||||
@@ -242,44 +260,17 @@ export function useDirectorySettings({
|
|||||||
|
|
||||||
const updateDirectory = useCallback(
|
const updateDirectory = useCallback(
|
||||||
(app: AppId, value?: string) => {
|
(app: AppId, value?: string) => {
|
||||||
updateDirectoryState(
|
updateDirectoryState(APP_DIRECTORY_META[app].key, value);
|
||||||
app === "claude"
|
|
||||||
? "claude"
|
|
||||||
: app === "codex"
|
|
||||||
? "codex"
|
|
||||||
: app === "gemini"
|
|
||||||
? "gemini"
|
|
||||||
: app === "opencode"
|
|
||||||
? "opencode"
|
|
||||||
: "openclaw",
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[updateDirectoryState],
|
[updateDirectoryState],
|
||||||
);
|
);
|
||||||
|
|
||||||
const browseDirectory = useCallback(
|
const browseDirectory = useCallback(
|
||||||
async (app: AppId) => {
|
async (app: AppId) => {
|
||||||
const key: DirectoryKey =
|
const key = APP_DIRECTORY_META[app].key;
|
||||||
app === "claude"
|
const settingsField = DIRECTORY_KEY_TO_SETTINGS_FIELD[key];
|
||||||
? "claude"
|
|
||||||
: app === "codex"
|
|
||||||
? "codex"
|
|
||||||
: app === "gemini"
|
|
||||||
? "gemini"
|
|
||||||
: app === "opencode"
|
|
||||||
? "opencode"
|
|
||||||
: "openclaw";
|
|
||||||
const currentValue =
|
const currentValue =
|
||||||
key === "claude"
|
(settings?.[settingsField] as string | undefined) ?? resolvedDirs[key];
|
||||||
? (settings?.claudeConfigDir ?? resolvedDirs.claude)
|
|
||||||
: key === "codex"
|
|
||||||
? (settings?.codexConfigDir ?? resolvedDirs.codex)
|
|
||||||
: key === "gemini"
|
|
||||||
? (settings?.geminiConfigDir ?? resolvedDirs.gemini)
|
|
||||||
: key === "opencode"
|
|
||||||
? (settings?.opencodeConfigDir ?? resolvedDirs.opencode)
|
|
||||||
: (settings?.openclawConfigDir ?? resolvedDirs.openclaw);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const picked = await settingsApi.selectConfigDirectory(currentValue);
|
const picked = await settingsApi.selectConfigDirectory(currentValue);
|
||||||
@@ -320,16 +311,7 @@ export function useDirectorySettings({
|
|||||||
|
|
||||||
const resetDirectory = useCallback(
|
const resetDirectory = useCallback(
|
||||||
async (app: AppId) => {
|
async (app: AppId) => {
|
||||||
const key: DirectoryKey =
|
const key = APP_DIRECTORY_META[app].key;
|
||||||
app === "claude"
|
|
||||||
? "claude"
|
|
||||||
: app === "codex"
|
|
||||||
? "codex"
|
|
||||||
: app === "gemini"
|
|
||||||
? "gemini"
|
|
||||||
: app === "opencode"
|
|
||||||
? "opencode"
|
|
||||||
: "openclaw";
|
|
||||||
if (!defaultsRef.current[key]) {
|
if (!defaultsRef.current[key]) {
|
||||||
const fallback = await computeDefaultConfigDir(app);
|
const fallback = await computeDefaultConfigDir(app);
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
@@ -358,22 +340,17 @@ export function useDirectorySettings({
|
|||||||
}, [updateDirectoryState]);
|
}, [updateDirectoryState]);
|
||||||
|
|
||||||
const resetAllDirectories = useCallback(
|
const resetAllDirectories = useCallback(
|
||||||
(
|
(overrides?: ResolvedAppDirectoryOverrides) => {
|
||||||
claudeDir?: string,
|
|
||||||
codexDir?: string,
|
|
||||||
geminiDir?: string,
|
|
||||||
opencodeDir?: string,
|
|
||||||
openclawDir?: 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: overrides?.claude ?? defaultsRef.current.claude,
|
||||||
codex: codexDir ?? defaultsRef.current.codex,
|
codex: overrides?.codex ?? defaultsRef.current.codex,
|
||||||
gemini: geminiDir ?? defaultsRef.current.gemini,
|
gemini: overrides?.gemini ?? defaultsRef.current.gemini,
|
||||||
opencode: opencodeDir ?? defaultsRef.current.opencode,
|
opencode: overrides?.opencode ?? defaultsRef.current.opencode,
|
||||||
openclaw: openclawDir ?? defaultsRef.current.openclaw,
|
openclaw: overrides?.openclaw ?? defaultsRef.current.openclaw,
|
||||||
|
hermes: overrides?.hermes ?? defaultsRef.current.hermes,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -107,13 +107,14 @@ export function useSettings(): UseSettingsResult {
|
|||||||
const resetSettings = useCallback(() => {
|
const resetSettings = useCallback(() => {
|
||||||
resetForm(data ?? null);
|
resetForm(data ?? null);
|
||||||
syncLanguage(initialLanguage);
|
syncLanguage(initialLanguage);
|
||||||
resetAllDirectories(
|
resetAllDirectories({
|
||||||
sanitizeDir(data?.claudeConfigDir),
|
claude: sanitizeDir(data?.claudeConfigDir),
|
||||||
sanitizeDir(data?.codexConfigDir),
|
codex: sanitizeDir(data?.codexConfigDir),
|
||||||
sanitizeDir(data?.geminiConfigDir),
|
gemini: sanitizeDir(data?.geminiConfigDir),
|
||||||
sanitizeDir(data?.opencodeConfigDir),
|
opencode: sanitizeDir(data?.opencodeConfigDir),
|
||||||
sanitizeDir(data?.openclawConfigDir),
|
openclaw: sanitizeDir(data?.openclawConfigDir),
|
||||||
);
|
hermes: sanitizeDir(data?.hermesConfigDir),
|
||||||
|
});
|
||||||
setRequiresRestart(false);
|
setRequiresRestart(false);
|
||||||
}, [
|
}, [
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -579,11 +579,14 @@
|
|||||||
"opencodeConfigDirDescription": "Override OpenCode configuration directory (opencode.json).",
|
"opencodeConfigDirDescription": "Override OpenCode configuration directory (opencode.json).",
|
||||||
"openclawConfigDir": "OpenClaw Configuration Directory",
|
"openclawConfigDir": "OpenClaw Configuration Directory",
|
||||||
"openclawConfigDirDescription": "Override OpenClaw configuration directory (openclaw.json).",
|
"openclawConfigDirDescription": "Override OpenClaw configuration directory (openclaw.json).",
|
||||||
|
"hermesConfigDir": "Hermes Configuration Directory",
|
||||||
|
"hermesConfigDirDescription": "Override Hermes configuration directory (config.yaml).",
|
||||||
"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",
|
"browsePlaceholderGemini": "e.g., /home/<your-username>/.gemini",
|
||||||
"browsePlaceholderOpencode": "e.g., /home/<your-username>/.config/opencode",
|
"browsePlaceholderOpencode": "e.g., /home/<your-username>/.config/opencode",
|
||||||
"browsePlaceholderOpenclaw": "e.g., /home/<your-username>/.openclaw",
|
"browsePlaceholderOpenclaw": "e.g., /home/<your-username>/.openclaw",
|
||||||
|
"browsePlaceholderHermes": "e.g., /home/<your-username>/.hermes",
|
||||||
"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",
|
||||||
@@ -1674,7 +1677,14 @@
|
|||||||
"title": "Hermes config warnings detected",
|
"title": "Hermes config warnings detected",
|
||||||
"parseFailed": "config.yaml could not be parsed as valid YAML. Fix the file before editing it here.",
|
"parseFailed": "config.yaml could not be parsed as valid YAML. Fix the file before editing it here.",
|
||||||
"configNotFound": "Hermes config.yaml not found. Create it at ~/.hermes/config.yaml or configure the path in settings.",
|
"configNotFound": "Hermes config.yaml not found. Create it at ~/.hermes/config.yaml or configure the path in settings.",
|
||||||
"envParseFailed": "The .env file could not be parsed."
|
"envParseFailed": "The .env file could not be parsed.",
|
||||||
|
"modelNoDefault": "No default model or provider is configured in the 'model' section.",
|
||||||
|
"customProvidersNotList": "custom_providers should be a YAML list (items prefixed with '-'), not a mapping.",
|
||||||
|
"modelProviderUnknown": "model.provider references a provider that is not configured.",
|
||||||
|
"modelDefaultNotInProvider": "model.default is not in the selected provider's models list.",
|
||||||
|
"duplicateProviderName": "custom_providers contains duplicate provider names — only one entry will be used.",
|
||||||
|
"duplicateProviderBaseUrl": "custom_providers contains duplicate base_urls — possible accidental copy.",
|
||||||
|
"schemaMigratedV12": "Hermes moved some providers into the 'providers:' dict. CC Switch only manages 'custom_providers:' — edit or remove those entries via Hermes Web UI."
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "Memory",
|
"title": "Memory",
|
||||||
@@ -1682,7 +1692,10 @@
|
|||||||
"userTab": "User Profile (USER.md)",
|
"userTab": "User Profile (USER.md)",
|
||||||
"usage": "{{current}} / {{limit}} characters",
|
"usage": "{{current}} / {{limit}} characters",
|
||||||
"overLimit": "Over budget — Hermes will truncate on next load",
|
"overLimit": "Over budget — Hermes will truncate on next load",
|
||||||
"disabled": "Memory is disabled in config.yaml. Enable it in Hermes config for edits to take effect.",
|
"enableOn": "Enabled",
|
||||||
|
"enableOff": "Disabled",
|
||||||
|
"disabledHint": "Hermes will skip this memory until re-enabled",
|
||||||
|
"toggleFailed": "Failed to toggle memory",
|
||||||
"saveSuccess": "Memory saved",
|
"saveSuccess": "Memory saved",
|
||||||
"saveFailed": "Failed to save memory",
|
"saveFailed": "Failed to save memory",
|
||||||
"loadFailed": "Failed to read memory file",
|
"loadFailed": "Failed to read memory file",
|
||||||
|
|||||||
@@ -579,11 +579,14 @@
|
|||||||
"opencodeConfigDirDescription": "OpenCode の設定ディレクトリ(opencode.json)を上書きします。",
|
"opencodeConfigDirDescription": "OpenCode の設定ディレクトリ(opencode.json)を上書きします。",
|
||||||
"openclawConfigDir": "OpenClaw 設定ディレクトリ",
|
"openclawConfigDir": "OpenClaw 設定ディレクトリ",
|
||||||
"openclawConfigDirDescription": "OpenClaw の設定ディレクトリ(openclaw.json)を上書きします。",
|
"openclawConfigDirDescription": "OpenClaw の設定ディレクトリ(openclaw.json)を上書きします。",
|
||||||
|
"hermesConfigDir": "Hermes 設定ディレクトリ",
|
||||||
|
"hermesConfigDirDescription": "Hermes の設定ディレクトリ(config.yaml)を上書きします。",
|
||||||
"browsePlaceholderClaude": "例: /home/<your-username>/.claude",
|
"browsePlaceholderClaude": "例: /home/<your-username>/.claude",
|
||||||
"browsePlaceholderCodex": "例: /home/<your-username>/.codex",
|
"browsePlaceholderCodex": "例: /home/<your-username>/.codex",
|
||||||
"browsePlaceholderGemini": "例: /home/<your-username>/.gemini",
|
"browsePlaceholderGemini": "例: /home/<your-username>/.gemini",
|
||||||
"browsePlaceholderOpencode": "例: /home/<your-username>/.config/opencode",
|
"browsePlaceholderOpencode": "例: /home/<your-username>/.config/opencode",
|
||||||
"browsePlaceholderOpenclaw": "例: /home/<your-username>/.openclaw",
|
"browsePlaceholderOpenclaw": "例: /home/<your-username>/.openclaw",
|
||||||
|
"browsePlaceholderHermes": "例: /home/<your-username>/.hermes",
|
||||||
"browseDirectory": "ディレクトリを選択",
|
"browseDirectory": "ディレクトリを選択",
|
||||||
"resetDefault": "デフォルトに戻す(保存後に反映)",
|
"resetDefault": "デフォルトに戻す(保存後に反映)",
|
||||||
"checkForUpdates": "アップデートを確認",
|
"checkForUpdates": "アップデートを確認",
|
||||||
@@ -1674,7 +1677,14 @@
|
|||||||
"title": "Hermes 設定の警告を検出",
|
"title": "Hermes 設定の警告を検出",
|
||||||
"parseFailed": "config.yaml を有効な YAML として解析できません。ここで編集する前にファイルを修正してください。",
|
"parseFailed": "config.yaml を有効な YAML として解析できません。ここで編集する前にファイルを修正してください。",
|
||||||
"configNotFound": "Hermes config.yaml が見つかりません。~/.hermes/config.yaml に作成するか、設定でパスを構成してください。",
|
"configNotFound": "Hermes config.yaml が見つかりません。~/.hermes/config.yaml に作成するか、設定でパスを構成してください。",
|
||||||
"envParseFailed": ".env ファイルを解析できません。"
|
"envParseFailed": ".env ファイルを解析できません。",
|
||||||
|
"modelNoDefault": "'model' セクションに既定モデルまたはプロバイダーが設定されていません。",
|
||||||
|
"customProvidersNotList": "custom_providers はマッピングではなく、YAML リスト('-' で始まる項目)である必要があります。",
|
||||||
|
"modelProviderUnknown": "model.provider が指すプロバイダーは設定に存在しません。",
|
||||||
|
"modelDefaultNotInProvider": "model.default は選択中のプロバイダーのモデル一覧に含まれていません。",
|
||||||
|
"duplicateProviderName": "custom_providers にプロバイダー名の重複があります。1 件のみ有効になります。",
|
||||||
|
"duplicateProviderBaseUrl": "custom_providers に重複した base_url があります。誤ってコピーされた可能性があります。",
|
||||||
|
"schemaMigratedV12": "Hermes は一部のプロバイダーを 'providers:' dict に移しました。CC Switch は 'custom_providers:' のみを管理します。該当する項目は Hermes Web UI で編集・削除してください。"
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "メモリ",
|
"title": "メモリ",
|
||||||
@@ -1682,7 +1692,10 @@
|
|||||||
"userTab": "ユーザープロファイル (USER.md)",
|
"userTab": "ユーザープロファイル (USER.md)",
|
||||||
"usage": "{{current}} / {{limit}} 文字",
|
"usage": "{{current}} / {{limit}} 文字",
|
||||||
"overLimit": "上限を超えました。Hermes は次回読み込み時に切り詰めます",
|
"overLimit": "上限を超えました。Hermes は次回読み込み時に切り詰めます",
|
||||||
"disabled": "Memory が config.yaml で無効化されています。Hermes 設定で有効化すると編集内容が反映されます。",
|
"enableOn": "有効",
|
||||||
|
"enableOff": "無効",
|
||||||
|
"disabledHint": "再度有効化するまで Hermes はこの記憶をスキップします",
|
||||||
|
"toggleFailed": "メモリの切り替えに失敗しました",
|
||||||
"saveSuccess": "記憶を保存しました",
|
"saveSuccess": "記憶を保存しました",
|
||||||
"saveFailed": "記憶の保存に失敗しました",
|
"saveFailed": "記憶の保存に失敗しました",
|
||||||
"loadFailed": "記憶ファイルの読み込みに失敗しました",
|
"loadFailed": "記憶ファイルの読み込みに失敗しました",
|
||||||
|
|||||||
@@ -579,11 +579,14 @@
|
|||||||
"opencodeConfigDirDescription": "覆盖 OpenCode 配置目录 (opencode.json)。",
|
"opencodeConfigDirDescription": "覆盖 OpenCode 配置目录 (opencode.json)。",
|
||||||
"openclawConfigDir": "OpenClaw 配置目录",
|
"openclawConfigDir": "OpenClaw 配置目录",
|
||||||
"openclawConfigDirDescription": "覆盖 OpenClaw 配置目录 (openclaw.json)。",
|
"openclawConfigDirDescription": "覆盖 OpenClaw 配置目录 (openclaw.json)。",
|
||||||
|
"hermesConfigDir": "Hermes 配置目录",
|
||||||
|
"hermesConfigDirDescription": "覆盖 Hermes 配置目录 (config.yaml)。",
|
||||||
"browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude",
|
"browsePlaceholderClaude": "例如:/home/<你的用户名>/.claude",
|
||||||
"browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex",
|
"browsePlaceholderCodex": "例如:/home/<你的用户名>/.codex",
|
||||||
"browsePlaceholderGemini": "例如:/home/<你的用户名>/.gemini",
|
"browsePlaceholderGemini": "例如:/home/<你的用户名>/.gemini",
|
||||||
"browsePlaceholderOpencode": "例如:/home/<你的用户名>/.config/opencode",
|
"browsePlaceholderOpencode": "例如:/home/<你的用户名>/.config/opencode",
|
||||||
"browsePlaceholderOpenclaw": "例如:/home/<你的用户名>/.openclaw",
|
"browsePlaceholderOpenclaw": "例如:/home/<你的用户名>/.openclaw",
|
||||||
|
"browsePlaceholderHermes": "例如:/home/<你的用户名>/.hermes",
|
||||||
"browseDirectory": "浏览目录",
|
"browseDirectory": "浏览目录",
|
||||||
"resetDefault": "恢复默认目录(需保存后生效)",
|
"resetDefault": "恢复默认目录(需保存后生效)",
|
||||||
"checkForUpdates": "检查更新",
|
"checkForUpdates": "检查更新",
|
||||||
@@ -1674,7 +1677,14 @@
|
|||||||
"title": "检测到 Hermes 配置警告",
|
"title": "检测到 Hermes 配置警告",
|
||||||
"parseFailed": "config.yaml 无法解析为有效 YAML。请先修复文件再在此编辑。",
|
"parseFailed": "config.yaml 无法解析为有效 YAML。请先修复文件再在此编辑。",
|
||||||
"configNotFound": "未找到 Hermes config.yaml。请在 ~/.hermes/config.yaml 创建或在设置中配置路径。",
|
"configNotFound": "未找到 Hermes config.yaml。请在 ~/.hermes/config.yaml 创建或在设置中配置路径。",
|
||||||
"envParseFailed": ".env 文件无法解析。"
|
"envParseFailed": ".env 文件无法解析。",
|
||||||
|
"modelNoDefault": "model 段未设置默认模型或供应商。",
|
||||||
|
"customProvidersNotList": "custom_providers 应为 YAML 列表(以 `-` 开头),而不是映射。",
|
||||||
|
"modelProviderUnknown": "model.provider 指向的供应商未在配置中找到。",
|
||||||
|
"modelDefaultNotInProvider": "model.default 指向的模型不在所选供应商的模型列表中。",
|
||||||
|
"duplicateProviderName": "custom_providers 中存在重复的供应商名,只会有一条生效。",
|
||||||
|
"duplicateProviderBaseUrl": "custom_providers 中存在重复的 base_url,可能是意外复制。",
|
||||||
|
"schemaMigratedV12": "Hermes 把部分供应商移到了 'providers:' dict。CC Switch 只管理 'custom_providers:',请在 Hermes Web UI 编辑或删除这些条目。"
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "记忆管理",
|
"title": "记忆管理",
|
||||||
@@ -1682,7 +1692,10 @@
|
|||||||
"userTab": "用户画像 (USER.md)",
|
"userTab": "用户画像 (USER.md)",
|
||||||
"usage": "已用 {{current}} / {{limit}} 字符",
|
"usage": "已用 {{current}} / {{limit}} 字符",
|
||||||
"overLimit": "已超过上限,Hermes 下次加载时会截断",
|
"overLimit": "已超过上限,Hermes 下次加载时会截断",
|
||||||
"disabled": "Memory 在 config.yaml 中已禁用,编辑后需在 Hermes 配置启用才会生效。",
|
"enableOn": "已启用",
|
||||||
|
"enableOff": "已禁用",
|
||||||
|
"disabledHint": "在重新启用前 Hermes 会跳过此记忆",
|
||||||
|
"toggleFailed": "切换记忆状态失败",
|
||||||
"saveSuccess": "记忆已保存",
|
"saveSuccess": "记忆已保存",
|
||||||
"saveFailed": "保存记忆失败",
|
"saveFailed": "保存记忆失败",
|
||||||
"loadFailed": "读取记忆文件失败",
|
"loadFailed": "读取记忆文件失败",
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ describe("useDirectorySettings", () => {
|
|||||||
if (app === "codex") return "/remote/codex";
|
if (app === "codex") return "/remote/codex";
|
||||||
if (app === "gemini") return "/remote/gemini";
|
if (app === "gemini") return "/remote/gemini";
|
||||||
if (app === "opencode") return "/remote/opencode";
|
if (app === "opencode") return "/remote/opencode";
|
||||||
return "/remote/openclaw";
|
if (app === "openclaw") return "/remote/openclaw";
|
||||||
|
return "/remote/hermes";
|
||||||
});
|
});
|
||||||
selectConfigDirectoryMock.mockReset();
|
selectConfigDirectoryMock.mockReset();
|
||||||
});
|
});
|
||||||
@@ -91,6 +92,7 @@ describe("useDirectorySettings", () => {
|
|||||||
gemini: "/remote/gemini",
|
gemini: "/remote/gemini",
|
||||||
opencode: "/remote/opencode",
|
opencode: "/remote/opencode",
|
||||||
openclaw: "/remote/openclaw",
|
openclaw: "/remote/openclaw",
|
||||||
|
hermes: "/remote/hermes",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -243,13 +245,13 @@ describe("useDirectorySettings", () => {
|
|||||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.resetAllDirectories(
|
result.current.resetAllDirectories({
|
||||||
"/server/claude",
|
claude: "/server/claude",
|
||||||
"/server/codex",
|
codex: "/server/codex",
|
||||||
"/server/gemini",
|
gemini: "/server/gemini",
|
||||||
"/server/opencode",
|
opencode: "/server/opencode",
|
||||||
"/server/openclaw",
|
openclaw: "/server/openclaw",
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.resolvedDirs.claude).toBe("/server/claude");
|
expect(result.current.resolvedDirs.claude).toBe("/server/claude");
|
||||||
|
|||||||
@@ -455,13 +455,14 @@ describe("useSettings hook", () => {
|
|||||||
expect(settingsFormMock.syncLanguage).toHaveBeenCalledWith(
|
expect(settingsFormMock.syncLanguage).toHaveBeenCalledWith(
|
||||||
settingsFormMock.initialLanguage,
|
settingsFormMock.initialLanguage,
|
||||||
);
|
);
|
||||||
expect(directorySettingsMock.resetAllDirectories).toHaveBeenCalledWith(
|
expect(directorySettingsMock.resetAllDirectories).toHaveBeenCalledWith({
|
||||||
"/server/claude",
|
claude: "/server/claude",
|
||||||
undefined,
|
codex: undefined,
|
||||||
"/server/gemini",
|
gemini: "/server/gemini",
|
||||||
"/server/opencode",
|
opencode: "/server/opencode",
|
||||||
"/server/openclaw",
|
openclaw: "/server/openclaw",
|
||||||
);
|
hermes: undefined,
|
||||||
|
});
|
||||||
expect(metadataMock.setRequiresRestart).toHaveBeenCalledWith(false);
|
expect(metadataMock.setRequiresRestart).toHaveBeenCalledWith(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user