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:
Alexey Pavlov
2026-05-30 21:46:28 +03:00
parent 612053b93d
commit 36c02a9a0a
2 changed files with 59 additions and 24 deletions
+16 -2
View File
@@ -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,
+43 -22
View File
@@ -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';
const client = axios.create({
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} 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 res = await axios.post(ANTHROPIC_URL, {
model: MODEL,
const res = await client.post('/chat/completions', {
model,
max_tokens: maxTokens,
system: systemPrompt,
messages: [{ role: 'user', content: userPrompt }],
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
}, {
headers: {
'x-api-key': config.anthropicApiKey,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json',
},
headers: { Authorization: `Bearer ${config.ai.apiKey}` },
});
const text = res.data.content?.[0]?.text;
if (!text) throw new Error('Empty response from Anthropic');
return text;
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 };