'use strict'; const express = require('express'); const path = require('path'); const fs = require('fs'); const PORT = 8195; const HOST = '0.0.0.0'; const app = express(); app.use(express.json()); app.use(express.static(__dirname)); // ── Config ────────────────────────────────────────────────────── const CONFIG_FILE = path.join(__dirname, 'config.json'); function loadConfig() { try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch { return { baseUrl: 'https://api.minimaxi.com' }; } } function saveConfig(cfg) { fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2)); } app.get('/api/config', (req, res) => { const cfg = loadConfig(); res.json({ baseUrl: cfg.baseUrl || 'https://api.minimaxi.com' }); }); app.post('/api/config', (req, res) => { const { baseUrl } = req.body; if (typeof baseUrl !== 'string') { return res.status(400).json({ error: '参数格式错误' }); } const cfg = { baseUrl: baseUrl.trim() || 'https://api.minimaxi.com' }; saveConfig(cfg); res.json({ ok: true }); }); // ── Image Generation ───────────────────────────────────────────── app.post('/api/generate', async (req, res) => { const { model, prompt, aspect_ratio, width, height, response_format, seed, n, prompt_optimizer, aigc_watermark, apiKey, } = req.body; // Validation if (!prompt || typeof prompt !== 'string' || !prompt.trim()) { return res.status(400).json({ error: '请输入图片描述' }); } if (model !== 'image-01') { return res.status(400).json({ error: 'model 参数无效' }); } if (!apiKey || typeof apiKey !== 'string') { return res.status(400).json({ error: '未配置 API Key,请先在设置中填写。' }); } // Build payload — only include optional fields that are set const payload = { 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; const baseUrl = loadConfig().baseUrl || 'https://api.minimaxi.com'; const endpoint = `${baseUrl}/v1/image_generation`; let response; try { response = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); } catch (err) { 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 apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(400).json({ error: '缺少 API Key。' }); } const baseUrl = loadConfig().baseUrl || 'https://api.minimaxi.com'; try { const response = await fetch(`${baseUrl}/v1/image_generation/${req.params.id}`, { headers: { 'Authorization': `Bearer ${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 }); } }); // ── SPA fallback ─────────────────────────────────────────────── app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'index.html')); }); app.listen(PORT, HOST, () => { console.log(`图片生成器运行于 http://0.0.0.0:${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; }