From bf32a393ccf7c686d4aa94033dedf546d8772e8d Mon Sep 17 00:00:00 2001 From: octo-patch Date: Wed, 22 Apr 2026 23:41:38 +0800 Subject: [PATCH] feat: add MiniMax image-01 T2I model and document MiniMax provider support - Add minimax-image-01 to t2iModels in src/lib/models.js and packages/studio/src/models.js (8 aspect ratios, up to 4 images per request, 1500-char prompt) - Add minimax-image-01 entry to models_dump.json (t2i section) - Update README: list MiniMax Image 01 in newly-added image models and call out existing MiniMax Hailuo 02/2.3 video model support - Add scripts/test_minimax_provider.js: validates model registration and optionally runs a live API smoke test (MUAPI_KEY env var) MiniMax Hailuo 02/2.3 T2V and I2V models were already present; this commit brings MiniMax image generation to feature parity. --- README.md | 2 + models_dump.json | 42 ++++++++ packages/studio/src/models.js | 44 ++++++++ scripts/test_minimax_provider.js | 179 +++++++++++++++++++++++++++++++ src/lib/models.js | 44 ++++++++ 5 files changed, 311 insertions(+) create mode 100644 scripts/test_minimax_provider.js diff --git a/README.md b/README.md index c890932..2e63424 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ The Image Studio automatically switches between two model sets: | **Nano Banana 2 Edit** | Image-to-Image | Up to **14 reference images** · Resolution 1K/2K/4K · Google Search enhancement | | **Seedream 5.0** | Text-to-Image | ByteDance · Quality basic/high · 8 aspect ratios · up to 4K | | **Seedream 5.0 Edit** | Image-to-Image | ByteDance · Natural language style transfer · Quality basic/high | +| **MiniMax Image 01** | Text-to-Image | MiniMax · 8 aspect ratios · up to 4 images per request · 1500 char prompt | #### Multi-Image Input @@ -239,6 +240,7 @@ The Video Studio follows the same pattern: | **Seedance 2.0 Extend** | Video Extension | ByteDance · Seamlessly continue any Seedance 2.0 generation · Preserves style, motion & audio · Optional continuation prompt · Duration 5 / 10 / 15s · Quality basic/high | | **Grok Imagine T2V** | Text-to-Video | xAI · Duration 6 / 10 / **15s** · Modes: fun / normal / spicy · Aspect ratios 9:16 / 16:9 / 2:3 / 3:2 / 1:1 | | **Grok Imagine I2V** | Image-to-Video | xAI · Duration 6 / 10 / **15s** · Modes: fun / normal / spicy · Cinematic motion from still images | +| **MiniMax Hailuo 02 / 2.3 Standard & Pro** | Text-to-Video / Image-to-Video | MiniMax · Full HD video · Multiple aspect ratios · Fast variant included | ### 🎙️ Lip Sync Studio diff --git a/models_dump.json b/models_dump.json index b69a0bb..25d83a9 100644 --- a/models_dump.json +++ b/models_dump.json @@ -2001,6 +2001,48 @@ "step": 0.01 } } + }, + { + "id": "minimax-image-01", + "name": "MiniMax Image 01", + "inputs": { + "prompt": { + "type": "string", + "title": "Prompt", + "name": "prompt", + "description": "Text prompt describing the image to generate (max 1500 characters).", + "examples": [ + "A serene mountain lake at sunset with golden reflections on the water, surrounded by pine forests and snow-capped peaks, photorealistic, 8k." + ] + }, + "aspect_ratio": { + "type": "string", + "title": "Aspect Ratio", + "name": "aspect_ratio", + "description": "Aspect ratio of the output image.", + "enum": [ + "16:9", + "9:16", + "1:1", + "4:3", + "3:4", + "3:2", + "2:3", + "21:9" + ], + "default": "1:1" + }, + "num_images": { + "type": "int", + "title": "Number of images", + "name": "num_images", + "description": "Number of images to generate in a single request.", + "default": 1, + "minValue": 1, + "maxValue": 4, + "step": 1 + } + } } ] } \ No newline at end of file diff --git a/packages/studio/src/models.js b/packages/studio/src/models.js index 0a9a010..ef93a19 100644 --- a/packages/studio/src/models.js +++ b/packages/studio/src/models.js @@ -2089,6 +2089,50 @@ export const t2iModels = [ "default": "basic" } } + }, + { + "id": "minimax-image-01", + "name": "MiniMax Image 01", + "endpoint": "minimax-image-01", + "family": "minimax", + "inputs": { + "prompt": { + "type": "string", + "title": "Prompt", + "name": "prompt", + "description": "Text prompt describing the image to generate (max 1500 characters).", + "examples": [ + "A serene mountain lake at sunset with golden reflections on the water, surrounded by pine forests and snow-capped peaks, photorealistic, 8k." + ] + }, + "aspect_ratio": { + "type": "string", + "title": "Aspect Ratio", + "name": "aspect_ratio", + "description": "Aspect ratio of the output image.", + "enum": [ + "16:9", + "9:16", + "1:1", + "4:3", + "3:4", + "3:2", + "2:3", + "21:9" + ], + "default": "1:1" + }, + "num_images": { + "type": "int", + "title": "Number of images", + "name": "num_images", + "description": "Number of images to generate in a single request.", + "default": 1, + "minValue": 1, + "maxValue": 4, + "step": 1 + } + } } ]; diff --git a/scripts/test_minimax_provider.js b/scripts/test_minimax_provider.js new file mode 100644 index 0000000..270cc30 --- /dev/null +++ b/scripts/test_minimax_provider.js @@ -0,0 +1,179 @@ +/** + * Test script for MiniMax provider integration. + * + * Verifies that the MiniMax Image 01 model is correctly registered in models.js + * and that the model definition has the expected structure. + * + * Usage: + * node scripts/test_minimax_provider.js + * + * Set MUAPI_KEY env var to run the live API smoke test: + * MUAPI_KEY=your_key node scripts/test_minimax_provider.js + */ + +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, ".."); + +// ── 1. Model registration check ────────────────────────────────────────────── + +const modelsContent = readFileSync( + join(ROOT, "src", "lib", "models.js"), + "utf-8" +); + +// Extract the t2iModels JSON array via a simple regex +const t2iMatch = modelsContent.match(/export const t2iModels = (\[[\s\S]*?\]);/); +if (!t2iMatch) { + console.error("FAIL: Could not parse t2iModels from src/lib/models.js"); + process.exit(1); +} + +let t2iModels; +try { + t2iModels = JSON.parse(t2iMatch[1]); +} catch (err) { + console.error("FAIL: t2iModels is not valid JSON:", err.message); + process.exit(1); +} + +const minimaxModel = t2iModels.find((m) => m.id === "minimax-image-01"); + +if (!minimaxModel) { + console.error( + 'FAIL: "minimax-image-01" not found in t2iModels.\n' + + "Expected it to be registered in src/lib/models.js." + ); + process.exit(1); +} + +// Validate required fields +const required = ["id", "name", "endpoint", "family", "inputs"]; +for (const field of required) { + if (!minimaxModel[field]) { + console.error(`FAIL: minimax-image-01 is missing required field: ${field}`); + process.exit(1); + } +} + +if (minimaxModel.family !== "minimax") { + console.error( + `FAIL: expected family "minimax", got "${minimaxModel.family}"` + ); + process.exit(1); +} + +if (!minimaxModel.inputs.prompt) { + console.error("FAIL: minimax-image-01 inputs missing 'prompt' field"); + process.exit(1); +} + +if (!minimaxModel.inputs.aspect_ratio?.enum?.includes("1:1")) { + console.error( + "FAIL: minimax-image-01 aspect_ratio enum does not include '1:1'" + ); + process.exit(1); +} + +console.log("PASS: minimax-image-01 is correctly registered in t2iModels"); +console.log( + ` endpoint=${minimaxModel.endpoint} family=${minimaxModel.family}` +); +console.log( + ` aspect ratios: ${minimaxModel.inputs.aspect_ratio.enum.join(", ")}` +); + +// ── 2. models_dump.json check ───────────────────────────────────────────────── + +const dump = JSON.parse( + readFileSync(join(ROOT, "models_dump.json"), "utf-8") +); +const dumpEntry = dump.t2i?.find((m) => m.id === "minimax-image-01"); +if (!dumpEntry) { + console.error( + 'FAIL: "minimax-image-01" not found in models_dump.json t2i section' + ); + process.exit(1); +} +console.log("PASS: minimax-image-01 found in models_dump.json"); + +// ── 3. Live API smoke test (optional) ──────────────────────────────────────── + +const apiKey = process.env.MUAPI_KEY; +if (!apiKey) { + console.log( + "\nINFO: Skipping live API test (set MUAPI_KEY env var to enable)." + ); + console.log("\nAll checks passed."); + process.exit(0); +} + +console.log("\nRunning live API smoke test against muapi.ai …"); + +const MUAPI_BASE = "https://api.muapi.ai"; + +async function testMiniMaxImageGeneration() { + const endpoint = minimaxModel.endpoint; + const url = `${MUAPI_BASE}/api/v1/${endpoint}`; + + const payload = { + prompt: "A simple test: a red apple on a white background.", + aspect_ratio: "1:1", + num_images: 1, + }; + + console.log(`POST ${url}`); + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json", "x-api-key": apiKey }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const text = await res.text(); + console.error(`FAIL: API returned ${res.status}: ${text.slice(0, 200)}`); + process.exit(1); + } + + const data = await res.json(); + const requestId = data.request_id || data.id; + if (!requestId) { + console.error("FAIL: No request_id in response:", JSON.stringify(data)); + process.exit(1); + } + console.log(`PASS: Generation queued — request_id=${requestId}`); + + // Poll for result (max 60 s) + const pollUrl = `${MUAPI_BASE}/api/v1/predictions/${requestId}/result`; + for (let i = 0; i < 30; i++) { + await new Promise((r) => setTimeout(r, 2000)); + const poll = await fetch(pollUrl, { + headers: { "Content-Type": "application/json", "x-api-key": apiKey }, + }); + if (!poll.ok) continue; + const result = await poll.json(); + const status = result.status?.toLowerCase(); + if (status === "completed" || status === "succeeded" || status === "success") { + const imageUrl = + result.outputs?.[0] || result.url || result.output?.url; + console.log(`PASS: Generation complete — image URL: ${imageUrl}`); + console.log("\nAll checks passed."); + return; + } + if (status === "failed" || status === "error") { + console.error("FAIL: Generation failed:", result.error); + process.exit(1); + } + console.log(` Polling … status=${status}`); + } + console.error("FAIL: Timed out waiting for generation result."); + process.exit(1); +} + +testMiniMaxImageGeneration().catch((err) => { + console.error("FAIL: Unexpected error:", err.message); + process.exit(1); +}); diff --git a/src/lib/models.js b/src/lib/models.js index f4c4073..763493f 100644 --- a/src/lib/models.js +++ b/src/lib/models.js @@ -2089,6 +2089,50 @@ export const t2iModels = [ "default": "basic" } } + }, + { + "id": "minimax-image-01", + "name": "MiniMax Image 01", + "endpoint": "minimax-image-01", + "family": "minimax", + "inputs": { + "prompt": { + "type": "string", + "title": "Prompt", + "name": "prompt", + "description": "Text prompt describing the image to generate (max 1500 characters).", + "examples": [ + "A serene mountain lake at sunset with golden reflections on the water, surrounded by pine forests and snow-capped peaks, photorealistic, 8k." + ] + }, + "aspect_ratio": { + "type": "string", + "title": "Aspect Ratio", + "name": "aspect_ratio", + "description": "Aspect ratio of the output image.", + "enum": [ + "16:9", + "9:16", + "1:1", + "4:3", + "3:4", + "3:2", + "2:3", + "21:9" + ], + "default": "1:1" + }, + "num_images": { + "type": "int", + "title": "Number of images", + "name": "num_images", + "description": "Number of images to generate in a single request.", + "default": 1, + "minValue": 1, + "maxValue": 4, + "step": 1 + } + } } ];