From c7f0b3ed4def53075585f844362224025d5389ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=20=28Claude=29?= Date: Wed, 10 Jun 2026 17:45:18 +0300 Subject: [PATCH] fix: getChannel arg order, postImages via Nyxos /images/generations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit generate.js: getChannel(userId, channelId) → getChannel(channelId, userId) channels.js: getChannel alias → getFullChannel postImages.js: убран /responses + gpt-5.5 (не работал на aiprimetech), заменён на Nyxos /images/generations с fallback на aiguoguo --- src/routes/generate.js | 8 ++-- src/services/channels.js | 1 + src/services/postImages.js | 83 ++++++++++++++++---------------------- 3 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/routes/generate.js b/src/routes/generate.js index fce36d9..a3b9311 100644 --- a/src/routes/generate.js +++ b/src/routes/generate.js @@ -50,7 +50,7 @@ router.post('/transform', async (req, res) => { if (!channelId || !originalPost || !action) { return res.status(400).json({ error: 'channelId, originalPost, action required' }); } - const channel = await channelsSvc.getChannel(userId, channelId); + const channel = await channelsSvc.getChannel(channelId, userId); if (!channel) return res.status(404).json({ error: 'Channel not found' }); const ai = require('../services/ai'); @@ -69,7 +69,7 @@ router.post('/post-image', async (req, res) => { const userId = parseInt(req.headers['x-user-id']) || null; if (!channelId || !post) return res.status(400).json({ error: 'channelId and post required' }); - const channel = await channelsSvc.getChannel(userId, channelId); + const channel = await channelsSvc.getChannel(channelId, userId); if (!channel) return res.status(404).json({ error: 'Channel not found' }); const { generatePostImage } = require('../services/postImages'); @@ -99,7 +99,7 @@ router.post('/topics-ideas', async (req, res) => { const { channelId, count = 7 } = req.body; const userId = parseInt(req.headers['x-user-id']) || null; if (!channelId) return res.status(400).json({ error: 'channelId required' }); - const channel = await channelsSvc.getChannel(userId, channelId); + const channel = await channelsSvc.getChannel(channelId, userId); if (!channel) return res.status(404).json({ error: 'Channel not found' }); const ai = require('../services/ai'); @@ -118,7 +118,7 @@ router.post('/from-url', async (req, res) => { const userId = parseInt(req.headers['x-user-id']) || null; if (!channelId || !url) return res.status(400).json({ error: 'channelId and url required' }); - const channel = await channelsSvc.getChannel(userId, channelId); + const channel = await channelsSvc.getChannel(channelId, userId); if (!channel) return res.status(404).json({ error: 'Channel not found' }); const { generateFromUrl } = require('../services/fromUrl'); diff --git a/src/services/channels.js b/src/services/channels.js index c5b87e8..d2af1e4 100644 --- a/src/services/channels.js +++ b/src/services/channels.js @@ -185,6 +185,7 @@ async function deleteChannel(channelId, userId) { } module.exports = { + getChannel: getFullChannel, // алиас для обратной совместимости getFullChannel, listChannels, createChannel, diff --git a/src/services/postImages.js b/src/services/postImages.js index c3220ad..9d100c0 100644 --- a/src/services/postImages.js +++ b/src/services/postImages.js @@ -85,61 +85,46 @@ ${style.image_prompt_instructions ? `\nChannel visual guidelines: ${style.image_ Composition: 16:9 wide format, balanced, suitable for social media. Strictly: no text, no letters, no logos, no faces of real people.`; - const model = config.ai.imageModelViaResponses || 'gpt-5.2'; - const wrappedInput = `Use the image_generation tool to create the following illustration. Do not write any text response, only call the tool.\n\n${prompt}`; + // Используем Nyxos /images/generations (первичный провайдер) + // с fallback на aiguoguo — тот же путь что для обложек статей + const model = config.ai.imageModel || 'gpt-image-2'; - const started = Date.now(); - let res; - try { - res = await axios.post( - `${config.ai.baseUrl}/responses`, - { - model, - input: wrappedInput, - tools: [{ type: 'image_generation' }], - tool_choice: { type: 'image_generation' }, - }, - { - headers: { Authorization: `Bearer ${config.ai.apiKey}` }, - timeout: 300_000, + async function tryProvider(baseUrl, apiKey) { + const started = Date.now(); + try { + const res = await axios.post( + `${baseUrl}/images/generations`, + { model, prompt: prompt.slice(0, 4000), n: 1, size: '1024x1024', response_format: 'url' }, + { headers: { Authorization: `Bearer ${apiKey}` }, timeout: 120_000 } + ); + const item = res.data?.data?.[0]; + 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(() => {}); + if (item.url) { + 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'); + throw new Error('No url or b64_json'); + } 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(() => {}); + throw err; + } + } + + let bytes; + try { + bytes = await tryProvider(config.ai.imageBaseUrl, config.ai.imageApiKey); } catch (err) { - aiUsage.log({ - provider: aiUsage.providerFromBaseUrl(config.ai.baseUrl), - 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; + const status = err.response?.status; + if (!status || status >= 500) { + console.warn('[postImages] primary failed, trying fallback...'); + bytes = await tryProvider(config.ai.imageFallbackBaseUrl, config.ai.imageFallbackApiKey); + } else { throw err; } } - const output = res.data?.output || []; - const imgCall = output.find(o => o.type === 'image_generation_call'); - if (!imgCall?.result) { - aiUsage.log({ - provider: aiUsage.providerFromBaseUrl(config.ai.baseUrl), - requestType: 'image_via_responses', model, imageCount: 1, - meta: { channel_id: channel.id }, - durationMs: Date.now() - started, succeeded: false, - errorMessage: 'No image generated', - }).catch(() => {}); - throw new Error('No image generated'); - } - - aiUsage.log({ - provider: aiUsage.providerFromBaseUrl(config.ai.baseUrl), - requestType: 'image_via_responses', model, imageCount: 1, - requestId: res.data?.id, - meta: { channel_id: channel.id }, - durationMs: Date.now() - started, succeeded: true, - }).catch(() => {}); - - const bytes = Buffer.from(imgCall.result, 'base64'); const tsKey = `post-${channel.id}-${Date.now()}`; - const ext = imgCall.output_format || 'png'; + const ext = 'png'; // Оптимизация через sharp если есть let publicUrl;