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();
|
||||
|
||||
module.exports = {
|
||||
port: process.env.PORT || 3030,
|
||||
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
||||
port: parseInt(process.env.ZEROPOST_PORT || 3030),
|
||||
|
||||
// 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: {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 5432,
|
||||
|
||||
+52
-31
@@ -1,34 +1,55 @@
|
||||
const axios = require('axios');
|
||||
const config = require('../config');
|
||||
|
||||
const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
|
||||
const MODEL = 'claude-sonnet-4-6';
|
||||
|
||||
/**
|
||||
* Generate text via Anthropic API
|
||||
* @param {string} systemPrompt
|
||||
* @param {string} userPrompt
|
||||
* @param {object} options - { maxTokens, temperature }
|
||||
*/
|
||||
const generate = async (systemPrompt, userPrompt, options = {}) => {
|
||||
const { maxTokens = 2000 } = options;
|
||||
|
||||
const res = await axios.post(ANTHROPIC_URL, {
|
||||
model: MODEL,
|
||||
max_tokens: maxTokens,
|
||||
system: systemPrompt,
|
||||
messages: [{ role: 'user', content: userPrompt }],
|
||||
}, {
|
||||
headers: {
|
||||
'x-api-key': config.anthropicApiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
const client = axios.create({
|
||||
baseURL: config.ai.baseUrl,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 120000,
|
||||
});
|
||||
|
||||
const text = res.data.content?.[0]?.text;
|
||||
if (!text) throw new Error('Empty response from Anthropic');
|
||||
return text;
|
||||
/**
|
||||
* Low-level chat completion call (OpenAI-compatible gateway)
|
||||
* @param {string} model
|
||||
* @param {string} systemPrompt
|
||||
* @param {string} userPrompt
|
||||
* @param {object} options - { maxTokens }
|
||||
*/
|
||||
const chat = async (model, systemPrompt, userPrompt, options = {}) => {
|
||||
const { maxTokens = 2000 } = options;
|
||||
|
||||
const res = await client.post('/chat/completions', {
|
||||
model,
|
||||
max_tokens: maxTokens,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
}, {
|
||||
headers: { Authorization: `Bearer ${config.ai.apiKey}` },
|
||||
});
|
||||
|
||||
const text = res.data.choices?.[0]?.message?.content;
|
||||
if (!text) throw new Error('Empty response from AI gateway');
|
||||
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 релевантных хэштега`;
|
||||
|
||||
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(', ')}` : ''}`;
|
||||
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 system = `Генерируй идеи для постов в Telegram-канале. Отвечай только JSON массивом строк, без пояснений.`;
|
||||
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 {
|
||||
return JSON.parse(raw.replace(/```json|```/g, '').trim());
|
||||
} 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