feat: add Hermes UI components, presets, and config panels (Phase 8)

- Add 7 provider presets (OpenRouter, Anthropic, OpenAI, Google, DeepSeek, Together, Nous)
- Create HermesFormFields + useHermesFormState for provider form integration
- Create Model/Agent/Env config panels with save/load functionality
- Create HermesHealthBanner for config warnings
- Add hermes icon (violet winged H) to icon system
- Integrate into App.tsx: 3 new view types (hermesModel/hermesAgent/hermesEnv),
  sidebar buttons (Brain/Bot/KeyRound), health banner, session support
- Integrate into ProviderForm: presets, form state, key validation, rendering
- Integrate into AddProviderDialog: universal tab exclusion, providerKey, base_url extraction
- Add i18n keys for all Hermes UI (zh/en/ja)
This commit is contained in:
Jason
2026-04-15 21:02:46 +08:00
parent a0b585992a
commit 240969d8c7
23 changed files with 1512 additions and 54 deletions
@@ -17,5 +17,6 @@ export { useOmoModelSource } from "./useOmoModelSource";
export { useOpencodeFormState } from "./useOpencodeFormState";
export { useOmoDraftState } from "./useOmoDraftState";
export { useOpenclawFormState } from "./useOpenclawFormState";
export { useHermesFormState } from "./useHermesFormState";
export { useCopilotAuth } from "./useCopilotAuth";
export { useCodexOauth } from "./useCodexOauth";
@@ -0,0 +1,138 @@
import { useState, useCallback, useMemo } from "react";
import type { AppId } from "@/lib/api";
import { useProvidersQuery } from "@/lib/query/queries";
interface UseHermesFormStateParams {
initialData?: {
settingsConfig?: Record<string, unknown>;
};
appId: AppId;
providerId?: string;
onSettingsConfigChange: (config: string) => void;
getSettingsConfig: () => string;
}
export const HERMES_DEFAULT_CONFIG = JSON.stringify(
{
name: "",
base_url: "",
api_key: "",
},
null,
2,
);
export interface HermesFormState {
hermesProviderKey: string;
setHermesProviderKey: (key: string) => void;
hermesBaseUrl: string;
hermesApiKey: string;
existingHermesKeys: string[];
handleHermesBaseUrlChange: (baseUrl: string) => void;
handleHermesApiKeyChange: (apiKey: string) => void;
resetHermesState: (config?: {
name?: string;
base_url?: string;
api_key?: string;
}) => void;
}
function parseHermesField<T>(
initialData: UseHermesFormStateParams["initialData"],
field: string,
fallback: T,
): T {
try {
if (initialData?.settingsConfig) {
return (initialData.settingsConfig[field] as T) || fallback;
}
const config = JSON.parse(HERMES_DEFAULT_CONFIG);
return (config[field] as T) || fallback;
} catch {
return fallback;
}
}
export function useHermesFormState({
initialData,
appId,
providerId,
onSettingsConfigChange,
getSettingsConfig,
}: UseHermesFormStateParams): HermesFormState {
const { data: hermesProvidersData } = useProvidersQuery("hermes");
const existingHermesKeys = useMemo(() => {
if (!hermesProvidersData?.providers) return [];
return Object.keys(hermesProvidersData.providers).filter(
(k) => k !== providerId,
);
}, [hermesProvidersData?.providers, providerId]);
const [hermesProviderKey, setHermesProviderKey] = useState<string>(() => {
if (appId !== "hermes") return "";
return providerId || "";
});
const [hermesBaseUrl, setHermesBaseUrl] = useState<string>(() => {
if (appId !== "hermes") return "";
return parseHermesField(initialData, "base_url", "");
});
const [hermesApiKey, setHermesApiKey] = useState<string>(() => {
if (appId !== "hermes") return "";
return parseHermesField(initialData, "api_key", "");
});
const updateHermesConfig = useCallback(
(updater: (config: Record<string, unknown>) => void) => {
try {
const config = JSON.parse(getSettingsConfig() || HERMES_DEFAULT_CONFIG);
updater(config);
onSettingsConfigChange(JSON.stringify(config, null, 2));
} catch {
// ignore
}
},
[getSettingsConfig, onSettingsConfigChange],
);
const handleHermesBaseUrlChange = useCallback(
(baseUrl: string) => {
setHermesBaseUrl(baseUrl);
updateHermesConfig((config) => {
config.base_url = baseUrl.trim().replace(/\/+$/, "");
});
},
[updateHermesConfig],
);
const handleHermesApiKeyChange = useCallback(
(apiKey: string) => {
setHermesApiKey(apiKey);
updateHermesConfig((config) => {
config.api_key = apiKey;
});
},
[updateHermesConfig],
);
const resetHermesState = useCallback(
(config?: { name?: string; base_url?: string; api_key?: string }) => {
setHermesProviderKey("");
setHermesBaseUrl(config?.base_url || "");
setHermesApiKey(config?.api_key || "");
},
[],
);
return {
hermesProviderKey,
setHermesProviderKey,
hermesBaseUrl,
hermesApiKey,
existingHermesKeys,
handleHermesBaseUrlChange,
handleHermesApiKeyChange,
resetHermesState,
};
}