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:
@@ -8,6 +8,8 @@ module.exports = {
|
||||
baseUrl: process.env.AI_BASE_URL || 'https://aiprimetech.io/v1',
|
||||
apiKey: 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
|
||||
models: {
|
||||
post: process.env.AI_MODEL_POST || 'claude-haiku-4-5-20251001',
|
||||
|
||||
+45
-15
@@ -160,6 +160,39 @@ async function generateCoverViaImagesEndpoint({ prompt }) {
|
||||
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).
|
||||
* 100% бесплатно, без API ключа, без регистрации.
|
||||
@@ -194,34 +227,31 @@ async function generateCover({ articleId, title, tags = [] }) {
|
||||
console.log(`[Cover] article=${articleId} style=${styleIdx}:${styleName}`);
|
||||
|
||||
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 {
|
||||
img = await generateCoverViaResponses({ prompt });
|
||||
img = await generateCoverViaImageGenerations({ prompt });
|
||||
} catch (err) {
|
||||
const msg = err.response?.data?.error?.message || err.message;
|
||||
console.warn(`[Cover] /responses path failed: ${msg.slice(0, 200)}`);
|
||||
console.warn(`[Cover] /images/generations failed: ${(err.response?.data?.error?.message || err.message).slice(0, 150)}`);
|
||||
try {
|
||||
img = await generateCoverViaImagesEndpoint({ prompt });
|
||||
usedPath = 'images-legacy';
|
||||
img = await generateCoverViaResponses({ prompt });
|
||||
usedPath = 'responses';
|
||||
} catch (err2) {
|
||||
const msg2 = err2.response?.data?.error?.message || err2.message;
|
||||
console.warn(`[Cover] legacy path failed too: ${msg2.slice(0, 200)}`);
|
||||
console.warn(`[Cover] /responses failed: ${(err2.response?.data?.error?.message || err2.message).slice(0, 150)}`);
|
||||
try {
|
||||
img = await generateCoverViaPollinations({ prompt });
|
||||
usedPath = 'pollinations';
|
||||
console.log(`[Cover] article=${articleId} using Pollinations.AI fallback`);
|
||||
img = await generateCoverViaImagesEndpoint({ prompt });
|
||||
usedPath = 'images-legacy';
|
||||
} 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (outerErr) {
|
||||
// Все внешние API упали — используем локальную SVG-генерацию
|
||||
console.log(`[Cover] article=${articleId} → local SVG generator (all external APIs unavailable)`);
|
||||
// Все внешние API упали — local SVG
|
||||
console.log(`[Cover] article=${articleId} → local SVG (all external APIs unavailable)`);
|
||||
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]);
|
||||
return localUrl;
|
||||
|
||||
Reference in New Issue
Block a user