Описание изменений
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user