fix(hermes): show active provider and wire add/enable/remove actions

Switching a Hermes provider previously only fired a toast because the frontend treated Hermes as non-additive (unlike backend AppType::is_additive_mode, which lists OpenCode | OpenClaw | Hermes) and relied on the unused is_current DB flag for highlighting. Align the UI model with the backend:

- Include Hermes in ProviderActions' isAdditiveMode so the main button switches between "Add" and "Remove".
- Drive the "current" highlight from model.provider (via useHermesModelConfig) instead of the DB is_current field; model.provider is Hermes's real SSOT for the active provider.
- Reuse OpenClaw's set-as-default button slot to expose an "Enable" action on Hermes that calls switchProvider, so providers already in config can be activated without re-adding. switch_normal + apply_switch_defaults already atomically update custom_providers and model.provider, so no backend change is needed.
- Invalidate liveProviderIds + modelConfig + health in parallel after add/update/delete/switch via a new invalidateHermesProviderCaches helper, replacing four copies of three sequential awaits.
This commit is contained in:
Jason
2026-04-19 09:32:43 +08:00
parent 5056978d80
commit 828ec2ce07
6 changed files with 86 additions and 45 deletions
+34 -21
View File
@@ -70,9 +70,11 @@ export function ProviderActions({
const { t } = useTranslation();
const iconButtonClass = "h-8 w-8 p-1";
// 累加模式应用(OpenCode 非 OMO OpenClaw
// 累加模式应用(OpenCode 非 OMO / OpenClaw / Hermes
const isAdditiveMode =
(appId === "opencode" && !isOmo) || appId === "openclaw";
(appId === "opencode" && !isOmo) ||
appId === "openclaw" ||
appId === "hermes";
// 故障转移模式下的按钮逻辑(累加模式和 OMO 应用不支持故障转移)
const isFailoverMode =
@@ -207,25 +209,36 @@ export function ProviderActions({
return (
<div className="flex items-center gap-1.5">
{appId === "openclaw" && isInConfig && onSetAsDefault && (
<Button
size="sm"
variant={isDefaultModel ? "secondary" : "default"}
onClick={isDefaultModel ? undefined : onSetAsDefault}
disabled={isDefaultModel}
className={cn(
"w-fit px-2.5",
isDefaultModel
? "bg-gray-200 text-muted-foreground dark:bg-gray-700 opacity-60 cursor-not-allowed"
: "bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700",
)}
>
<Zap className="h-4 w-4" />
{isDefaultModel
? t("provider.isDefault", { defaultValue: "当前默认" })
: t("provider.setAsDefault", { defaultValue: "设为默认" })}
</Button>
)}
{(appId === "openclaw" || appId === "hermes") &&
isInConfig &&
onSetAsDefault &&
(() => {
const activeLabel =
appId === "hermes"
? t("provider.inUse", { defaultValue: "已在用" })
: t("provider.isDefault", { defaultValue: "当前默认" });
const inactiveLabel =
appId === "hermes"
? t("provider.enable", { defaultValue: "启用" })
: t("provider.setAsDefault", { defaultValue: "设为默认" });
return (
<Button
size="sm"
variant={isDefaultModel ? "secondary" : "default"}
onClick={isDefaultModel ? undefined : onSetAsDefault}
disabled={isDefaultModel}
className={cn(
"w-fit px-2.5",
isDefaultModel
? "bg-gray-200 text-muted-foreground dark:bg-gray-700 opacity-60 cursor-not-allowed"
: "bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700",
)}
>
<Zap className="h-4 w-4" />
{isDefaultModel ? activeLabel : inactiveLabel}
</Button>
);
})()}
<Button
size="sm"
+4 -2
View File
@@ -184,9 +184,11 @@ export function ProviderCard({
provider.meta?.providerType === PROVIDER_TYPES.CODEX_OAUTH;
// 获取用量数据以判断是否有多套餐
// 累加模式应用(OpenCode/OpenClaw):使用 isInConfig 代替 isCurrent
// 累加模式应用(OpenCode/OpenClaw/Hermes):使用 isInConfig 代替 isCurrent
const shouldAutoQuery =
appId === "opencode" || appId === "openclaw" ? isInConfig : isCurrent;
appId === "opencode" || appId === "openclaw" || appId === "hermes"
? isInConfig
: isCurrent;
const autoQueryInterval = shouldAutoQuery
? provider.meta?.usage_script?.autoQueryInterval || 0
: 0;
+19 -4
View File
@@ -25,7 +25,10 @@ import {
useOpenClawLiveProviderIds,
useOpenClawDefaultModel,
} from "@/hooks/useOpenClaw";
import { useHermesLiveProviderIds } from "@/hooks/useHermes";
import {
useHermesLiveProviderIds,
useHermesModelConfig,
} from "@/hooks/useHermes";
import { useStreamCheck } from "@/hooks/useStreamCheck";
import { ProviderCard } from "@/components/providers/ProviderCard";
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
@@ -109,6 +112,10 @@ export function ProviderList({
// Hermes: 查询 live 配置中的供应商 ID 列表,用于判断 isInConfig
const { data: hermesLiveIds } = useHermesLiveProviderIds(appId === "hermes");
// Hermes: 读取当前 model.provider,用于判断哪个供应商是"当前激活"(高亮)
const { data: hermesModelConfig } = useHermesModelConfig(appId === "hermes");
const hermesCurrentProviderId = hermesModelConfig?.provider;
// 判断供应商是否已添加到配置(累加模式应用:OpenCode/OpenClaw/Hermes
const isProviderInConfig = useCallback(
(providerId: string): boolean => {
@@ -334,6 +341,8 @@ export function ProviderList({
const isOmoCurrent = isOmo && provider.id === (currentOmoId || "");
const isOmoSlimCurrent =
isOmoSlim && provider.id === (currentOmoSlimId || "");
const isHermesCurrent =
appId === "hermes" && hermesCurrentProviderId === provider.id;
return (
<SortableProviderCard
key={provider.id}
@@ -343,7 +352,9 @@ export function ProviderList({
? isOmoCurrent
: isOmoSlim
? isOmoSlimCurrent
: provider.id === currentProviderId
: appId === "hermes"
? isHermesCurrent
: provider.id === currentProviderId
}
appId={appId}
isInConfig={isProviderInConfig(provider.id)}
@@ -370,8 +381,12 @@ export function ProviderList({
handleToggleFailover(provider.id, enabled)
}
activeProviderId={activeProviderId}
// OpenClaw: default model
isDefaultModel={isProviderDefaultModel(provider.id)}
// OpenClaw: default model / Hermes: model.provider === provider.id
isDefaultModel={
appId === "hermes"
? isHermesCurrent
: isProviderDefaultModel(provider.id)
}
onSetAsDefault={
onSetAsDefault ? () => onSetAsDefault(provider) : undefined
}