mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 19:12:04 +08:00
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:
+5
-1
@@ -1021,7 +1021,11 @@ function App() {
|
|||||||
}
|
}
|
||||||
onCreate={() => setIsAddOpen(true)}
|
onCreate={() => setIsAddOpen(true)}
|
||||||
onSetAsDefault={
|
onSetAsDefault={
|
||||||
activeApp === "openclaw" ? setAsDefaultModel : undefined
|
activeApp === "openclaw"
|
||||||
|
? setAsDefaultModel
|
||||||
|
: activeApp === "hermes"
|
||||||
|
? switchProvider
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -70,9 +70,11 @@ export function ProviderActions({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const iconButtonClass = "h-8 w-8 p-1";
|
const iconButtonClass = "h-8 w-8 p-1";
|
||||||
|
|
||||||
// 累加模式应用(OpenCode 非 OMO 和 OpenClaw)
|
// 累加模式应用(OpenCode 非 OMO / OpenClaw / Hermes)
|
||||||
const isAdditiveMode =
|
const isAdditiveMode =
|
||||||
(appId === "opencode" && !isOmo) || appId === "openclaw";
|
(appId === "opencode" && !isOmo) ||
|
||||||
|
appId === "openclaw" ||
|
||||||
|
appId === "hermes";
|
||||||
|
|
||||||
// 故障转移模式下的按钮逻辑(累加模式和 OMO 应用不支持故障转移)
|
// 故障转移模式下的按钮逻辑(累加模式和 OMO 应用不支持故障转移)
|
||||||
const isFailoverMode =
|
const isFailoverMode =
|
||||||
@@ -207,25 +209,36 @@ export function ProviderActions({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
{appId === "openclaw" && isInConfig && onSetAsDefault && (
|
{(appId === "openclaw" || appId === "hermes") &&
|
||||||
<Button
|
isInConfig &&
|
||||||
size="sm"
|
onSetAsDefault &&
|
||||||
variant={isDefaultModel ? "secondary" : "default"}
|
(() => {
|
||||||
onClick={isDefaultModel ? undefined : onSetAsDefault}
|
const activeLabel =
|
||||||
disabled={isDefaultModel}
|
appId === "hermes"
|
||||||
className={cn(
|
? t("provider.inUse", { defaultValue: "已在用" })
|
||||||
"w-fit px-2.5",
|
: t("provider.isDefault", { defaultValue: "当前默认" });
|
||||||
isDefaultModel
|
const inactiveLabel =
|
||||||
? "bg-gray-200 text-muted-foreground dark:bg-gray-700 opacity-60 cursor-not-allowed"
|
appId === "hermes"
|
||||||
: "bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700",
|
? t("provider.enable", { defaultValue: "启用" })
|
||||||
)}
|
: t("provider.setAsDefault", { defaultValue: "设为默认" });
|
||||||
>
|
return (
|
||||||
<Zap className="h-4 w-4" />
|
<Button
|
||||||
{isDefaultModel
|
size="sm"
|
||||||
? t("provider.isDefault", { defaultValue: "当前默认" })
|
variant={isDefaultModel ? "secondary" : "default"}
|
||||||
: t("provider.setAsDefault", { defaultValue: "设为默认" })}
|
onClick={isDefaultModel ? undefined : onSetAsDefault}
|
||||||
</Button>
|
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
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -184,9 +184,11 @@ export function ProviderCard({
|
|||||||
provider.meta?.providerType === PROVIDER_TYPES.CODEX_OAUTH;
|
provider.meta?.providerType === PROVIDER_TYPES.CODEX_OAUTH;
|
||||||
|
|
||||||
// 获取用量数据以判断是否有多套餐
|
// 获取用量数据以判断是否有多套餐
|
||||||
// 累加模式应用(OpenCode/OpenClaw):使用 isInConfig 代替 isCurrent
|
// 累加模式应用(OpenCode/OpenClaw/Hermes):使用 isInConfig 代替 isCurrent
|
||||||
const shouldAutoQuery =
|
const shouldAutoQuery =
|
||||||
appId === "opencode" || appId === "openclaw" ? isInConfig : isCurrent;
|
appId === "opencode" || appId === "openclaw" || appId === "hermes"
|
||||||
|
? isInConfig
|
||||||
|
: isCurrent;
|
||||||
const autoQueryInterval = shouldAutoQuery
|
const autoQueryInterval = shouldAutoQuery
|
||||||
? provider.meta?.usage_script?.autoQueryInterval || 0
|
? provider.meta?.usage_script?.autoQueryInterval || 0
|
||||||
: 0;
|
: 0;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ import {
|
|||||||
useOpenClawLiveProviderIds,
|
useOpenClawLiveProviderIds,
|
||||||
useOpenClawDefaultModel,
|
useOpenClawDefaultModel,
|
||||||
} from "@/hooks/useOpenClaw";
|
} from "@/hooks/useOpenClaw";
|
||||||
import { useHermesLiveProviderIds } from "@/hooks/useHermes";
|
import {
|
||||||
|
useHermesLiveProviderIds,
|
||||||
|
useHermesModelConfig,
|
||||||
|
} from "@/hooks/useHermes";
|
||||||
import { useStreamCheck } from "@/hooks/useStreamCheck";
|
import { useStreamCheck } from "@/hooks/useStreamCheck";
|
||||||
import { ProviderCard } from "@/components/providers/ProviderCard";
|
import { ProviderCard } from "@/components/providers/ProviderCard";
|
||||||
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
|
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
|
||||||
@@ -109,6 +112,10 @@ export function ProviderList({
|
|||||||
// Hermes: 查询 live 配置中的供应商 ID 列表,用于判断 isInConfig
|
// Hermes: 查询 live 配置中的供应商 ID 列表,用于判断 isInConfig
|
||||||
const { data: hermesLiveIds } = useHermesLiveProviderIds(appId === "hermes");
|
const { data: hermesLiveIds } = useHermesLiveProviderIds(appId === "hermes");
|
||||||
|
|
||||||
|
// Hermes: 读取当前 model.provider,用于判断哪个供应商是"当前激活"(高亮)
|
||||||
|
const { data: hermesModelConfig } = useHermesModelConfig(appId === "hermes");
|
||||||
|
const hermesCurrentProviderId = hermesModelConfig?.provider;
|
||||||
|
|
||||||
// 判断供应商是否已添加到配置(累加模式应用:OpenCode/OpenClaw/Hermes)
|
// 判断供应商是否已添加到配置(累加模式应用:OpenCode/OpenClaw/Hermes)
|
||||||
const isProviderInConfig = useCallback(
|
const isProviderInConfig = useCallback(
|
||||||
(providerId: string): boolean => {
|
(providerId: string): boolean => {
|
||||||
@@ -334,6 +341,8 @@ export function ProviderList({
|
|||||||
const isOmoCurrent = isOmo && provider.id === (currentOmoId || "");
|
const isOmoCurrent = isOmo && provider.id === (currentOmoId || "");
|
||||||
const isOmoSlimCurrent =
|
const isOmoSlimCurrent =
|
||||||
isOmoSlim && provider.id === (currentOmoSlimId || "");
|
isOmoSlim && provider.id === (currentOmoSlimId || "");
|
||||||
|
const isHermesCurrent =
|
||||||
|
appId === "hermes" && hermesCurrentProviderId === provider.id;
|
||||||
return (
|
return (
|
||||||
<SortableProviderCard
|
<SortableProviderCard
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
@@ -343,7 +352,9 @@ export function ProviderList({
|
|||||||
? isOmoCurrent
|
? isOmoCurrent
|
||||||
: isOmoSlim
|
: isOmoSlim
|
||||||
? isOmoSlimCurrent
|
? isOmoSlimCurrent
|
||||||
: provider.id === currentProviderId
|
: appId === "hermes"
|
||||||
|
? isHermesCurrent
|
||||||
|
: provider.id === currentProviderId
|
||||||
}
|
}
|
||||||
appId={appId}
|
appId={appId}
|
||||||
isInConfig={isProviderInConfig(provider.id)}
|
isInConfig={isProviderInConfig(provider.id)}
|
||||||
@@ -370,8 +381,12 @@ export function ProviderList({
|
|||||||
handleToggleFailover(provider.id, enabled)
|
handleToggleFailover(provider.id, enabled)
|
||||||
}
|
}
|
||||||
activeProviderId={activeProviderId}
|
activeProviderId={activeProviderId}
|
||||||
// OpenClaw: default model
|
// OpenClaw: default model / Hermes: model.provider === provider.id
|
||||||
isDefaultModel={isProviderDefaultModel(provider.id)}
|
isDefaultModel={
|
||||||
|
appId === "hermes"
|
||||||
|
? isHermesCurrent
|
||||||
|
: isProviderDefaultModel(provider.id)
|
||||||
|
}
|
||||||
onSetAsDefault={
|
onSetAsDefault={
|
||||||
onSetAsDefault ? () => onSetAsDefault(provider) : undefined
|
onSetAsDefault ? () => onSetAsDefault(provider) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -1,4 +1,9 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
type QueryClient,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
import { hermesApi } from "@/lib/api/hermes";
|
import { hermesApi } from "@/lib/api/hermes";
|
||||||
import { providersApi } from "@/lib/api/providers";
|
import { providersApi } from "@/lib/api/providers";
|
||||||
import type {
|
import type {
|
||||||
@@ -20,6 +25,19 @@ export const hermesKeys = {
|
|||||||
health: ["hermes", "health"] as const,
|
health: ["hermes", "health"] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate all Hermes caches that may change when a provider is
|
||||||
|
* added/updated/deleted/switched. Runs invalidations in parallel so the
|
||||||
|
* caller doesn't await three sequential refetches.
|
||||||
|
*/
|
||||||
|
export function invalidateHermesProviderCaches(queryClient: QueryClient) {
|
||||||
|
return Promise.all([
|
||||||
|
queryClient.invalidateQueries({ queryKey: hermesKeys.liveProviderIds }),
|
||||||
|
queryClient.invalidateQueries({ queryKey: hermesKeys.modelConfig }),
|
||||||
|
queryClient.invalidateQueries({ queryKey: hermesKeys.health }),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Query hooks
|
// Query hooks
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { Provider, SessionMeta, Settings } from "@/types";
|
|||||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||||
import { generateUUID } from "@/utils/uuid";
|
import { generateUUID } from "@/utils/uuid";
|
||||||
import { openclawKeys } from "@/hooks/useOpenClaw";
|
import { openclawKeys } from "@/hooks/useOpenClaw";
|
||||||
import { hermesKeys } from "@/hooks/useHermes";
|
import { invalidateHermesProviderCaches } from "@/hooks/useHermes";
|
||||||
|
|
||||||
export const useAddProviderMutation = (appId: AppId) => {
|
export const useAddProviderMutation = (appId: AppId) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -77,9 +77,7 @@ export const useAddProviderMutation = (appId: AppId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appId === "hermes") {
|
if (appId === "hermes") {
|
||||||
await queryClient.invalidateQueries({
|
await invalidateHermesProviderCaches(queryClient);
|
||||||
queryKey: hermesKeys.health,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -135,9 +133,7 @@ export const useUpdateProviderMutation = (appId: AppId) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (appId === "hermes") {
|
if (appId === "hermes") {
|
||||||
await queryClient.invalidateQueries({
|
await invalidateHermesProviderCaches(queryClient);
|
||||||
queryKey: hermesKeys.health,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
toast.success(
|
toast.success(
|
||||||
t("notifications.updateSuccess", {
|
t("notifications.updateSuccess", {
|
||||||
@@ -193,9 +189,7 @@ export const useDeleteProviderMutation = (appId: AppId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appId === "hermes") {
|
if (appId === "hermes") {
|
||||||
await queryClient.invalidateQueries({
|
await invalidateHermesProviderCaches(queryClient);
|
||||||
queryKey: hermesKeys.health,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -263,12 +257,7 @@ export const useSwitchProviderMutation = (appId: AppId) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (appId === "hermes") {
|
if (appId === "hermes") {
|
||||||
await queryClient.invalidateQueries({
|
await invalidateHermesProviderCaches(queryClient);
|
||||||
queryKey: hermesKeys.liveProviderIds,
|
|
||||||
});
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: hermesKeys.health,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user