feat: RouterAI as 3rd image fallback via /responses + image_generation
- app_settings: ROUTERAI_BASE_URL, ROUTERAI_API_KEY, ROUTERAI_IMAGE_MODEL - config/index.js: routeraiBaseUrl, routeraiApiKey, routeraiImageModel - covers.js: generateCoverViaRouterAI() через /responses endpoint Цепочка: aiguoguo → Nyxos → RouterAI → local SVG
This commit is contained in:
@@ -70,6 +70,9 @@ async function reloadAi() {
|
|||||||
config.ai.imageApiKey = pick('AI_IMAGE_API_KEY', 'AI_IMAGE_API_KEY', config.ai.apiKey);
|
config.ai.imageApiKey = pick('AI_IMAGE_API_KEY', 'AI_IMAGE_API_KEY', config.ai.apiKey);
|
||||||
config.ai.imageFallbackBaseUrl = (s['AI_IMAGE_FALLBACK_BASE_URL'] && s['AI_IMAGE_FALLBACK_BASE_URL'].trim()) || 'https://api.aiguoguo199.com/v1';
|
config.ai.imageFallbackBaseUrl = (s['AI_IMAGE_FALLBACK_BASE_URL'] && s['AI_IMAGE_FALLBACK_BASE_URL'].trim()) || 'https://api.aiguoguo199.com/v1';
|
||||||
config.ai.imageFallbackApiKey = (s['AI_IMAGE_FALLBACK_API_KEY'] && s['AI_IMAGE_FALLBACK_API_KEY'].trim()) || config.ai.imageApiKey;
|
config.ai.imageFallbackApiKey = (s['AI_IMAGE_FALLBACK_API_KEY'] && s['AI_IMAGE_FALLBACK_API_KEY'].trim()) || config.ai.imageApiKey;
|
||||||
|
config.ai.routeraiBaseUrl = (s['ROUTERAI_BASE_URL'] || 'https://routerai.ru/api/v1').trim();
|
||||||
|
config.ai.routeraiApiKey = (s['ROUTERAI_API_KEY'] || '').trim() || null;
|
||||||
|
config.ai.routeraiImageModel = (s['ROUTERAI_IMAGE_MODEL'] || 'openai/gpt-5-image-mini').trim();
|
||||||
config.ai.imageModel = pick('AI_IMAGE_MODEL', 'AI_MODEL_IMAGE', 'gpt-image-2');
|
config.ai.imageModel = pick('AI_IMAGE_MODEL', 'AI_MODEL_IMAGE', 'gpt-image-2');
|
||||||
config.ai.imageModelViaResponses = pick('AI_IMAGE_MODEL_VIA_RESPONSES', 'AI_MODEL_IMAGE_VIA_RESPONSES', 'gpt-5.5');
|
config.ai.imageModelViaResponses = pick('AI_IMAGE_MODEL_VIA_RESPONSES', 'AI_MODEL_IMAGE_VIA_RESPONSES', 'gpt-5.5');
|
||||||
config.ai.models.post = pick('AI_TEXT_MODEL_POST', 'AI_MODEL_POST', 'claude-haiku-4-5-20251001');
|
config.ai.models.post = pick('AI_TEXT_MODEL_POST', 'AI_MODEL_POST', 'claude-haiku-4-5-20251001');
|
||||||
|
|||||||
+39
-1
@@ -270,6 +270,35 @@ async function generateCoverViaImagesEndpoint({ prompt }) {
|
|||||||
* Основной путь — /images/generations (aiguoguo199.com или любой OpenAI-совместимый).
|
* Основной путь — /images/generations (aiguoguo199.com или любой OpenAI-совместимый).
|
||||||
* Более стабильный чем /responses, поддерживает gpt-image-1-mini/gpt-image-2.
|
* Более стабильный чем /responses, поддерживает gpt-image-1-mini/gpt-image-2.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* RouterAI — стабильный провайдер через /responses + image_generation tool.
|
||||||
|
* Используется как третий fallback когда aiguoguo и Nyxos недоступны.
|
||||||
|
*/
|
||||||
|
async function generateCoverViaRouterAI({ prompt }) {
|
||||||
|
const base = config.ai.routeraiBaseUrl;
|
||||||
|
const key = config.ai.routeraiApiKey;
|
||||||
|
const model = config.ai.routeraiImageModel || 'openai/gpt-5-image-mini';
|
||||||
|
const started = Date.now();
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${base}/responses`, {
|
||||||
|
model,
|
||||||
|
input: `Use the image_generation tool to create this illustration. Only call the tool, no text.\n\n${prompt.slice(0, 3000)}`,
|
||||||
|
tools: [{ type: 'image_generation' }],
|
||||||
|
tool_choice: { type: 'image_generation' },
|
||||||
|
}, {
|
||||||
|
headers: { Authorization: `Bearer ${key}` },
|
||||||
|
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, durationMs: Date.now()-started, succeeded: true }).catch(() => {});
|
||||||
|
return { bytes: Buffer.from(imgCall.result, 'base64'), format: imgCall.output_format || 'png' };
|
||||||
|
} catch (err) {
|
||||||
|
aiUsage.log({ provider: 'routerai', requestType: 'image_via_responses', model, imageCount: 1, durationMs: Date.now()-started, succeeded: false, errorMessage: (err.response?.data?.error?.message || err.message || '').slice(0,500) }).catch(() => {});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function generateCoverViaImageGenerations({ prompt }) {
|
async function generateCoverViaImageGenerations({ prompt }) {
|
||||||
const model = config.ai.imageModel || 'gpt-image-2';
|
const model = config.ai.imageModel || 'gpt-image-2';
|
||||||
|
|
||||||
@@ -302,8 +331,17 @@ async function generateCoverViaImageGenerations({ 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 fallback aiguoguo...`);
|
console.warn(`[Cover] primary 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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user