Тестовый коммит после удаления husky

This commit is contained in:
2025-03-05 01:02:09 +03:00
parent b0f7a64a96
commit d90e1b93a9
120 changed files with 8191 additions and 8530 deletions

View File

@@ -0,0 +1,158 @@
const { ChatOllama } = require('@langchain/ollama');
const { pool } = require('../db');
// Инициализация модели Ollama
const model = new ChatOllama({
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
model: process.env.OLLAMA_MODEL || 'llama2',
});
/**
* Обработка сообщения пользователя и получение ответа от ИИ
* @param {number} userId - ID пользователя
* @param {string} message - Текст сообщения
* @param {string} language - Язык пользователя
* @returns {Promise<string>} - Ответ ИИ
*/
async function processMessage(userId, message, language = 'ru') {
try {
// Получение информации о пользователе
const userInfo = await getUserInfo(userId);
// Получение истории диалога (последние 10 сообщений)
const history = await getConversationHistory(userId);
// Формирование контекста для ИИ
const context = `
Пользователь: ${userInfo.username || 'Пользователь'} (ID: ${userId})
Язык: ${language}
Роль: ${userInfo.is_admin ? 'Администратор' : 'Пользователь'}
История диалога:
${history}
Текущее сообщение: ${message}
`;
// Временная заглушка для ответа ИИ
// В будущем здесь будет интеграция с реальной моделью ИИ
const responses = {
ru: [
'Спасибо за ваше сообщение! Чем я могу помочь?',
'Я понимаю ваш запрос. Давайте разберемся с этим вопросом.',
'Интересный вопрос! Вот что я могу предложить...',
'Я обработал вашу информацию. Есть ли у вас дополнительные вопросы?',
'Я готов помочь вам с этим запросом. Нужны ли дополнительные детали?',
],
en: [
'Thank you for your message! How can I help you?',
"I understand your request. Let's figure this out.",
"Interesting question! Here's what I can suggest...",
"I've processed your information. Do you have any additional questions?",
"I'm ready to help you with this request. Do you need any additional details?",
],
};
const langResponses = responses[language] || responses['ru'];
const randomIndex = Math.floor(Math.random() * langResponses.length);
// Имитация задержки ответа ИИ
await new Promise((resolve) => setTimeout(resolve, 500));
return langResponses[randomIndex];
} catch (error) {
console.error('Error processing message:', error);
return 'Извините, произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз позже.';
}
}
/**
* Получение информации о пользователе
* @param {number} userId - ID пользователя
* @returns {Promise<Object>} - Информация о пользователе
*/
async function getUserInfo(userId) {
try {
const userResult = await pool.query(
`SELECT u.id, u.username, u.address, u.is_admin, u.language, r.name as role
FROM users u
JOIN roles r ON u.role_id = r.id
WHERE u.id = $1`,
[userId]
);
if (userResult.rows.length === 0) {
return { id: userId };
}
// Получение идентификаторов пользователя
const identitiesResult = await pool.query(
`SELECT identity_type, identity_value, verified
FROM user_identities
WHERE user_id = $1`,
[userId]
);
const user = userResult.rows[0];
user.identities = identitiesResult.rows;
return user;
} catch (error) {
console.error('Error getting user info:', error);
return { id: userId };
}
}
/**
* Получение истории диалога
* @param {number} userId - ID пользователя
* @param {number} limit - Максимальное количество сообщений
* @returns {Promise<string>} - История диалога в текстовом формате
*/
async function getConversationHistory(userId, limit = 10) {
try {
// Получение последнего активного диалога пользователя
const conversationResult = await pool.query(
`SELECT id FROM conversations
WHERE user_id = $1
ORDER BY updated_at DESC
LIMIT 1`,
[userId]
);
if (conversationResult.rows.length === 0) {
return '';
}
const conversationId = conversationResult.rows[0].id;
// Получение последних сообщений из диалога
const messagesResult = await pool.query(
`SELECT sender_type, content, created_at
FROM messages
WHERE conversation_id = $1
ORDER BY created_at DESC
LIMIT $2`,
[conversationId, limit]
);
// Формирование истории в текстовом формате
const history = messagesResult.rows
.reverse()
.map((msg) => {
const sender = msg.sender_type === 'user' ? 'Пользователь' : 'ИИ';
return `${sender}: ${msg.content}`;
})
.join('\n\n');
return history;
} catch (error) {
console.error('Error getting conversation history:', error);
return '';
}
}
module.exports = {
processMessage,
getUserInfo,
getConversationHistory,
};

View File

@@ -1,47 +0,0 @@
const fs = require('fs');
const path = require('path');
const { Document } = require('langchain/document');
const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter');
// Функция для загрузки документов из файлов
async function loadDocumentsFromFiles(directory) {
const documents = [];
try {
const files = fs.readdirSync(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stat = fs.statSync(filePath);
if (stat.isFile() && (file.endsWith('.txt') || file.endsWith('.md'))) {
const content = fs.readFileSync(filePath, 'utf-8');
documents.push(
new Document({
pageContent: content,
metadata: {
source: filePath,
filename: file,
},
})
);
}
}
// Разделяем документы на чанки
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
const splitDocs = await textSplitter.splitDocuments(documents);
return splitDocs;
} catch (error) {
console.error('Error loading documents:', error);
throw error;
}
}
module.exports = { loadDocumentsFromFiles };

View File

