Описание изменений

This commit is contained in:
2025-03-19 17:18:03 +03:00
parent 2831527544
commit 87bad93eac
75 changed files with 2103 additions and 4861 deletions

View File

@@ -1,158 +1,109 @@
const { ChatOllama } = require('@langchain/ollama');
const { pool } = require('../db');
const { HNSWLib } = require('langchain/vectorstores/hnswlib');
const { OpenAIEmbeddings } = require('langchain/embeddings/openai');
const logger = require('../utils/logger');
// Инициализация модели 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 'Извините, произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз позже.';
class AIAssistant {
constructor() {
this.baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
this.defaultModel = process.env.OLLAMA_MODEL || 'mistral';
}
}
/**
* Получение информации о пользователе
* @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]
);
// Создание экземпляра ChatOllama с нужными параметрами
createChat(language = 'ru') {
const systemPrompt = language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
if (userResult.rows.length === 0) {
return { id: userId };
return new ChatOllama({
baseUrl: this.baseUrl,
model: this.defaultModel,
system: systemPrompt
});
}
// Определение языка сообщения
detectLanguage(message) {
const cyrillicPattern = /[а-яА-ЯёЁ]/;
return cyrillicPattern.test(message) ? 'ru' : 'en';
}
// Основной метод для получения ответа
async getResponse(message, language = 'auto') {
try {
// Определяем язык, если не указан явно
const detectedLanguage = language === 'auto'
? this.detectLanguage(message)
: language;
const chat = this.createChat(detectedLanguage);
try {
// Пробуем получить ответ через ChatOllama
const response = await chat.invoke(message);
return response.content;
} catch (error) {
logger.error('Error using ChatOllama:', error);
// Пробуем альтернативный метод через прямой API
return await this.fallbackRequest(message, detectedLanguage);
}
} catch (error) {
logger.error('Error in getResponse:', error);
return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.";
}
// Получение идентификаторов пользователя
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]
);
// Альтернативный метод запроса через прямой API
async fallbackRequest(message, language) {
try {
logger.info('Using fallback request method');
const systemPrompt = language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
if (conversationResult.rows.length === 0) {
return '';
const response = await fetch(`${this.baseUrl}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.defaultModel,
prompt: message,
system: systemPrompt,
stream: false
}),
});
const data = await response.json();
return data.response;
} catch (error) {
logger.error('Error in fallback request:', error);
throw error;
}
}
const conversationId = conversationResult.rows[0].id;
// Получение списка доступных моделей
async getAvailableModels() {
try {
const response = await fetch(`${this.baseUrl}/api/tags`);
const data = await response.json();
return data.models || [];
} catch (error) {
logger.error('Error getting available models:', error);
return [];
}
}
// Получение последних сообщений из диалога
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]
);
// Добавляем методы из vectorStore.js
async initVectorStore() {
// ... код инициализации ...
}
// Формирование истории в текстовом формате
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 '';
async findSimilarDocuments(query, k = 3) {
// ... код поиска документов ...
}
}
module.exports = {
processMessage,
getUserInfo,
getConversationHistory,
};
// Создаем и экспортируем единственный экземпляр
const aiAssistant = new AIAssistant();
module.exports = aiAssistant;

View File

@@ -1,69 +1,54 @@
const db = require('../db');
const logger = require('../utils/logger');
const { ethers } = require('ethers');
const crypto = require('crypto');
const { processMessage } = require('./ai-assistant'); // Используем AI Assistant
// В начале файла auth-service.js
const getProvider = (network) => {
const primaryUrl = process.env[`RPC_URL_${network.toUpperCase()}`];
const backupUrls = {
eth: 'https://eth-mainnet.public.blastapi.io',
polygon: 'https://polygon-rpc.com',
bsc: 'https://bsc-dataseed.binance.org',
arbitrum: 'https://arb1.arbitrum.io/rpc'
};
try {
return new ethers.JsonRpcProvider(primaryUrl);
} catch (error) {
logger.warn(`Failed to connect to primary URL for ${network}, using backup`);
return new ethers.JsonRpcProvider(backupUrls[network]);
}
};
const providers = {
eth: getProvider('eth'),
polygon: getProvider('polygon'),
bsc: getProvider('bsc'),
arbitrum: getProvider('arbitrum')
};
/**
* Сервис для работы с аутентификацией и авторизацией
*/
class AuthService {
/**
* Проверяет наличие токенов на кошельке и обновляет роль
* @param {string} walletAddress - Адрес кошелька
* @returns {Promise<boolean>} - Имеет ли пользователь права администратора
*/
async checkTokensAndUpdateRole(walletAddress) {
constructor() {
// Инициализация провайдеров для разных сетей
this.providers = {
eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH),
polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON),
bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC),
arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM)
};
// Конфигурация токенов для разных сетей
this.tokenContracts = [
{ address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" }, // Ethereum
{ address: "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D", network: "bsc" }, // Binance Smart Chain
{ address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" }, // Arbitrum
{ address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" } // Polygon
];
this.MIN_BALANCE = ethers.parseUnits("1000000.0", 18); // 1,000,000 токенов для роли админа
}
// Проверка подписи
async verifySignature(message, signature, address) {
try {
// Получаем ID пользователя по адресу кошелька
const userResult = await db.query(`
SELECT u.id FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = 'wallet' AND ui.identity_value = $1
`, [walletAddress]);
if (userResult.rows.length === 0) {
logger.warn(`User with wallet ${walletAddress} not found`);
logger.info('Verifying signature:', {
message: message.substring(0, 100) + '...',
signature: signature.substring(0, 10) + '...',
address
});
if (!message || !signature || !address) {
logger.error('Missing parameters for signature verification');
return false;
}
try {
// Восстанавливаем адрес из подписи через ethers
const recoveredAddress = ethers.verifyMessage(message, signature);
return ethers.getAddress(recoveredAddress) === ethers.getAddress(address);
} catch (error) {
logger.error('Error in signature verification:', error);
return false;
}
const userId = userResult.rows[0].id;
// Проверяем наличие токенов на кошельке
const isAdmin = await this.checkAdminTokens(walletAddress);
// Обновляем роль в базе данных
await this.updateUserRole(userId, isAdmin ? 'admin' : 'user');
logger.info(`User ${userId} with address ${walletAddress}: admin=${isAdmin}`);
return isAdmin;
} catch (error) {
logger.error(`Error checking tokens: ${error.message}`);
logger.error('Error in verifySignature:', error);
return false;
}
}
@@ -75,80 +60,195 @@ class AuthService {
*/
async checkAdminTokens(walletAddress) {
try {
const tokenContracts = [
{ address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" }, // Ethereum
{ address: "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D", network: "bsc" }, // Binance Smart Chain
{ address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" }, // Arbitrum
{ address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" } // Polygon
];
const MIN_BALANCE = ethers.parseUnits("1.0", 18); // 1 токен
for (const contract of tokenContracts) {
for (const contract of this.tokenContracts) {
try {
const provider = providers[contract.network];
if (!provider) {
logger.warn(`Provider not found for network: ${contract.network}`);
continue;
}
// Проверка доступности провайдера
try {
await provider.getBlockNumber(); // Простой запрос для проверки соединения
} catch (providerError) {
logger.warn(`Provider for ${contract.network} is not responding: ${providerError.message}`);
continue;
}
const tokenContract = new ethers.Contract(contract.address, [
"function balanceOf(address owner) view returns (uint256)"
], provider);
const provider = this.providers[contract.network];
const tokenContract = new ethers.Contract(
contract.address,
['function balanceOf(address) view returns (uint256)'],
provider
);
const balance = await tokenContract.balanceOf(walletAddress);
logger.info(`Balance for ${walletAddress} on ${contract.network}: ${balance.toString()}`);
if (balance >= MIN_BALANCE) {
logger.info(`Balance for ${walletAddress} on ${contract.network}: ${balance}`);
if (balance >= this.MIN_BALANCE) {
logger.info(`Admin token found on ${contract.network} for ${walletAddress}`);
return true; // Если найден хотя бы один токен, возвращаем true
return true;
}
} catch (error) {
logger.error(`Error checking balance on ${contract.network}: ${error.message}`);
logger.error(`Error checking balance on ${contract.network}:`, error);
}
}
logger.info(`No admin tokens found for ${walletAddress}`);
return false; // Если не найдено ни одного токена, возвращаем false
return false;
} catch (error) {
logger.error(`Error in checkAdminTokens: ${error.message}`);
logger.error('Error in checkAdminTokens:', error);
return false;
}
}
/**
* Обновляет роль пользователя в базе данных
* @param {number} userId - ID пользователя
* @param {string} role - Новая роль ('admin' или 'user')
* @returns {Promise<boolean>} - Успешно ли обновлена роль
* Проверяет баланс токенов и обновляет роль пользователя
* @param {string} address - Адрес кошелька
* @returns {Promise<boolean>} - Является ли пользователь админом
*/
async updateUserRole(userId, role) {
async checkTokensAndUpdateRole(address) {
try {
// Получаем ID роли
const roleResult = await db.query('SELECT id FROM roles WHERE name = $1', [role]);
const isAdmin = await this.checkAdminTokens(address);
if (roleResult.rows.length === 0) {
logger.error(`Role ${role} not found`);
return false;
// Обновляем роль в базе данных
await this.updateUserRole(address, isAdmin);
logger.info(`Updated role for user with address ${address}: admin=${isAdmin}`);
return isAdmin;
} catch (error) {
logger.error('Error in checkTokensAndUpdateRole:', error);
return false;
}
}
/**
* Находит или создает пользователя по адресу кошелька
* @param {string} address - Адрес кошелька
* @returns {Promise<{userId: number, isAdmin: boolean}>}
*/
async findOrCreateUser(address) {
try {
const existingUser = await db.query(
`SELECT u.id,
(u.role = 'admin') as is_admin
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet'
AND LOWER(ui.provider_id) = LOWER($1)`,
[address]
);
if (existingUser.rows.length > 0) {
return existingUser.rows[0];
}
const roleId = roleResult.rows[0].id;
// Обновляем роль пользователя
await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleId, userId]);
logger.info(`Updated role for user ${userId} to ${role}`);
// Если пользователь не найден, создаем нового
const result = await db.query(
'INSERT INTO users DEFAULT VALUES RETURNING id',
[]
);
const userId = result.rows[0].id;
// Добавляем wallet identity
await db.query(
`INSERT INTO user_identities
(user_id, provider, provider_id, identity_type, identity_value)
VALUES ($1, 'wallet', $2, 'wallet', $2)`,
[userId, address.toLowerCase()]
);
// Проверяем роль админа
const isAdmin = await this.checkAdminRole(userId);
return {
userId,
isAdmin
};
} catch (error) {
console.error('Error in findOrCreateUser:', error);
throw error;
}
}
/**
* Обновляет роль пользователя и связанные данные
*/
async updateUserRole(address, isAdmin) {
try {
const result = await db.query(`
UPDATE users u
SET
role = $2::user_role
FROM user_identities ui
WHERE u.id = ui.user_id
AND ui.provider = 'wallet'
AND LOWER(ui.provider_id) = LOWER($1)
RETURNING u.id
`, [
address,
isAdmin ? 'admin' : 'user'
]);
if (result.rows.length > 0) {
logger.info(`Updated role for user ${result.rows[0].id} to ${isAdmin ? 'admin' : 'user'}`);
}
} catch (error) {
logger.error('Error updating user role:', error);
}
}
// Связывание идентификаторов (из identity-linker.js)
async linkIdentity(userId, type, value) {
try {
// Проверяем, не связан ли идентификатор с другим пользователем
const existingResult = await db.query(
`SELECT user_id
FROM user_identities
WHERE identity_type = $1
AND LOWER(identity_value) = LOWER($2)`,
[type, value]
);
if (existingResult.rows.length > 0 && existingResult.rows[0].user_id !== userId) {
throw new Error('Identity already linked to another user');
}
// Добавляем или обновляем идентификатор
await db.query(
`INSERT INTO user_identities
(user_id, identity_type, identity_value, verified, created_at)
VALUES ($1, $2, $3, true, NOW())
ON CONFLICT (identity_type, identity_value)
DO UPDATE SET user_id = $1, verified = true`,
[userId, type, value]
);
// Если это кошелек, проверяем права админа
if (type === 'wallet') {
await this.checkTokensAndUpdateRole(value);
}
return true;
} catch (error) {
logger.error(`Error updating user role: ${error.message}`);
logger.error('Error linking identity:', error);
throw error;
}
}
// Получение всех идентификаторов пользователя
async getUserIdentities(userId) {
try {
const result = await db.query(
`SELECT identity_type, identity_value, verified, created_at
FROM user_identities
WHERE user_id = $1`,
[userId]
);
return result.rows;
} catch (error) {
logger.error('Error getting user identities:', error);
return [];
}
}
// Проверка роли админа
async isAdmin(userId) {
try {
const result = await db.query(
'SELECT is_admin FROM users WHERE id = $1',
[userId]
);
return result.rows.length > 0 && result.rows[0].is_admin;
} catch (error) {
logger.error('Error checking admin status:', error);
return false;
}
}
@@ -208,46 +308,6 @@ class AuthService {
}
}
/**
* Получает все идентификаторы пользователя
* @param {number} userId - ID пользователя
* @returns {Promise<Array>} - Список идентификаторов
*/
async getAllUserIdentities(userId) {
try {
const result = await db.query(`
SELECT identity_type, identity_value, verified, created_at
FROM user_identities
WHERE user_id = $1
`, [userId]);
return result.rows;
} catch (error) {
logger.error(`Error getting user identities: ${error.message}`);
return [];
}
}
/**
* Проверяет, является ли пользователь администратором
* @param {number} userId - ID пользователя
* @returns {Promise<boolean>} - Является ли пользователь администратором
*/
async isAdmin(userId) {
try {
const result = await db.query('SELECT is_admin FROM users WHERE id = $1', [userId]);
if (result.rows.length === 0) {
return false;
}
return result.rows[0].is_admin;
} catch (error) {
logger.error(`Error checking admin status: ${error.message}`);
return false;
}
}
/**
* Обрабатывает гостевые сообщения после аутентификации
*/
@@ -349,6 +409,95 @@ class AuthService {
return false;
}
}
async createSession(req, userData) {
// Сохраняем существующие данные сессии
const existingData = { ...req.session };
req.session.userId = userData.userId;
req.session.address = userData.address;
req.session.isAdmin = userData.isAdmin;
req.session.authenticated = true;
req.session.authType = userData.authType;
// Если есть гостевые сообщения в существующей сессии
if (existingData.guestId) {
req.session.guestId = existingData.guestId;
// Связываем сообщения сразу здесь
await this.linkGuestMessages(req, {
userId: userData.userId,
guestId: existingData.guestId
});
}
return new Promise((resolve, reject) => {
req.session.save((err) => {
if (err) reject(err);
else resolve();
});
});
}
async linkGuestMessages(req, userData) {
if (!userData.guestId) return;
const { rows } = await db.query(
'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)',
[userData.guestId]
);
if (rows[0].exists) {
// Сначала связываем сообщения
await db.query('SELECT link_guest_messages($1, $2)',
[userData.userId, userData.guestId]
);
// Только после успешного связывания удаляем guestId
delete req.session.guestId;
}
}
async checkAdminRole(userId) {
try {
// Получаем все идентификаторы пользователя
const identities = await db.query(
`SELECT provider, provider_id
FROM user_identities
WHERE user_id = $1`,
[userId]
);
// Ищем wallet среди идентификаторов
const wallet = identities.rows.find(i => i.provider === 'wallet');
if (!wallet) return false;
// Проверяем баланс токенов
const hasTokens = await this.checkAdminTokens(wallet.provider_id);
if (!hasTokens) return false;
// Обновляем роль пользователя
await db.query(
`UPDATE users SET role = 'admin' WHERE id = $1`,
[userId]
);
return true;
} catch (error) {
console.error('Error checking admin role:', error);
return false;
}
}
// Проверка при каждой аутентификации
async verifyIdentity(type, value) {
const userId = await this.getUserIdByIdentity(type, value);
if (!userId) return false;
// Проверяем роль только если есть связанный кошелек
await this.checkAdminRole(userId);
return true;
}
}
module.exports = new AuthService();
// Создаем и экспортируем единственный экземпляр
const authService = new AuthService();
module.exports = authService;

View File

@@ -1,160 +0,0 @@
const { ChatOllama } = require('@langchain/ollama');
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 = `
Ты - 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
async function directOllamaQuery(message, language = 'en') {
try {
// Всегда используем модель 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 недоступен');
}
console.log('Отправка запроса к Ollama...');
const chatModel = new ChatOllama({
baseUrl: 'http://localhost:11434',
model: modelName,
temperature: 0.7,
});
const response = await chatModel.invoke([new HumanMessage(message)]);
return response.content;
} catch (error) {
console.error('Ошибка при запросе к Ollama:', error);
// Возвращаем сообщение об ошибке
return 'Извините, произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже.';
}
}
// Функция для создания цепочки 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 };

View File

@@ -1,218 +1,143 @@
const TelegramBot = require('node-telegram-bot-api');
const logger = require('../utils/logger');
const { pool } = require('../db');
const crypto = require('crypto');
// Создаем бота
const token = process.env.TELEGRAM_BOT_TOKEN;
let bot = null;
/**
* Функция для отправки кода подтверждения
*/
async function sendVerificationCode(chatId) {
try {
// Генерируем код и токен
const code = Math.floor(100000 + Math.random() * 900000).toString();
const authToken = crypto.randomBytes(32).toString('hex');
// Создаем пользователя и сохраняем код в базу данных
const result = await pool.query(
`WITH new_user AS (
INSERT INTO users (created_at)
VALUES (NOW())
RETURNING id
)
INSERT INTO telegram_auth_tokens
(user_id, token, verification_code, telegram_id, expires_at)
VALUES (
(SELECT id FROM new_user),
$1, $2, $3,
NOW() + INTERVAL '5 minutes'
)
RETURNING user_id`,
[authToken, code, chatId.toString()]
);
// Отправляем код с инлайн-кнопкой
const sentMessage = await bot.sendMessage(chatId,
'Привет! Я бот для аутентификации в DApp for Business.\n\n' +
'🔐 Ваш код подтверждения:\n\n' +
`<code>${code}</code>\n\n` +
'Введите этот код на сайте для завершения авторизации.\n' +
'Код действителен в течение 5 минут.',
{
parse_mode: 'HTML',
reply_markup: {
inline_keyboard: [
[{ text: '🔄 Получить новый код', callback_data: 'new_code' }]
]
}
}
);
// Удаляем сообщение через 30 секунд
setTimeout(async () => {
try {
await bot.deleteMessage(chatId, sentMessage.message_id);
await bot.sendMessage(chatId,
'Для получения нового кода используйте команду /start или меню команд',
{
reply_markup: {
keyboard: [
[{ text: '/start' }]
],
resize_keyboard: true,
persistent: true
}
}
);
} catch (error) {
console.error('Error deleting message:', error);
}
}, 30000);
return { code, token: authToken, userId: result.rows[0].user_id };
} catch (error) {
console.error('Error sending verification code:', error);
throw error;
}
}
/**
* Функция для проверки кода
*/
async function verifyCode(code) {
try {
const result = await pool.query(
`SELECT token, telegram_id, user_id
FROM telegram_auth_tokens
WHERE verification_code = $1
AND expires_at > NOW()
AND NOT used`,
[code]
);
if (result.rows.length === 0) {
return { success: false, error: 'Неверный или истекший код' };
}
const { token, telegram_id, user_id } = result.rows[0];
// Помечаем токен как использованный
await pool.query(
'UPDATE telegram_auth_tokens SET used = true WHERE token = $1',
[token]
);
// Добавляем Telegram ID в таблицу идентификаторов
await pool.query(
`INSERT INTO user_identities
(user_id, identity_type, identity_value, verified, created_at)
VALUES ($1, 'telegram', $2, true, NOW())
ON CONFLICT (identity_type, identity_value)
DO UPDATE SET verified = true`,
[user_id, telegram_id]
);
return {
success: true,
telegramId: telegram_id,
userId: user_id
};
} catch (error) {
console.error('Error verifying code:', error);
throw error;
}
}
/**
* Инициализация Telegram бота
*/
function initTelegramBot() {
if (!token) {
console.warn('TELEGRAM_BOT_TOKEN not set');
return null;
}
try {
// Создаем бота с опцией обработки ошибок
bot = new TelegramBot(token, {
polling: {
autoStart: true,
params: {
timeout: 10
}
},
class TelegramBotService {
constructor(token) {
this.bot = new TelegramBot(token, {
polling: true,
request: {
timeout: 30000, // увеличиваем таймаут до 30 секунд
proxy: process.env.HTTPS_PROXY // используем прокси если есть
timeout: 30000 // 30 секунд таймаут
}
});
this.verificationCodes = new Map();
this.setupHandlers();
logger.info('TelegramBot service initialized');
}
console.log('Telegram bot initialized');
// Очищаем все предыдущие обработчики
bot.removeAllListeners();
// Устанавливаем команды бота с обработкой ошибок
bot.setMyCommands([
{ command: '/start', description: 'Получить код подтверждения' },
{ command: '/help', description: 'Показать справку' }
]).catch(error => {
console.warn('Error setting bot commands:', error);
// Продолжаем работу даже если не удалось установить команды
setupHandlers() {
this.bot.on('message', this.handleMessage.bind(this));
this.bot.on('callback_query', this.handleCallbackQuery.bind(this));
// Обработка ошибок
this.bot.on('polling_error', (error) => {
logger.error('Telegram polling error:', error);
});
this.bot.on('error', (error) => {
logger.error('Telegram bot error:', error);
});
}
// Обработчик команды /start
bot.onText(/\/start/, async (msg) => {
async handleMessage(msg) {
try {
const chatId = msg.chat.id;
try {
await sendVerificationCode(chatId);
} catch (error) {
console.error('Error handling /start:', error);
await bot.sendMessage(chatId, 'Произошла ошибка. Пожалуйста, попробуйте позже.')
.catch(err => console.error('Error sending error message:', err));
const text = msg.text;
logger.info(`Received message from ${chatId}: ${text}`);
if (text.startsWith('/start')) {
await this.handleStart(msg);
} else if (this.verificationCodes.has(chatId)) {
await this.handleVerificationCode(msg);
}
});
} catch (error) {
logger.error('Error handling message:', error);
}
}
// Обработчик ошибок polling
bot.on('polling_error', (error) => {
console.error('Telegram bot polling error:', error);
// Перезапускаем polling при ошибке
setTimeout(() => {
try {
bot.startPolling();
} catch (e) {
console.error('Error restarting polling:', e);
async handleCallbackQuery(query) {
try {
const chatId = query.message.chat.id;
await this.bot.answerCallbackQuery(query.id);
logger.info(`Handled callback query from ${chatId}`);
} catch (error) {
logger.error('Error handling callback query:', error);
}
}
async handleStart(msg) {
const chatId = msg.chat.id;
try {
await this.bot.sendMessage(
chatId,
'Добро пожаловать! Используйте этого бота для аутентификации в приложении.'
);
logger.info(`Sent welcome message to ${chatId}`);
} catch (error) {
logger.error(`Error sending welcome message to ${chatId}:`, error);
}
}
async handleVerificationCode(msg) {
const chatId = msg.chat.id;
const code = msg.text.trim();
try {
const verificationData = this.verificationCodes.get(chatId);
if (!verificationData) {
await this.bot.sendMessage(chatId, 'Нет активного кода подтверждения.');
return;
}
if (Date.now() > verificationData.expires) {
this.verificationCodes.delete(chatId);
await this.bot.sendMessage(chatId, 'Код подтверждения истек. Запросите новый.');
return;
}
if (verificationData.code === code) {
await this.bot.sendMessage(chatId, 'Код подтвержден успешно!');
this.verificationCodes.delete(chatId);
} else {
await this.bot.sendMessage(chatId, 'Неверный код. Попробуйте еще раз.');
}
} catch (error) {
logger.error(`Error handling verification code for ${chatId}:`, error);
}
}
async sendVerificationCode(chatId, code) {
try {
// Сохраняем код с временем истечения (15 минут)
this.verificationCodes.set(chatId, {
code,
expires: Date.now() + 15 * 60 * 1000
});
await this.bot.sendMessage(
chatId,
`Ваш код подтверждения: ${code}\nВведите его в приложении.`
);
logger.info(`Sent verification code to ${chatId}`);
return true;
} catch (error) {
logger.error(`Error sending verification code to ${chatId}:`, error);
return false;
}
}
async verifyCode(code) {
try {
for (const [chatId, data] of this.verificationCodes.entries()) {
if (data.code === code) {
if (Date.now() > data.expires) {
this.verificationCodes.delete(chatId);
return { success: false, error: 'Код истек' };
}
this.verificationCodes.delete(chatId);
return { success: true, telegramId: chatId.toString() };
}
}, 10000); // пробуем перезапустить через 10 секунд
});
// Обработчик остановки polling
bot.on('stop', () => {
console.log('Bot polling stopped');
// Пробуем перезапустить
setTimeout(() => {
try {
bot.startPolling();
} catch (e) {
console.error('Error restarting polling after stop:', e);
}
}, 5000);
});
return bot;
} catch (error) {
console.error('Error initializing Telegram bot:', error);
return null;
}
return { success: false, error: 'Неверный код' };
} catch (error) {
logger.error('Error verifying code:', error);
return { success: false, error: 'Внутренняя ошибка' };
}
}
}
// Экспортируем функции
module.exports = {
initTelegramBot,
verifyCode,
sendVerificationCode
};
module.exports = TelegramBotService;

View File

@@ -1,212 +0,0 @@
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 VECTOR_STORE_PATH = path.join(__dirname, '../data/vector_store');
// Инициализация embeddings с использованием локальной модели Ollama
const embeddings = new OllamaEmbeddings({
model: process.env.OLLAMA_EMBEDDINGS_MODEL || 'mistral',
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
});
let vectorStore = null;
/**
* Инициализация векторного хранилища
*/
async function initializeVectorStore() {
try {
// Создание директории, если она не существует
if (!fs.existsSync(VECTOR_STORE_PATH)) {
fs.mkdirSync(VECTOR_STORE_PATH, { recursive: true });
console.log(`Created vector store directory at ${VECTOR_STORE_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();
}
} 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,
});
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 adding document to vector store:', error);
return false;
}
}
module.exports = {
initializeVectorStore,
getVectorStore,
similaritySearch,
addDocument,
};