refactor: 完整重构,支持 MiniMax 全部文生图参数
- 新增 image-01 / image-01-live 双模型切换 - image-01-live 支持画风类型 + 权重 - 支持生成数量 (n=1-9)、随机种子、自定义分辨率 - 支持自动优化 prompt、AI 水印开关 - 支持 URL / Base64 双输出格式 - 任务 ID + 成功/失败计数显示 - 错误码友好提示(限流/余额/敏感内容等) - Node.js 22 内置 fetch 替代 node-fetch
This commit is contained in:
144
app.js
144
app.js
@@ -1,18 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
// ============================================================
|
||||
// Config
|
||||
// ============================================================
|
||||
|
||||
const express = require('express');
|
||||
const fetch = require('node-fetch');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const PORT = 8195;
|
||||
const HOST = '0.0.0.0';
|
||||
|
||||
// Config file stored alongside app.js
|
||||
const CONFIG_FILE = path.join(__dirname, 'config.json');
|
||||
|
||||
function loadConfig() {
|
||||
@@ -27,22 +21,15 @@ function saveConfig(cfg) {
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// App
|
||||
// ============================================================
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(express.static(__dirname));
|
||||
|
||||
// --- Settings ---
|
||||
// ── Config ──────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/config', (req, res) => {
|
||||
const cfg = loadConfig();
|
||||
res.json({
|
||||
hasApiKey: !!cfg.apiKey,
|
||||
baseUrl: cfg.baseUrl || 'https://api.minimax.io',
|
||||
});
|
||||
res.json({ hasApiKey: !!cfg.apiKey, baseUrl: cfg.baseUrl || 'https://api.minimaxi.com' });
|
||||
});
|
||||
|
||||
app.post('/api/config', (req, res) => {
|
||||
@@ -50,66 +37,135 @@ app.post('/api/config', (req, res) => {
|
||||
if (typeof apiKey !== 'string' || typeof baseUrl !== 'string') {
|
||||
return res.status(400).json({ error: '参数格式错误' });
|
||||
}
|
||||
const cfg = { apiKey: apiKey.trim(), baseUrl: baseUrl.trim() || 'https://api.minimax.io' };
|
||||
const cfg = { apiKey: apiKey.trim(), baseUrl: baseUrl.trim() || 'https://api.minimaxi.com' };
|
||||
saveConfig(cfg);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// --- Image generation ---
|
||||
// ── Image Generation ─────────────────────────────────────────────
|
||||
|
||||
app.post('/api/generate', async (req, res) => {
|
||||
const { prompt, aspect_ratio, model } = req.body;
|
||||
const {
|
||||
model, prompt, style, aspect_ratio,
|
||||
width, height, response_format, seed,
|
||||
n, prompt_optimizer, aigc_watermark,
|
||||
} = req.body;
|
||||
|
||||
// Validation
|
||||
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
||||
return res.status(400).json({ error: '请输入图片描述内容' });
|
||||
return res.status(400).json({ error: '请输入图片描述' });
|
||||
}
|
||||
if (model !== 'image-01' && model !== 'image-01-live') {
|
||||
return res.status(400).json({ error: 'model 参数无效' });
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
if (!cfg.apiKey) {
|
||||
return res.status(400).json({ error: '未配置 API Key,请先在设置中填写 MiniMax API Key。' });
|
||||
return res.status(400).json({ error: '未配置 API Key,请先在设置中填写。' });
|
||||
}
|
||||
|
||||
const baseUrl = cfg.baseUrl || 'https://api.minimax.io';
|
||||
const endpoint = `${baseUrl}/v1/image_generation`;
|
||||
|
||||
// Build payload — only include optional fields that are set
|
||||
const payload = {
|
||||
model: model || 'image-01',
|
||||
prompt: prompt.trim(),
|
||||
aspect_ratio: aspect_ratio || '1:1',
|
||||
response_format: 'base64',
|
||||
model,
|
||||
prompt: prompt.trim(),
|
||||
response_format: response_format || 'url',
|
||||
};
|
||||
|
||||
if (aspect_ratio) payload.aspect_ratio = aspect_ratio;
|
||||
if (width && height) { payload.width = width; payload.height = height; }
|
||||
if (seed) payload.seed = seed;
|
||||
if (n) payload.n = Math.min(Math.max(Number(n), 1), 9);
|
||||
if (prompt_optimizer) payload.prompt_optimizer = true;
|
||||
if (aigc_watermark) payload.aigc_watermark = true;
|
||||
|
||||
// style only for image-01-live
|
||||
if (model === 'image-01-live' && style) {
|
||||
const { style_type, style_weight } = style;
|
||||
if (style_type) {
|
||||
payload.style = { style_type };
|
||||
if (style_weight != null) payload.style.style_weight = style_weight;
|
||||
}
|
||||
}
|
||||
|
||||
const baseUrl = cfg.baseUrl || 'https://api.minimaxi.com';
|
||||
const endpoint = `${baseUrl}/v1/image_generation`;
|
||||
|
||||
let response;
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cfg.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
const msg = data.error?.message || data.error || `HTTP ${response.status}`;
|
||||
return res.status(response.status).json({ error: msg });
|
||||
}
|
||||
|
||||
// Return base64 images
|
||||
const images = data.data?.image_base64 || [];
|
||||
res.json({ images });
|
||||
} catch (err) {
|
||||
console.error('[generate] error:', err.message);
|
||||
res.status(500).json({ error: '无法连接 MiniMax API:' + err.message });
|
||||
console.error('[generate] network error:', err.message);
|
||||
return res.status(502).json({ error: '无法连接 MiniMax API:' + err.message });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
const code = data.base_resp?.status_code;
|
||||
const msg = data.base_resp?.status_msg || data.error || `HTTP ${response.status}`;
|
||||
const friendly = friendlyError(code, msg);
|
||||
return res.status(response.status).json({ error: friendly });
|
||||
}
|
||||
|
||||
// Return images + task id + metadata
|
||||
res.json({
|
||||
images: data.data?.image_urls || data.data?.image_base64 || [],
|
||||
id: data.id,
|
||||
success_count: data.metadata?.success_count ?? (Array.isArray(data.data?.image_urls || data.data?.image_base64) ? 1 : 0),
|
||||
failed_count: data.metadata?.failed_count ?? 0,
|
||||
});
|
||||
});
|
||||
|
||||
// ── Task Status (future-proofing) ───────────────────────────────
|
||||
|
||||
app.get('/api/task/:id', async (req, res) => {
|
||||
const cfg = loadConfig();
|
||||
if (!cfg.apiKey) {
|
||||
return res.status(400).json({ error: '未配置 API Key。' });
|
||||
}
|
||||
const baseUrl = cfg.baseUrl || 'https://api.minimaxi.com';
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/v1/image_generation/${req.params.id}`, {
|
||||
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
return res.status(response.status).json({ error: data.base_resp?.status_msg || '请求失败' });
|
||||
}
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
res.status(502).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Serve index.html at root ---
|
||||
// ── SPA fallback ───────────────────────────────────────────────
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'index.html'));
|
||||
});
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
console.log(`Image Generator running at http://${HOST === '0.0.0.0' ? '10.0.10.110' : HOST}:${PORT}`);
|
||||
console.log(`图片生成器运行于 http://${HOST === '0.0.0.0' ? '10.0.10.110' : HOST}:${PORT}`);
|
||||
});
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────
|
||||
|
||||
function friendlyError(code, msg) {
|
||||
const map = {
|
||||
1002: '触发限流,请稍后再试',
|
||||
1004: '账号鉴权失败,请检查 API Key 是否正确',
|
||||
1008: '账号余额不足',
|
||||
1026: '图片描述涉及敏感内容,请修改后重试',
|
||||
2013: '参数异常,请检查输入是否合规',
|
||||
2049: '无效的 API Key',
|
||||
};
|
||||
if (code && map[code]) return map[code];
|
||||
return msg;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user