fix: getChannel arg order, postImages via Nyxos /images/generations

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
This commit is contained in:
Ник (Claude)
2026-06-10 17:45:18 +03:00
parent 80d1885feb
commit c7f0b3ed4d
3 changed files with 39 additions and 53 deletions
+4 -4
View File
@@ -50,7 +50,7 @@ router.post('/transform', async (req, res) => {
if (!channelId || !originalPost || !action) { if (!channelId || !originalPost || !action) {
return res.status(400).json({ error: 'channelId, originalPost, action required' }); 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' }); if (!channel) return res.status(404).json({ error: 'Channel not found' });
const ai = require('../services/ai'); 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; const userId = parseInt(req.headers['x-user-id']) || null;
if (!channelId || !post) return res.status(400).json({ error: 'channelId and post required' }); 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' }); if (!channel) return res.status(404).json({ error: 'Channel not found' });
const { generatePostImage } = require('../services/postImages'); const { generatePostImage } = require('../services/postImages');
@@ -99,7 +99,7 @@ router.post('/topics-ideas', async (req, res) => {
const { channelId, count = 7 } = req.body; const { channelId, count = 7 } = req.body;
const userId = parseInt(req.headers['x-user-id']) || null; const userId = parseInt(req.headers['x-user-id']) || null;
if (!channelId) return res.status(400).json({ error: 'channelId required' }); 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' }); if (!channel) return res.status(404).json({ error: 'Channel not found' });
const ai = require('../services/ai'); 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; const userId = parseInt(req.headers['x-user-id']) || null;
if (!channelId || !url) return res.status(400).json({ error: 'channelId and url required' }); 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' }); if (!channel) return res.status(404).json({ error: 'Channel not found' });
const { generateFromUrl } = require('../services/fromUrl'); const { generateFromUrl } = require('../services/fromUrl');
+1
View File
@@ -185,6 +185,7 @@ async function deleteChannel(channelId, userId) {
} }
module.exports = { module.exports = {
getChannel: getFullChannel, // алиас для обратной совместимости
getFullChannel, getFullChannel,
listChannels, listChannels,
createChannel, createChannel,
+29 -44
View File
@@ -85,61 +85,46 @@ ${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.`;
const model = config.ai.imageModelViaResponses || 'gpt-5.2'; // Используем Nyxos /images/generations (первичный провайдер)
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}`; // с fallback на aiguoguo — тот же путь что для обложек статей
const model = config.ai.imageModel || 'gpt-image-2';
async function tryProvider(baseUrl, apiKey) {
const started = Date.now(); const started = Date.now();
let res;
try { try {
res = await axios.post( const res = await axios.post(
`${config.ai.baseUrl}/responses`, `${baseUrl}/images/generations`,
{ { model, prompt: prompt.slice(0, 4000), n: 1, size: '1024x1024', response_format: 'url' },
model, { headers: { Authorization: `Bearer ${apiKey}` }, timeout: 120_000 }
input: wrappedInput,
tools: [{ type: 'image_generation' }],
tool_choice: { type: 'image_generation' },
},
{
headers: { Authorization: `Bearer ${config.ai.apiKey}` },
timeout: 300_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) { } catch (err) {
aiUsage.log({ 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(() => {});
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; 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({ let bytes;
provider: aiUsage.providerFromBaseUrl(config.ai.baseUrl), try {
requestType: 'image_via_responses', model, imageCount: 1, bytes = await tryProvider(config.ai.imageBaseUrl, config.ai.imageApiKey);
requestId: res.data?.id, } catch (err) {
meta: { channel_id: channel.id }, const status = err.response?.status;
durationMs: Date.now() - started, succeeded: true, if (!status || status >= 500) {
}).catch(() => {}); console.warn('[postImages] primary failed, trying fallback...');
bytes = await tryProvider(config.ai.imageFallbackBaseUrl, config.ai.imageFallbackApiKey);
} else { throw err; }
}
const bytes = Buffer.from(imgCall.result, 'base64');
const tsKey = `post-${channel.id}-${Date.now()}`; const tsKey = `post-${channel.id}-${Date.now()}`;
const ext = imgCall.output_format || 'png'; const ext = 'png';
// Оптимизация через sharp если есть // Оптимизация через sharp если есть
let publicUrl; let publicUrl;