Files
image-generator/app.js

161 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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;
}