From f1fbe324d1b2dbe75109b1b6676874017691c5dd Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 7 Feb 2026 09:37:59 +0800 Subject: [PATCH] feat(openclaw): add default model button and fix sessionsApi export - Add "Enable as Default" button to OpenClaw provider cards - Place enable button before add/remove button for better UX - Disable remove button when provider is set as default - Add sessionsApi re-export from api barrel file to fix white screen - Add i18n keys for default model feature (zh/en/ja) --- src/App.tsx | 4 ++ src/components/providers/ProviderActions.tsx | 33 ++++++++++++- src/components/providers/ProviderCard.tsx | 9 ++++ src/components/providers/ProviderList.tsx | 32 +++++++++++++ src/hooks/useProviderActions.ts | 49 +++++++++++++++++++- src/i18n/locales/en.json | 7 ++- src/i18n/locales/ja.json | 7 ++- src/i18n/locales/zh.json | 7 ++- src/lib/api/index.ts | 1 + src/lib/query/mutations.ts | 3 ++ 10 files changed, 146 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 5c237e4c5..a4af76b4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -198,6 +198,7 @@ function App() { switchProvider, deleteProvider, saveUsageScript, + setAsDefaultModel, } = useProviderActions(activeApp); const disableOmoMutation = useDisableCurrentOmo(); @@ -670,6 +671,9 @@ function App() { activeApp === "claude" ? handleOpenTerminal : undefined } onCreate={() => setIsAddOpen(true)} + onSetAsDefault={ + activeApp === "openclaw" ? setAsDefaultModel : undefined + } /> diff --git a/src/components/providers/ProviderActions.tsx b/src/components/providers/ProviderActions.tsx index b54c04a4a..d53c0a486 100644 --- a/src/components/providers/ProviderActions.tsx +++ b/src/components/providers/ProviderActions.tsx @@ -10,6 +10,7 @@ import { Terminal, TestTube2, Trash2, + Zap, } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; @@ -36,6 +37,9 @@ interface ProviderActionsProps { isAutoFailoverEnabled?: boolean; isInFailoverQueue?: boolean; onToggleFailover?: (enabled: boolean) => void; + // OpenClaw: default model + isDefaultModel?: boolean; + onSetAsDefault?: () => void; } export function ProviderActions({ @@ -58,6 +62,9 @@ export function ProviderActions({ isAutoFailoverEnabled = false, isInFailoverQueue = false, onToggleFailover, + // OpenClaw: default model + isDefaultModel = false, + onSetAsDefault, }: ProviderActionsProps) { const { t } = useTranslation(); const iconButtonClass = "h-8 w-8 p-1"; @@ -120,10 +127,12 @@ export function ProviderActions({ if (isAdditiveMode) { if (isInConfig) { return { - disabled: false, + disabled: isDefaultModel === true, variant: "secondary" as const, - className: + className: cn( "bg-orange-100 text-orange-600 hover:bg-orange-200 dark:bg-orange-900/50 dark:text-orange-400 dark:hover:bg-orange-900/70", + isDefaultModel && "opacity-40 cursor-not-allowed", + ), icon: , text: t("provider.removeFromConfig", { defaultValue: "移除" }), }; @@ -191,6 +200,26 @@ export function ProviderActions({ return (
+ {appId === "openclaw" && isInConfig && onSetAsDefault && ( + + )} +
diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index ebc4b7cf2..8c4f635cc 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -19,6 +19,7 @@ import { useQuery } from "@tanstack/react-query"; import type { Provider } from "@/types"; import type { AppId } from "@/lib/api"; import { providersApi } from "@/lib/api/providers"; +import { openclawApi } from "@/lib/api/openclaw"; import { useDragSort } from "@/hooks/useDragSort"; import { ProviderCard } from "@/components/providers/ProviderCard"; import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState"; @@ -51,6 +52,7 @@ interface ProviderListProps { isProxyRunning?: boolean; // 代理服务运行状态 isProxyTakeover?: boolean; // 代理接管模式(Live配置已被接管) activeProviderId?: string; // 代理当前实际使用的供应商 ID(用于故障转移模式下标注绿色边框) + onSetAsDefault?: (provider: Provider) => void; // OpenClaw: set as default model } export function ProviderList({ @@ -71,6 +73,7 @@ export function ProviderList({ isProxyRunning = false, isProxyTakeover = false, activeProviderId, + onSetAsDefault, }: ProviderListProps) { const { t } = useTranslation(); const { sortedProviders, sensors, handleDragEnd } = useDragSort( @@ -105,6 +108,22 @@ export function ProviderList({ [appId, opencodeLiveIds, openclawLiveIds], ); + // OpenClaw: query default model to determine which provider is default + const { data: openclawDefaultModel } = useQuery({ + queryKey: ["openclawDefaultModel"], + queryFn: () => openclawApi.getDefaultModel(), + enabled: appId === "openclaw", + }); + + const isProviderDefaultModel = useCallback( + (providerId: string): boolean => { + if (appId !== "openclaw" || !openclawDefaultModel?.primary) return false; + return openclawDefaultModel.primary.startsWith(providerId + "/"); + }, + [appId, openclawDefaultModel], + ); + + // 故障转移相关 const { data: isAutoFailoverEnabled } = useAutoFailoverEnabled(appId); const { data: failoverQueue } = useFailoverQueue(appId); const addToQueue = useAddToFailoverQueue(); @@ -253,6 +272,11 @@ export function ProviderList({ handleToggleFailover(provider.id, enabled) } activeProviderId={activeProviderId} + // OpenClaw: default model + isDefaultModel={isProviderDefaultModel(provider.id)} + onSetAsDefault={ + onSetAsDefault ? () => onSetAsDefault(provider) : undefined + } /> ); })} @@ -365,6 +389,9 @@ interface SortableProviderCardProps { isInFailoverQueue: boolean; onToggleFailover: (enabled: boolean) => void; activeProviderId?: string; + // OpenClaw: default model + isDefaultModel?: boolean; + onSetAsDefault?: () => void; } function SortableProviderCard({ @@ -392,6 +419,8 @@ function SortableProviderCard({ isInFailoverQueue, onToggleFailover, activeProviderId, + isDefaultModel, + onSetAsDefault, }: SortableProviderCardProps) { const { setNodeRef, @@ -441,6 +470,9 @@ function SortableProviderCard({ isInFailoverQueue={isInFailoverQueue} onToggleFailover={onToggleFailover} activeProviderId={activeProviderId} + // OpenClaw: default model + isDefaultModel={isDefaultModel} + onSetAsDefault={onSetAsDefault} /> ); diff --git a/src/hooks/useProviderActions.ts b/src/hooks/useProviderActions.ts index 0f1d9bb91..2f2083df8 100644 --- a/src/hooks/useProviderActions.ts +++ b/src/hooks/useProviderActions.ts @@ -3,7 +3,12 @@ import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { providersApi, settingsApi, openclawApi, type AppId } from "@/lib/api"; -import type { Provider, UsageScript } from "@/types"; +import type { + Provider, + UsageScript, + OpenClawProviderConfig, + OpenClawDefaultModel, +} from "@/types"; import type { OpenClawSuggestedDefaults } from "@/config/openclawProviderPresets"; import { useAddProviderMutation, @@ -218,12 +223,54 @@ export function useProviderActions(activeApp: AppId) { [activeApp, queryClient, t], ); + // Set provider as default model (OpenClaw only) + const setAsDefaultModel = useCallback( + async (provider: Provider) => { + const config = provider.settingsConfig as OpenClawProviderConfig; + if (!config.models || config.models.length === 0) { + toast.error( + t("notifications.openclawNoModels", { + defaultValue: "该供应商没有配置模型", + }), + ); + return; + } + + const model: OpenClawDefaultModel = { + primary: `${provider.id}/${config.models[0].id}`, + fallbacks: config.models.slice(1).map((m) => `${provider.id}/${m.id}`), + }; + + try { + await openclawApi.setDefaultModel(model); + await queryClient.invalidateQueries({ + queryKey: ["openclawDefaultModel"], + }); + toast.success( + t("notifications.openclawDefaultModelSet", { + defaultValue: "已设为默认模型", + }), + { closeButton: true }, + ); + } catch (error) { + const detail = + extractErrorMessage(error) || + t("notifications.openclawDefaultModelSetFailed", { + defaultValue: "设置默认模型失败", + }); + toast.error(detail); + } + }, + [queryClient, t], + ); + return { addProvider, updateProvider, switchProvider, deleteProvider, saveUsageScript, + setAsDefaultModel, isLoading: addProviderMutation.isPending || updateProviderMutation.isPending || diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 8d9a687ed..d9df8cbe6 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -88,6 +88,8 @@ "addOpenCodeProvider": "Add OpenCode Provider", "addToConfig": "Add", "removeFromConfig": "Remove", + "setAsDefault": "Enable", + "isDefault": "Default", "inConfig": "Added", "addProviderHint": "Fill in the information to quickly switch providers in the list.", "editClaudeProvider": "Edit Claude Code Provider", @@ -163,7 +165,10 @@ "settingsSaveFailed": "Failed to save settings: {{error}}", "openAIChatFormatHint": "This provider uses OpenAI Chat format and requires the proxy service to be enabled", "openLinkFailed": "Failed to open link", - "openclawModelsRegistered": "Models have been registered to /model list" + "openclawModelsRegistered": "Models have been registered to /model list", + "openclawDefaultModelSet": "Set as default model", + "openclawDefaultModelSetFailed": "Failed to set default model", + "openclawNoModels": "No models configured" }, "confirm": { "deleteProvider": "Delete Provider", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 3f7458300..02effd72b 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -88,6 +88,8 @@ "addOpenCodeProvider": "OpenCode プロバイダーを追加", "addToConfig": "追加", "removeFromConfig": "削除", + "setAsDefault": "有効化", + "isDefault": "デフォルト", "inConfig": "追加済み", "addProviderHint": "一覧にすばやく切り替えられるよう、ここに情報を入力してください。", "editClaudeProvider": "Claude Code プロバイダーを編集", @@ -163,7 +165,10 @@ "settingsSaveFailed": "設定の保存に失敗しました: {{error}}", "openAIChatFormatHint": "このプロバイダーは OpenAI Chat フォーマットを使用しており、プロキシサービスの有効化が必要です", "openLinkFailed": "リンクを開けませんでした", - "openclawModelsRegistered": "モデルが /model リストに登録されました" + "openclawModelsRegistered": "モデルが /model リストに登録されました", + "openclawDefaultModelSet": "デフォルトモデルに設定しました", + "openclawDefaultModelSetFailed": "デフォルトモデルの設定に失敗しました", + "openclawNoModels": "モデルが設定されていません" }, "confirm": { "deleteProvider": "プロバイダーを削除", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 3d9d9ec54..c4012ac51 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -88,6 +88,8 @@ "addOpenCodeProvider": "添加 OpenCode 供应商", "addToConfig": "添加", "removeFromConfig": "移除", + "setAsDefault": "启用", + "isDefault": "默认", "inConfig": "已添加", "addProviderHint": "填写信息后即可在列表中快速切换供应商。", "editClaudeProvider": "编辑 Claude Code 供应商", @@ -163,7 +165,10 @@ "settingsSaveFailed": "保存设置失败:{{error}}", "openAIChatFormatHint": "此供应商使用 OpenAI Chat 格式,需要开启代理服务才能正常使用", "openLinkFailed": "链接打开失败", - "openclawModelsRegistered": "模型已注册到 /model 列表" + "openclawModelsRegistered": "模型已注册到 /model 列表", + "openclawDefaultModelSet": "已设为默认模型", + "openclawDefaultModelSetFailed": "设置默认模型失败", + "openclawNoModels": "该供应商没有配置模型" }, "confirm": { "deleteProvider": "删除供应商", diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 38d6b09e2..6a797f1ea 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -8,6 +8,7 @@ export { usageApi } from "./usage"; export { vscodeApi } from "./vscode"; export { proxyApi } from "./proxy"; export { openclawApi } from "./openclaw"; +export { sessionsApi } from "./sessions"; export * as configApi from "./config"; export type { ProviderSwitchEvent } from "./providers"; export type { Prompt } from "./prompts"; diff --git a/src/lib/query/mutations.ts b/src/lib/query/mutations.ts index c5a263deb..da83c204f 100644 --- a/src/lib/query/mutations.ts +++ b/src/lib/query/mutations.ts @@ -187,6 +187,9 @@ export const useSwitchProviderMutation = (appId: AppId) => { await queryClient.invalidateQueries({ queryKey: ["openclawLiveProviderIds"], }); + await queryClient.invalidateQueries({ + queryKey: ["openclawDefaultModel"], + }); } try {