diff --git a/src/components/hermes/HermesHealthBanner.tsx b/src/components/hermes/HermesHealthBanner.tsx index bf6875c6f..81723bd2c 100644 --- a/src/components/hermes/HermesHealthBanner.tsx +++ b/src/components/hermes/HermesHealthBanner.tsx @@ -30,6 +30,41 @@ function getWarningText( return t("hermes.health.envParseFailed", { defaultValue: "The .env file could not be parsed.", }); + case "model_no_default": + return t("hermes.health.modelNoDefault", { + defaultValue: + "No default model or provider is configured in the 'model' section.", + }); + case "custom_providers_not_list": + return t("hermes.health.customProvidersNotList", { + defaultValue: + "custom_providers should be a YAML list (items prefixed with '-'), not a mapping.", + }); + case "model_provider_unknown": + return t("hermes.health.modelProviderUnknown", { + defaultValue: + "model.provider references a provider that is not configured.", + }); + case "model_default_not_in_provider": + return t("hermes.health.modelDefaultNotInProvider", { + defaultValue: + "model.default is not in the selected provider's models list.", + }); + case "duplicate_provider_name": + return t("hermes.health.duplicateProviderName", { + defaultValue: + "custom_providers contains duplicate provider names — only one entry will be used.", + }); + case "duplicate_provider_base_url": + return t("hermes.health.duplicateProviderBaseUrl", { + defaultValue: + "custom_providers contains duplicate base_urls — possible accidental copy.", + }); + case "schema_migrated_v12": + return t("hermes.health.schemaMigratedV12", { + defaultValue: + "Hermes moved some providers into the 'providers:' dict. CC Switch only manages 'custom_providers:' — edit or remove those entries via Hermes Web UI.", + }); default: return fallback; } diff --git a/src/components/hermes/HermesMemoryPanel.tsx b/src/components/hermes/HermesMemoryPanel.tsx index 3e8417bc7..ad84c1c33 100644 --- a/src/components/hermes/HermesMemoryPanel.tsx +++ b/src/components/hermes/HermesMemoryPanel.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { ExternalLink } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import MarkdownEditor from "@/components/MarkdownEditor"; import { @@ -10,6 +11,7 @@ import { useHermesMemoryLimits, useOpenHermesWebUI, useSaveHermesMemory, + useToggleHermesMemoryEnabled, } from "@/hooks/useHermes"; import { useDarkMode } from "@/hooks/useDarkMode"; import type { HermesMemoryKind } from "@/types"; @@ -30,6 +32,7 @@ const MemoryTabPane: React.FC = ({ const darkMode = useDarkMode(); const { data, isLoading } = useHermesMemory(kind, true); const saveMutation = useSaveHermesMemory(); + const toggleMutation = useToggleHermesMemoryEnabled(); const [content, setContent] = useState(""); const [loaded, setLoaded] = useState(false); @@ -57,11 +60,34 @@ const MemoryTabPane: React.FC = ({ return (
- {!enabled && ( -
- {t("hermes.memory.disabled")} +
+
+ + toggleMutation.mutate({ kind, enabled: next }) + } + /> + + {enabled + ? t("hermes.memory.enableOn") + : t("hermes.memory.enableOff")} +
- )} + {!enabled && ( + + {t("hermes.memory.disabledHint")} + + )} +
{isLoading && !loaded ? (
diff --git a/src/hooks/useHermes.ts b/src/hooks/useHermes.ts index 1576224c0..1b1c9174c 100644 --- a/src/hooks/useHermes.ts +++ b/src/hooks/useHermes.ts @@ -125,6 +125,34 @@ export function useSaveHermesMemory() { }); } +/** + * Toggle one memory blob's on/off flag in Hermes' `config.yaml`. Invalidates + * the limits query so the switch UI and disabled banner update immediately. + */ +export function useToggleHermesMemoryEnabled() { + const queryClient = useQueryClient(); + const { t } = useTranslation(); + return useMutation({ + mutationFn: ({ + kind, + enabled, + }: { + kind: HermesMemoryKind; + enabled: boolean; + }) => hermesApi.setMemoryEnabled(kind, enabled), + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: hermesKeys.memoryLimits, + }); + }, + onError: (error) => { + toast.error(t("hermes.memory.toggleFailed"), { + description: extractErrorMessage(error) || undefined, + }); + }, + }); +} + /** * Returns a handler that probes the local Hermes Web UI, opens it in the * system browser, and surfaces a localized toast on failure. Callers only diff --git a/src/lib/api/hermes.ts b/src/lib/api/hermes.ts index a11e37923..3fba5c953 100644 --- a/src/lib/api/hermes.ts +++ b/src/lib/api/hermes.ts @@ -53,4 +53,15 @@ export const hermesApi = { async getMemoryLimits(): Promise { return await invoke("get_hermes_memory_limits"); }, + + /** + * Toggle the on/off flag for one memory blob. Other fields in the `memory:` + * section (budgets, external provider config) are preserved. + */ + async setMemoryEnabled( + kind: HermesMemoryKind, + enabled: boolean, + ): Promise { + await invoke("set_hermes_memory_enabled", { kind, enabled }); + }, };