forked from admin/zeropost-engine
feat: переход на OpenAI-совместимый шлюз aiprimetech.io
- ai.js: chat/completions вместо /v1/messages, разделены ключи (Claude/GPT) - config: AI_BASE_URL, AI_API_KEY, AI_IMAGE_API_KEY, per-task модели в env - модели по умолчанию: Haiku 4.5 для постов и идей, Sonnet 4.6 для статей, gpt-image-1 для картинок - добавлена функция image() для генерации изображений
This commit is contained in:
+16
-2
@@ -1,8 +1,22 @@
|
|||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
port: process.env.PORT || 3030,
|
port: parseInt(process.env.ZEROPOST_PORT || 3030),
|
||||||
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
||||||
|
// AI gateway (OpenAI-compatible: aiprimetech.io)
|
||||||
|
ai: {
|
||||||
|
baseUrl: process.env.AI_BASE_URL || 'https://aiprimetech.io/v1',
|
||||||
|
apiKey: process.env.AI_API_KEY,
|
||||||
|
imageApiKey: process.env.AI_IMAGE_API_KEY || process.env.AI_API_KEY,
|
||||||
|
// Per-task model selection — tune cost vs quality here
|
||||||
|
models: {
|
||||||
|
post: process.env.AI_MODEL_POST || 'claude-haiku-4-5-20251001',
|
||||||
|
article: process.env.AI_MODEL_ARTICLE || 'claude-sonnet-4-6',
|
||||||
|
topics: process.env.AI_MODEL_TOPICS || 'claude-haiku-4-5-20251001',
|
||||||
|
image: process.env.AI_MODEL_IMAGE || 'gpt-image-1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
db: {
|
db: {
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: process.env.DB_PORT || 5432,
|
port: process.env.DB_PORT || 5432,
|
||||||
|
|||||||
+43
-22
@@ -1,34 +1,55 @@
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
|
||||||
const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
|
const client = axios.create({
|
||||||
const MODEL = 'claude-sonnet-4-6';
|
baseURL: config.ai.baseUrl,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
timeout: 120000,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate text via Anthropic API
|
* Low-level chat completion call (OpenAI-compatible gateway)
|
||||||
|
* @param {string} model
|
||||||
* @param {string} systemPrompt
|
* @param {string} systemPrompt
|
||||||
* @param {string} userPrompt
|
* @param {string} userPrompt
|
||||||
* @param {object} options - { maxTokens, temperature }
|
* @param {object} options - { maxTokens }
|
||||||
*/
|
*/
|
||||||
const generate = async (systemPrompt, userPrompt, options = {}) => {
|
const chat = async (model, systemPrompt, userPrompt, options = {}) => {
|
||||||
const { maxTokens = 2000 } = options;
|
const { maxTokens = 2000 } = options;
|
||||||
|
|
||||||
const res = await axios.post(ANTHROPIC_URL, {
|
const res = await client.post('/chat/completions', {
|
||||||
model: MODEL,
|
model,
|
||||||
max_tokens: maxTokens,
|
max_tokens: maxTokens,
|
||||||
system: systemPrompt,
|
messages: [
|
||||||
messages: [{ role: 'user', content: userPrompt }],
|
{ role: 'system', content: systemPrompt },
|
||||||
|
{ role: 'user', content: userPrompt },
|
||||||
|
],
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: { Authorization: `Bearer ${config.ai.apiKey}` },
|
||||||
'x-api-key': config.anthropicApiKey,
|
|
||||||
'anthropic-version': '2023-06-01',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = res.data.content?.[0]?.text;
|
const text = res.data.choices?.[0]?.message?.content;
|
||||||
if (!text) throw new Error('Empty response from Anthropic');
|
if (!text) throw new Error('Empty response from AI gateway');
|
||||||
return text;
|
return text.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an image (GPT/DALL-E via gateway, separate key)
|
||||||
|
* @param {string} prompt
|
||||||
|
* @param {object} options - { size }
|
||||||
|
* @returns {string} image URL or b64
|
||||||
|
*/
|
||||||
|
const image = async (prompt, options = {}) => {
|
||||||
|
const { size = '1024x1024' } = options;
|
||||||
|
const res = await client.post('/images/generations', {
|
||||||
|
model: config.ai.models.image,
|
||||||
|
prompt,
|
||||||
|
n: 1,
|
||||||
|
size,
|
||||||
|
}, {
|
||||||
|
headers: { Authorization: `Bearer ${config.ai.imageApiKey}` },
|
||||||
|
});
|
||||||
|
return res.data.data?.[0]?.url || res.data.data?.[0]?.b64_json;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +66,7 @@ ${channelContext ? `Контекст канала: ${channelContext}` : ''}
|
|||||||
- В конце добавь 2-4 релевантных хэштега`;
|
- В конце добавь 2-4 релевантных хэштега`;
|
||||||
|
|
||||||
const user = `Напиши пост на тему: "${topic}"`;
|
const user = `Напиши пост на тему: "${topic}"`;
|
||||||
return generate(system, user, { maxTokens: 1000 });
|
return chat(config.ai.models.post, system, user, { maxTokens: 1000 });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +81,7 @@ const generateArticle = async ({ topic, language = 'ru', keywords = [] }) => {
|
|||||||
- Простой и понятный язык`;
|
- Простой и понятный язык`;
|
||||||
|
|
||||||
const user = `Напиши статью на тему: "${topic}"${keywords.length ? `\nКлючевые слова: ${keywords.join(', ')}` : ''}`;
|
const user = `Напиши статью на тему: "${topic}"${keywords.length ? `\nКлючевые слова: ${keywords.join(', ')}` : ''}`;
|
||||||
return generate(system, user, { maxTokens: 3000 });
|
return chat(config.ai.models.article, system, user, { maxTokens: 3000 });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,12 +90,12 @@ const generateArticle = async ({ topic, language = 'ru', keywords = [] }) => {
|
|||||||
const generateTopics = async ({ channelContext, count = 10, language = 'ru' }) => {
|
const generateTopics = async ({ channelContext, count = 10, language = 'ru' }) => {
|
||||||
const system = `Генерируй идеи для постов в Telegram-канале. Отвечай только JSON массивом строк, без пояснений.`;
|
const system = `Генерируй идеи для постов в Telegram-канале. Отвечай только JSON массивом строк, без пояснений.`;
|
||||||
const user = `Придумай ${count} идей для постов. Контекст канала: "${channelContext}". Язык: ${language}. Формат: ["тема1","тема2",...]`;
|
const user = `Придумай ${count} идей для постов. Контекст канала: "${channelContext}". Язык: ${language}. Формат: ["тема1","тема2",...]`;
|
||||||
const raw = await generate(system, user, { maxTokens: 800 });
|
const raw = await chat(config.ai.models.topics, system, user, { maxTokens: 800 });
|
||||||
try {
|
try {
|
||||||
return JSON.parse(raw.replace(/```json|```/g, '').trim());
|
return JSON.parse(raw.replace(/```json|```/g, '').trim());
|
||||||
} catch {
|
} catch {
|
||||||
return raw.split('\n').filter(Boolean).slice(0, count);
|
return raw.split('\n').map(s => s.replace(/^[-*\d.\s]+/, '').trim()).filter(Boolean).slice(0, count);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { generate, generatePost, generateArticle, generateTopics };
|
module.exports = { chat, image, generatePost, generateArticle, generateTopics };
|
||||||
|
|||||||
Reference in New Issue
Block a user