fix: image generation via aiguoguo199.com /images/generations

- Новый основной источник: api.aiguoguo199.com (gpt-image-1-mini)
- Убран response_format (не поддерживается aiguoguo)
- Поддержка b64_json и url response форматов
- Цепочка: aiguoguo → aiprimetech /responses → legacy → local SVG
- AI_IMAGE_BASE_URL и AI_MODEL_IMAGE в config
This commit is contained in:
Nik (Claude)
2026-06-08 16:21:43 +03:00
parent 1df24a8655
commit 0c01ed7e62
2 changed files with 47 additions and 15 deletions
+2
View File
@@ -8,6 +8,8 @@ module.exports = {
baseUrl: process.env.AI_BASE_URL || 'https://aiprimetech.io/v1', baseUrl: process.env.AI_BASE_URL || 'https://aiprimetech.io/v1',
apiKey: process.env.AI_API_KEY, apiKey: process.env.AI_API_KEY,
imageApiKey: process.env.AI_IMAGE_API_KEY || process.env.AI_API_KEY, imageApiKey: process.env.AI_IMAGE_API_KEY || process.env.AI_API_KEY,
imageBaseUrl: process.env.AI_IMAGE_BASE_URL || process.env.AI_BASE_URL || 'https://aiprimetech.io/v1',
imageModel: process.env.AI_MODEL_IMAGE || 'gpt-image-1-mini',
// Per-task model selection — tune cost vs quality here // Per-task model selection — tune cost vs quality here
models: { models: {
post: process.env.AI_MODEL_POST || 'claude-haiku-4-5-20251001', post: process.env.AI_MODEL_POST || 'claude-haiku-4-5-20251001',
+45 -15
View File
@@ -160,6 +160,39 @@ async function generateCoverViaImagesEndpoint({ prompt }) {
throw new Error('No image data'); throw new Error('No image data');
} }
/**
* Основной путь — /images/generations (aiguoguo199.com или любой OpenAI-совместимый).
* Более стабильный чем /responses, поддерживает gpt-image-1-mini/gpt-image-2.
*/
async function generateCoverViaImageGenerations({ prompt }) {
const model = config.ai.imageModel || 'gpt-image-1-mini';
const baseUrl = config.ai.imageBaseUrl || config.ai.baseUrl;
const res = await axios.post(
`${baseUrl}/images/generations`,
{
model,
prompt: prompt.slice(0, 4000),
n: 1,
size: '1024x1024',
},
{
headers: { Authorization: `Bearer ${config.ai.imageApiKey}` },
timeout: 120_000,
}
);
const item = res.data?.data?.[0];
if (!item) throw new Error('No image data in response');
// b64_json или url
const b64 = item.b64_json;
if (b64) return { bytes: Buffer.from(b64, 'base64'), format: 'png' };
if (item.url) {
const r = await axios.get(item.url, { responseType: 'arraybuffer', timeout: 60_000 });
return { bytes: Buffer.from(r.data), format: 'png' };
}
throw new Error('No b64_json or url in response');
return { bytes: Buffer.from(b64, 'base64'), format: 'png' };
}
/** /**
* Резервный путь — Pollinations.AI (https://pollinations.ai). * Резервный путь — Pollinations.AI (https://pollinations.ai).
* 100% бесплатно, без API ключа, без регистрации. * 100% бесплатно, без API ключа, без регистрации.
@@ -194,34 +227,31 @@ async function generateCover({ articleId, title, tags = [] }) {
console.log(`[Cover] article=${articleId} style=${styleIdx}:${styleName}`); console.log(`[Cover] article=${articleId} style=${styleIdx}:${styleName}`);
let img; let img;
let usedPath = 'responses'; let usedPath = 'images-generations';
// Пробуем все внешние API, при любой ошибке — сразу local SVG // Цепочка: 1) aiguoguo /images/generations → 2) aiprimetech /responses → 3) legacy → 4) local SVG
try { try {
try { try {
img = await generateCoverViaResponses({ prompt }); img = await generateCoverViaImageGenerations({ prompt });
} catch (err) { } catch (err) {
const msg = err.response?.data?.error?.message || err.message; console.warn(`[Cover] /images/generations failed: ${(err.response?.data?.error?.message || err.message).slice(0, 150)}`);
console.warn(`[Cover] /responses path failed: ${msg.slice(0, 200)}`);
try { try {
img = await generateCoverViaImagesEndpoint({ prompt }); img = await generateCoverViaResponses({ prompt });
usedPath = 'images-legacy'; usedPath = 'responses';
} catch (err2) { } catch (err2) {
const msg2 = err2.response?.data?.error?.message || err2.message; console.warn(`[Cover] /responses failed: ${(err2.response?.data?.error?.message || err2.message).slice(0, 150)}`);
console.warn(`[Cover] legacy path failed too: ${msg2.slice(0, 200)}`);
try { try {
img = await generateCoverViaPollinations({ prompt }); img = await generateCoverViaImagesEndpoint({ prompt });
usedPath = 'pollinations'; usedPath = 'images-legacy';
console.log(`[Cover] article=${articleId} using Pollinations.AI fallback`);
} catch (err3) { } catch (err3) {
console.warn(`[Cover] Pollinations fallback failed: ${err3.message.slice(0, 200)}`); console.warn(`[Cover] legacy failed: ${(err3.response?.data?.error?.message || err3.message).slice(0, 150)}`);
throw new Error('all_external_failed'); throw new Error('all_external_failed');
} }
} }
} }
} catch (outerErr) { } catch (outerErr) {
// Все внешние API упали — используем локальную SVG-генерацию // Все внешние API упали — local SVG
console.log(`[Cover] article=${articleId} → local SVG generator (all external APIs unavailable)`); console.log(`[Cover] article=${articleId} → local SVG (all external APIs unavailable)`);
const localUrl = await localGen.generateLocalCover({ articleId, title, category: tags?.[0] || '' }); const localUrl = await localGen.generateLocalCover({ articleId, title, category: tags?.[0] || '' });
await query('UPDATE articles SET cover_url=$1, updated_at=NOW() WHERE id=$2', [localUrl, articleId]); await query('UPDATE articles SET cover_url=$1, updated_at=NOW() WHERE id=$2', [localUrl, articleId]);
return localUrl; return localUrl;