forked from admin/zeropost-engine
feat: routerai as primary image provider, Nyxos as fallback
- covers.js: RouterAI /responses → Nyxos /images/generations → SVG - postImages.js: RouterAI /responses → Nyxos /images/generations - config: imageBaseUrl = routerai, imageFallbackBaseUrl = nyxos - app_settings: AI_IMAGE_BASE_URL = routerai, MODEL = gpt-5-image-mini
This commit is contained in:
+3
-12
@@ -325,23 +325,14 @@ async function generateCoverViaImageGenerations({ prompt }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Основной: Nyxos Plus
|
// Основной: RouterAI /responses (стабильный)
|
||||||
try {
|
try {
|
||||||
return await tryProvider(config.ai.imageBaseUrl, config.ai.imageApiKey);
|
return await generateCoverViaRouterAI({ prompt });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const status = err.response?.status;
|
const status = err.response?.status;
|
||||||
if (!status || status >= 500) {
|
if (!status || status >= 500) {
|
||||||
console.warn(`[Cover] primary failed (${status||'timeout'}), trying Nyxos fallback...`);
|
console.warn(`[Cover] RouterAI failed (${status||'timeout'}), trying Nyxos fallback...`);
|
||||||
try {
|
|
||||||
return await tryProvider(config.ai.imageFallbackBaseUrl, config.ai.imageFallbackApiKey);
|
return await tryProvider(config.ai.imageFallbackBaseUrl, config.ai.imageFallbackApiKey);
|
||||||
} catch (err2) {
|
|
||||||
const status2 = err2.response?.status;
|
|
||||||
if ((!status2 || status2 >= 500) && config.ai.routeraiApiKey) {
|
|
||||||
console.warn(`[Cover] Nyxos failed (${status2||'timeout'}), trying RouterAI /responses...`);
|
|
||||||
return await generateCoverViaRouterAI({ prompt });
|
|
||||||
}
|
|
||||||
throw err2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-17
@@ -85,41 +85,56 @@ ${style.image_prompt_instructions ? `\nChannel visual guidelines: ${style.image_
|
|||||||
Composition: 16:9 wide format, balanced, suitable for social media.
|
Composition: 16:9 wide format, balanced, suitable for social media.
|
||||||
Strictly: no text, no letters, no logos, no faces of real people.`;
|
Strictly: no text, no letters, no logos, no faces of real people.`;
|
||||||
|
|
||||||
// Используем Nyxos /images/generations (первичный провайдер)
|
// RouterAI /responses (primary) → Nyxos /images/generations (fallback)
|
||||||
// с fallback на aiguoguo — тот же путь что для обложек статей
|
const model = config.ai.routeraiImageModel || 'openai/gpt-5-image-mini';
|
||||||
const model = config.ai.imageModel || 'gpt-image-2';
|
|
||||||
|
|
||||||
async function tryProvider(baseUrl, apiKey) {
|
async function tryRouterAI() {
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const res = await axios.post(`${config.ai.routeraiBaseUrl}/responses`, {
|
||||||
`${baseUrl}/images/generations`,
|
model,
|
||||||
{ model, prompt: prompt.slice(0, 4000), n: 1, size: '1024x1024', response_format: 'url' },
|
input: `Use the image_generation tool to create this illustration. Only call the tool, no text.\n\n${prompt.slice(0, 3000)}`,
|
||||||
{ headers: { Authorization: `Bearer ${apiKey}` }, timeout: 45_000 }
|
tools: [{ type: 'image_generation' }],
|
||||||
|
tool_choice: { type: 'image_generation' },
|
||||||
|
}, { headers: { Authorization: `Bearer ${config.ai.routeraiApiKey}` }, timeout: 120_000 });
|
||||||
|
const imgCall = (res.data?.output || []).find(o => o.type === 'image_generation_call');
|
||||||
|
if (!imgCall?.result) throw new Error('No image in RouterAI response');
|
||||||
|
aiUsage.log({ provider: 'routerai', requestType: 'image_via_responses', model, imageCount: 1, meta: { channel_id: channel.id }, durationMs: Date.now()-started, succeeded: true }).catch(() => {});
|
||||||
|
return Buffer.from(imgCall.result, 'base64');
|
||||||
|
} catch (err) {
|
||||||
|
aiUsage.log({ provider: 'routerai', requestType: 'image_via_responses', model, imageCount: 1, meta: { channel_id: channel.id }, durationMs: Date.now()-started, succeeded: false, errorMessage: (err.response?.data?.error?.message || err.message || '').slice(0, 500) }).catch(() => {});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tryNyxos() {
|
||||||
|
const m = config.ai.imageModel || 'gpt-image-2';
|
||||||
|
const started = Date.now();
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${config.ai.imageFallbackBaseUrl}/images/generations`,
|
||||||
|
{ model: m, prompt: prompt.slice(0, 4000), n: 1, size: '1024x1024', response_format: 'url' },
|
||||||
|
{ headers: { Authorization: `Bearer ${config.ai.imageFallbackApiKey}` }, timeout: 90_000 }
|
||||||
);
|
);
|
||||||
const item = res.data?.data?.[0];
|
const item = res.data?.data?.[0];
|
||||||
if (!item) throw new Error('No image data');
|
if (!item) throw new Error('No image data');
|
||||||
aiUsage.log({ provider: aiUsage.providerFromBaseUrl(baseUrl), requestType: 'image', model, imageCount: 1, meta: { channel_id: channel.id }, succeeded: true }).catch(() => {});
|
aiUsage.log({ provider: 'nyxos', requestType: 'image', model: m, imageCount: 1, meta: { channel_id: channel.id }, succeeded: true }).catch(() => {});
|
||||||
if (item.url) {
|
if (item.url) { const r = await axios.get(item.url, { responseType: 'arraybuffer', timeout: 60_000 }); return Buffer.from(r.data); }
|
||||||
const r = await axios.get(item.url, { responseType: 'arraybuffer', timeout: 60_000 });
|
|
||||||
return Buffer.from(r.data);
|
|
||||||
}
|
|
||||||
if (item.b64_json) return Buffer.from(item.b64_json, 'base64');
|
if (item.b64_json) return Buffer.from(item.b64_json, 'base64');
|
||||||
throw new Error('No url or b64_json');
|
throw new Error('No url or b64_json');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
aiUsage.log({ provider: aiUsage.providerFromBaseUrl(baseUrl), requestType: 'image', model, imageCount: 1, meta: { channel_id: channel.id }, succeeded: false, errorMessage: (err.response?.data?.error?.message || err.message || '').slice(0, 500) }).catch(() => {});
|
aiUsage.log({ provider: 'nyxos', requestType: 'image', model: m, imageCount: 1, meta: { channel_id: channel.id }, succeeded: false, errorMessage: (err.response?.data?.error?.message || err.message || '').slice(0, 500) }).catch(() => {});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes;
|
let bytes;
|
||||||
try {
|
try {
|
||||||
bytes = await tryProvider(config.ai.imageBaseUrl, config.ai.imageApiKey);
|
bytes = await tryRouterAI();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const status = err.response?.status;
|
const status = err.response?.status;
|
||||||
if (!status || status >= 500) {
|
if (!status || status >= 500) {
|
||||||
console.warn('[postImages] primary failed, trying fallback...');
|
console.warn('[postImages] RouterAI failed, trying Nyxos fallback...');
|
||||||
bytes = await tryProvider(config.ai.imageFallbackBaseUrl, config.ai.imageFallbackApiKey);
|
bytes = await tryNyxos();
|
||||||
} else { throw err; }
|
} else { throw err; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user