forked from admin/zeropost-engine
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:
@@ -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');
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ async function deleteChannel(channelId, userId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
getChannel: getFullChannel, // алиас для обратной совместимости
|
||||||
getFullChannel,
|
getFullChannel,
|
||||||
listChannels,
|
listChannels,
|
||||||
createChannel,
|
createChannel,
|
||||||
|
|||||||
+34
-49
@@ -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';
|
||||||
|
|
||||||
const started = Date.now();
|
async function tryProvider(baseUrl, apiKey) {
|
||||||
let res;
|
const started = Date.now();
|
||||||
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' }],
|
const item = res.data?.data?.[0];
|
||||||
tool_choice: { type: 'image_generation' },
|
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) {
|
||||||
headers: { Authorization: `Bearer ${config.ai.apiKey}` },
|
const r = await axios.get(item.url, { responseType: 'arraybuffer', timeout: 60_000 });
|
||||||
timeout: 300_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) {
|
} catch (err) {
|
||||||
aiUsage.log({
|
const status = err.response?.status;
|
||||||
provider: aiUsage.providerFromBaseUrl(config.ai.baseUrl),
|
if (!status || status >= 500) {
|
||||||
requestType: 'image_via_responses',
|
console.warn('[postImages] primary failed, trying fallback...');
|
||||||
model, imageCount: 1,
|
bytes = await tryProvider(config.ai.imageFallbackBaseUrl, config.ai.imageFallbackApiKey);
|
||||||
meta: { channel_id: channel.id },
|
} else { throw err; }
|
||||||
durationMs: Date.now() - started, succeeded: false,
|
|
||||||
errorMessage: (err.response?.data?.error?.message || err.message || '').slice(0, 500),
|
|
||||||
}).catch(() => {});
|
|
||||||
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 tsKey = `post-${channel.id}-${Date.now()}`;
|
||||||
const ext = imgCall.output_format || 'png';
|
const ext = 'png';
|
||||||
|
|
||||||
// Оптимизация через sharp если есть
|
// Оптимизация через sharp если есть
|
||||||
let publicUrl;
|
let publicUrl;
|
||||||
|
|||||||
Reference in New Issue
Block a user