Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions desktop/renderer/src/components/ModelSetupDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[] = [
{
Expand All @@ -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,
},
];
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -296,7 +310,7 @@ async function saveAndStart() {
{
id: modelName,
name: modelName,
...(family.apiFormat !== "anthropic" ? { input: ["text", "image"] } : {}),
input: ["text", "image"],
},
],
};
Expand All @@ -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;
}
Expand Down
11 changes: 7 additions & 4 deletions desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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 ---
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading