diff --git a/README.md b/README.md
index 9fd3f14758..996b5b6abf 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,7 @@ Typical users of ChatALL are:
| [iFLYTEK SPARK](http://xinghuo.xfyun.cn/) | Yes | Coming soon | |
| [Kimi](https://kimi.moonshot.cn/ ) | Yes | No API | |
| [Llama 2 13B & 70B](https://ai.meta.com/llama/) | Yes | No API | |
+| [MiniMax](https://www.minimax.io/) | No | Yes | |
| [MOSS](https://moss.fastnlp.top/) | Yes | No API | |
| [Perplexity](https://www.perplexity.ai/) | Yes | No API | |
| [Phind](https://www.phind.com/) | Yes | No API | |
diff --git a/README_ZH-CN.md b/README_ZH-CN.md
index 981730859d..6806972970 100644
--- a/README_ZH-CN.md
+++ b/README_ZH-CN.md
@@ -48,6 +48,7 @@ ChatALL 的典型用户是:
| [讯飞星火](http://xinghuo.xfyun.cn/) | 支持 | 即将推出 | |
| [Kimi](https://kimi.moonshot.cn/) | 支持 | 无 API | |
| [Llama 2 13B 和 70B](https://ai.meta.com/llama/) | 支持 | 无 API | |
+| [MiniMax](https://www.minimax.io/) | 不支持 | 支持 | |
| [MOSS](https://moss.fastnlp.top/) | 支持 | 无 API | |
| [Perplexity](https://www.perplexity.ai/) | 支持 | 无 | |
| [Phind](https://www.phind.com/) | 支持 | 无 API | |
diff --git a/public/bots/minimax-logo.svg b/public/bots/minimax-logo.svg
new file mode 100644
index 0000000000..243dfc8785
--- /dev/null
+++ b/public/bots/minimax-logo.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/bots/minimax-m25-highspeed-logo.png b/public/bots/minimax-m25-highspeed-logo.png
new file mode 100644
index 0000000000..17d0c5f6d9
Binary files /dev/null and b/public/bots/minimax-m25-highspeed-logo.png differ
diff --git a/public/bots/minimax-m25-logo.png b/public/bots/minimax-m25-logo.png
new file mode 100644
index 0000000000..cfdc2c15c7
Binary files /dev/null and b/public/bots/minimax-m25-logo.png differ
diff --git a/src/bots/index.js b/src/bots/index.js
index 3f989ff94c..7bfdb34a23 100644
--- a/src/bots/index.js
+++ b/src/bots/index.js
@@ -66,6 +66,9 @@ import ClaudeAPIHaikuBot from "./anthropic/ClaudeAPIHaikuBot";
import Grok2APIBot from "./xai/Grok2APIBot";
import Grok3APIBot from "./xai/Grok3APIBot";
import Grok3MiniAPIBot from "./xai/Grok3MiniAPIBot";
+import MiniMaxM27Bot from "./minimax/MiniMaxM27Bot";
+import MiniMaxM27HighspeedBot from "./minimax/MiniMaxM27HighspeedBot";
+import MiniMaxM3Bot from "./minimax/MiniMaxM3Bot";
const all = [
Qihoo360AIBrainBot.getInstance(),
@@ -134,6 +137,9 @@ const all = [
Grok2APIBot.getInstance(),
Grok3APIBot.getInstance(),
Grok3MiniAPIBot.getInstance(),
+ MiniMaxM27Bot.getInstance(),
+ MiniMaxM27HighspeedBot.getInstance(),
+ MiniMaxM3Bot.getInstance(),
];
const disabled = ["HuggingChatBot"];
@@ -230,6 +236,9 @@ export const botTags = {
bots.getBotByClassName("Grok2APIBot"),
bots.getBotByClassName("Grok3APIBot"),
bots.getBotByClassName("Grok3MiniAPIBot"),
+ bots.getBotByClassName("MiniMaxM27Bot"),
+ bots.getBotByClassName("MiniMaxM27HighspeedBot"),
+ bots.getBotByClassName("MiniMaxM3Bot"),
],
madeInChina: [
bots.getBotByClassName("Qihoo360AIBrainBot"),
@@ -243,6 +252,9 @@ export const botTags = {
bots.getBotByClassName("ChatGLMBot"),
bots.getBotByClassName("ChatGLM4Bot"),
bots.getBotByClassName("KimiBot"),
+ bots.getBotByClassName("MiniMaxM27Bot"),
+ bots.getBotByClassName("MiniMaxM27HighspeedBot"),
+ bots.getBotByClassName("MiniMaxM3Bot"),
],
};
export default bots;
diff --git a/src/bots/minimax/MiniMaxAPIBot.js b/src/bots/minimax/MiniMaxAPIBot.js
new file mode 100644
index 0000000000..e992c5621c
--- /dev/null
+++ b/src/bots/minimax/MiniMaxAPIBot.js
@@ -0,0 +1,41 @@
+import LangChainBot from "../LangChainBot";
+import store from "@/store";
+import { ChatOpenAI } from "@langchain/openai";
+
+export default class MiniMaxAPIBot extends LangChainBot {
+ static _brandId = "minimaxApi";
+ static _className = "MiniMaxAPIBot";
+
+ constructor() {
+ super();
+ }
+
+ async _checkAvailability() {
+ let available = false;
+
+ if (store.state.minimaxApi.apiKey) {
+ this.setupModel();
+ available = true;
+ }
+ return available;
+ }
+
+ _setupModel() {
+ const chatModel = new ChatOpenAI({
+ configuration: {
+ basePath: store.state.minimaxApi.alterUrl
+ ? store.state.minimaxApi.alterUrl
+ : "https://api.minimax.io/v1",
+ },
+ openAIApiKey: store.state.minimaxApi.apiKey,
+ modelName: this.constructor._model ? this.constructor._model : "",
+ temperature: store.state.minimaxApi.temperature,
+ streaming: true,
+ });
+ return chatModel;
+ }
+
+ getPastRounds() {
+ return store.state.minimaxApi.pastRounds;
+ }
+}
diff --git a/src/bots/minimax/MiniMaxM27Bot.js b/src/bots/minimax/MiniMaxM27Bot.js
new file mode 100644
index 0000000000..3b92e54763
--- /dev/null
+++ b/src/bots/minimax/MiniMaxM27Bot.js
@@ -0,0 +1,10 @@
+import MiniMaxAPIBot from "./MiniMaxAPIBot";
+
+export default class MiniMaxM27Bot extends MiniMaxAPIBot {
+ static _className = "MiniMaxM27Bot";
+ static _logoFilename = "minimax-logo.svg";
+ static _model = "MiniMax-M2.7";
+ constructor() {
+ super();
+ }
+}
diff --git a/src/bots/minimax/MiniMaxM27HighspeedBot.js b/src/bots/minimax/MiniMaxM27HighspeedBot.js
new file mode 100644
index 0000000000..86ea57e84a
--- /dev/null
+++ b/src/bots/minimax/MiniMaxM27HighspeedBot.js
@@ -0,0 +1,10 @@
+import MiniMaxAPIBot from "./MiniMaxAPIBot";
+
+export default class MiniMaxM27HighspeedBot extends MiniMaxAPIBot {
+ static _className = "MiniMaxM27HighspeedBot";
+ static _logoFilename = "minimax-logo.svg";
+ static _model = "MiniMax-M2.7-highspeed";
+ constructor() {
+ super();
+ }
+}
diff --git a/src/bots/minimax/MiniMaxM3Bot.js b/src/bots/minimax/MiniMaxM3Bot.js
new file mode 100644
index 0000000000..d8cfe5b366
--- /dev/null
+++ b/src/bots/minimax/MiniMaxM3Bot.js
@@ -0,0 +1,10 @@
+import MiniMaxAPIBot from "./MiniMaxAPIBot";
+
+export default class MiniMaxM3Bot extends MiniMaxAPIBot {
+ static _className = "MiniMaxM3Bot";
+ static _logoFilename = "minimax-logo.svg";
+ static _model = "MiniMax-M3";
+ constructor() {
+ super();
+ }
+}
diff --git a/src/components/BotSettings/MiniMaxAPIBotSettings.vue b/src/components/BotSettings/MiniMaxAPIBotSettings.vue
new file mode 100644
index 0000000000..66206bc46b
--- /dev/null
+++ b/src/components/BotSettings/MiniMaxAPIBotSettings.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue
index b639dd4315..fb33dc7665 100644
--- a/src/components/SettingsModal.vue
+++ b/src/components/SettingsModal.vue
@@ -128,6 +128,7 @@ import { resolveTheme, applyTheme, Mode } from "../theme";
import ClaudeAPIBotSettings from "./BotSettings/ClaudeAPIBotSettings.vue";
import GroqAPIBotSettings from "./BotSettings/GroqAPIBotSettings.vue";
import xAIAPIBotSettings from "./BotSettings/xAIAPIBotSettings.vue";
+import MiniMaxAPIBotSettings from "./BotSettings/MiniMaxAPIBotSettings.vue";
const { ipcRenderer } = window.require("electron");
const { t: $t, locale } = useI18n();
@@ -156,6 +157,7 @@ const botSettings = [
{ brand: "huggingChat", component: HuggingChatBotSettings },
{ brand: "kimi", component: KimiBotSettings },
{ brand: "lmsys", component: LMSYSBotSettings },
+ { brand: "minimaxApi", component: MiniMaxAPIBotSettings },
{ brand: "mistral", component: MistralBotSettings },
{ brand: "moss", component: MOSSBotSettings },
{ brand: "openaiApi", component: OpenAIAPIBotSettings },
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index a2a895cfa3..c21d56f5ac 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index b2483a03e6..4dd53915aa 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -317,6 +317,18 @@
"gemma-7b-it": "Gemma 7b",
"gemma2-9b-it": "Gemma2 9b"
},
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ },
"xaiApi": {
"name": "xAI API",
"grok-2-latest": "Grok-2",
diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json
index 2238dac17b..2ea8bdbe88 100644
--- a/src/i18n/locales/es.json
+++ b/src/i18n/locales/es.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index e3327bb793..b98d42fc82 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json
index ad0f563897..32c58f8ab4 100644
--- a/src/i18n/locales/it.json
+++ b/src/i18n/locales/it.json
@@ -395,5 +395,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index dedb86e53d..87a9aa3742 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json
index a92bf0a5aa..4d7a797491 100644
--- a/src/i18n/locales/ko.json
+++ b/src/i18n/locales/ko.json
@@ -396,5 +396,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json
index 0f1e4adb0e..8c38a3e86a 100644
--- a/src/i18n/locales/ru.json
+++ b/src/i18n/locales/ru.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/vi.json b/src/i18n/locales/vi.json
index a68bbca440..79b8158e94 100644
--- a/src/i18n/locales/vi.json
+++ b/src/i18n/locales/vi.json
@@ -372,5 +372,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API URL",
+ "alterUrlPrompt": "Custom API endpoint URL (default: https://api.minimax.io/v1)",
+ "temperature": "Temperature",
+ "temperaturePrompt": "What sampling temperature to use, between 0.1 and 1",
+ "temperature01": "Precise",
+ "temperature1": "Creative"
+ }
}
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index e7f3ddf1bf..ce0560c693 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API 地址",
+ "alterUrlPrompt": "自定义 API 端点地址(默认:https://api.minimax.io/v1)",
+ "temperature": "温度",
+ "temperaturePrompt": "采样温度,范围 0.1 到 1",
+ "temperature01": "精确",
+ "temperature1": "创意"
+ }
}
diff --git a/src/i18n/locales/zhtw.json b/src/i18n/locales/zhtw.json
index d727742046..61344f8aa4 100644
--- a/src/i18n/locales/zhtw.json
+++ b/src/i18n/locales/zhtw.json
@@ -393,5 +393,17 @@
"10": "10",
"25": "25",
"50": "50",
- "100": "100"
+ "100": "100",
+ "minimaxApi": {
+ "name": "MiniMax API",
+ "MiniMax-M27": "MiniMax-M2.7",
+ "MiniMax-M27-highspeed": "MiniMax-M2.7-highspeed",
+ "MiniMax-M3": "MiniMax-M3",
+ "alterUrl": "API 地址",
+ "alterUrlPrompt": "自定义 API 端点地址(默认:https://api.minimax.io/v1)",
+ "temperature": "温度",
+ "temperaturePrompt": "采样温度,范围 0.1 到 1",
+ "temperature01": "精确",
+ "temperature1": "创意"
+ }
}
diff --git a/src/store/index.js b/src/store/index.js
index c48e1b5b1f..5be2184224 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -126,6 +126,12 @@ export default createStore({
apiKey: "",
pastRounds: 5,
},
+ minimaxApi: {
+ apiKey: "",
+ temperature: 1,
+ pastRounds: 5,
+ alterUrl: "",
+ },
currentChatIndex: 0,
updateCounter: 0,
theme: undefined,
@@ -277,6 +283,9 @@ export default createStore({
setXaiApi(state, values) {
state.xaiApi = { ...state.xaiApi, ...values };
},
+ setMiniMaxApi(state, values) {
+ state.minimaxApi = { ...state.minimaxApi, ...values };
+ },
setLatestPromptIndex(state, promptIndex) {
Chats.table.update(state.currentChatIndex, {
latestPromptIndex: promptIndex,
diff --git a/tests/minimax-integration.test.js b/tests/minimax-integration.test.js
new file mode 100644
index 0000000000..0ab032820d
--- /dev/null
+++ b/tests/minimax-integration.test.js
@@ -0,0 +1,282 @@
+/**
+ * Integration tests for MiniMax API via OpenAI-compatible endpoint.
+ *
+ * Requires MINIMAX_API_KEY environment variable to be set.
+ *
+ * Run: MINIMAX_API_KEY= node tests/minimax-integration.test.js
+ */
+
+const https = require("https");
+
+const API_KEY = process.env.MINIMAX_API_KEY;
+const BASE_URL = "https://api.minimax.io/v1";
+
+let passed = 0;
+let failed = 0;
+
+function assert(condition, message) {
+ if (condition) {
+ console.log(` ✓ ${message}`);
+ passed++;
+ } else {
+ console.error(` ✗ ${message}`);
+ failed++;
+ }
+}
+
+function describe(name, fn) {
+ console.log(`\n${name}`);
+ return fn();
+}
+
+function chatCompletion(model, messages, options = {}) {
+ return new Promise((resolve, reject) => {
+ const url = new URL(`${BASE_URL}/chat/completions`);
+
+ const body = JSON.stringify({
+ model,
+ messages,
+ temperature: options.temperature || 1.0,
+ max_tokens: options.max_tokens || 100,
+ stream: false,
+ });
+
+ const req = https.request(
+ url,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${API_KEY}`,
+ },
+ },
+ (res) => {
+ let data = "";
+ res.on("data", (chunk) => (data += chunk));
+ res.on("end", () => {
+ try {
+ resolve({ status: res.statusCode, body: JSON.parse(data) });
+ } catch (e) {
+ resolve({ status: res.statusCode, body: data });
+ }
+ });
+ },
+ );
+
+ req.on("error", reject);
+ req.setTimeout(60000, () => {
+ req.destroy();
+ reject(new Error("Request timed out"));
+ });
+ req.write(body);
+ req.end();
+ });
+}
+
+async function run() {
+ if (!API_KEY) {
+ console.error("ERROR: MINIMAX_API_KEY environment variable is not set");
+ process.exit(1);
+ }
+
+ // ─── Test 1: MiniMax-M3 basic completion ─────────────────────
+ await describe("MiniMax-M3 basic chat completion", async () => {
+ try {
+ const result = await chatCompletion("MiniMax-M3", [
+ { role: "user", content: "Say hello in exactly one word." },
+ ]);
+
+ assert(result.status === 200, `Status code is 200 (got ${result.status})`);
+ assert(result.body.choices, "Response has choices array");
+ assert(
+ result.body.choices.length > 0,
+ "Response has at least one choice",
+ );
+ assert(
+ result.body.choices[0].message,
+ "First choice has a message",
+ );
+ assert(
+ typeof result.body.choices[0].message.content === "string",
+ "Message content is a string",
+ );
+ assert(
+ result.body.choices[0].message.content.length > 0,
+ `Got response: "${result.body.choices[0].message.content.substring(0, 50)}"`,
+ );
+ assert(
+ result.body.model,
+ `Model in response: ${result.body.model}`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Test 2: MiniMax-M2.7 basic completion ────────────────────
+ await describe("MiniMax-M2.7 basic chat completion", async () => {
+ try {
+ const result = await chatCompletion("MiniMax-M2.7", [
+ { role: "user", content: "Say hello in exactly one word." },
+ ]);
+
+ assert(result.status === 200, `Status code is 200 (got ${result.status})`);
+ assert(result.body.choices, "Response has choices array");
+ assert(
+ result.body.choices.length > 0,
+ "Response has at least one choice",
+ );
+ assert(
+ result.body.choices[0].message,
+ "First choice has a message",
+ );
+ assert(
+ typeof result.body.choices[0].message.content === "string",
+ "Message content is a string",
+ );
+ assert(
+ result.body.choices[0].message.content.length > 0,
+ `Got response: "${result.body.choices[0].message.content.substring(0, 50)}"`,
+ );
+ assert(
+ result.body.model,
+ `Model in response: ${result.body.model}`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Test 3: MiniMax-M2.7-highspeed basic completion ─────────
+ await describe("MiniMax-M2.7-highspeed basic chat completion", async () => {
+ try {
+ const result = await chatCompletion("MiniMax-M2.7-highspeed", [
+ { role: "user", content: "What is 3+3? Answer with just the number." },
+ ]);
+
+ assert(result.status === 200, `Status code is 200 (got ${result.status})`);
+ assert(result.body.choices, "Response has choices array");
+ assert(
+ result.body.choices.length > 0,
+ "Response has at least one choice",
+ );
+ assert(
+ result.body.choices[0].message.content.length > 0,
+ `Got response: "${result.body.choices[0].message.content.substring(0, 50)}"`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Test 4: MiniMax-M2.7-highspeed alternate completion ──────────
+ await describe("MiniMax-M2.7-highspeed alternate basic chat completion", async () => {
+ try {
+ const result = await chatCompletion("MiniMax-M2.7-highspeed", [
+ { role: "user", content: "What is 2+2? Answer with just the number." },
+ ]);
+
+ assert(result.status === 200, `Status code is 200 (got ${result.status})`);
+ assert(result.body.choices, "Response has choices array");
+ assert(
+ result.body.choices.length > 0,
+ "Response has at least one choice",
+ );
+ assert(
+ result.body.choices[0].message.content.length > 0,
+ `Got response: "${result.body.choices[0].message.content.substring(0, 50)}"`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Test 5: Temperature handling ──────────────────────────────
+ await describe("Temperature handling", async () => {
+ try {
+ // Test with valid temperature (1.0)
+ const result = await chatCompletion(
+ "MiniMax-M2.7-highspeed",
+ [{ role: "user", content: "Hi" }],
+ { temperature: 1.0, max_tokens: 10 },
+ );
+ assert(
+ result.status === 200,
+ `Temperature 1.0 works (status ${result.status})`,
+ );
+
+ // Test with valid low temperature (0.1)
+ const result2 = await chatCompletion(
+ "MiniMax-M2.7-highspeed",
+ [{ role: "user", content: "Hi" }],
+ { temperature: 0.1, max_tokens: 10 },
+ );
+ assert(
+ result2.status === 200,
+ `Temperature 0.1 works (status ${result2.status})`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Test 6: Multi-turn conversation ───────────────────────────
+ await describe("Multi-turn conversation", async () => {
+ try {
+ const result = await chatCompletion("MiniMax-M2.7-highspeed", [
+ { role: "user", content: "Remember the number 42." },
+ {
+ role: "assistant",
+ content: "I'll remember the number 42.",
+ },
+ {
+ role: "user",
+ content: "What number did I ask you to remember? Reply with just the number.",
+ },
+ ]);
+
+ assert(result.status === 200, `Status code is 200 (got ${result.status})`);
+ assert(
+ result.body.choices[0].message.content.includes("42"),
+ `Response includes "42": "${result.body.choices[0].message.content.substring(0, 100)}"`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Test 7: System message ────────────────────────────────────
+ await describe("System message support", async () => {
+ try {
+ const result = await chatCompletion("MiniMax-M2.7-highspeed", [
+ {
+ role: "system",
+ content: "You are a helpful assistant. Always end your response with the word PINEAPPLE.",
+ },
+ { role: "user", content: "Say hi." },
+ ]);
+
+ assert(result.status === 200, `Status code is 200 (got ${result.status})`);
+ assert(
+ result.body.choices[0].message.content.length > 0,
+ `Got response with system message: "${result.body.choices[0].message.content.substring(0, 100)}"`,
+ );
+ } catch (err) {
+ assert(false, `Request failed: ${err.message}`);
+ }
+ });
+
+ // ─── Summary ───────────────────────────────────────────────────
+ console.log(`\n${"─".repeat(50)}`);
+ console.log(`Results: ${passed} passed, ${failed} failed`);
+ if (failed > 0) {
+ process.exit(1);
+ } else {
+ console.log("All integration tests passed!");
+ }
+}
+
+run().catch((err) => {
+ console.error("Fatal error:", err);
+ process.exit(1);
+});
diff --git a/tests/minimax-unit.test.js b/tests/minimax-unit.test.js
new file mode 100644
index 0000000000..bd79776361
--- /dev/null
+++ b/tests/minimax-unit.test.js
@@ -0,0 +1,349 @@
+/**
+ * Unit tests for MiniMax provider integration in ChatALL.
+ *
+ * Run: node tests/minimax-unit.test.js
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+let passed = 0;
+let failed = 0;
+
+function assert(condition, message) {
+ if (condition) {
+ console.log(` ✓ ${message}`);
+ passed++;
+ } else {
+ console.error(` ✗ ${message}`);
+ failed++;
+ }
+}
+
+function describe(name, fn) {
+ console.log(`\n${name}`);
+ fn();
+}
+
+// ─── Bot files exist ───────────────────────────────────────────────
+describe("Bot files exist", () => {
+ const botsDir = path.join(__dirname, "..", "src", "bots", "minimax");
+
+ assert(
+ fs.existsSync(path.join(botsDir, "MiniMaxAPIBot.js")),
+ "MiniMaxAPIBot.js exists",
+ );
+ assert(
+ fs.existsSync(path.join(botsDir, "MiniMaxM27Bot.js")),
+ "MiniMaxM27Bot.js exists",
+ );
+ assert(
+ fs.existsSync(path.join(botsDir, "MiniMaxM27HighspeedBot.js")),
+ "MiniMaxM27HighspeedBot.js exists",
+ );
+ assert(
+ fs.existsSync(path.join(botsDir, "MiniMaxM3Bot.js")),
+ "MiniMaxM3Bot.js exists",
+ );
+});
+
+// ─── Bot file contents ────────────────────────────────────────────
+describe("MiniMaxAPIBot.js contents", () => {
+ const content = fs.readFileSync(
+ path.join(__dirname, "..", "src", "bots", "minimax", "MiniMaxAPIBot.js"),
+ "utf-8",
+ );
+
+ assert(
+ content.includes('import { ChatOpenAI } from "@langchain/openai"'),
+ "Uses ChatOpenAI from @langchain/openai",
+ );
+ assert(
+ content.includes("https://api.minimax.io/v1"),
+ "Default base URL is https://api.minimax.io/v1",
+ );
+ assert(
+ content.includes("minimaxApi.apiKey"),
+ "Uses minimaxApi.apiKey from store",
+ );
+ assert(
+ content.includes("minimaxApi.temperature"),
+ "Uses minimaxApi.temperature from store",
+ );
+ assert(
+ content.includes("minimaxApi.alterUrl"),
+ "Supports custom API URL via alterUrl",
+ );
+ assert(content.includes("streaming: true"), "Streaming is enabled");
+ assert(
+ content.includes('static _brandId = "minimaxApi"'),
+ 'Brand ID is "minimaxApi"',
+ );
+});
+
+describe("MiniMaxM3Bot.js contents", () => {
+ const content = fs.readFileSync(
+ path.join(__dirname, "..", "src", "bots", "minimax", "MiniMaxM3Bot.js"),
+ "utf-8",
+ );
+
+ assert(
+ content.includes('static _model = "MiniMax-M3"'),
+ 'Model is "MiniMax-M3"',
+ );
+ assert(
+ content.includes("extends MiniMaxAPIBot"),
+ "Extends MiniMaxAPIBot",
+ );
+ assert(
+ content.includes('static _className = "MiniMaxM3Bot"'),
+ 'Class name is "MiniMaxM3Bot"',
+ );
+});
+
+describe("MiniMaxM27Bot.js contents", () => {
+ const content = fs.readFileSync(
+ path.join(__dirname, "..", "src", "bots", "minimax", "MiniMaxM27Bot.js"),
+ "utf-8",
+ );
+
+ assert(
+ content.includes('static _model = "MiniMax-M2.7"'),
+ 'Model is "MiniMax-M2.7"',
+ );
+ assert(
+ content.includes("extends MiniMaxAPIBot"),
+ "Extends MiniMaxAPIBot",
+ );
+ assert(
+ content.includes('static _className = "MiniMaxM27Bot"'),
+ 'Class name is "MiniMaxM27Bot"',
+ );
+});
+
+describe("MiniMaxM27HighspeedBot.js contents", () => {
+ const content = fs.readFileSync(
+ path.join(
+ __dirname,
+ "..",
+ "src",
+ "bots",
+ "minimax",
+ "MiniMaxM27HighspeedBot.js",
+ ),
+ "utf-8",
+ );
+
+ assert(
+ content.includes('static _model = "MiniMax-M2.7-highspeed"'),
+ 'Model is "MiniMax-M2.7-highspeed"',
+ );
+ assert(
+ content.includes("extends MiniMaxAPIBot"),
+ "Extends MiniMaxAPIBot",
+ );
+ assert(
+ content.includes('static _className = "MiniMaxM27HighspeedBot"'),
+ 'Class name is "MiniMaxM27HighspeedBot"',
+ );
+});
+
+// ─── Settings component ───────────────────────────────────────────
+describe("Settings component", () => {
+ const settingsPath = path.join(
+ __dirname,
+ "..",
+ "src",
+ "components",
+ "BotSettings",
+ "MiniMaxAPIBotSettings.vue",
+ );
+ assert(fs.existsSync(settingsPath), "MiniMaxAPIBotSettings.vue exists");
+
+ const content = fs.readFileSync(settingsPath, "utf-8");
+ assert(
+ content.includes("CommonBotSettings"),
+ "Uses CommonBotSettings component",
+ );
+ assert(
+ content.includes("apiKey"),
+ "Has API key setting",
+ );
+ assert(
+ content.includes("temperature"),
+ "Has temperature setting",
+ );
+ assert(
+ content.includes("pastRounds"),
+ "Has pastRounds setting",
+ );
+ assert(
+ content.includes("alterUrl"),
+ "Has alterUrl setting for custom API endpoint",
+ );
+});
+
+// ─── Store configuration ──────────────────────────────────────────
+describe("Store configuration", () => {
+ const storeContent = fs.readFileSync(
+ path.join(__dirname, "..", "src", "store", "index.js"),
+ "utf-8",
+ );
+
+ assert(
+ storeContent.includes("minimaxApi:"),
+ "minimaxApi state exists in store",
+ );
+ assert(
+ storeContent.includes("setMiniMaxApi"),
+ "setMiniMaxApi mutation exists",
+ );
+
+ // Check default values
+ const stateMatch = storeContent.match(
+ /minimaxApi:\s*\{[^}]+\}/s,
+ );
+ if (stateMatch) {
+ const stateBlock = stateMatch[0];
+ assert(
+ stateBlock.includes('apiKey: ""'),
+ "Default apiKey is empty string",
+ );
+ assert(
+ stateBlock.includes("temperature: 1"),
+ "Default temperature is 1",
+ );
+ assert(
+ stateBlock.includes("pastRounds: 5"),
+ "Default pastRounds is 5",
+ );
+ assert(
+ stateBlock.includes('alterUrl: ""'),
+ "Default alterUrl is empty string",
+ );
+ } else {
+ assert(false, "Could not parse minimaxApi state block");
+ }
+});
+
+// ─── Bot registration ─────────────────────────────────────────────
+describe("Bot registration in index.js", () => {
+ const indexContent = fs.readFileSync(
+ path.join(__dirname, "..", "src", "bots", "index.js"),
+ "utf-8",
+ );
+
+ assert(
+ indexContent.includes(
+ 'import MiniMaxM27Bot from "./minimax/MiniMaxM27Bot"',
+ ),
+ "MiniMaxM27Bot is imported",
+ );
+ assert(
+ indexContent.includes(
+ 'import MiniMaxM27HighspeedBot from "./minimax/MiniMaxM27HighspeedBot"',
+ ),
+ "MiniMaxM27HighspeedBot is imported",
+ );
+ assert(
+ indexContent.includes(
+ 'import MiniMaxM3Bot from "./minimax/MiniMaxM3Bot"',
+ ),
+ "MiniMaxM3Bot is imported",
+ );
+ assert(
+ indexContent.includes("MiniMaxM27Bot.getInstance()"),
+ "MiniMaxM27Bot is registered in all array",
+ );
+ assert(
+ indexContent.includes("MiniMaxM27HighspeedBot.getInstance()"),
+ "MiniMaxM27HighspeedBot is registered in all array",
+ );
+ assert(
+ indexContent.includes("MiniMaxM3Bot.getInstance()"),
+ "MiniMaxM3Bot is registered in all array",
+ );
+
+ // Check API tag
+ assert(
+ indexContent.includes('bots.getBotByClassName("MiniMaxM27Bot")'),
+ "MiniMaxM27Bot is in botTags",
+ );
+ assert(
+ indexContent.includes('bots.getBotByClassName("MiniMaxM27HighspeedBot")'),
+ "MiniMaxM27HighspeedBot is in botTags",
+ );
+ assert(
+ indexContent.includes('bots.getBotByClassName("MiniMaxM3Bot")'),
+ "MiniMaxM3Bot is in botTags",
+ );
+});
+
+// ─── i18n entries ─────────────────────────────────────────────────
+describe("i18n entries", () => {
+ const enLocale = JSON.parse(
+ fs.readFileSync(
+ path.join(__dirname, "..", "src", "i18n", "locales", "en.json"),
+ "utf-8",
+ ),
+ );
+
+ assert(enLocale.minimaxApi, "minimaxApi key exists in en.json");
+ assert(
+ enLocale.minimaxApi.name === "MiniMax API",
+ 'name is "MiniMax API"',
+ );
+ assert(
+ enLocale.minimaxApi["MiniMax-M27"] === "MiniMax-M2.7",
+ "MiniMax-M2.7 model name is correct",
+ );
+ assert(
+ enLocale.minimaxApi["MiniMax-M27-highspeed"] === "MiniMax-M2.7-highspeed",
+ "MiniMax-M2.7-highspeed model name is correct",
+ );
+ assert(
+ enLocale.minimaxApi["MiniMax-M3"] === "MiniMax-M3",
+ "MiniMax-M3 model name is correct",
+ );
+
+ // Check Chinese locale
+ const zhLocale = JSON.parse(
+ fs.readFileSync(
+ path.join(__dirname, "..", "src", "i18n", "locales", "zh.json"),
+ "utf-8",
+ ),
+ );
+ assert(zhLocale.minimaxApi, "minimaxApi key exists in zh.json");
+});
+
+// ─── Logo files ───────────────────────────────────────────────────
+describe("Logo files", () => {
+ const logosDir = path.join(__dirname, "..", "public", "bots");
+
+ assert(
+ fs.existsSync(path.join(logosDir, "minimax-logo.svg")),
+ "minimax-logo.svg exists",
+ );
+});
+
+// ─── README entries ───────────────────────────────────────────────
+describe("README entries", () => {
+ const readme = fs.readFileSync(
+ path.join(__dirname, "..", "README.md"),
+ "utf-8",
+ );
+ assert(readme.includes("MiniMax"), "MiniMax is mentioned in README.md");
+ assert(
+ readme.includes("https://www.minimax.io/"),
+ "MiniMax link is in README.md",
+ );
+});
+
+// ─── Summary ──────────────────────────────────────────────────────
+console.log(`\n${"─".repeat(50)}`);
+console.log(`Results: ${passed} passed, ${failed} failed`);
+if (failed > 0) {
+ process.exit(1);
+} else {
+ console.log("All unit tests passed!");
+}