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',
|
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
@@ -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 {
|
||||||
|
img = await generateCoverViaImageGenerations({ prompt });
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[Cover] /images/generations failed: ${(err.response?.data?.error?.message || err.message).slice(0, 150)}`);
|
||||||
try {
|
try {
|
||||||
img = await generateCoverViaResponses({ prompt });
|
img = await generateCoverViaResponses({ prompt });
|
||||||
} catch (err) {
|
usedPath = 'responses';
|
||||||
const msg = err.response?.data?.error?.message || err.message;
|
} catch (err2) {
|
||||||
console.warn(`[Cover] /responses path failed: ${msg.slice(0, 200)}`);
|
console.warn(`[Cover] /responses failed: ${(err2.response?.data?.error?.message || err2.message).slice(0, 150)}`);
|
||||||
try {
|
try {
|
||||||
img = await generateCoverViaImagesEndpoint({ prompt });
|
img = await generateCoverViaImagesEndpoint({ prompt });
|
||||||
usedPath = 'images-legacy';
|
usedPath = 'images-legacy';
|
||||||
} catch (err2) {
|
|
||||||
const msg2 = err2.response?.data?.error?.message || err2.message;
|
|
||||||
console.warn(`[Cover] legacy path failed too: ${msg2.slice(0, 200)}`);
|
|
||||||
try {
|
|
||||||
img = await generateCoverViaPollinations({ prompt });
|
|
||||||
usedPath = 'pollinations';
|
|
||||||
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user