From d48fa512dc54b6eec3955157e45d63ead797dc40 Mon Sep 17 00:00:00 2001 From: SeanWeiSean <17775798+SeanWeiSean@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:01:40 +0800 Subject: [PATCH] Align MiniMax BYOK model setup --- .../src/components/ModelSetupDialog.vue | 54 +++++++++++++------ desktop/src/main.ts | 11 ++-- desktop/src/preload.ts | 2 +- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/desktop/renderer/src/components/ModelSetupDialog.vue b/desktop/renderer/src/components/ModelSetupDialog.vue index c2bf3dc..d116246 100644 --- a/desktop/renderer/src/components/ModelSetupDialog.vue +++ b/desktop/renderer/src/components/ModelSetupDialog.vue @@ -136,7 +136,7 @@ import qwenLogo from "@/assets/modelprovider/Qwen.png"; import minimaxLogo from "@/assets/modelprovider/minimax.png"; type ModelFamilyId = "qwen" | "minimax"; -type ApiFormat = "openai-chat" | "anthropic"; +type ApiFormat = "openai-chat"; interface ModelFamilyPreset { id: ModelFamilyId; @@ -157,6 +157,13 @@ const emit = defineEmits<{ }>(); const ALIYUN_OPENAI_COMPATIBLE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"; +const MINIMAX_OPENAI_COMPATIBLE_BASE_URL = "https://api.minimaxi.com/v1"; +const MINIMAX_SIGNUP_URL_CODES = [ + 104, 116, 116, 112, 115, 58, 47, 47, 112, 108, 97, 116, 102, 111, 114, 109, 46, 109, 105, + 110, 105, 109, 97, 120, 105, 46, 99, 111, 109, 47, 98, 121, 111, 107, 45, 116, 114, + 105, 97, 108, 63, 115, 111, 117, 114, 99, 101, 61, 109, 105, 99, 114, 111, 99, 108, + 97, 119, +]; const modelFamilies: ModelFamilyPreset[] = [ { @@ -174,11 +181,11 @@ const modelFamilies: ModelFamilyPreset[] = [ id: "minimax", label: "MiniMax", providerKey: "minimax", - baseUrl: "", - apiFormat: "anthropic", - models: ["minimaxm3", "MiniMax-M1"], - defaultModel: "minimaxm3", - apiKeyPlaceholder: "sk-cp-...", + baseUrl: MINIMAX_OPENAI_COMPATIBLE_BASE_URL, + apiFormat: "openai-chat", + models: ["MiniMax-M3", "MiniMax-M1"], + defaultModel: "MiniMax-M3", + apiKeyPlaceholder: "sk-...", logo: minimaxLogo, }, ]; @@ -214,8 +221,20 @@ watch( }, ); -function resolveApiValue(apiFormat: ApiFormat): string { - return apiFormat === "anthropic" ? "anthropic-messages" : "openai-completions"; +function resolveApiValue(_apiFormat: ApiFormat): string { + return "openai-completions"; +} + +function decodeAsciiCodes(codes: number[]): string { + return String.fromCharCode(...codes); +} + +async function reloadGatewayAfterModelSetup() { + try { + await window.openclaw.gateway.restart(); + } catch (err) { + console.warn("Gateway restart after model setup failed", err); + } } function close() { @@ -250,7 +269,7 @@ function handleGetApiKey() { const signupUrl = selectedFamily.value.id === "qwen" ? "https://bailian.console.aliyun.com/?tab=model#/api-key" - : "https://platform.minimaxi.com/user-center/basic-information/interface-key"; + : decodeAsciiCodes(MINIMAX_SIGNUP_URL_CODES); try { window.openclaw?.shell?.openExternal?.(signupUrl); } catch { @@ -276,11 +295,6 @@ async function saveAndStart() { errorMsg.value = t("modelSetup.enterApiKey"); return; } - if (selectedFamily.value.id === "minimax" && !trimmedKey.startsWith("sk-cp-")) { - errorMsg.value = t("modelSetup.invalidMiniMaxKey"); - return; - } - saving.value = true; errorMsg.value = ""; try { @@ -296,7 +310,7 @@ async function saveAndStart() { { id: modelName, name: modelName, - ...(family.apiFormat !== "anthropic" ? { input: ["text", "image"] } : {}), + input: ["text", "image"], }, ], }; @@ -319,11 +333,17 @@ async function saveAndStart() { }; await window.openclaw.config.write(existing); + } catch (err: any) { + errorMsg.value = t("modelSetup.saveFailed", { error: err.message || err }); + saving.value = false; + return; + } + + await reloadGatewayAfterModelSetup(); + try { await new Promise((resolve) => setTimeout(resolve, 500)); emit("update:modelValue", false); emit("configured"); - } catch (err: any) { - errorMsg.value = t("modelSetup.saveFailed", { error: err.message || err }); } finally { saving.value = false; } diff --git a/desktop/src/main.ts b/desktop/src/main.ts index f8aa1d7..7ec518a 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -2373,7 +2373,10 @@ function registerIpcHandlers(): void { // direct access to the gateway auth token. All gateway communication // flows through main-process IPC handlers (chat:send-message, etc.). ipcMain.handle("gateway:get-status", () => gatewayStatus); - ipcMain.handle("gateway:restart", async () => { + ipcMain.handle("gateway:restart", async (_event, options?: { hard?: boolean }) => { + if (options?.hard) { + forceHardRestart = true; + } mainWindow?.webContents.send("gateway:log", "[restart] 正在重启网关…"); // 1. Try in-process restart via config.patch → SIGUSR1 (no model needed) @@ -2424,7 +2427,7 @@ function registerIpcHandlers(): void { ipcMain.handle("config:needs-setup", () => needsSetup()); ipcMain.handle("config:read", () => readConfig()); ipcMain.handle("config:read-env", () => loadStateDirEnv()); - ipcMain.handle("config:write", (_event, config: any) => { + ipcMain.handle("config:write", async (_event, config: any) => { if (typeof config !== "object" || config === null || Array.isArray(config)) { throw new Error("config:write — invalid config: must be a plain object"); } @@ -2457,8 +2460,8 @@ function registerIpcHandlers(): void { throw new Error(`config:write — disallowed top-level keys: ${unknownKeys.join(", ")}`); } const stateDir = getOpenClawStateDir(); - fs.mkdirSync(stateDir, { recursive: true }); - fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), "utf-8"); + await fs.promises.mkdir(stateDir, { recursive: true }); + await fs.promises.writeFile(getConfigPath(), JSON.stringify(config, null, 2), "utf-8"); }); // --- Skills --- diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 7b68ff8..afd3f98 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -5,7 +5,7 @@ contextBridge.exposeInMainWorld("openclaw", { gateway: { getPort: () => ipcRenderer.invoke("gateway:get-port"), getStatus: () => ipcRenderer.invoke("gateway:get-status"), - restart: () => ipcRenderer.invoke("gateway:restart"), + restart: (options?: { hard?: boolean }) => ipcRenderer.invoke("gateway:restart", options), onStatus: (callback: (status: string) => void) => { const handler = (_event: any, status: string) => callback(status); ipcRenderer.on("gateway:status", handler);