From 95b95da6abf57ddb5f2658d41dca4cd2f3da165e Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Tue, 11 Aug 2026 11:20:06 -0400 Subject: [PATCH] refactor(openclaw): align provider model editor (#6201) Co-authored-by: Jason --- src/components/providers/ProviderActions.tsx | 85 +++- src/components/providers/ProviderCard.tsx | 14 +- src/components/providers/ProviderList.tsx | 8 +- .../providers/forms/OpenClawFormFields.tsx | 481 +++++++++--------- src/hooks/useProviderActions.ts | 29 +- src/i18n/locales/en.json | 30 +- src/i18n/locales/ja.json | 30 +- src/i18n/locales/zh-TW.json | 32 +- src/i18n/locales/zh.json | 32 +- tests/components/OpenClawFormFields.test.tsx | 84 +++ .../OpenClawProviderActions.test.tsx | 58 +++ tests/hooks/useProviderActions.test.tsx | 35 +- 12 files changed, 590 insertions(+), 328 deletions(-) create mode 100644 tests/components/OpenClawFormFields.test.tsx create mode 100644 tests/components/OpenClawProviderActions.test.tsx diff --git a/src/components/providers/ProviderActions.tsx b/src/components/providers/ProviderActions.tsx index e62d1584f..68c56166b 100644 --- a/src/components/providers/ProviderActions.tsx +++ b/src/components/providers/ProviderActions.tsx @@ -2,6 +2,7 @@ import { Activity, BarChart3, Check, + ChevronDown, Copy, Edit, Loader2, @@ -14,9 +15,21 @@ import { } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; import type { AppId } from "@/lib/api"; +interface OpenClawDefaultModelOption { + id: string; + name?: string; +} + interface ProviderActionsProps { appId?: AppId; isCurrent: boolean; @@ -41,7 +54,8 @@ interface ProviderActionsProps { isReadOnly?: boolean; // OpenClaw: default model isDefaultModel?: boolean; - onSetAsDefault?: () => void; + defaultModelOptions?: OpenClawDefaultModelOption[]; + onSetAsDefault?: (modelId?: string) => void; } // 主按钮的呈现状态。title 用于 disabled 态向用户解释为何不可点击; @@ -79,6 +93,7 @@ export function ProviderActions({ isReadOnly = false, // OpenClaw: default model isDefaultModel = false, + defaultModelOptions = [], onSetAsDefault, }: ProviderActionsProps) { const { t } = useTranslation(); @@ -240,18 +255,72 @@ export function ProviderActions({ appId === "hermes" ? t("provider.enable", { defaultValue: "启用" }) : t("provider.setAsDefault", { defaultValue: "设为默认" }); + const defaultButtonClassName = 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", + ); + + if ( + appId === "openclaw" && + !isDefaultModel && + defaultModelOptions.length > 1 + ) { + return ( + + + + + + + {t("openclaw.selectDefaultModel", { + defaultValue: "选择默认模型", + })} + + {defaultModelOptions.map((model) => ( + onSetAsDefault(model.id)} + className="flex min-w-0 flex-col items-start gap-0.5" + > + + {model.name?.trim() || model.id} + + {model.name?.trim() && model.name.trim() !== model.id && ( + + {model.id} + + )} + + ))} + + + ); + } + return ( +
- handleModelChange(index, "id", e.target.value) + onChange={(event) => + handleModelChange(index, "id", event.target.value) } placeholder={t("openclaw.modelIdPlaceholder", { defaultValue: "claude-3-sonnet", })} - className="flex-1" + aria-label={t("openclaw.modelId", { + defaultValue: "模型 ID", + })} + className="min-w-0 flex-1" /> {fetchedModels.length > 0 && ( )}
- -
- - handleModelChange(index, "name", e.target.value) + onChange={(event) => + handleModelChange(index, "name", event.target.value) } placeholder={t("openclaw.modelNamePlaceholder", { defaultValue: "Claude 3 Sonnet", })} + aria-label={t("openclaw.modelName", { + defaultValue: "显示名称", + })} + className="min-w-0 flex-1" /> -
- - - - {/* Advanced Options (Collapsible) */} - toggleModelAdvanced(index)} - > - - - - {/* Reasoning, Input Types row */} -
-
- -
+
+ + {isExpanded && ( +
+
+
+ + {t("openclaw.reasoning", { + defaultValue: "支持扩展思考", + })} + handleModelChange(index, "reasoning", checked) } /> - - {model.reasoning - ? t("openclaw.reasoningOn", { - defaultValue: "启用", - }) - : t("openclaw.reasoningOff", { - defaultValue: "关闭", - })} -
-
-
- - {/* "text" is checked by default but can be unchecked — - some models genuinely don't support text input, and - OpenClaw works fine with an empty or image-only array. */} -
+
+ + {t("openclaw.inputTypes", { + defaultValue: "输入类型", + })} + {(["text", "image"] as const).map((type) => (
-
-
- {/* Context Window and Max Tokens row */} -
-
-