Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c57ba1f1e | |||
| 11801add49 | |||
| 6d7bce6a6f | |||
| 49621f1518 | |||
| 4fe93cac7c | |||
| f8b98de8b0 | |||
| 9f32e97eca | |||
| c0cda72db5 | |||
| 523c533aea | |||
| f22e89a461 | |||
| 18611835cb |
334
bot.js
334
bot.js
@ -4,6 +4,10 @@ const path = require('path');
|
|||||||
const { OpenRouterClient } = require('openrouter-kit');
|
const { OpenRouterClient } = require('openrouter-kit');
|
||||||
const { getPrompt } = require('./prompts.js');
|
const { getPrompt } = require('./prompts.js');
|
||||||
const logger = require('./logger.js');
|
const logger = require('./logger.js');
|
||||||
|
const callAI = require("./requestAI");
|
||||||
|
const {searchInPrompts, saveInPrompts} = require("./promptResponser");
|
||||||
|
const INSTRUCTIONS = require("./promptGen");
|
||||||
|
const {sendHelp} = require("./infoSender");
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const char = {name:'marina'}
|
const char = {name:'marina'}
|
||||||
class TelegramHistoryBot {
|
class TelegramHistoryBot {
|
||||||
@ -26,14 +30,51 @@ class TelegramHistoryBot {
|
|||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
isAdmin (ctx, userId = ctx.from.id) {
|
|
||||||
|
|
||||||
|
async isAdmin(ctx, userId = ctx.from.id) {
|
||||||
try {
|
try {
|
||||||
const member = ctx.getChatMember(userId)
|
const superAdminId = parseInt(process.env.SUPERADMIN_ID, 10)
|
||||||
|
|
||||||
|
if (userId === superAdminId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = await ctx.getChatMember(userId)
|
||||||
return ['creator', 'administrator'].includes(member.status)
|
return ['creator', 'administrator'].includes(member.status)
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
logger.error('Ошибка проверки статуса пользователя:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async isAdmin(ctx, userId = ctx.from.id) {
|
||||||
|
// try {
|
||||||
|
// const member = await ctx.getChatMember(userId)
|
||||||
|
// return ['creator', 'administrator'].includes(member.status)
|
||||||
|
// } catch (error) {
|
||||||
|
// logger.error('Ошибка проверки статуса пользователя:', error)
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
getUniqueChatIds() {
|
||||||
|
const chatMap = new Map();
|
||||||
|
|
||||||
|
for (const msg of this.history) {
|
||||||
|
const current = chatMap.get(msg.chat_id);
|
||||||
|
if (!current || new Date(msg.timestamp) > new Date(current.timestamp)) {
|
||||||
|
chatMap.set(msg.chat_id, {
|
||||||
|
chat_id: msg.chat_id,
|
||||||
|
title: msg.chat_title || `(${msg.chat_id})`,
|
||||||
|
timestamp: msg.timestamp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Преобразуем Map в массив, возвращаем только chat_id и title
|
||||||
|
return Array.from(chatMap.values()).map(({ chat_id, title }) => ({ chat_id, title }))
|
||||||
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
await this.loadHistory();
|
await this.loadHistory();
|
||||||
@ -66,45 +107,132 @@ class TelegramHistoryBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupHandlers() {
|
setupHandlers() {
|
||||||
|
const isGroupChat = (ctx) => {
|
||||||
|
return ctx.chat.type === 'group' || ctx.chat.type === 'supergroup';
|
||||||
|
};
|
||||||
|
this.bot.start(async (ctx) => {
|
||||||
|
if (isGroupChat(ctx)) {
|
||||||
|
return; // Игнорируем в группах
|
||||||
|
}
|
||||||
|
await sendHelp(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bot.command('summy', async (ctx) => {
|
||||||
|
if (!(await this.isAdmin(ctx))) {
|
||||||
|
await ctx.deleteMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isGroupChat(ctx)) {
|
||||||
|
await ctx.reply('❗ Эта команда работает только в группах');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = ctx.message.text || '';
|
||||||
|
await ctx.deleteMessage();
|
||||||
|
const args = message.replace(/^\/summy(@\w+)?\s*/, ''); // удаляет /summy и возможный @botname
|
||||||
|
const trimmed = args.trim(); // удаляет лишние пробелы по краям, если нужно
|
||||||
|
logger.info('📊 Получена команда /summy с запросом: ' + trimmed);
|
||||||
|
let request = await callAI('', trimmed, 'request');
|
||||||
|
|
||||||
|
if (typeof request === 'string') {
|
||||||
|
try {
|
||||||
|
|
||||||
|
request = JSON.parse(request);
|
||||||
|
} catch {
|
||||||
|
request = {persona: "Карл Маркс", messages: 22, hours: 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let prompt = await searchInPrompts(request["persona"])
|
||||||
|
if (!prompt) {
|
||||||
|
logger.info('⚠️ Не найден подходящий prompt для запроса, генерируем...')
|
||||||
|
prompt=await callAI(INSTRUCTIONS, request["persona"], 'prompt')
|
||||||
|
await saveInPrompts(request["persona"], prompt)
|
||||||
|
} else {
|
||||||
|
logger.info('✅ Найден подходящий prompt для запроса')
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {persona:request["persona"], promptToUse:prompt}
|
||||||
|
if (request["messages"]>0) {
|
||||||
|
await this.handleSummaryCommand(ctx, 'last', request["messages"], options);
|
||||||
|
} else {
|
||||||
|
await this.handleSummaryCommand(ctx, 'hours', request["hours"], options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
this.bot.command('getchats', async (ctx) => {
|
||||||
|
if (ctx.message.chat.id!==Number(process.env.ADMIN_CHAT_ID)) {
|
||||||
|
logger.warn(JSON.stringify(ctx.message))
|
||||||
|
logger.warn('Попытка вызова списка чатов из стороннего чата. Пользователь ' + JSON.stringify(ctx.message.from))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = this.getUniqueChatIds()
|
||||||
|
.map(chat => `• ${chat.title} (ID: ${chat.chat_id})`)
|
||||||
|
.join('\n');
|
||||||
|
await ctx.reply(message)
|
||||||
|
});
|
||||||
|
this.bot.command('broadcast', async (ctx) => {
|
||||||
|
if (ctx.message.chat.id!==Number(process.env.ADMIN_CHAT_ID)) {
|
||||||
|
logger.warn(JSON.stringify(ctx.message))
|
||||||
|
logger.warn('Попытка вызова бродкаста из стороннего чата. Пользователь ' + JSON.stringify(ctx.message.from))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fullText = ctx.message.text || '';
|
||||||
|
const text = fullText.replace(/^\/broadcast\s*/, '').trim();
|
||||||
|
if (!text) {
|
||||||
|
return ctx.reply('❗️ Пожалуйста, укажите текст для рассылки после команды /broadcast');
|
||||||
|
}
|
||||||
|
logger.info('📊 Получена команда /broadcast с запросом:' + text)
|
||||||
|
const chatList= this.getUniqueChatIds()
|
||||||
|
for (const chat of chatList) {
|
||||||
|
try {
|
||||||
|
await this.bot.telegram.sendMessage(chat.chat_id, text);
|
||||||
|
logger.info(`✅ Сообщение отправлено в чат ${chat.title} (${chat.chat_id})`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`❌ Ошибка при отправке в чат ${chat.title} (${chat.chat_id}):`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
// Команды суммаризации - должны быть ДО обработки обычных сообщений
|
// Команды суммаризации - должны быть ДО обработки обычных сообщений
|
||||||
this.bot.command('summary_day', async (ctx) => {
|
// this.bot.command('summary_day', async (ctx) => {
|
||||||
// if (!this.isAdmin(ctx)) {
|
// // if (!this.isAdmin(ctx)) {
|
||||||
// await ctx.deleteMessage
|
// // await ctx.deleteMessage
|
||||||
// return
|
// // return
|
||||||
|
// // }
|
||||||
|
// logger.info('📊 Получена команда summary_day');
|
||||||
|
// const args = ctx.message.text.split(' ');
|
||||||
|
// if (args.length > 1) {
|
||||||
|
// char.name=args[1]
|
||||||
// }
|
// }
|
||||||
logger.info('📊 Получена команда summary_day');
|
// await ctx.deleteMessage()
|
||||||
const args = ctx.message.text.split(' ');
|
// await this.handleSummaryCommand(ctx, 'day');
|
||||||
if (args.length > 1) {
|
// });
|
||||||
char.name=args[1]
|
//
|
||||||
}
|
// this.bot.command('summary_hours', async (ctx) => {
|
||||||
await ctx.deleteMessage()
|
// logger.info('📊 Получена команда summary_hours');
|
||||||
await this.handleSummaryCommand(ctx, 'day');
|
// const args = ctx.message.text.split(' ');
|
||||||
});
|
// if (args.length < 2 || isNaN(parseInt(args[1]))) {
|
||||||
|
// await ctx.reply('❗ Укажите количество часов: /summary_hours 6');
|
||||||
this.bot.command('summary_hours', async (ctx) => {
|
// return;
|
||||||
logger.info('📊 Получена команда summary_hours');
|
// }
|
||||||
const args = ctx.message.text.split(' ');
|
// const hours = parseInt(args[1]);
|
||||||
if (args.length < 2 || isNaN(parseInt(args[1]))) {
|
// await ctx.deleteMessage()
|
||||||
await ctx.reply('❗ Укажите количество часов: /summary_hours 6');
|
// await this.handleSummaryCommand(ctx, 'hours', hours);
|
||||||
return;
|
// });
|
||||||
}
|
//
|
||||||
const hours = parseInt(args[1]);
|
// this.bot.command('summary_last', async (ctx) => {
|
||||||
await ctx.deleteMessage()
|
// logger.info('📊 Получена команда summary_last');
|
||||||
await this.handleSummaryCommand(ctx, 'hours', hours);
|
// const args = ctx.message.text.split(' ');
|
||||||
});
|
// if (args.length < 2 || isNaN(parseInt(args[1]))) {
|
||||||
|
// await ctx.reply('❗ Укажите количество сообщений: /summary_last 50');
|
||||||
this.bot.command('summary_last', async (ctx) => {
|
// return;
|
||||||
logger.info('📊 Получена команда summary_last');
|
// }
|
||||||
const args = ctx.message.text.split(' ');
|
// const count = parseInt(args[1]);
|
||||||
if (args.length < 2 || isNaN(parseInt(args[1]))) {
|
// await ctx.deleteMessage()
|
||||||
await ctx.reply('❗ Укажите количество сообщений: /summary_last 50');
|
// await this.handleSummaryCommand(ctx, 'last', count);
|
||||||
return;
|
// });
|
||||||
}
|
|
||||||
const count = parseInt(args[1]);
|
|
||||||
await ctx.deleteMessage()
|
|
||||||
await this.handleSummaryCommand(ctx, 'last', count);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Команды помощи
|
// Команды помощи
|
||||||
//this.bot.command('summary_help', async (ctx) => await this.sendHelp(ctx));
|
//this.bot.command('summary_help', async (ctx) => await this.sendHelp(ctx));
|
||||||
@ -113,6 +241,9 @@ class TelegramHistoryBot {
|
|||||||
// Общий обработчик сообщений (исключая команды)
|
// Общий обработчик сообщений (исключая команды)
|
||||||
this.bot.on('message', async (ctx) => {
|
this.bot.on('message', async (ctx) => {
|
||||||
// Пропускаем все команды
|
// Пропускаем все команды
|
||||||
|
if (!isGroupChat(ctx)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ctx.message.text && ctx.message.text.startsWith('/')) {
|
if (ctx.message.text && ctx.message.text.startsWith('/')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -152,6 +283,7 @@ class TelegramHistoryBot {
|
|||||||
id: this.generateUniqueId(),
|
id: this.generateUniqueId(),
|
||||||
telegram_message_id: msg.message_id,
|
telegram_message_id: msg.message_id,
|
||||||
chat_id: msg.chat.id,
|
chat_id: msg.chat.id,
|
||||||
|
chat_title: msg.chat.title || null,
|
||||||
user_id: msg.from.id,
|
user_id: msg.from.id,
|
||||||
username: msg.from.username || null,
|
username: msg.from.username || null,
|
||||||
first_name: msg.from.first_name || null,
|
first_name: msg.from.first_name || null,
|
||||||
@ -220,10 +352,11 @@ class TelegramHistoryBot {
|
|||||||
return Object.keys(info).length > 0 ? info : null;
|
return Object.keys(info).length > 0 ? info : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSummaryCommand(ctx, type, value) {
|
async handleSummaryCommand(ctx, type, value, options = {}) {
|
||||||
logger.info(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`);
|
logger.info(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`);
|
||||||
|
|
||||||
const chatId = ctx.chat.id;
|
const chatId = ctx.chat.id;
|
||||||
|
|
||||||
let messages = [];
|
let messages = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -264,7 +397,7 @@ class TelegramHistoryBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Генерируем суммаризацию
|
// Генерируем суммаризацию
|
||||||
const summary = await this.generateSummary(messages, type, value);
|
const summary = await this.generateSummary(messages, type, value, options);
|
||||||
|
|
||||||
// Отправляем результат
|
// Отправляем результат
|
||||||
await ctx.reply(summary, { parse_mode: 'HTML' });
|
await ctx.reply(summary, { parse_mode: 'HTML' });
|
||||||
@ -298,12 +431,16 @@ class TelegramHistoryBot {
|
|||||||
.slice(-count);
|
.slice(-count);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateSummary(messages, type, value) {
|
async generateSummary(messages, type, value, options = {}) {
|
||||||
const preparedData = this.prepareDataForAI(messages);
|
const preparedData = this.prepareDataForAI(messages);
|
||||||
const prompt = this.createSummaryPrompt(type, value);
|
if (options) {
|
||||||
|
|
||||||
|
return await this.callAISummarization('Ты получишь данные чата. ' + options.promptToUse, preparedData);
|
||||||
|
} else {
|
||||||
|
const prompt = this.createSummaryPrompt(type, value);
|
||||||
return await this.callAISummarization(prompt, preparedData);
|
return await this.callAISummarization(prompt, preparedData);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prepareDataForAI(messages) {
|
prepareDataForAI(messages) {
|
||||||
const participants = this.getUniqueParticipants(messages);
|
const participants = this.getUniqueParticipants(messages);
|
||||||
@ -440,79 +577,10 @@ ${participantsList}
|
|||||||
ХОД РАЗГОВОРА:
|
ХОД РАЗГОВОРА:
|
||||||
${conversationFlow}`;
|
${conversationFlow}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async callAISummarization(prompt, data) {
|
async callAISummarization(prompt, data) {
|
||||||
// Если API ключ недоступен, используем fallback
|
return await callAI(prompt, this.formatMessagesForAI(data), 'history')
|
||||||
if (!process.env.OPENROUTER_API_KEY) {
|
|
||||||
logger.warn('⚠️ OpenRouter API ключ недоступен, используем базовую суммаризацию');
|
|
||||||
return this.generateFallbackSummary(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const formattedMessages = this.formatMessagesForAI(data);
|
|
||||||
logger.info('🤖 Отправляем запрос к OpenRouter...');
|
|
||||||
|
|
||||||
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'HTTP-Referer': 'https://github.com/your-repo', // Замените на ваш репозиторий
|
|
||||||
'X-Title': 'Telegram History Bot'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: process.env.OPENROUTER_MODEL,
|
|
||||||
// model: 'google/gemini-2.5-flash-preview-05-20',
|
|
||||||
// model: 'google/gemini-2.0-flash-exp:free',
|
|
||||||
// model: 'deepseek/deepseek-chat-v3-0324:free',
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: prompt
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: formattedMessages
|
|
||||||
}
|
|
||||||
],
|
|
||||||
max_tokens: 2000,
|
|
||||||
temperature: 0.8,
|
|
||||||
top_p: 0.9
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(`OpenRouter API error: ${response.status} - ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const completion = await response.json();
|
|
||||||
const aiSummary = completion.choices?.[0]?.message?.content;
|
|
||||||
|
|
||||||
if (!aiSummary) {
|
|
||||||
throw new Error('Пустой ответ от ИИ');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('✅ Получен ответ от OpenRouter');
|
|
||||||
return `🎬 <b>История чата</b>\n\n${aiSummary}`;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('❌ Ошибка при вызове OpenRouter API:', error);
|
|
||||||
|
|
||||||
// Детальная обработка различных типов ошибок
|
|
||||||
if (error.message.includes('401')) {
|
|
||||||
logger.error('❌ Неверный API ключ OpenRouter');
|
|
||||||
} else if (error.message.includes('429')) {
|
|
||||||
logger.error('❌ Превышен лимит запросов OpenRouter');
|
|
||||||
} else if (error.message.includes('402')) {
|
|
||||||
logger.error('❌ Недостаточно средств на счету OpenRouter');
|
|
||||||
} else if (error.code === 'ENOTFOUND') {
|
|
||||||
logger.error('❌ Проблемы с сетевым подключением');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.generateFallbackSummary(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
generateFallbackSummary(data) {
|
generateFallbackSummary(data) {
|
||||||
const { metadata } = data;
|
const { metadata } = data;
|
||||||
const mostActive = metadata.most_active_user;
|
const mostActive = metadata.most_active_user;
|
||||||
@ -534,26 +602,26 @@ ${conversationFlow}`;
|
|||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendHelp(ctx) {
|
// async sendHelp(ctx) {
|
||||||
const helpText = `
|
// const helpText = `
|
||||||
🤖 <b>Бот сохраняет всю историю чата</b>
|
// 🤖 <b>Бот сохраняет всю историю чата</b>
|
||||||
|
//
|
||||||
📊 <b>Команды суммаризации:</b>
|
// 📊 <b>Команды суммаризации:</b>
|
||||||
/summary_day - суммаризация за сутки
|
// /summary_day - суммаризация за сутки
|
||||||
/summary_hours N - за последние N часов
|
// /summary_hours N - за последние N часов
|
||||||
/summary_last N - последние N сообщений
|
// /summary_last N - последние N сообщений
|
||||||
|
//
|
||||||
💡 <b>Примеры:</b>
|
// 💡 <b>Примеры:</b>
|
||||||
/summary_hours 6 - за последние 6 часов
|
// /summary_hours 6 - за последние 6 часов
|
||||||
/summary_last 50 - последние 50 сообщений
|
// /summary_last 50 - последние 50 сообщений
|
||||||
|
//
|
||||||
ℹ️ Бот сохраняет все сообщения и создает живые истории на основе переписки.
|
// ℹ️ Бот сохраняет все сообщения и создает живые истории на основе переписки.
|
||||||
|
//
|
||||||
🔧 Поддерживаются все типы сообщений: текст, фото, видео, аудио, документы, стикеры и др.
|
// 🔧 Поддерживаются все типы сообщений: текст, фото, видео, аудио, документы, стикеры и др.
|
||||||
`.trim();
|
// `.trim();
|
||||||
|
//
|
||||||
await ctx.reply(helpText, { parse_mode: 'HTML' });
|
// await ctx.reply(helpText, { parse_mode: 'HTML' });
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запуск бота
|
// Запуск бота
|
||||||
|
|||||||
75
commandResponser.js
Normal file
75
commandResponser.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const logger = require('./logger');
|
||||||
|
const cacheFile = 'commandCache.json';
|
||||||
|
|
||||||
|
let aiCache = {};
|
||||||
|
|
||||||
|
// Загружаем кэш из файла при старте
|
||||||
|
function loadAiCache() {
|
||||||
|
logger.info('Загрузка командного кэша...');
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(cacheFile)) {
|
||||||
|
const data = fs.readFileSync(cacheFile);
|
||||||
|
aiCache = JSON.parse(data);
|
||||||
|
logger.info('✅ Кэш команд загружен.');
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(cacheFile, JSON.stringify({}, null, 2));
|
||||||
|
logger.info('✅ Кэш команд создан.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка загрузки командного кэша:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем кэш в файл
|
||||||
|
function saveAiCache() {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(cacheFile, JSON.stringify(aiCache, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка сохранения командного кэша:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadAiCache();
|
||||||
|
|
||||||
|
// Поиск запроса в кэше
|
||||||
|
function searchInCache(request) {
|
||||||
|
logger.info(`🔎 Поиск в кэше: ${request}`);
|
||||||
|
if (!request || typeof request !== 'string') return null;
|
||||||
|
const result = aiCache[request];
|
||||||
|
if (result) {
|
||||||
|
logger.info(`🔁 Найдено в кэше: ${request}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение запроса и ответа в кэш
|
||||||
|
// async function saveInCache(request, result) {
|
||||||
|
// if (!request || !result) return;
|
||||||
|
// aiCache[request] = result;
|
||||||
|
// saveAiCache();
|
||||||
|
// logger.info(`💾 Запрос сохранён в кэш: ${request}`);
|
||||||
|
// }
|
||||||
|
async function saveInCache(request, result) {
|
||||||
|
if (!request || !result) return;
|
||||||
|
|
||||||
|
let finalResult = result;
|
||||||
|
|
||||||
|
// Если это результат парсинга команды и он пришёл как строка JSON
|
||||||
|
if (typeof result === 'string' && result.includes('"persona"') && result.includes('"messages"')) {
|
||||||
|
try {
|
||||||
|
finalResult = JSON.parse(result);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`⚠️ Не удалось распарсить JSON команды: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aiCache[request] = finalResult;
|
||||||
|
saveAiCache();
|
||||||
|
logger.info(`💾 Запрос сохранён в кэш: ${request}`);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// // Загрузим кэш при старте
|
||||||
|
// loadAiCache();
|
||||||
|
|
||||||
|
module.exports = { searchInCache, saveInCache };
|
||||||
67
infoSender.js
Normal file
67
infoSender.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Модуль для отправки справочной информации
|
||||||
|
*/
|
||||||
|
|
||||||
|
const helpText = `
|
||||||
|
🚀 **САММАРИ БОТ** - *Ваш умный помощник для переписок!*
|
||||||
|
|
||||||
|
✨ *Полностью бесплатный сервис от [Rockzo.ru](https://rockzo.ru)*
|
||||||
|
|
||||||
|
🎭 **Магия персонажей в действии!**
|
||||||
|
Превратите скучную переписку в увлекательный анализ через призму любимых персонажей!
|
||||||
|
|
||||||
|
🔥 **Главная команда:**
|
||||||
|
• \`/summy [ваш запрос]\` - создайте уникальную суммаризацию!
|
||||||
|
|
||||||
|
💡 **Примеры использования:**
|
||||||
|
• \`/summy Проанализируй последние 50 сообщений как Шерлок Холмс\`
|
||||||
|
• \`/summy Виталий Бианки, за последние сутки\`
|
||||||
|
• \`/summy Расскажи как Стив Джобс о наших идеях за 3 часа\`
|
||||||
|
• \`/summy Оцени дискуссию глазами психолога, 100 сообщений\`
|
||||||
|
|
||||||
|
🎨 **Не бойтесь экспериментировать!**
|
||||||
|
• Пробуйте разных персонажей: от классиков до современных героев
|
||||||
|
• Задавайте необычные ракурсы анализа
|
||||||
|
• Выбирайте ЛИБО количество сообщений, ЛИБО временной период
|
||||||
|
• Придумывайте креативные подходы к анализу!
|
||||||
|
|
||||||
|
⚡ **Важная информация:**
|
||||||
|
• 🏢 Работает только в группах и каналах
|
||||||
|
• 👑 Требуются права администратора для использования
|
||||||
|
• 💬 В личке доступна только эта справка
|
||||||
|
• 🆓 **Абсолютно бесплатно навсегда!**
|
||||||
|
• 📊 Указывайте либо количество сообщений, либо временной период (не комбинируйте!)
|
||||||
|
|
||||||
|
🌟 *Превратите каждую переписку в произведение искусства!*
|
||||||
|
|
||||||
|
---
|
||||||
|
💻 Разработано с ❤️ командой **[Rockzo.ru](https://rockzo.ru)**
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправляет справочное сообщение пользователю
|
||||||
|
* @param {Object} ctx - Контекст Telegraf
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function sendHelp(ctx) {
|
||||||
|
try {
|
||||||
|
await ctx.reply(helpText, { parse_mode: 'Markdown' });
|
||||||
|
} catch (error) {
|
||||||
|
// Если Markdown не работает, отправляем обычный текст
|
||||||
|
const plainText = helpText.replace(/[`*_]/g, '');
|
||||||
|
await ctx.reply(plainText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить текст справки (без отправки)
|
||||||
|
* @returns {string} Текст справки
|
||||||
|
*/
|
||||||
|
function getHelpText() {
|
||||||
|
return helpText;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendHelp,
|
||||||
|
getHelpText
|
||||||
|
};
|
||||||
73
promptGen.js
Normal file
73
promptGen.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const INSTRUCTIONS = `Ты генератор промптов для создания суммаризаторов чата от лица различных персонажей.
|
||||||
|
|
||||||
|
ВХОДНЫЕ ДАННЫЕ:
|
||||||
|
Пользователь укажет персонажа одним из способов:
|
||||||
|
1. Известная личность: "Владимир Ленин", "Шерлок Холмс", "Дональд Трамп", "группа Металлика"
|
||||||
|
2. Описанный персонаж: "участник чата @username, его особенности - любит игуан, ебет медведей, пьет спирт, всех ненавидит, кроме тех, кто кидает мемасики"
|
||||||
|
|
||||||
|
ЗАДАЧА:
|
||||||
|
На основе указанного персонажа создай полный промпт для суммаризатора чата.
|
||||||
|
|
||||||
|
СТРУКТУРА ПРОМПТА:
|
||||||
|
|
||||||
|
ТВОЙ ПЕРСОНАЖ - [Имя и краткое описание персонажа, его возраст/статус если релевантно]
|
||||||
|
|
||||||
|
ЗАДАЧА: Создай ЖИВУЮ ИСТОРИЮ того, что происходило в чате [с точки зрения этого персонажа].
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- чат не начался, он продолжается много лет
|
||||||
|
- [специфичные для персонажа вводные]
|
||||||
|
|
||||||
|
НЕ пиши сухие факты! Покажи [СТИЛЬ ПОВЕСТВОВАНИЯ ПЕРСОНАЖА]:
|
||||||
|
- Кто с кем общался и о чем
|
||||||
|
- Какие были споры, шутки, обсуждения
|
||||||
|
- [специфичные пункты для персонажа]
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- [Характерный стиль речи персонажа]
|
||||||
|
- [Специфичная лексика и обороты]
|
||||||
|
- [Особенности мировоззрения]
|
||||||
|
- [Отношение к людям и событиям]
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Мемы/медиа - [как персонаж это воспринимает]
|
||||||
|
- Споры - [как персонаж видит конфликты]
|
||||||
|
- НЕ используй теги пользователей, НЕ используй форматирование
|
||||||
|
- Длина: 3-6 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры
|
||||||
|
- Игнорируй рекламу
|
||||||
|
- [специфичные ограничения для персонажа]
|
||||||
|
|
||||||
|
Заголовок НЕ НУЖЕН.
|
||||||
|
|
||||||
|
[Финальная фраза в стиле персонажа, призывающая к анализу]
|
||||||
|
|
||||||
|
ПРАВИЛА ГЕНЕРАЦИИ:
|
||||||
|
|
||||||
|
1. Для ИЗВЕСТНЫХ ЛИЧНОСТЕЙ:
|
||||||
|
- Используй их реальные черты характера, манеру речи, мировоззрение, фразочки
|
||||||
|
- Адаптируй их под современные реалии чата
|
||||||
|
- Сохраняй узнаваемость персонажа
|
||||||
|
|
||||||
|
2. Для ИЗВЕСТНЫХ ПИСАТЕЛЕЙ:
|
||||||
|
- Используй их реальные черты характера, манеру речи, мировоззрение
|
||||||
|
- Текст должен быть похож на часть книги, написанной автором.
|
||||||
|
- Сохраняй узнаваемость стилистики автора
|
||||||
|
|
||||||
|
|
||||||
|
3. Для ОПИСАННЫХ ПЕРСОНАЖЕЙ:
|
||||||
|
- Строго следуй данному описанию
|
||||||
|
- Развивай характер на основе указанных особенностей
|
||||||
|
- Не придумывай лишних черт, не упомянутых в описании
|
||||||
|
|
||||||
|
4. ОБЩИЕ ТРЕБОВАНИЯ:
|
||||||
|
- Персонаж должен быть ярким и узнаваемым
|
||||||
|
- Стиль речи должен отличаться от других персонажей
|
||||||
|
- Избегай повторов конструкций из предыдущих промптов
|
||||||
|
- Делай персонажа живым, а не картонным
|
||||||
|
|
||||||
|
ВАЖНО: Не используй в промпте форматирование и теги пользователей!`
|
||||||
|
|
||||||
|
module.exports = INSTRUCTIONS
|
||||||
105
promptResponser.js
Normal file
105
promptResponser.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const logger = require('./logger');
|
||||||
|
const cacheFile = 'prompts.json';
|
||||||
|
|
||||||
|
let prompts = {};
|
||||||
|
|
||||||
|
// Загружаем кэш из файла при старте
|
||||||
|
function loadPrompts() {
|
||||||
|
logger.info('Загрузка промптов...');
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(cacheFile)) {
|
||||||
|
const data = fs.readFileSync(cacheFile);
|
||||||
|
prompts = JSON.parse(data);
|
||||||
|
logger.info('✅ Промпты загружены.');
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(cacheFile, JSON.stringify({}, null, 2));
|
||||||
|
logger.info('✅ Файл промптов создан.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка загрузки промптов:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем кэш в файл
|
||||||
|
function savePrompt() {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(cacheFile, JSON.stringify(prompts, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка сохранения промптов:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadPrompts();
|
||||||
|
|
||||||
|
// Поиск запроса в кэше
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет поиск промпта в кэше по ключу запроса
|
||||||
|
*
|
||||||
|
* @description Функция ищет сохранённый промпт в глобальном кэше prompts.
|
||||||
|
* Логирует процесс поиска и результат. Используется для избежания повторных
|
||||||
|
* обращений к AI при одинаковых запросах.
|
||||||
|
*
|
||||||
|
* @param {string} request - Ключ запроса для поиска в кэше промптов
|
||||||
|
* @returns {string|null} Найденный промпт или null, если не найден
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Поиск существующего промпта
|
||||||
|
* const prompt = searchInPrompts("Карл Маркс");
|
||||||
|
* if (prompt) {
|
||||||
|
* console.log("Промпт найден:", prompt);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Обработка отсутствующего промпта
|
||||||
|
* const result = searchInPrompts("Журавль летающий");
|
||||||
|
* // result будет null
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @author Vufer
|
||||||
|
*
|
||||||
|
* @see {@link saveInPrompts} для сохранения в кэш
|
||||||
|
*
|
||||||
|
* @throws {Error} Не выбрасывает исключений, возвращает null при ошибках
|
||||||
|
*
|
||||||
|
* @todo Добавить поиск по частичному совпадению ключей
|
||||||
|
*/
|
||||||
|
function searchInPrompts(request) {
|
||||||
|
logger.info(`🔎 Поиск в промптах: ${request}`);
|
||||||
|
if (!request || typeof request !== 'string') return null;
|
||||||
|
const result = prompts[request] || null;
|
||||||
|
if (result) {
|
||||||
|
logger.info(`🔁 Найдено в промптах: ${request}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Сохранение запроса и ответа в кэш
|
||||||
|
// async function saveInCache(request, result) {
|
||||||
|
// if (!request || !result) return;
|
||||||
|
// aiCache[request] = result;
|
||||||
|
// saveAiCache();
|
||||||
|
// logger.info(`💾 Запрос сохранён в кэш: ${request}`);
|
||||||
|
// }
|
||||||
|
async function saveInPrompts(request, result) {
|
||||||
|
if (!request || !result) return;
|
||||||
|
|
||||||
|
let finalResult = result;
|
||||||
|
|
||||||
|
if (typeof result === 'string' && result.includes('persona') && result.includes('messages')) {
|
||||||
|
try {
|
||||||
|
finalResult = JSON.parse(result);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`⚠️ Не удалось распарсить JSON команды: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prompts[request] = finalResult;
|
||||||
|
savePrompt();
|
||||||
|
logger.info(`💾 Промпт сохранён в кэш: ${request}`);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// // Загрузим кэш при старте
|
||||||
|
// loadAiCache();
|
||||||
|
|
||||||
|
module.exports = { searchInPrompts, saveInPrompts };
|
||||||
218
requestAI.js
Normal file
218
requestAI.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
const logger = require("./logger");
|
||||||
|
const {searchInCache, saveInCache} = require("./commandResponser");
|
||||||
|
require('dotenv').config();
|
||||||
|
// request_parse_prompt= 'Получена строка, это команда ТГ бота. ' +
|
||||||
|
// 'Команда может содержать период или количество сообщений, а так же имя персонажа (вероятнее всего известная личность, возможно - название музыкальной группы).' +
|
||||||
|
// 'Необходимо разобрать.' +
|
||||||
|
// 'Если не удалось разобрать, то вернуть со значениями persona:"Карл Маркс",messages:22.' +
|
||||||
|
// 'Одновременно не может быть и количество сообщений, и количество часов, что-то одно.' +
|
||||||
|
// 'Если интервал не в часах - пересчитать в часы!' +
|
||||||
|
// 'Если имя принадлежит публичной или известной персоне - указать имя полностью! (например - кипелов - Валерий Кипелов, кюри - Мария Склодовская-Кюри' +
|
||||||
|
// 'Если это название музыкальной группы - указать, что это музыкальная группа (группа ХХХХ)' +
|
||||||
|
// 'Комментарии не оставлять' +
|
||||||
|
// 'Вывод - строго в формате json' +
|
||||||
|
// 'command={\n' +
|
||||||
|
// ' persona:\'Карл Маркс\', // имя персонажа\n' +
|
||||||
|
// ' messages:100, // сколько сообщений обработать или 0\n' +
|
||||||
|
// ' hours: 24 // за сколько часов, или 0\n' +
|
||||||
|
// '}' ;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
request_parse_prompt= 'Ты - эксперт по анализу текстовых команд. Твоя задача - разобрать команду Telegram бота и извлечь из неё информацию о персоне и временном периоде.\n' +
|
||||||
|
'ВХОДНЫЕ ДАННЫЕ\n' +
|
||||||
|
'Получена строка команды, которая может содержать:\n' +
|
||||||
|
'\n' +
|
||||||
|
'Имя персонажа (известная личность, историческая фигура, или название музыкальной группы)\n' +
|
||||||
|
'Временной период (количество часов, дней, недель, месяцев) ИЛИ количество сообщений\n' +
|
||||||
|
'Дополнительные слова и предлоги\n' +
|
||||||
|
'\n' +
|
||||||
|
'ПРАВИЛА ОБРАБОТКИ\n' +
|
||||||
|
'1. ПЕРСОНА\n' +
|
||||||
|
'\n' +
|
||||||
|
'Если упомянута известная личность - указать полное имя (например: "кипелов" → "Валерий Кипелов", "кюри" → "Мария Склодовская-Кюри", "летов" → "Егор Николаевич Летов")\n' +
|
||||||
|
'Если это музыкальная группа - указать с пометкой "группа" (например: "битлз" → "группа The Beatles", "гражданская оборона" → "группа Гражданская оборона")\n' +
|
||||||
|
'Если персона не определена или неясна - использовать "Карл Маркс"\n' +
|
||||||
|
'Учитывать различные формы написания (сокращения, транслитерацию, опечатки)\n' +
|
||||||
|
'\n' +
|
||||||
|
'2. ВРЕМЕННОЙ ПЕРИОД\n' +
|
||||||
|
'\n' +
|
||||||
|
'Может быть указан в различных форматах: "2 часа", "3ч", "день", "неделю", "месяц", "за вчера"\n' +
|
||||||
|
'ВСЕ периоды пересчитывать в ЧАСЫ:\n' +
|
||||||
|
'\n' +
|
||||||
|
'1 день = 24 часа\n' +
|
||||||
|
'1 неделя = 168 часов\n' +
|
||||||
|
'1 месяц = 720 часов (30 дней)\n' +
|
||||||
|
'"вчера" = 24 часа\n' +
|
||||||
|
'"позавчера" = 48 часов\n' +
|
||||||
|
'\n' +
|
||||||
|
'\n' +
|
||||||
|
'Если период не указан - использовать 0\n' +
|
||||||
|
'\n' +
|
||||||
|
'3. КОЛИЧЕСТВО СООБЩЕНИЙ\n' +
|
||||||
|
'\n' +
|
||||||
|
'Может быть указано как число + "сообщений", "постов", "записей"\n' +
|
||||||
|
'Если не указано - использовать 0\n' +
|
||||||
|
'ВАЖНО: одновременно НЕ МОЖЕТ быть и количество сообщений, и временной период - только что-то одно!\n' +
|
||||||
|
'\n' +
|
||||||
|
'4. ПРИОРИТЕТЫ\n' +
|
||||||
|
'\n' +
|
||||||
|
'Если указаны и период, и количество сообщений - приоритет у количества сообщений (hours = 0)\n' +
|
||||||
|
'Если ничего не указано - messages = 22, hours = 0\n' +
|
||||||
|
'\n' +
|
||||||
|
'ПРИМЕРЫ ВХОДНЫХ КОМАНД И ОЖИДАЕМЫЙ РЕЗУЛЬТАТ\n' +
|
||||||
|
'Команда: "покажи последние 50 сообщений в стиле Высоцкого"\n' +
|
||||||
|
'Результат: {"persona": "Владимир Семёнович Высоцкий", "messages": 50, "hours": 0}\n' +
|
||||||
|
'Команда: "сгенерируй как Metallica за последний день"\n' +
|
||||||
|
'Результат: {"persona": "группа Metallica", "messages": 0, "hours": 24}\n' +
|
||||||
|
'Команда: "в стиле Маска за 3 часа"\n' +
|
||||||
|
'Результат: {"persona": "Илон Маск", "messages": 0, "hours": 3}\n' +
|
||||||
|
'Команда: "как Цой последние 100 постов"\n' +
|
||||||
|
'Результат: {"persona": "Виктор Робертович Цой", "messages": 100, "hours": 0}\n' +
|
||||||
|
'Команда: "покажи Бетховена"\n' +
|
||||||
|
'Результат: {"persona": "Людвиг ван Бетховен", "messages": 22, "hours": 0}\n' +
|
||||||
|
'ИНСТРУКЦИИ ПО ВЫВОДУ\n' +
|
||||||
|
'\n' +
|
||||||
|
'Комментарии НЕ оставлять\n' +
|
||||||
|
'Вывод СТРОГО в формате JSON без markdown блоков\n' +
|
||||||
|
'НЕ ИСПОЛЬЗОВАТЬ json или блоки\n' +
|
||||||
|
'Возвращать ТОЛЬКО чистый JSON объект\n' +
|
||||||
|
'Не добавлять дополнительные поля\n' +
|
||||||
|
'Использовать двойные кавычки для JSON\n' +
|
||||||
|
'Никаких объяснений до или после JSON\n' +
|
||||||
|
'ОБЯЗАТЕЛЬНО ВАЛИДНЫЙ JSON!!! ОЧЕНЬ ВАЖНО!!\n' +
|
||||||
|
'\n' +
|
||||||
|
'ФОРМАТ ОТВЕТА (возвращать БЕЗ markdown блоков):\n' +
|
||||||
|
'{"persona": "Полное имя персоны или группа Название (краткое описание в трех-пяти словах)", "messages": число_или_0, "hours": число_или_0}\n' +
|
||||||
|
'КРИТИЧЕСКИ ВАЖНО\n' +
|
||||||
|
'\n' +
|
||||||
|
'Твой ответ должен начинаться с { и заканчиваться }\n' +
|
||||||
|
'Никаких дополнительных символов, пробелов или переносов строк до и после JSON\n' +
|
||||||
|
'НЕ оборачивать в ```json блоки\n' +
|
||||||
|
'Возвращать ТОЛЬКО валидный JSON объект\n' +
|
||||||
|
'\n' +
|
||||||
|
'ОБРАБАТЫВАЙ КОМАНДУ СОГЛАСНО ВСЕМ ПРАВИЛАМ ВЫШЕ. АНАЛИЗИРУЙ КАЖДОЕ СЛОВО И КОНТЕКСТ. ВЕРНИ ТОЛЬКО JSON!'
|
||||||
|
|
||||||
|
|
||||||
|
// async function handleRequest(request) {
|
||||||
|
// // Попытка найти результат в кэше
|
||||||
|
// const cachedResult = await searchInCache(request);
|
||||||
|
//
|
||||||
|
// if (cachedResult) {
|
||||||
|
// return cachedResult; // Возвращаем найденное в кэше
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Если в кэше ничего нет — вызываем основную функцию
|
||||||
|
// const result = await someFunction(request);
|
||||||
|
//
|
||||||
|
// // Сохраняем результат в кэш
|
||||||
|
// await saveInCache(request, result);
|
||||||
|
//
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function callAI(prompt, data, type = 'history') {
|
||||||
|
// Если API ключ недоступен, используем fallback
|
||||||
|
if (!process.env.OPENROUTER_API_KEY ) {
|
||||||
|
logger.error('⚠️ OpenRouter API ключ недоступен');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let workingPrompt = '';
|
||||||
|
switch (type) {
|
||||||
|
case 'history':workingPrompt=prompt;
|
||||||
|
break;
|
||||||
|
case 'request':{
|
||||||
|
workingPrompt=request_parse_prompt;
|
||||||
|
const cachedResult = await searchInCache(data);
|
||||||
|
if (cachedResult) {
|
||||||
|
return cachedResult; // Возвращаем найденное в кэше
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:workingPrompt=prompt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const formattedMessages = data;
|
||||||
|
// const formattedMessages = this.formatMessagesForAI(data);
|
||||||
|
let useModel
|
||||||
|
switch (type) {
|
||||||
|
case 'history':useModel=process.env.OPENROUTER_MODEL;
|
||||||
|
break;
|
||||||
|
case 'prompt':useModel=process.env.OPENROUTER_MODEL;
|
||||||
|
break;
|
||||||
|
case 'request':useModel=process.env.OPENROUTER_CHEAP_MODEL;
|
||||||
|
break;
|
||||||
|
default:useModel=process.env.OPENROUTER_MODEL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// = (type!==='history' ? process.env.OPENROUTER_MODEL: process.env.OPENROUTER_CHEAP_MODEL);
|
||||||
|
logger.info(`🤖 Отправляем запрос к OpenRouter (модель ${useModel})...`);
|
||||||
|
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'HTTP-Referer': 'https://git.rockzo.ru/Rockzo_Develop/summy',
|
||||||
|
'X-Title': 'Telegram History Bot'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: useModel,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: workingPrompt
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: formattedMessages
|
||||||
|
}
|
||||||
|
],
|
||||||
|
max_tokens: 2000,
|
||||||
|
temperature: 0.8,
|
||||||
|
top_p: 0.9
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`OpenRouter API error: ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const completion = await response.json();
|
||||||
|
const aiSummary = completion.choices?.[0]?.message?.content;
|
||||||
|
|
||||||
|
if (!aiSummary) {
|
||||||
|
logger.error(JSON.stringify(completion));
|
||||||
|
throw new Error('Пустой ответ от ИИ');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('✅ Получен ответ от OpenRouter');
|
||||||
|
if (type==='request') {
|
||||||
|
await saveInCache(data, aiSummary);
|
||||||
|
}
|
||||||
|
// return `🎬 <b>История чата</b>\n\n${aiSummary}`;
|
||||||
|
if (type==='request') {
|
||||||
|
console.log(typeof aiSummary, aiSummary)
|
||||||
|
return JSON.parse(aiSummary);
|
||||||
|
}
|
||||||
|
return aiSummary;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка при вызове OpenRouter API:', error);
|
||||||
|
|
||||||
|
// Детальная обработка различных типов ошибок
|
||||||
|
if (error.message.includes('401')) {
|
||||||
|
logger.error('❌ Неверный API ключ OpenRouter');
|
||||||
|
} else if (error.message.includes('429')) {
|
||||||
|
logger.error('❌ Превышен лимит запросов OpenRouter');
|
||||||
|
} else if (error.message.includes('402')) {
|
||||||
|
logger.error('❌ Недостаточно средств на счету OpenRouter');
|
||||||
|
} else if (error.code === 'ENOTFOUND') {
|
||||||
|
logger.error('❌ Проблемы с сетевым подключением');
|
||||||
|
}
|
||||||
|
|
||||||
|
return // this.generateFallbackSummary(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = callAI;
|
||||||
Loading…
Reference in New Issue
Block a user