mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 04:02:02 +08:00
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:
@@ -41,7 +41,8 @@ export function AddProviderDialog({
|
||||
}: AddProviderDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
// OpenCode and OpenClaw don't support universal providers
|
||||
const showUniversalTab = appId !== "opencode" && appId !== "openclaw";
|
||||
const showUniversalTab =
|
||||
appId !== "opencode" && appId !== "openclaw" && appId !== "hermes";
|
||||
const [activeTab, setActiveTab] = useState<"app-specific" | "universal">(
|
||||
"app-specific",
|
||||
);
|
||||
@@ -106,7 +107,7 @@ export function AddProviderDialog({
|
||||
|
||||
// OpenCode/OpenClaw: pass providerKey for ID generation
|
||||
if (
|
||||
(appId === "opencode" || appId === "openclaw") &&
|
||||
(appId === "opencode" || appId === "openclaw" || appId === "hermes") &&
|
||||
values.providerKey
|
||||
) {
|
||||
providerData.providerKey = values.providerKey;
|
||||
@@ -203,6 +204,10 @@ export function AddProviderDialog({
|
||||
if (parsedConfig.baseUrl) {
|
||||
addUrl(parsedConfig.baseUrl as string);
|
||||
}
|
||||
} else if (appId === "hermes") {
|
||||
if (parsedConfig.base_url) {
|
||||
addUrl(parsedConfig.base_url as string);
|
||||
}
|
||||
}
|
||||
|
||||
const urls = Array.from(urlSet);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormLabel } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ApiKeySection } from "./shared";
|
||||
import type { ProviderCategory } from "@/types";
|
||||
|
||||
interface HermesFormFieldsProps {
|
||||
baseUrl: string;
|
||||
onBaseUrlChange: (value: string) => void;
|
||||
apiKey: string;
|
||||
onApiKeyChange: (value: string) => void;
|
||||
category?: ProviderCategory;
|
||||
shouldShowApiKeyLink: boolean;
|
||||
websiteUrl: string;
|
||||
isPartner?: boolean;
|
||||
partnerPromotionKey?: string;
|
||||
}
|
||||
|
||||
export function HermesFormFields({
|
||||
baseUrl,
|
||||
onBaseUrlChange,
|
||||
apiKey,
|
||||
onApiKeyChange,
|
||||
category,
|
||||
shouldShowApiKeyLink,
|
||||
websiteUrl,
|
||||
isPartner,
|
||||
partnerPromotionKey,
|
||||
}: HermesFormFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Base URL */}
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="hermes-baseurl">
|
||||
{t("hermes.form.baseUrl", { defaultValue: "API Endpoint" })}
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="hermes-baseurl"
|
||||
value={baseUrl}
|
||||
onChange={(e) => onBaseUrlChange(e.target.value)}
|
||||
placeholder="https://api.example.com/v1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("hermes.form.baseUrlHint", {
|
||||
defaultValue: "The API endpoint URL for this provider.",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Key */}
|
||||
<ApiKeySection
|
||||
value={apiKey}
|
||||
onChange={onApiKeyChange}
|
||||
category={category}
|
||||
shouldShowLink={shouldShowApiKeyLink}
|
||||
websiteUrl={websiteUrl}
|
||||
isPartner={isPartner}
|
||||
partnerPromotionKey={partnerPromotionKey}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -37,8 +37,13 @@ import {
|
||||
type OpenClawProviderPreset,
|
||||
type OpenClawSuggestedDefaults,
|
||||
} from "@/config/openclawProviderPresets";
|
||||
import {
|
||||
hermesProviderPresets,
|
||||
type HermesProviderPreset,
|
||||
} from "@/config/hermesProviderPresets";
|
||||
import { OpenCodeFormFields } from "./OpenCodeFormFields";
|
||||
import { OpenClawFormFields } from "./OpenClawFormFields";
|
||||
import { HermesFormFields } from "./HermesFormFields";
|
||||
import type { UniversalProviderPreset } from "@/config/universalProviderPresets";
|
||||
import {
|
||||
applyTemplateValues,
|
||||
@@ -80,6 +85,7 @@ import {
|
||||
useOpencodeFormState,
|
||||
useOmoDraftState,
|
||||
useOpenclawFormState,
|
||||
useHermesFormState,
|
||||
useCopilotAuth,
|
||||
useCodexOauth,
|
||||
} from "./hooks";
|
||||
@@ -95,6 +101,7 @@ import {
|
||||
} from "./helpers/opencodeFormUtils";
|
||||
import { resolveManagedAccountId } from "@/lib/authBinding";
|
||||
import { useOpenClawLiveProviderIds } from "@/hooks/useOpenClaw";
|
||||
import { useHermesLiveProviderIds } from "@/hooks/useHermes";
|
||||
|
||||
type PresetEntry = {
|
||||
id: string;
|
||||
@@ -103,7 +110,8 @@ type PresetEntry = {
|
||||
| CodexProviderPreset
|
||||
| GeminiProviderPreset
|
||||
| OpenCodeProviderPreset
|
||||
| OpenClawProviderPreset;
|
||||
| OpenClawProviderPreset
|
||||
| HermesProviderPreset;
|
||||
};
|
||||
|
||||
interface ProviderFormProps {
|
||||
@@ -449,6 +457,11 @@ export function ProviderForm({
|
||||
id: `openclaw-${index}`,
|
||||
preset,
|
||||
}));
|
||||
} else if (appId === "hermes") {
|
||||
return hermesProviderPresets.map<PresetEntry>((preset, index) => ({
|
||||
id: `hermes-${index}`,
|
||||
preset,
|
||||
}));
|
||||
}
|
||||
return providerPresets
|
||||
.filter((p) => !p.hidden)
|
||||
@@ -644,6 +657,18 @@ export function ProviderForm({
|
||||
isLoading: isOpenclawLiveProviderIdsLoading,
|
||||
} = useOpenClawLiveProviderIds(appId === "openclaw");
|
||||
|
||||
const hermesForm = useHermesFormState({
|
||||
initialData,
|
||||
appId,
|
||||
providerId,
|
||||
onSettingsConfigChange: (config) => form.setValue("settingsConfig", config),
|
||||
getSettingsConfig: () => form.getValues("settingsConfig"),
|
||||
});
|
||||
const {
|
||||
data: hermesLiveProviderIds = [],
|
||||
isLoading: isHermesLiveProviderIdsLoading,
|
||||
} = useHermesLiveProviderIds(appId === "hermes");
|
||||
|
||||
const additiveExistingProviderKeys = useMemo(() => {
|
||||
if (appId === "opencode" && !isAnyOmoCategory) {
|
||||
return Array.from(
|
||||
@@ -666,10 +691,22 @@ export function ProviderForm({
|
||||
);
|
||||
}
|
||||
|
||||
if (appId === "hermes") {
|
||||
return Array.from(
|
||||
new Set(
|
||||
[...hermesForm.existingHermesKeys, ...hermesLiveProviderIds].filter(
|
||||
(key) => key !== providerId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [
|
||||
appId,
|
||||
existingOpencodeKeys,
|
||||
hermesForm.existingHermesKeys,
|
||||
hermesLiveProviderIds,
|
||||
isAnyOmoCategory,
|
||||
openclawForm.existingOpenclawKeys,
|
||||
openclawLiveProviderIds,
|
||||
@@ -685,11 +722,15 @@ export function ProviderForm({
|
||||
if (appId === "openclaw") {
|
||||
return isOpenclawLiveProviderIdsLoading;
|
||||
}
|
||||
if (appId === "hermes") {
|
||||
return isHermesLiveProviderIdsLoading;
|
||||
}
|
||||
return false;
|
||||
}, [
|
||||
appId,
|
||||
isAnyOmoCategory,
|
||||
isEditMode,
|
||||
isHermesLiveProviderIdsLoading,
|
||||
isOpenclawLiveProviderIdsLoading,
|
||||
isOpencodeLiveProviderIdsLoading,
|
||||
]);
|
||||
@@ -702,9 +743,13 @@ export function ProviderForm({
|
||||
if (appId === "openclaw") {
|
||||
return openclawLiveProviderIds.includes(providerId);
|
||||
}
|
||||
if (appId === "hermes") {
|
||||
return hermesLiveProviderIds.includes(providerId);
|
||||
}
|
||||
return false;
|
||||
}, [
|
||||
appId,
|
||||
hermesLiveProviderIds,
|
||||
isAnyOmoCategory,
|
||||
isEditMode,
|
||||
openclawLiveProviderIds,
|
||||
@@ -796,6 +841,34 @@ export function ProviderForm({
|
||||
}
|
||||
}
|
||||
|
||||
// Hermes: validate provider key
|
||||
if (appId === "hermes") {
|
||||
const keyPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||
if (!hermesForm.hermesProviderKey.trim()) {
|
||||
toast.error(t("hermes.form.providerKeyRequired"));
|
||||
return;
|
||||
}
|
||||
if (!keyPattern.test(hermesForm.hermesProviderKey)) {
|
||||
toast.error(t("hermes.form.providerKeyInvalid"));
|
||||
return;
|
||||
}
|
||||
if (isProviderKeyLockStateLoading) {
|
||||
toast.error(
|
||||
t("providerForm.providerKeyStatusLoading", {
|
||||
defaultValue: "正在加载供应商标识状态,请稍后再试",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!isProviderKeyLocked &&
|
||||
additiveExistingProviderKeys.includes(hermesForm.hermesProviderKey)
|
||||
) {
|
||||
toast.error(t("hermes.form.providerKeyDuplicate"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 非官方供应商必填校验:端点和 API Key
|
||||
// cloud_provider(如 Bedrock)通过模板变量处理认证,跳过通用校验
|
||||
// GitHub Copilot 使用 OAuth 认证,不需要 API Key
|
||||
@@ -968,6 +1041,8 @@ export function ProviderForm({
|
||||
}
|
||||
} else if (appId === "openclaw") {
|
||||
payload.providerKey = openclawForm.openclawProviderKey;
|
||||
} else if (appId === "hermes") {
|
||||
payload.providerKey = hermesForm.hermesProviderKey;
|
||||
}
|
||||
|
||||
if (isAnyOmoCategory && !payload.presetCategory) {
|
||||
@@ -1181,6 +1256,20 @@ export function ProviderForm({
|
||||
formWebsiteUrl: form.watch("websiteUrl") || "",
|
||||
});
|
||||
|
||||
// 使用 API Key 链接 hook (Hermes)
|
||||
const {
|
||||
shouldShowApiKeyLink: shouldShowHermesApiKeyLink,
|
||||
websiteUrl: hermesWebsiteUrl,
|
||||
isPartner: isHermesPartner,
|
||||
partnerPromotionKey: hermesPartnerPromotionKey,
|
||||
} = useApiKeyLink({
|
||||
appId: "hermes",
|
||||
category,
|
||||
selectedPresetId,
|
||||
presetEntries,
|
||||
formWebsiteUrl: form.watch("websiteUrl") || "",
|
||||
});
|
||||
|
||||
// 使用端点测速候选 hook
|
||||
const speedTestEndpoints = useSpeedTestEndpoints({
|
||||
appId,
|
||||
@@ -1212,6 +1301,9 @@ export function ProviderForm({
|
||||
if (appId === "openclaw") {
|
||||
openclawForm.resetOpenclawState();
|
||||
}
|
||||
if (appId === "hermes") {
|
||||
hermesForm.resetHermesState();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1316,6 +1408,23 @@ export function ProviderForm({
|
||||
return;
|
||||
}
|
||||
|
||||
// Hermes preset handling
|
||||
if (appId === "hermes") {
|
||||
const preset = entry.preset as HermesProviderPreset;
|
||||
const config = preset.settingsConfig;
|
||||
|
||||
hermesForm.resetHermesState(config);
|
||||
|
||||
form.reset({
|
||||
name: preset.nameKey ? t(preset.nameKey) : preset.name,
|
||||
websiteUrl: preset.websiteUrl ?? "",
|
||||
settingsConfig: JSON.stringify(config, null, 2),
|
||||
icon: preset.icon ?? "",
|
||||
iconColor: preset.iconColor ?? "",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = entry.preset as ProviderPreset;
|
||||
const config = applyTemplateValues(
|
||||
preset.settingsConfig,
|
||||
@@ -1508,6 +1617,79 @@ export function ProviderForm({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : appId === "hermes" ? (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="hermes-key">
|
||||
{t("hermes.form.providerKey", {
|
||||
defaultValue: "Provider Key",
|
||||
})}
|
||||
<span className="text-destructive ml-1">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="hermes-key"
|
||||
value={hermesForm.hermesProviderKey}
|
||||
onChange={(e) =>
|
||||
hermesForm.setHermesProviderKey(
|
||||
e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""),
|
||||
)
|
||||
}
|
||||
placeholder={t("hermes.form.providerKeyPlaceholder", {
|
||||
defaultValue: "my-provider",
|
||||
})}
|
||||
disabled={
|
||||
isProviderKeyLocked || isProviderKeyLockStateLoading
|
||||
}
|
||||
className={
|
||||
(additiveExistingProviderKeys.includes(
|
||||
hermesForm.hermesProviderKey,
|
||||
) &&
|
||||
!isProviderKeyLocked) ||
|
||||
(hermesForm.hermesProviderKey.trim() !== "" &&
|
||||
!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(
|
||||
hermesForm.hermesProviderKey,
|
||||
))
|
||||
? "border-destructive"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
{additiveExistingProviderKeys.includes(
|
||||
hermesForm.hermesProviderKey,
|
||||
) &&
|
||||
!isProviderKeyLocked && (
|
||||
<p className="text-xs text-destructive">
|
||||
{t("hermes.form.providerKeyDuplicate")}
|
||||
</p>
|
||||
)}
|
||||
{hermesForm.hermesProviderKey.trim() !== "" &&
|
||||
!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(
|
||||
hermesForm.hermesProviderKey,
|
||||
) && (
|
||||
<p className="text-xs text-destructive">
|
||||
{t("hermes.form.providerKeyInvalid")}
|
||||
</p>
|
||||
)}
|
||||
{!(
|
||||
additiveExistingProviderKeys.includes(
|
||||
hermesForm.hermesProviderKey,
|
||||
) && !isProviderKeyLocked
|
||||
) &&
|
||||
(hermesForm.hermesProviderKey.trim() === "" ||
|
||||
/^[a-z0-9]+(-[a-z0-9]+)*$/.test(
|
||||
hermesForm.hermesProviderKey,
|
||||
)) && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isProviderKeyLocked
|
||||
? t("hermes.form.providerKeyLockedHint", {
|
||||
defaultValue:
|
||||
"This provider is in Hermes config; key is locked.",
|
||||
})
|
||||
: t("hermes.form.providerKeyHint", {
|
||||
defaultValue:
|
||||
"Lowercase letters, numbers, and hyphens only. Used as the provider name in config.yaml.",
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
@@ -1701,6 +1883,21 @@ export function ProviderForm({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Hermes 专属字段 */}
|
||||
{appId === "hermes" && (
|
||||
<HermesFormFields
|
||||
baseUrl={hermesForm.hermesBaseUrl}
|
||||
onBaseUrlChange={hermesForm.handleHermesBaseUrlChange}
|
||||
apiKey={hermesForm.hermesApiKey}
|
||||
onApiKeyChange={hermesForm.handleHermesApiKeyChange}
|
||||
category={category}
|
||||
shouldShowApiKeyLink={shouldShowHermesApiKeyLink}
|
||||
websiteUrl={hermesWebsiteUrl}
|
||||
isPartner={isHermesPartner}
|
||||
partnerPromotionKey={hermesPartnerPromotionKey}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 配置编辑器:Codex、Claude、Gemini 分别使用不同的编辑器 */}
|
||||
{appId === "codex" ? (
|
||||
<>
|
||||
@@ -1784,7 +1981,7 @@ export function ProviderForm({
|
||||
</div>
|
||||
{settingsConfigErrorField}
|
||||
</>
|
||||
) : appId === "openclaw" ? (
|
||||
) : appId === "openclaw" || appId === "hermes" ? (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="settingsConfig">
|
||||
@@ -1793,12 +1990,20 @@ export function ProviderForm({
|
||||
<JsonEditor
|
||||
value={form.getValues("settingsConfig")}
|
||||
onChange={(config) => form.setValue("settingsConfig", config)}
|
||||
placeholder={`{
|
||||
placeholder={
|
||||
appId === "hermes"
|
||||
? `{
|
||||
"name": "my-provider",
|
||||
"base_url": "https://api.example.com/v1",
|
||||
"api_key": ""
|
||||
}`
|
||||
: `{
|
||||
"baseUrl": "https://api.example.com/v1",
|
||||
"apiKey": "your-api-key-here",
|
||||
"api": "openai-completions",
|
||||
"models": []
|
||||
}`}
|
||||
}`
|
||||
}
|
||||
rows={14}
|
||||
showValidation={true}
|
||||
language="json"
|
||||
@@ -1836,7 +2041,8 @@ export function ProviderForm({
|
||||
|
||||
{!isAnyOmoCategory &&
|
||||
appId !== "opencode" &&
|
||||
appId !== "openclaw" && (
|
||||
appId !== "openclaw" &&
|
||||
appId !== "hermes" && (
|
||||
<ProviderAdvancedConfig
|
||||
testConfig={testConfig}
|
||||
pricingConfig={pricingConfig}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user