@@ -1,68 +1,246 @@
const { pool } = require('../db');
const nodemailer = require('nodemailer');
const { ChatOllama } = require('@langchain/ollama');
const { PGVectorStore } = require('@langchain/community/vectorstores/pgvector');
const { Pool } = require('pg');
const Imap = require('imap');
const { simpleParser } = require('mailparser');
const { checkMailServer } = require('../utils/checkMail');
const { sleep, isValidEmail } = require('../utils/helpers');
const { linkIdentity, getUserIdByIdentity } = require('../utils/identity-linker');
require('dotenv').config();
const simpleParser = require('mailparser').simpleParser;
const { processMessage } = require('./ai-assistant');
class EmailBotService {
constructor() {
this.enabled = false;
console.log('EmailBotService: Сервис отключен (заглушка)');
// Конфигурация для отправки писем
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_SMTP_HOST,
port: process.env.EMAIL_SMTP_PORT,
secure: process.env.EMAIL_SMTP_PORT === '465', // true для 465, false для других портов
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
});
// Конфигурация для получения писем
const imapConfig = {
user: process.env.EMAIL_USER,
password: process.env.EMAIL_PASSWORD,
host: process.env.EMAIL_IMAP_HOST,
port: process.env.EMAIL_IMAP_PORT,
tls: true,
tlsOptions: { rejectUnauthorized: false },
};
/**
* Инициализация сервиса электронной почты
*/
function initEmailBot() {
if (!process.env.EMAIL_USER || !process.env.EMAIL_PASSWORD) {
console.warn('EMAIL_USER or EMAIL_PASSWORD not set, Email integration disabled');
return null;
}
async start() {
console.log('EmailBotService: Запуск сервиса отключен (заглушка)');
return false;
}
console.log('Email bot initialized');
async stop() {
console.log('EmailBotService: Остановка сервиса отключена (заглушка)');
return true;
}
// Запуск проверки почты каждые 5 минут
const checkInterval = 5 * 60 * 1000; // 5 минут
setInterval(checkEmails, checkInterval);
isEnabled() {
return this.enabled;
// Первая проверка при запуске
checkEmails();
return {
sendEmail,
checkEmails,
};
}
/**
* Проверка новых писем
*/
function checkEmails() {
const imap = new Imap(imapConfig);
imap.once('ready', () => {
imap.openBox('INBOX', false, (err, box) => {
if (err) {
console.error('Error opening inbox:', err);
return;
}
// Поиск непрочитанных писем
imap.search(['UNSEEN'], (err, results) => {
if (err) {
console.error('Error searching emails:', err);
return;
}
if (results.length === 0) {
console.log('No new emails');
imap.end();
return;
}
console.log(`Found ${results.length} new emails`);
const f = imap.fetch(results, { bodies: '' });
f.on('message', (msg, seqno) => {
msg.on('body', (stream, info) => {
simpleParser(stream, async (err, parsed) => {
if (err) {
console.error('Error parsing email:', err);
return;
}
try {
// Обработка письма
await processEmail(parsed);
// Пометить как прочитанное
imap.setFlags(results, ['\\Seen'], (err) => {
if (err) {
console.error('Error marking email as read:', err);
}
});
} catch (error) {
console.error('Error processing email:', error);
}
});
});
});
f.once('error', (err) => {
console.error('Fetch error:', err);
});
f.once('end', () => {
imap.end();
});
});
});
});
imap.once('error', (err) => {
console.error('IMAP error:', err);
});
imap.connect();
}
/**
* Обработка полученного письма
* @param {Object} email - Распарсенное письмо
*/
async function processEmail(email) {
try {
const from = email.from.value[0].address;
const subject = email.subject;
const text = email.text || '';
console.log(`Processing email from ${from}, subject: ${subject}`);
// Поиск пользователя по email
const userResult = await pool.query(
`SELECT u.* FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = 'email' AND ui.identity_value = $1 AND ui.verified = TRUE`,
[from]
);
if (userResult.rows.length === 0) {
console.log(`No verified user found for email ${from}`);
// Отправка ответа о необходимости регистрации
await sendEmail(
from,
'Регистрация в системе',
'Для использования ИИ-ассистента через email, пожалуйста, зарегистрируйтесь на нашем сайте и подтвердите свой email.'
);
return;
}
const user = userResult.rows[0];
// Получение или создание диалога
const conversationResult = await pool.query(
`SELECT * FROM conversations
WHERE user_id = $1
ORDER BY updated_at DESC
LIMIT 1`,
[user.id]
);
let conversationId;
if (conversationResult.rows.length === 0) {
// Создание нового диалога
const newConversationResult = await pool.query(
`INSERT INTO conversations (user_id, title)
VALUES ($1, $2)
RETURNING id`,
[user.id, subject || 'Email диалог']
);
conversationId = newConversationResult.rows[0].id;
} else {
conversationId = conversationResult.rows[0].id;
}
// Сохранение сообщения пользователя
await pool.query(
`INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel)
VALUES ($1, $2, $3, $4, $5)`,
[conversationId, 'user', user.id, text, 'email']
);
// Обработка сообщения ИИ-ассистентом
const aiResponse = await processMessage(user.id, text, user.language || 'ru');
// Сохранение ответа ИИ
await pool.query(
`INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel)
VALUES ($1, $2, $3, $4, $5)`,
[conversationId, 'ai', null, aiResponse, 'email']
);
// Обновление времени последнего обновления диалога
await pool.query(
`UPDATE conversations
SET updated_at = NOW()
WHERE id = $1`,
[conversationId]
);
// Отправка ответа пользователю
await sendEmail(from, `Re: ${subject}`, aiResponse);
console.log(`Sent response to ${from}`);
} catch (error) {
console.error('Error processing email:', error);
throw error;
}
}
// В обработчике команд добавьте код для связывания аккаунтов
async function processCommand(email, command, args) {
if (command === 'link' && args.length > 0) {
const ethAddress = args[0];
// Проверяем формат Ethereum-адреса
if (!/^0x[a-fA-F0-9]{40}$/.test(ethAddress)) {
return 'Неверный формат Ethereum-адреса. Используйте формат 0x...';
}
try {
// Получаем ID пользователя по Ethereum-адресу
const userId = await getUserIdByIdentity('ethereum', ethAddress);
if (!userId) {
return 'Пользователь с таким Ethereum-адресом не найден. Сначала войдите через веб-интерфейс.';
}
// Связываем Email-аккаунт с пользователем
const success = await linkIdentity(userId, 'email', email);
if (success) {
return `Ваш Email-аккаунт успешно связан с Ethereum-адресом ${ethAddress}`;
} else {
return 'Не удалось связать аккаунты. Возможно, этот Email-аккаунт уже связан с другим пользователем.';
}
} catch (error) {
console.error('Ошибка при связывании аккаунтов:', error);
return 'Произошла ошибка при связывании аккаунтов. Попробуйте позже.';
}
/**
* Отправка email
* @param {string} to - Адрес получателя
* @param {string} subject - Тема письма
* @param {string} text - Текст письма
* @returns {Promise<Object>} - Результат отправки
*/
async function sendEmail(to, subject, text) {
try {
const info = await transporter.sendMail({
from: process.env.EMAIL_USER,
to,
subject,
text,
});
console.log('Email sent:', info.messageId);
return info;
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
// Обработка других команд...
}
module.exports = EmailBotService;
module.exports = {
initEmailBot,
sendEmail,
checkEmails,
};

32
backend/services/index.js Normal file
View File

@@ -0,0 +1,32 @@
const { initTelegramBot } = require('./telegram-service');
const { initEmailBot, sendEmail, checkEmails } = require('./emailBot');
const {
initializeVectorStore,
getVectorStore,
similaritySearch,
addDocument,
} = require('./vectorStore');
const { processMessage, getUserInfo, getConversationHistory } = require('./ai-assistant');
// ... другие импорты
module.exports = {
// Telegram
initTelegramBot,
// Email
initEmailBot,
sendEmail,
checkEmails,
// Vector Store
initializeVectorStore,
getVectorStore,
similaritySearch,
addDocument,
// AI Assistant
processMessage,
getUserInfo,
getConversationHistory,
// ... другие экспорты
};

View File

@@ -1,7 +1,9 @@
const { ChatOllama } = require('@langchain/ollama');
const { RetrievalQAChain } = require("langchain/chains");
const { PromptTemplate } = require("@langchain/core/prompts");
const { RetrievalQAChain } = require('langchain/chains');
const { PromptTemplate } = require('@langchain/core/prompts');
const axios = require('axios');
const { Ollama } = require('ollama');
const { HumanMessage } = require('@langchain/core/messages');
// Создаем шаблон для контекстного запроса
const PROMPT_TEMPLATE = `
@@ -19,17 +21,17 @@ const PROMPT_TEMPLATE = `
// Функция для проверки доступности Ollama
async function checkOllamaAvailability() {
console.log('Проверка доступности Ollama...');
try {
// Добавляем таймаут для запроса
const response = await axios.get('http://localhost:11434/api/tags', {
timeout: 5000 // 5 секунд таймаут
timeout: 5000, // 5 секунд таймаут
});
if (response.status === 200) {
console.log('Ollama доступен. Доступные модели:');
if (response.data && response.data.models) {
response.data.models.forEach(model => {
response.data.models.forEach((model) => {
console.log(`- ${model.name}`);
});
}
@@ -42,32 +44,45 @@ async function checkOllamaAvailability() {
}
}
// Функция для прямого запроса к Ollama API
async function directOllamaQuery(message, model = 'mistral') {
// Функция для прямого запроса к Ollama
async function directOllamaQuery(message, language = 'en') {
try {
console.log(`Отправка запроса к Ollama (модель: ${model}):`, message);
// Проверяем доступность Ollama перед отправкой запроса
const isAvailable = await checkOllamaAvailability();
if (!isAvailable) {
throw new Error('Сервер Ollama недоступен');
// Всегда используем модель mistral, независимо от языка
const modelName = 'mistral';
console.log(`Отправка запроса к Ollama (модель: ${modelName}, язык: ${language}): ${message}`);
// Проверяем доступность Ollama
console.log('Проверка доступности Ollama...');
const ollama = new Ollama();
try {
const models = await ollama.list();
console.log('Ollama доступен. Доступные модели:');
models.models.forEach((model) => {
console.log(`- ${model.name}`);
});
} catch (error) {
console.error('Ошибка при проверке доступности Ollama:', error);
throw new Error('Ollama недоступен');
}
// Создаем экземпляр ChatOllama
const ollama = new ChatOllama({
console.log('Отправка запроса к Ollama...');
const chatModel = new ChatOllama({
baseUrl: 'http://localhost:11434',
model: model,
model: modelName,
temperature: 0.7,
});
console.log('Отправка запроса к Ollama...');
const result = await ollama.invoke(message);
console.log('Получен ответ от Ollama');
return result.content;
const response = await chatModel.invoke([new HumanMessage(message)]);
return response.content;
} catch (error) {
console.error('Ошибка при запросе к Ollama:', error);
throw error;
// Возвращаем сообщение об ошибке
return 'Извините, произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже.';
}
}
@@ -98,27 +113,23 @@ async function createOllamaChain(vectorStore) {
// Создаем шаблон запроса
const prompt = new PromptTemplate({
template: PROMPT_TEMPLATE,
inputVariables: ["context", "query"],
inputVariables: ['context', 'query'],
});
console.log('Шаблон запроса создан');
console.log('Получаем retriever из векторного хранилища...');
const retriever = vectorStore.asRetriever();
console.log('Retriever получен');
console.log('Создаем цепочку для поиска и ответа...');
// Создаем цепочку для поиска и ответа
const chain = RetrievalQAChain.fromLLM(
model,
retriever,
{
returnSourceDocuments: true,
prompt: prompt,
inputKey: "query",
outputKey: "text",
verbose: true
}
);
const chain = RetrievalQAChain.fromLLM(model, retriever, {
returnSourceDocuments: true,
prompt: prompt,
inputKey: 'query',
outputKey: 'text',
verbose: true,
});
console.log('Цепочка для поиска и ответа создана');
return chain;
@@ -146,4 +157,4 @@ async function getOllamaModel() {
}
}
module.exports = { getOllamaModel, createOllamaChain, checkOllamaAvailability, directOllamaQuery };
module.exports = { getOllamaModel, createOllamaChain, checkOllamaAvailability, directOllamaQuery };

View File

@@ -0,0 +1,262 @@
const TelegramBot = require('node-telegram-bot-api');
const { pool } = require('../db');
const { processMessage } = require('./ai-assistant');
// Инициализация бота
const token = process.env.TELEGRAM_BOT_TOKEN;
let bot = null;
if (token) {
bot = new TelegramBot(token, { polling: true });
console.log('Telegram bot initialized');
} else {
console.warn('TELEGRAM_BOT_TOKEN not set, Telegram integration disabled');
}
/**
* Инициализация Telegram бота
*/
function initTelegramBot() {
if (!bot) return;
// Обработка команды /start
bot.onText(/\/start/, async (msg) => {
const chatId = msg.chat.id;
const userId = msg.from.id;
const username =
msg.from.username || `${msg.from.first_name} ${msg.from.last_name || ''}`.trim();
try {
// Проверка существования пользователя
const user = await findOrCreateUser(userId, username, chatId);
// Приветственное сообщение
bot.sendMessage(chatId, `Привет, ${username}! Я ИИ-ассистент. Чем могу помочь?`);
} catch (error) {
console.error('Error handling /start command:', error);
bot.sendMessage(
chatId,
'Произошла ошибка при обработке команды. Пожалуйста, попробуйте позже.'
);
}
});
// Обработка текстовых сообщений
bot.on('message', async (msg) => {
if (!msg.text || msg.text.startsWith('/')) return;
const chatId = msg.chat.id;
const userId = msg.from.id;
const username =
msg.from.username || `${msg.from.first_name} ${msg.from.last_name || ''}`.trim();
try {
// Проверка существования пользователя
const user = await findOrCreateUser(userId, username, chatId);
// Получение или создание диалога
const conversation = await getOrCreateConversation(user.id);
// Сохранение сообщения пользователя
await saveMessage(conversation.id, 'user', user.id, msg.text, 'telegram');
// Обработка сообщения ИИ-ассистентом
const aiResponse = await processMessage(user.id, msg.text, user.language || 'ru');
// Сохранение ответа ИИ
await saveMessage(conversation.id, 'ai', null, aiResponse, 'telegram');
// Отправка ответа
bot.sendMessage(chatId, aiResponse);
} catch (error) {
console.error('Error processing message:', error);
bot.sendMessage(
chatId,
'Произошла ошибка при обработке сообщения. Пожалуйста, попробуйте позже.'
);
}
});
console.log('Telegram bot handlers registered');
}
/**
* Поиск или создание пользователя по Telegram ID
* @param {number} telegramId - Telegram ID пользователя
* @param {string} username - Имя пользователя
* @param {number} chatId - ID чата
* @returns {Promise<Object>} - Информация о пользователе
*/
async function findOrCreateUser(telegramId, username, chatId) {
try {
// Поиск пользователя по Telegram ID
const userIdResult = await pool.query(
`SELECT user_id FROM user_identities
WHERE identity_type = 'telegram' AND identity_value = $1`,
[telegramId.toString()]
);
if (userIdResult.rows.length > 0) {
// Пользователь найден
const userId = userIdResult.rows[0].user_id;
// Получение информации о пользователе
const userResult = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
return userResult.rows[0];
} else {
// Создание нового пользователя
const userResult = await pool.query(
`INSERT INTO users (
username,
role_id,
is_admin,
language,
address
) VALUES (
$1,
(SELECT id FROM roles WHERE name = 'user'),
FALSE,
'ru',
'0x' || encode(gen_random_bytes(20), 'hex')
) RETURNING *`,
[username]
);
const newUser = userResult.rows[0];
// Добавление идентификатора Telegram
await pool.query(
`INSERT INTO user_identities (
user_id,
identity_type,
identity_value,
verified
) VALUES ($1, 'telegram', $2, TRUE)`,
[newUser.id, telegramId.toString()]
);
// Сохранение метаданных Telegram
await pool.query(
`INSERT INTO user_preferences (
user_id,
preference_key,
preference_value
) VALUES ($1, 'telegram_chat_id', $2)`,
[newUser.id, chatId.toString()]
);
return newUser;
}
} catch (error) {
console.error('Error finding or creating user:', error);
throw error;
}
}
/**
* Получение или создание диалога для пользователя
* @param {number} userId - ID пользователя
* @returns {Promise<Object>} - Информация о диалоге
*/
async function getOrCreateConversation(userId) {
try {
// Поиск активного диалога
const conversationResult = await pool.query(
`SELECT * FROM conversations
WHERE user_id = $1
ORDER BY updated_at DESC
LIMIT 1`,
[userId]
);
if (conversationResult.rows.length > 0) {
// Обновление времени последней активности
await pool.query('UPDATE conversations SET updated_at = NOW() WHERE id = $1', [
conversationResult.rows[0].id,
]);
return conversationResult.rows[0];
} else {
// Создание нового диалога
const newConversationResult = await pool.query(
`INSERT INTO conversations (user_id, title)
VALUES ($1, $2)
RETURNING *`,
[userId, 'Диалог в Telegram']
);
return newConversationResult.rows[0];
}
} catch (error) {
console.error('Error getting or creating conversation:', error);
throw error;
}
}
/**
* Сохранение сообщения
* @param {number} conversationId - ID диалога
* @param {string} senderType - Тип отправителя ('user', 'ai')
* @param {number|null} senderId - ID отправителя
* @param {string} content - Текст сообщения
* @param {string} channel - Канал ('telegram')
* @returns {Promise<Object>} - Информация о сообщении
*/
async function saveMessage(conversationId, senderType, senderId, content, channel) {
try {
const messageResult = await pool.query(
`INSERT INTO messages (
conversation_id,
sender_type,
sender_id,
content,
channel
) VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[conversationId, senderType, senderId, content, channel]
);
return messageResult.rows[0];
} catch (error) {
console.error('Error saving message:', error);
throw error;
}
}
/**
* Отправка сообщения пользователю через Telegram
* @param {number} userId - ID пользователя
* @param {string} message - Текст сообщения
* @returns {Promise<boolean>} - Успешность отправки
*/
async function sendMessageToUser(userId, message) {
if (!bot) return false;
try {
// Получение Telegram chat ID пользователя
const chatIdResult = await pool.query(
`SELECT preference_value FROM user_preferences
WHERE user_id = $1 AND preference_key = 'telegram_chat_id'`,
[userId]
);
if (chatIdResult.rows.length === 0) {
return false;
}
const chatId = chatIdResult.rows[0].preference_value;
// Отправка сообщения
await bot.sendMessage(chatId, message);
return true;
} catch (error) {
console.error('Error sending message to user:', error);
return false;
}
}
module.exports = {
initTelegramBot,
sendMessageToUser,
};

View File

@@ -1,402 +0,0 @@
const TelegramBot = require('node-telegram-bot-api');
const { ChatOllama } = require('@langchain/ollama');
const axios = require('axios');
const dns = require('dns').promises;
require('dotenv').config();
const { sleep } = require('../utils/helpers');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const { linkIdentity, getUserIdByIdentity } = require('../utils/identity-linker');
class TelegramBotService {
constructor() {
// Проверяем наличие токена
if (!process.env.TELEGRAM_BOT_TOKEN) {
throw new Error('Token is required');
}
this.isRunning = false;
this.maxRetries = 3;
this.retryDelay = 5000; // 5 секунд между попытками
// Создаем бота без polling
this.bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, {
polling: false,
request: {
proxy: null,
agentOptions: {
rejectUnauthorized: true,
minVersion: 'TLSv1.2'
},
timeout: 30000
}
});
this.token = process.env.TELEGRAM_BOT_TOKEN;
this.chat = new ChatOllama({
model: 'mistral',
baseUrl: 'http://localhost:11434'
});
// Добавляем настройки прокси для axios
this.axiosConfig = {
timeout: 5000,
proxy: false,
httpsAgent: new (require('https').Agent)({
rejectUnauthorized: true,
minVersion: 'TLSv1.2'
})
};
this.initialize();
}
setupHandlers() {
this.bot.onText(/.*/, async (msg) => {
try {
const chatId = msg.chat.id;
const userQuestion = msg.text;
// Пропускаем команды
if (userQuestion.startsWith('/')) {
return;
}
console.log('Получен вопрос:', userQuestion);
// Используем локальную модель
const result = await this.chat.invoke(userQuestion);
const assistantResponse = result.content;
await this.bot.sendMessage(chatId, assistantResponse);
} catch (error) {
console.error('Telegram bot error:', error);
await this.bot.sendMessage(msg.chat.id,
'Извините, произошла ошибка при обработке вашего запроса. ' +
'Попробуйте повторить позже или обратитесь к администратору.'
);
}
});
this.bot.onText(/\/link (.+)/, async (msg, match) => {
const chatId = msg.chat.id;
const ethAddress = match[1];
// Проверяем формат Ethereum-адреса
if (!/^0x[a-fA-F0-9]{40}$/.test(ethAddress)) {
this.bot.sendMessage(chatId, 'Неверный формат Ethereum-адреса. Используйте формат 0x...');
return;
}
try {
// Получаем ID пользователя по Ethereum-адресу
const userId = await getUserIdByIdentity('ethereum', ethAddress);
if (!userId) {
this.bot.sendMessage(chatId, 'Пользователь с таким Ethereum-адресом не найден. Сначала войдите через веб-интерфейс.');
return;
}
// Связываем Telegram-аккаунт с пользователем
const success = await linkIdentity(userId, 'telegram', chatId.toString());
if (success) {
this.bot.sendMessage(chatId, `Ваш Telegram-аккаунт успешно связан с Ethereum-адресом ${ethAddress}`);
} else {
this.bot.sendMessage(chatId, 'Не удалось связать аккаунты. Возможно, этот Telegram-аккаунт уже связан с другим пользователем.');
}
} catch (error) {
console.error('Ошибка при связывании аккаунтов:', error);
this.bot.sendMessage(chatId, 'Произошла ошибка при связывании аккаунтов. Попробуйте позже.');
}
});
}
setupCommands() {
this.bot.onText(/\/start/, async (msg) => {
const welcomeMessage = `
👋 Здравствуйте! Я - ассистент DApp for Business.
Я готов помочь вам с вопросами о:
• Разработке dApps
• Блокчейн-технологиях
• Web3 и криптовалютах
Просто задавайте вопросы, а если нужна помощь -
используйте команду /help
`;
await this.bot.sendMessage(msg.chat.id, welcomeMessage);
});
this.bot.onText(/\/help/, async (msg) => {
const helpMessage = `
🤖 Я - ассистент DApp for Business
Я могу помочь вам с:
• Разработкой децентрализованных приложений
• Интеграцией блокчейн-технологий в бизнес
• Консультациями по Web3 и криптовалютам
Команды:
/start - начать работу с ботом
/help - показать это сообщение
/status - проверить состояние бота
Просто задавайте вопросы на русском или английском языке!
`;
await this.bot.sendMessage(msg.chat.id, helpMessage);
});
this.bot.onText(/\/status/, async (msg) => {
try {
const status = {
isRunning: this.isRunning,
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
connections: {
telegram: Boolean(this.bot),
ollama: Boolean(this.chat)
}
};
const statusMessage = `
📊 Статус бота:
🟢 Статус: ${status.isRunning ? 'Работает' : 'Остановлен'}
⏱ Время работы: ${Math.floor(status.uptime / 60)} мин
🔗 Подключения:
• Telegram API: ${status.connections.telegram ? '✅' : '❌'}
• Ollama: ${status.connections.ollama ? '✅' : '❌'}
💾 Использование памяти:
• Heap: ${Math.round(status.memoryUsage.heapUsed / 1024 / 1024)}MB
• RSS: ${Math.round(status.memoryUsage.rss / 1024 / 1024)}MB
`;
await this.bot.sendMessage(msg.chat.id, statusMessage);
} catch (error) {
console.error('Ошибка при получении статуса:', error);
await this.bot.sendMessage(msg.chat.id, 'Ошибка при получении статуса бота');
}
});
}
async initialize() {
let retries = 0;
while (retries < this.maxRetries) {
try {
console.log(`Попытка инициализации Telegram бота (${retries + 1}/${this.maxRetries})...`);
// Сначала проверяем DNS и доступность
try {
console.log('Проверка DNS для api.telegram.org...');
const addresses = await dns.resolve4('api.telegram.org');
console.log('IP адреса api.telegram.org:', addresses);
// Пинг для проверки доступности (теперь ждем результат)
try {
const { stdout } = await exec('ping -c 1 api.telegram.org');
console.log('Результат ping:', stdout);
} catch (pingError) {
console.error('Ошибка при выполнении ping:', pingError);
throw new Error('Сервер Telegram недоступен');
}
} catch (error) {
console.error('Ошибка сетевой проверки:', error);
throw error;
}
// Затем проверяем API
try {
const response = await axios.get(
`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/getMe`,
this.axiosConfig
);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log('Успешное подключение к API Telegram:', {
botInfo: response.data.result
});
} catch (error) {
console.error('Ошибка при проверке API Telegram:', {
message: error.message,
code: error.code,
response: error.response?.data,
config: {
url: error.config?.url,
method: error.config?.method,
timeout: error.config?.timeout
}
});
throw error;
}
// Основная инициализация бота
await this.initBot();
console.log('Telegram bot service initialized');
return;
} catch (error) {
retries++;
console.error('Ошибка при инициализации Telegram бота:', {
name: error.name,
message: error.message,
code: error.code,
response: error.response?.data,
stack: error.stack
});
if (retries < this.maxRetries) {
console.log(`Повторная попытка через ${this.retryDelay/1000} секунд...`);
await sleep(this.retryDelay);
} else {
console.error('Превышено максимальное количество попыток подключения к Telegram');
throw error;
}
}
}
}
async initBot() {
try {
// Проверяем, не запущен ли уже бот
const webhookInfo = await this.bot.getWebHookInfo();
// Если есть webhook или активный polling, пробуем остановить
if (webhookInfo.url || webhookInfo.has_custom_certificate) {
console.log('Удаляем существующий webhook...');
await this.bot.deleteWebHook();
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Пробуем получить обновления с большим таймаутом
try {
console.log('Проверяем наличие других экземпляров бота...');
const updates = await this.bot.getUpdates({
offset: -1,
limit: 1,
timeout: 0
});
console.log('Проверка существующих подключений:', updates);
} catch (error) {
if (error.code === 409) {
console.log('Обнаружен активный бот, пробуем остановить...');
await this.stop();
await new Promise(resolve => setTimeout(resolve, 5000));
// Повторная попытка получить обновления
await this.bot.getUpdates({ offset: -1, limit: 1, timeout: 0 });
}
}
// Небольшая пауза перед запуском поллинга
await new Promise(resolve => setTimeout(resolve, 1000));
// Запускаем polling
console.log('Запускаем polling...');
await this.bot.startPolling({
interval: 2000,
params: {
timeout: 10
}
});
this.isRunning = true;
this.setupHandlers();
this.setupErrorHandlers();
this.setupCommands();
} catch (error) {
if (error.code === 409) {
console.log('Бот уже запущен в другом процессе');
this.isRunning = false;
} else {
console.error('Ошибка при инициализации Telegram бота:', error);
throw error;
}
}
}
setupErrorHandlers() {
this.bot.on('polling_error', (error) => {
console.error('Telegram polling error:', {
code: error.code,
message: error.message,
stack: error.stack
});
// Обработка различных ошибок
if (this.isRunning && (error.code === 'EFATAL' || error.code === 'ETELEGRAM')) {
console.log('Переподключение к Telegram через 5 секунд...');
setTimeout(async () => {
try {
await this.stop();
await this.initBot();
} catch (err) {
console.error('Ошибка при перезапуске бота:', err);
}
}, 5000);
} else if (error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED') {
// Для ошибок соединения пробуем сразу переподключиться
this.bot.startPolling();
}
});
// Обработка других ошибок
this.bot.on('error', (error) => {
console.error('Telegram bot error:', error);
// Пробуем переподключиться при любой ошибке
setTimeout(() => this.bot.startPolling(), 5000);
});
// Обработка webhook ошибок
this.bot.on('webhook_error', (error) => {
console.error('Telegram webhook error:', error);
});
}
async stop() {
if (this.isRunning) {
console.log('Останавливаем Telegram бота...');
try {
// Сначала отключаем обработчики
this.bot.removeAllListeners();
// Останавливаем поллинг
await this.bot.stopPolling();
// Очищаем очередь обновлений
await this.bot.getUpdates({
offset: -1,
limit: 1,
timeout: 1
});
this.isRunning = false;
console.log('Telegram бот остановлен');
} catch (error) {
console.error('Ошибка при остановке бота:', error);
// Принудительно отмечаем как остановленный
this.isRunning = false;
}
}
}
async checkTelegramAvailability() {
const { stdout } = await exec('ping -c 1 api.telegram.org');
const match = stdout.match(/time=(\d+(\.\d+)?)/);
if (match) {
const pingTime = parseFloat(match[1]);
console.log(`Время отклика Telegram API: ${pingTime}ms`);
if (pingTime > 1000) { // Если пинг больше секунды
console.warn('Внимание: высокая задержка при подключении к Telegram API');
}
}
return stdout;
}
}
module.exports = TelegramBotService;

View File

@@ -1,134 +1,212 @@
const { HNSWLib } = require("@langchain/community/vectorstores/hnswlib");
const { OllamaEmbeddings } = require("@langchain/ollama");
const { DirectoryLoader } = require("langchain/document_loaders/fs/directory");
const { TextLoader } = require("langchain/document_loaders/fs/text");
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
const { HNSWLib } = require('langchain/vectorstores/hnswlib');
const { OllamaEmbeddings } = require('langchain/embeddings/ollama');
const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter');
const { DirectoryLoader } = require('langchain/document_loaders/fs/directory');
const { TextLoader } = require('langchain/document_loaders/fs/text');
const { PDFLoader } = require('langchain/document_loaders/fs/pdf');
const fs = require('fs');
const path = require('path');
// Путь к директории с документами
const DOCS_DIR = path.join(__dirname, '../data/documents');
// Путь к директории для хранения векторного индекса
const VECTOR_STORE_DIR = path.join(__dirname, '../data/vector_store');
// Путь к директории для хранения векторной базы данных
const VECTOR_STORE_PATH = path.join(__dirname, '../data/vector_store');
// Создаем директории, если они не существуют
if (!fs.existsSync(DOCS_DIR)) {
fs.mkdirSync(DOCS_DIR, { recursive: true });
console.log(`Создана директория для документов: ${DOCS_DIR}`);
}
// Инициализация embeddings с использованием локальной модели Ollama
const embeddings = new OllamaEmbeddings({
model: process.env.OLLAMA_EMBEDDINGS_MODEL || 'mistral',
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
});
if (!fs.existsSync(VECTOR_STORE_DIR)) {
fs.mkdirSync(VECTOR_STORE_DIR, { recursive: true });
console.log(`Создана директория для векторного хранилища: ${VECTOR_STORE_DIR}`);
}
// Глобальная переменная для хранения экземпляра векторного хранилища
let vectorStore = null;
// Функция для инициализации векторного хранилища
/**
* Инициализация векторного хранилища
*/
async function initializeVectorStore() {
try {
console.log('Инициализация векторного хранилища...');
// Проверяем, существует ли директория с документами
if (!fs.existsSync(DOCS_DIR)) {
console.warn(`Директория с документами не найдена: ${DOCS_DIR}`);
return null;
// Создание директории, если она не существует
if (!fs.existsSync(VECTOR_STORE_PATH)) {
fs.mkdirSync(VECTOR_STORE_PATH, { recursive: true });
console.log(`Created vector store directory at ${VECTOR_STORE_PATH}`);
}
// Проверяем, есть ли документы в директории
const files = fs.readdirSync(DOCS_DIR);
if (files.length === 0) {
console.warn(`В директории с документами нет файлов: ${DOCS_DIR}`);
return null;
}
console.log(`Найдено ${files.length} файлов в директории с документами`);
// Загружаем документы из директории
const loader = new DirectoryLoader(
DOCS_DIR,
{
".txt": (path) => new TextLoader(path),
".md": (path) => new TextLoader(path),
// Проверка наличия файлов индекса
const indexFiles = fs.readdirSync(VECTOR_STORE_PATH);
if (indexFiles.length > 0 && indexFiles.includes('hnswlib.index')) {
// Загрузка существующего индекса
console.log('Loading existing vector store...');
try {
vectorStore = await HNSWLib.load(VECTOR_STORE_PATH, embeddings);
console.log('Vector store loaded successfully');
} catch (loadError) {
console.error('Error loading existing vector store:', loadError);
console.log('Creating new vector store...');
await createVectorStore();
}
);
console.log('Загрузка документов...');
const docs = await loader.load();
console.log(`Загружено ${docs.length} документов`);
if (docs.length === 0) {
console.warn('Не удалось загрузить документы');
return null;
} else {
// Создание нового индекса
console.log('Creating new vector store...');
await createVectorStore();
}
// Разбиваем документы на чанки
return vectorStore;
} catch (error) {
console.error('Error initializing vector store:', error);
// Создаем пустой векторный индекс в случае ошибки
vectorStore = new HNSWLib(embeddings, {
space: 'cosine',
numDimensions: 4096, // Размерность для Ollama embeddings (зависит от модели)
});
await vectorStore.save(VECTOR_STORE_PATH);
return vectorStore;
}
}
/**
* Создание нового векторного хранилища из документов
*/
async function createVectorStore() {
try {
// Проверяем наличие директории documents
const docsPath = path.join(__dirname, '../data/documents');
// Если директория documents не существует, проверяем директорию docs
if (!fs.existsSync(docsPath)) {
const altDocsPath = path.join(__dirname, '../data/docs');
// Если директория docs существует, используем ее
if (fs.existsSync(altDocsPath)) {
console.log(`Using documents directory at ${altDocsPath}`);
return await processDocumentsDirectory(altDocsPath);
}
// Иначе создаем директорию documents
fs.mkdirSync(docsPath, { recursive: true });
console.log(`Created documents directory at ${docsPath}`);
// Создание примера документа
const sampleDocPath = path.join(docsPath, 'sample.txt');
fs.writeFileSync(sampleDocPath, 'Это пример документа для векторного хранилища.');
}
return await processDocumentsDirectory(docsPath);
} catch (error) {
console.error('Error creating vector store:', error);
throw error;
}
}
/**
* Обработка директории с документами
* @param {string} docsPath - Путь к директории с документами
*/
async function processDocumentsDirectory(docsPath) {
try {
// Загрузка документов
const loader = new DirectoryLoader(docsPath, {
'.txt': (path) => new TextLoader(path),
'.pdf': (path) => new PDFLoader(path),
});
const docs = await loader.load();
console.log(`Loaded ${docs.length} documents`);
if (docs.length === 0) {
// Создаем пустой векторный индекс, если нет документов
vectorStore = new HNSWLib(embeddings, {
space: 'cosine',
numDimensions: 4096, // Размерность для Ollama embeddings (зависит от модели)
});
} else {
// Разделение документов на чанки
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
const splitDocs = await textSplitter.splitDocuments(docs);
console.log(`Split into ${splitDocs.length} chunks`);
// Создание векторного хранилища
vectorStore = await HNSWLib.fromDocuments(splitDocs, embeddings);
}
// Сохранение векторного хранилища
await vectorStore.save(VECTOR_STORE_PATH);
console.log('Vector store created and saved successfully');
return vectorStore;
} catch (error) {
console.error('Error processing documents directory:', error);
throw error;
}
}
/**
* Получение векторного хранилища
* @returns {HNSWLib|null} Векторное хранилище
*/
function getVectorStore() {
return vectorStore;
}
/**
* Поиск похожих документов
* @param {string} query - Запрос для поиска
* @param {number} k - Количество результатов
* @returns {Promise<Array>} - Массив похожих документов
*/
async function similaritySearch(query, k = 5) {
if (!vectorStore) {
await initializeVectorStore();
}
try {
const results = await vectorStore.similaritySearch(query, k);
return results;
} catch (error) {
console.error('Error performing similarity search:', error);
return [];
}
}
/**
* Добавление нового документа в векторное хранилище
* @param {string} text - Текст документа
* @param {Object} metadata - Метаданные документа
* @returns {Promise<boolean>} - Успешность добавления
*/
async function addDocument(text, metadata = {}) {
if (!vectorStore) {
await initializeVectorStore();
}
try {
// Разделение документа на чанки
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
console.log('Разбиение документов на чанки...');
const splitDocs = await textSplitter.splitDocuments(docs);
console.log(`Документы разбиты на ${splitDocs.length} чанков`);
// Создаем эмбеддинги с помощью Ollama
console.log('Создание эмбеддингов...');
const embeddings = new OllamaEmbeddings({
model: "mistral",
baseUrl: "http://localhost:11434",
});
// Проверяем, существует ли уже векторное хранилище
if (fs.existsSync(path.join(VECTOR_STORE_DIR, 'hnswlib.index'))) {
console.log('Загрузка существующего векторного хранилища...');
try {
vectorStore = await HNSWLib.load(
VECTOR_STORE_DIR,
embeddings
);
console.log('Векторное хранилище успешно загружено');
return vectorStore;
} catch (error) {
console.error('Ошибка при загрузке векторного хранилища:', error);
console.log('Создание нового векторного хранилища...');
}
}
// Создаем новое векторное хранилище
console.log('Создание нового векторного хранилища...');
vectorStore = await HNSWLib.fromDocuments(
splitDocs,
embeddings
);
// Сохраняем векторное хранилище
console.log('Сохранение векторного хранилища...');
await vectorStore.save(VECTOR_STORE_DIR);
console.log('Векторное хранилище успешно сохранено');
return vectorStore;
const docs = await textSplitter.createDocuments([text], [metadata]);
// Добавление документов в векторное хранилище
await vectorStore.addDocuments(docs);
// Сохранение обновленного векторного хранилища
await vectorStore.save(VECTOR_STORE_PATH);
console.log('Document added to vector store successfully');
return true;
} catch (error) {
console.error('Ошибка при инициализации векторного хранилища:', error);
console.log('Приложение продолжит работу без векторного хранилища');
// Возвращаем заглушку вместо реального хранилища
return {
addDocuments: async () => console.log('Векторное хранилище недоступно: addDocuments'),
similaritySearch: async () => {
console.log('Векторное хранилище недоступно: similaritySearch');
return [];
}
};
console.error('Error adding document to vector store:', error);
return false;
}
}
// Функция для получения экземпляра векторного хранилища
async function getVectorStore() {
if (!vectorStore) {
vectorStore = await initializeVectorStore();
}
return vectorStore;
}
module.exports = { initializeVectorStore, getVectorStore };
module.exports = {
initializeVectorStore,
getVectorStore,
similaritySearch,
addDocument,
};