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 {