Описание изменений
This commit is contained in:
47
backend/services/documents.js
Normal file
47
backend/services/documents.js
Normal file
@@ -0,0 +1,47 @@
|
||||
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 };
|
||||
@@ -6,280 +6,63 @@ 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();
|
||||
|
||||
class EmailBotService {
|
||||
constructor(vectorStore) {
|
||||
if (!vectorStore) {
|
||||
throw new Error('Vector store is required');
|
||||
}
|
||||
|
||||
console.log('Инициализация Email бота...');
|
||||
console.log('Проверяем настройки почты:', {
|
||||
smtp: {
|
||||
host: process.env.EMAIL_SMTP_HOST,
|
||||
port: process.env.EMAIL_SMTP_PORT
|
||||
},
|
||||
imap: {
|
||||
host: process.env.EMAIL_IMAP_HOST,
|
||||
port: process.env.EMAIL_IMAP_PORT
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализация базы данных
|
||||
this.pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
});
|
||||
|
||||
this.vectorStore = vectorStore;
|
||||
|
||||
// Инициализация LLM
|
||||
this.chat = new ChatOllama({
|
||||
model: 'mistral',
|
||||
baseUrl: 'http://localhost:11434'
|
||||
});
|
||||
|
||||
// Настройка почтового клиента для отправки
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_SMTP_HOST,
|
||||
port: process.env.EMAIL_SMTP_PORT,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
minVersion: 'TLSv1',
|
||||
ciphers: 'HIGH:MEDIUM:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:!LOW:!SSLv2:!MD5'
|
||||
},
|
||||
debug: true,
|
||||
logger: true
|
||||
});
|
||||
|
||||
// Проверяем подключение к SMTP
|
||||
this.transporter.verify((error, success) => {
|
||||
if (error) {
|
||||
console.error('Ошибка подключения к SMTP:', {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack
|
||||
});
|
||||
setTimeout(() => this.initSMTP(), 30000);
|
||||
}
|
||||
});
|
||||
|
||||
// Настройка IMAP для получения писем
|
||||
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 },
|
||||
keepalive: true,
|
||||
authTimeout: 30000,
|
||||
connTimeout: 30000
|
||||
}
|
||||
this.imap = new Imap(imapConfig);
|
||||
|
||||
// Добавляем обработчик для всех событий IMAP
|
||||
this.imap.on('*', function(event, data) {
|
||||
console.log('IMAP Event:', event, data);
|
||||
});
|
||||
|
||||
// Проверяем MX записи
|
||||
const domain = process.env.EMAIL_USER ? process.env.EMAIL_USER.split('@')[1] : '';
|
||||
if (domain) {
|
||||
checkMailServer(domain).then(records => {
|
||||
if (!records) {
|
||||
console.error('Не удалось найти MX записи для домена');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('EMAIL_USER не настроен в .env файле');
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
this.initSMTP();
|
||||
this.initIMAP();
|
||||
console.log('Email bot service initialized');
|
||||
constructor() {
|
||||
this.enabled = false;
|
||||
console.log('EmailBotService: Сервис отключен (заглушка)');
|
||||
}
|
||||
|
||||
async initSMTP() {
|
||||
try {
|
||||
console.log('Попытка подключения к SMTP...');
|
||||
await this.transporter.verify();
|
||||
console.log('SMTP сервер готов к отправке сообщений');
|
||||
} catch (error) {
|
||||
console.error('Ошибка подключения к SMTP:', {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async initIMAP() {
|
||||
try {
|
||||
await this.initEmailListener();
|
||||
console.log('IMAP подключение установлено');
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации IMAP:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async initEmailListener() {
|
||||
try {
|
||||
this.imap.on('ready', () => {
|
||||
this.imap.openBox('INBOX', false, (err, box) => {
|
||||
if (err) throw err;
|
||||
|
||||
// Слушаем новые письма
|
||||
this.imap.on('mail', () => {
|
||||
this.checkNewEmails();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.imap.on('error', (err) => {
|
||||
console.log('IMAP ошибка:', err);
|
||||
if (err.source === 'timeout-auth') {
|
||||
setTimeout(() => {
|
||||
console.log('Попытка переподключения к IMAP...');
|
||||
this.imap.connect();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
this.imap.connect();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при инициализации IMAP:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async processEmail(message) {
|
||||
try {
|
||||
// Очищаем и валидируем email адрес
|
||||
const cleanEmail = message.from.replace(/[<>]/g, '').trim();
|
||||
if (!isValidEmail(cleanEmail)) {
|
||||
console.log('Некорректный email адрес:', message.from);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, не является ли отправитель no-reply адресом
|
||||
if (cleanEmail.toLowerCase().includes('no-reply') ||
|
||||
cleanEmail.toLowerCase().includes('noreply')) {
|
||||
console.log('Пропускаем письмо от no-reply адреса:', cleanEmail);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем валидность домена получателя
|
||||
const domain = cleanEmail.split('@')[1];
|
||||
try {
|
||||
console.log(`Проверяем MX записи для домена ${domain}...`);
|
||||
const records = await checkMailServer(domain);
|
||||
if (!records || records.length === 0) {
|
||||
console.log('Пропускаем письмо - домен не найден:', domain);
|
||||
return;
|
||||
}
|
||||
console.log('Найдены MX записи:', records);
|
||||
} catch (err) {
|
||||
console.error('Ошибка при проверке MX записей:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ответ от Ollama
|
||||
const result = await this.chat.invoke(message.text);
|
||||
|
||||
// Отправляем ответ
|
||||
await this.transporter.sendMail({
|
||||
from: process.env.EMAIL_USER,
|
||||
to: cleanEmail,
|
||||
subject: `Re: ${message.subject}`,
|
||||
text: result.content
|
||||
});
|
||||
|
||||
console.log('Ответ отправлен:', {
|
||||
to: cleanEmail,
|
||||
subject: message.subject
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обработке email:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async checkNewEmails() {
|
||||
try {
|
||||
const messages = await new Promise((resolve, reject) => {
|
||||
this.imap.search(['UNSEEN'], (err, results) => {
|
||||
if (err) reject(err);
|
||||
|
||||
if (!results || !results.length) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetch = this.imap.fetch(results, {
|
||||
bodies: '',
|
||||
markSeen: true
|
||||
});
|
||||
|
||||
const messages = [];
|
||||
|
||||
fetch.on('message', (msg) => {
|
||||
msg.on('body', (stream) => {
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.once('end', () => {
|
||||
messages.push({
|
||||
from: buffer.match(/From: (.*)/i)?.[1],
|
||||
subject: buffer.match(/Subject: (.*)/i)?.[1],
|
||||
text: buffer.split('\n\n').slice(1).join('\n\n')
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
fetch.once('error', reject);
|
||||
fetch.once('end', () => resolve(messages));
|
||||
});
|
||||
});
|
||||
|
||||
// Добавляем задержку между обработкой писем
|
||||
for (const message of messages) {
|
||||
await this.processEmail(message);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 секунда между письмами
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке новых писем:', error);
|
||||
}
|
||||
async start() {
|
||||
console.log('EmailBotService: Запуск сервиса отключен (заглушка)');
|
||||
return false;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.isRunning) {
|
||||
console.log('Останавливаем Email бота...');
|
||||
|
||||
// Закрываем SMTP соединение
|
||||
if (this.transporter) {
|
||||
await this.transporter.close();
|
||||
}
|
||||
|
||||
// Закрываем IMAP соединение
|
||||
if (this.imap) {
|
||||
this.imap.end();
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
console.log('Email бот остановлен');
|
||||
}
|
||||
console.log('EmailBotService: Остановка сервиса отключена (заглушка)');
|
||||
return true;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
// В обработчике команд добавьте код для связывания аккаунтов
|
||||
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 'Произошла ошибка при связывании аккаунтов. Попробуйте позже.';
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка других команд...
|
||||
}
|
||||
|
||||
module.exports = EmailBotService;
|
||||
149
backend/services/ollama.js
Normal file
149
backend/services/ollama.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { RetrievalQAChain } = require("langchain/chains");
|
||||
const { PromptTemplate } = require("@langchain/core/prompts");
|
||||
const axios = require('axios');
|
||||
|
||||
// Создаем шаблон для контекстного запроса
|
||||
const PROMPT_TEMPLATE = `
|
||||
Ты - AI-ассистент для бизнеса, специализирующийся на блокчейн-технологиях и Web3.
|
||||
Используй следующий контекст для ответа на вопрос пользователя.
|
||||
Если ты не знаешь ответа, просто скажи, что не знаешь, не пытайся придумать ответ.
|
||||
|
||||
Контекст: {context}
|
||||
|
||||
Вопрос: {query}
|
||||
|
||||
Ответ:
|
||||
`;
|
||||
|
||||
// Функция для проверки доступности Ollama
|
||||
async function checkOllamaAvailability() {
|
||||
console.log('Проверка доступности Ollama...');
|
||||
|
||||
try {
|
||||
// Добавляем таймаут для запроса
|
||||
const response = await axios.get('http://localhost:11434/api/tags', {
|
||||
timeout: 5000 // 5 секунд таймаут
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
console.log('Ollama доступен. Доступные модели:');
|
||||
if (response.data && response.data.models) {
|
||||
response.data.models.forEach(model => {
|
||||
console.log(`- ${model.name}`);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ollama недоступен:', error.message);
|
||||
console.log('Приложение продолжит работу без Ollama');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для прямого запроса к Ollama API
|
||||
async function directOllamaQuery(message, model = 'mistral') {
|
||||
try {
|
||||
console.log(`Отправка запроса к Ollama (модель: ${model}):`, message);
|
||||
|
||||
// Проверяем доступность Ollama перед отправкой запроса
|
||||
const isAvailable = await checkOllamaAvailability();
|
||||
if (!isAvailable) {
|
||||
throw new Error('Сервер Ollama недоступен');
|
||||
}
|
||||
|
||||
// Создаем экземпляр ChatOllama
|
||||
const ollama = new ChatOllama({
|
||||
baseUrl: 'http://localhost:11434',
|
||||
model: model,
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
console.log('Отправка запроса к Ollama...');
|
||||
const result = await ollama.invoke(message);
|
||||
console.log('Получен ответ от Ollama');
|
||||
|
||||
return result.content;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при запросе к Ollama:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для создания цепочки Ollama с RAG
|
||||
async function createOllamaChain(vectorStore) {
|
||||
try {
|
||||
console.log('Создаем модель Ollama...');
|
||||
// Создаем модель Ollama
|
||||
const model = new ChatOllama({
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
||||
model: process.env.OLLAMA_MODEL || 'mistral',
|
||||
temperature: 0.2,
|
||||
timeout: 60000, // 60 секунд таймаут
|
||||
});
|
||||
console.log('Модель Ollama создана');
|
||||
|
||||
// Проверяем модель прямым запросом
|
||||
try {
|
||||
console.log('Тестируем модель прямым запросом...');
|
||||
const testResponse = await model.invoke('Тестовый запрос');
|
||||
console.log('Тест модели успешен:', testResponse);
|
||||
} catch (testError) {
|
||||
console.error('Ошибка при тестировании модели:', testError);
|
||||
// Продолжаем выполнение, даже если тест не прошел
|
||||
}
|
||||
|
||||
console.log('Создаем шаблон запроса...');
|
||||
// Создаем шаблон запроса
|
||||
const prompt = new PromptTemplate({
|
||||
template: PROMPT_TEMPLATE,
|
||||
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
|
||||
}
|
||||
);
|
||||
console.log('Цепочка для поиска и ответа создана');
|
||||
|
||||
return chain;
|
||||
} catch (error) {
|
||||
console.error('Error creating Ollama chain:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для получения модели Ollama
|
||||
async function getOllamaModel() {
|
||||
try {
|
||||
// Создаем модель Ollama
|
||||
const model = new ChatOllama({
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
||||
model: process.env.OLLAMA_MODEL || 'mistral',
|
||||
temperature: 0.2,
|
||||
timeout: 60000, // 60 секунд таймаут
|
||||
});
|
||||
|
||||
return model;
|
||||
} catch (error) {
|
||||
console.error('Error creating Ollama model:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getOllamaModel, createOllamaChain, checkOllamaAvailability, directOllamaQuery };
|
||||
@@ -6,10 +6,12 @@ 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(token) {
|
||||
if (!token) {
|
||||
constructor() {
|
||||
// Проверяем наличие токена
|
||||
if (!process.env.TELEGRAM_BOT_TOKEN) {
|
||||
throw new Error('Token is required');
|
||||
}
|
||||
|
||||
@@ -18,7 +20,7 @@ class TelegramBotService {
|
||||
this.retryDelay = 5000; // 5 секунд между попытками
|
||||
|
||||
// Создаем бота без polling
|
||||
this.bot = new TelegramBot(token, {
|
||||
this.bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, {
|
||||
polling: false,
|
||||
request: {
|
||||
proxy: null,
|
||||
@@ -30,7 +32,7 @@ class TelegramBotService {
|
||||
}
|
||||
});
|
||||
|
||||
this.token = token;
|
||||
this.token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
this.chat = new ChatOllama({
|
||||
model: 'mistral',
|
||||
baseUrl: 'http://localhost:11434'
|
||||
@@ -45,6 +47,8 @@ class TelegramBotService {
|
||||
minVersion: 'TLSv1.2'
|
||||
})
|
||||
};
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
setupHandlers() {
|
||||
@@ -73,6 +77,39 @@ class TelegramBotService {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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() {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { PGVectorStore } = require('@langchain/community/vectorstores/pgvector');
|
||||
|
||||
class TelegramBotService {
|
||||
constructor(token, vectorStore) {
|
||||
this.bot = new TelegramBot(token, { polling: true });
|
||||
this.vectorStore = vectorStore;
|
||||
this.chat = new ChatOllama({
|
||||
model: 'mistral',
|
||||
baseUrl: 'http://localhost:11434'
|
||||
});
|
||||
|
||||
this.userRequests = new Map(); // для отслеживания запросов
|
||||
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
isRateLimited(userId) {
|
||||
const now = Date.now();
|
||||
const userReqs = this.userRequests.get(userId) || [];
|
||||
|
||||
// Очищаем старые запросы
|
||||
const recentReqs = userReqs.filter(time => now - time < 60000);
|
||||
|
||||
// Максимум 10 запросов в минуту
|
||||
if (recentReqs.length >= 10) return true;
|
||||
|
||||
recentReqs.push(now);
|
||||
this.userRequests.set(userId, recentReqs);
|
||||
return false;
|
||||
}
|
||||
|
||||
setupHandlers() {
|
||||
this.bot.on('message', async (msg) => {
|
||||
const userId = msg.from.id;
|
||||
|
||||
if (this.isRateLimited(userId)) {
|
||||
await this.bot.sendMessage(msg.chat.id,
|
||||
'Пожалуйста, подождите минуту перед следующим запросом.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const chatId = msg.chat.id;
|
||||
const userQuestion = msg.text;
|
||||
|
||||
// Поиск релевантных документов
|
||||
const relevantDocs = await this.vectorStore.similaritySearch(userQuestion, 3);
|
||||
|
||||
// Формируем контекст из найденных документов
|
||||
const context = relevantDocs.map(doc => doc.pageContent).join('\n');
|
||||
|
||||
// Получаем ответ от LLM
|
||||
const response = await this.chat.invoke([
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are a helpful assistant. Use this context to answer: ${context}`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: userQuestion
|
||||
}
|
||||
]);
|
||||
|
||||
await this.bot.sendMessage(chatId, response);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Telegram bot error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramBotService;
|
||||
134
backend/services/vectorStore.js
Normal file
134
backend/services/vectorStore.js
Normal file
@@ -0,0 +1,134 @@
|
||||
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 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');
|
||||
|
||||
// Создаем директории, если они не существуют
|
||||
if (!fs.existsSync(DOCS_DIR)) {
|
||||
fs.mkdirSync(DOCS_DIR, { recursive: true });
|
||||
console.log(`Создана директория для документов: ${DOCS_DIR}`);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Проверяем, есть ли документы в директории
|
||||
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),
|
||||
}
|
||||
);
|
||||
|
||||
console.log('Загрузка документов...');
|
||||
const docs = await loader.load();
|
||||
console.log(`Загружено ${docs.length} документов`);
|
||||
|
||||
if (docs.length === 0) {
|
||||
console.warn('Не удалось загрузить документы');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Разбиваем документы на чанки
|
||||
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;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при инициализации векторного хранилища:', error);
|
||||
console.log('Приложение продолжит работу без векторного хранилища');
|
||||
// Возвращаем заглушку вместо реального хранилища
|
||||
return {
|
||||
addDocuments: async () => console.log('Векторное хранилище недоступно: addDocuments'),
|
||||
similaritySearch: async () => {
|
||||
console.log('Векторное хранилище недоступно: similaritySearch');
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для получения экземпляра векторного хранилища
|
||||
async function getVectorStore() {
|
||||
if (!vectorStore) {
|
||||
vectorStore = await initializeVectorStore();
|
||||
}
|
||||
return vectorStore;
|
||||
}
|
||||
|
||||
module.exports = { initializeVectorStore, getVectorStore };
|
||||
Reference in New Issue
Block a user