diff --git a/backend/.gitignore b/backend/.gitignore index a53cd4f..7132e9f 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -26,4 +26,7 @@ yarn-error.log* # Coverage directory used by tools like istanbul coverage/ -coverage.json \ No newline at end of file +coverage.json + +# Sessions directory +sessions/ \ No newline at end of file diff --git a/backend/contracts/MyContract.sol b/backend/contracts/MyContract.sol index e50c519..bfd5520 100644 --- a/backend/contracts/MyContract.sol +++ b/backend/contracts/MyContract.sol @@ -5,6 +5,11 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract MyContract is Ownable, ReentrancyGuard { + // Явно объявляем функцию owner + function owner() public view override returns (address) { + return super.owner(); + } + uint256 public price; event Purchase(address buyer, uint256 amount); diff --git a/backend/migrations/init.sql b/backend/migrations/init.sql index 9341fb4..850a29a 100644 --- a/backend/migrations/init.sql +++ b/backend/migrations/init.sql @@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS users ( DROP TABLE IF EXISTS documents; CREATE TABLE documents ( id bigserial PRIMARY KEY, - content text, + content text NOT NULL, metadata jsonb, embedding vector(4096) ); @@ -24,7 +24,8 @@ CREATE TABLE IF NOT EXISTS chat_history ( message TEXT NOT NULL, response TEXT NOT NULL, context_docs INTEGER[], - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + is_approved BOOLEAN DEFAULT false ); -- Даем права пользователю @@ -37,4 +38,7 @@ UPDATE users SET address = LOWER(address); -- Удаляем дубликаты DELETE FROM users a USING users b WHERE a.id > b.id -AND LOWER(a.address) = LOWER(b.address); \ No newline at end of file +AND LOWER(a.address) = LOWER(b.address); + +ALTER TABLE chat_history +ADD COLUMN IF NOT EXISTS is_approved BOOLEAN DEFAULT false; \ No newline at end of file diff --git a/backend/routes/api.js b/backend/routes/api.js index 4a25f35..bd8e055 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -7,6 +7,9 @@ const { RunnableSequence } = require('@langchain/core/runnables'); const { StringOutputParser } = require('@langchain/core/output_parsers'); const { PromptTemplate } = require('@langchain/core/prompts'); const { Pool } = require('pg'); +const { ethers } = require('ethers'); +const contractABI = require('../artifacts/contracts/MyContract.sol/MyContract.json').abi; +const crypto = require('crypto'); require('dotenv').config(); const pool = new Pool({ @@ -16,30 +19,117 @@ const pool = new Pool({ const chat = new ChatOllama({ model: 'mistral', - baseUrl: 'http://localhost:11434' + baseUrl: 'http://localhost:11434', + temperature: 0.7, + format: 'json' }); const embeddings = new OllamaEmbeddings({ model: 'mistral', - baseUrl: 'http://localhost:11434' + baseUrl: 'http://localhost:11434', + requestOptions: { + headers: { + 'Content-Type': 'application/json' + } + }, + dimensions: 4096, + stripNewLines: true, + maxConcurrency: 1, + maxRetries: 3, + timeout: 10000 }); let vectorStore; +let contract; async function initVectorStore() { - vectorStore = await PGVectorStore.initialize( - embeddings, - { - postgresConnectionOptions: { - connectionString: process.env.DATABASE_URL + try { + console.log('Начинаем инициализацию векторного хранилища...'); + vectorStore = await PGVectorStore.initialize( + embeddings, + { + postgresConnectionOptions: { + connectionString: process.env.DATABASE_URL + }, + tableName: 'documents', + columns: { + idColumnName: 'id', + vectorColumnName: 'embedding', + contentColumnName: 'content', + metadataColumnName: 'metadata', + } + } + ); + console.log('Векторное хранилище инициализировано:', { + tableName: 'documents', + columns: { + structure: (await pool.query(` + SELECT + column_name, + data_type, + is_nullable, + column_default + FROM information_schema.columns + WHERE table_name = 'documents' + ORDER BY ordinal_position + `)).rows.map(row => ({ + name: row.column_name, + type: row.data_type, + nullable: row.is_nullable, + default: row.column_default + })) }, - tableName: 'documents' + config: { + tableName: vectorStore.tableName, + columns: vectorStore.columns, + client: vectorStore.client ? 'Connected' : 'Not connected', + embeddings: vectorStore.embeddings ? 'Initialized' : 'Not initialized' + } + }); + } catch (error) { + console.error('Ошибка инициализации векторного хранилища:', error); + throw error; + } +} + +async function initContract() { + try { + const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL); + // Проверяем подключение к сети + const network = await provider.getNetwork(); + console.log('Подключены к сети:', network.chainId); + + contract = new ethers.Contract( + process.env.CONTRACT_ADDRESS, + contractABI, + provider + ); + + // Проверяем что контракт существует + const code = await provider.getCode(process.env.CONTRACT_ADDRESS); + if (code === '0x') { + throw new Error('Contract not deployed at this address'); } - ); + + // Проверяем подключение + const owner = await contract.owner(); + console.log('Владелец контракта:', owner); + console.log('Контракт инициализирован:', process.env.CONTRACT_ADDRESS); + } catch (error) { + console.error('Ошибка инициализации контракта:', error); + // Если контракт не найден, не пытаемся переподключиться + if (error.message.includes('not deployed')) { + console.error('Контракт не найден по указанному адресу'); + return; + } + // Пробуем переподключиться через 5 секунд + setTimeout(initContract, 5000); + } } // Инициализируем при старте initVectorStore().catch(console.error); +initContract().catch(console.error); // Проверяем подключение к БД при старте pool.connect((err, client, release) => { @@ -59,16 +149,38 @@ function requireAuth(req, res, next) { next(); } +// Генерация случайного nonce +function generateNonce() { + return crypto.randomBytes(16).toString('base64').replace(/[^a-zA-Z0-9]/g, ''); +} + +// Получение nonce для подписи +router.get('/nonce', (req, res) => { + try { + setCorsHeaders(res); + const nonce = generateNonce(); + console.log('Сгенерирован новый nonce:', nonce); + res.json({ nonce }); + } catch (error) { + console.error('Ошибка генерации nonce:', error); + res.status(500).json({ error: 'Server error' }); + } +}); + +// Верификация подписи router.post('/verify', async (req, res) => { try { const { message, signature } = req.body; - // ... верификация подписи ... - - // Сохраняем в сессию - req.session.authenticated = true; - req.session.siwe = message; - req.session.userAddress = message.address; + // Обновляем данные сессии + Object.assign(req.session, { + authenticated: true, + siwe: message, + userAddress: message.address, + cookie: { + maxAge: 7 * 24 * 60 * 60 * 1000 + } + }); // Ждем сохранения await new Promise((resolve) => { @@ -81,7 +193,20 @@ router.post('/verify', async (req, res) => { address: req.session.userAddress }); - res.json({ ok: true }); + // Проверяем права админа сразу после входа + const contractOwner = await contract.owner(); + const isAdmin = message.address.toLowerCase() === contractOwner.toLowerCase(); + + console.log('Проверка прав после входа:', { + userAddress: message.address, + contractOwner, + isAdmin + }); + + res.json({ + ok: true, + isAdmin + }); } catch (error) { console.error('Verify error:', error); res.status(400).json({ error: error.message }); @@ -89,13 +214,13 @@ router.post('/verify', async (req, res) => { }); // Создаем шаблон промпта для RAG -const TEMPLATE = `Вы - ассистент в DApp приложении. -Используйте этот контекст для ответа на вопрос: -{context} +const TEMPLATE = `Вы - ассистент в DApp приложении. Используйте следующий контекст для ответа: -Вопрос: {question} +Контекст: {context} +Вопрос пользователя: {question} -Ответ должен быть полезным, точным и основанным на предоставленном контексте.`; +Отвечайте кратко и по существу, основываясь на предоставленном контексте. Если контекст пустой или не релевантный, +используйте свои базовые знания о DApp и блокчейне.`; const prompt = PromptTemplate.fromTemplate(TEMPLATE); @@ -103,106 +228,145 @@ const prompt = PromptTemplate.fromTemplate(TEMPLATE); const chain = RunnableSequence.from([ { context: async (input) => { - const results = await vectorStore.similaritySearch(input.question); - return results.map(doc => doc.content).join('\n\n'); + try { + const results = await vectorStore.similaritySearch( + input.question, + 1, + { type: 'approved_chat' } + ); + if (!results.length) return ''; + return results + .filter(doc => doc.pageContent) + .map(doc => doc.pageContent) + .join('\n\n'); + } catch (error) { + console.error('Ошибка поиска контекста:', error); + return ''; + } }, - question: (input) => input.question + question: (input) => input.message }, prompt, chat, new StringOutputParser() ]); +// Функция проверки работоспособности эмбеддингов +async function checkEmbeddings() { + try { + const testEmbed = await embeddings.embedQuery('test'); + console.log('Эмбеддинги работают, размерность:', testEmbed.length); + if (testEmbed.length !== 4096) { + throw new Error(`Неверная размерность: ${testEmbed.length}, ожидалось: 4096`); + } + return true; + } catch (error) { + console.error('Ошибка эмбеддингов:', error); + return false; + } +} + router.post('/chat', requireAuth, async (req, res) => { try { - if (req.session.siwe.address.toLowerCase() !== req.body.userAddress.toLowerCase()) { - return res.status(401).json({ - error: 'Address mismatch' - }); - } - - if (!vectorStore) { - throw new Error('Vector store not initialized'); - } - const { message, userAddress } = req.body; - console.log('Session in chat:', req.session); - console.log('User address:', userAddress); + const { message } = req.body; + const userAddress = req.session.siwe.address; // Получаем или создаем пользователя - let user = await pool.query( - 'INSERT INTO users (address) VALUES (LOWER($1)) ON CONFLICT (address) DO UPDATE SET address = LOWER($1) RETURNING id', + let userResult = await pool.query( + 'SELECT id FROM users WHERE LOWER(address) = LOWER($1)', [userAddress] ); - const userId = user.rows[0].id; + + if (userResult.rows.length === 0) { + userResult = await pool.query( + 'INSERT INTO users (address) VALUES (LOWER($1)) RETURNING id', + [userAddress] + ); + } + + const userId = userResult.rows[0].id; - // Получаем релевантные документы для контекста - const results = await vectorStore.similaritySearch(message); - console.log('Found documents:', results); - const contextIds = results.map(doc => doc.id); - - const response = await chain.invoke({ + // Создаем входные данные для chain + const input = { + message: message, question: message - }); + }; - // Сохраняем историю - await pool.query( - 'INSERT INTO chat_history (user_id, message, response, context_docs) VALUES ($1, $2, $3, $4)', - [userId, message, response, contextIds] - ); + // Проверяем эмбеддинги перед использованием + if (!await checkEmbeddings()) { + console.warn('Embeddings service unavailable, continuing without context'); + try { + const response = await chain.invoke(input); + + // Сохраняем в базу без контекста + await pool.query( + 'INSERT INTO chat_history (user_id, message, response) VALUES ($1, $2, $3)', + [userId, message, response] + ); + + return res.json({ response }); + } catch (error) { + console.error('Ошибка генерации ответа:', error); + throw error; + } + } - res.json({ response: response }); + const response = await chain.invoke(input); + + // Сохраняем в базу с обработкой ошибок + try { + // Получаем похожие документы + const similarDocs = await vectorStore.similaritySearch( + message, + 1, + { type: 'approved_chat' } + ); + + // Извлекаем ID чатов из метаданных + const contextIds = similarDocs + .map(doc => doc.metadata?.chatId) + .filter(id => typeof id === 'number'); + + await pool.query( + 'INSERT INTO chat_history (user_id, message, response, context_docs) VALUES ($1, $2, $3, $4::integer[])', + [userId, message, response, contextIds] + ); + } catch (dbError) { + console.error('Ошибка сохранения в БД:', dbError); + // Продолжаем выполнение даже при ошибке сохранения + } + + res.json({ response }); } catch (error) { console.error('Ошибка чата:', error); - if (error.code === 'unsupported_country_region_territory') { - return res.status(503).json({ - error: 'Сервис временно недоступен в вашем регионе' - }); - } - res.status(500).json({ error: 'Ошибка сервера' }); + res.status(500).json({ + error: error.message, + details: error.stack + }); } }); // Получение истории чата router.get('/chat/history', requireAuth, async (req, res) => { try { - const userAddress = req.session.siwe.address; - console.log('Запрос истории чата для:', userAddress); + setCorsHeaders(res); - // Получаем ID пользователя - const userResult = await pool.query( - 'SELECT id FROM users WHERE LOWER(address) = LOWER($1) ORDER BY created_at ASC LIMIT 1', - [userAddress] - ); - console.log('Найден пользователь:', userResult.rows); - - if (userResult.rows.length === 0) { - return res.status(404).json({ error: 'User not found' }); - } - - const userId = userResult.rows[0].id; - - // Получаем историю чата - const history = await pool.query( - `SELECT - ch.id, - LOWER(u.address) as address, - ch.message, - ch.response, - ch.created_at + const userAddress = req.session.siwe.address; + + // Получаем историю чата пользователя + const result = await pool.query( + `SELECT ch.* FROM chat_history ch JOIN users u ON ch.user_id = u.id - WHERE ch.user_id = $1 - ORDER BY created_at DESC`, - [userId] + WHERE LOWER(u.address) = LOWER($1) + ORDER BY ch.created_at DESC`, + [userAddress] ); - console.log('История чата:', history.rows); - - res.json({ - history: history.rows - }); + + res.json({ history: result.rows }); } catch (error) { console.error('Ошибка получения истории:', error); - res.status(500).json({ error: 'Ошибка сервера' }); + res.status(500).json({ error: 'Server error' }); } }); @@ -221,4 +385,433 @@ router.get('/users', requireAuth, async (req, res) => { } }); +// Проверка на админа +router.get('/admin/check', requireAuth, async (req, res) => { + try { + if (!contract) { + await initContract(); + if (!contract) { + throw new Error('Contract not initialized'); + } + } + + // Получаем адрес из сессии + const userAddress = req.session.siwe.address; + console.log('Проверка админа, адрес из сессии:', userAddress); + + const contractOwner = await contract.owner(); + console.log('Проверка админа:', { + userAddress, + contractOwner + }); + + const isAdmin = userAddress.toLowerCase() === contractOwner.toLowerCase(); + console.log('Результат проверки админа:', isAdmin); + + res.json({ isAdmin }); + } catch (error) { + console.error('Ошибка проверки админа:', error); + res.status(500).json({ + error: 'Server error', + details: error.message, + code: error.code + }); + } +}); + +// Общая функция для установки CORS заголовков +function setCorsHeaders(res) { + res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5173'); + res.header('Access-Control-Allow-Credentials', 'true'); + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Accept'); +} + +// Получение всех чатов для админа +router.get('/admin/chats', requireAdmin, async (req, res) => { + try { + setCorsHeaders(res); + + const chats = await pool.query(` + SELECT + ch.id, + LOWER(u.address) as address, + ch.message, + ch.response, + ch.created_at, + ch.context_docs, + EXISTS ( + SELECT 1 FROM documents d + WHERE d.metadata->>'chatId' = ch.id::text + AND d.metadata->>'type' = 'approved_chat' + ) as is_approved + FROM chat_history ch + JOIN users u ON ch.user_id = u.id + ORDER BY ch.created_at DESC + `); + + console.log('Получено чатов:', chats.rows.length); + if (chats.rows.length > 0) { + console.log('Пример чата:', { + id: chats.rows[0].id, + address: chats.rows[0].address, + is_approved: chats.rows[0].is_approved + }); + } + + res.json({ chats: chats.rows }); + } catch (error) { + console.error('Ошибка получения чатов:', error); + res.status(500).json({ + error: 'Server error', + details: error.message + }); + } +}); + +// Одобрение чата для обучения +router.post('/admin/approve', requireAuth, async (req, res) => { + try { + const userAddress = req.session.siwe.address; + const contractOwner = await contract.owner(); + + if (userAddress.toLowerCase() !== contractOwner.toLowerCase()) { + return res.status(403).json({ error: 'Not authorized' }); + } + + const { chatId } = req.body; + + // Обновляем статус в базе + await pool.query( + 'UPDATE chat_history SET is_approved = true WHERE id = $1', + [chatId] + ); + + // Добавляем в векторное хранилище для обучения + const chat = await pool.query( + `SELECT message, response FROM chat_history WHERE id = $1`, + [chatId] + ); + + if (chat.rows.length > 0) { + const { message, response } = chat.rows[0]; + console.log('Добавляем в векторное хранилище:', { + message: message.substring(0, 50) + '...', + response: response.substring(0, 50) + '...', + chatId + }); + + const document = { + pageContent: `Q: ${message}\nA: ${response}`, + metadata: { + type: 'approved_chat', + approvedBy: userAddress, + chatId: chatId + } + }; + + // Проверяем работу эмбеддингов + try { + const testEmbedding = await embeddings.embedQuery('test'); + console.log('Эмбеддинги работают, размерность:', testEmbedding.length); + } catch (error) { + console.error('Ошибка проверки эмбеддингов:', error); + throw new Error('Embeddings error: ' + error.message); + } + + console.log('Документ для добавления:', { + pageContent: document.pageContent.substring(0, 100) + '...', + metadata: document.metadata, + vectorStore: { + tableName: vectorStore.tableName, + columns: vectorStore.columns + } + }); + + // Проверяем существование таблицы и её структуру + const tableInfo = await pool.query(` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'documents' + ); + `); + console.log('Таблица documents существует:', tableInfo.rows[0].exists); + + if (tableInfo.rows[0].exists) { + const columns = await pool.query(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'documents' + ORDER BY ordinal_position; + `); + console.log('Структура таблицы documents:', + columns.rows.map(row => `${row.column_name} (${row.data_type})`) + ); + } + + await vectorStore.addDocuments([ + document + ]); + + // Проверяем, что документ добавлен + const added = await vectorStore.similaritySearch( + document.pageContent, + 1, + { chatId: chatId } + ); + console.log('Проверка добавления документа:', { + found: added.length > 0, + document: added[0]?.pageContent.substring(0, 100) + '...' + }); + + console.log('Успешно добавлено в векторное хранилище'); + } + + res.json({ success: true }); + } catch (error) { + console.error('Ошибка одобрения:', error); + res.status(500).json({ + error: 'Server error', + details: error.message, + code: error.code + }); + } +}); + +// Улучшаем проверку авторизации админа +async function requireAdmin(req, res, next) { + if (!req.session?.siwe?.address) { + return res.status(401).json({ + error: 'Not authenticated', + details: 'Please sign in first' + }); + } + + try { + // Получаем адреса + const userAddress = req.session.siwe.address; + const contractOwner = await contract.owner(); + + console.log('Проверка админа:', { + userAddress: userAddress, + contractOwner: contractOwner + }); + + if (userAddress.toLowerCase() !== contractOwner.toLowerCase()) { + return res.status(403).json({ + error: 'Not authorized', + details: 'Only contract owner can access this endpoint' + }); + } + + next(); + } catch (error) { + console.error('Ошибка проверки админа:', error); + return res.status(500).json({ + error: 'Server error', + details: error.message + }); + } +} + +// Получение векторного хранилища для админа +router.get('/admin/vectors', requireAdmin, async (req, res) => { + try { + setCorsHeaders(res); + + // Добавляем колонку created_at если её нет + await pool.query(` + ALTER TABLE documents + ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + `); + console.log('Проверена/добавлена колонка created_at'); + + // Проверяем структуру таблицы + const tableInfo = await pool.query(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'documents' + `); + console.log('Структура таблицы documents:', tableInfo.rows); + + // Получаем все документы из векторного хранилища + const documents = await pool.query(` + SELECT + d.id, + d.content, + d.metadata, + length(d.embedding::text) as embedding_size, + COALESCE(d.created_at, CURRENT_TIMESTAMP) as created_at, + CASE + WHEN d.metadata->>'type' = 'approved_chat' THEN true + ELSE false + END as is_approved + FROM documents d + ORDER BY d.created_at DESC NULLS LAST + `); + + // Форматируем ответ + const vectors = documents.rows.map(doc => ({ + id: doc.id, + content: doc.content, + metadata: doc.metadata, + embedding_size: doc.embedding ? 4096 : 0, // Фиксированный размер для mistral + created: doc.created_at, + is_approved: doc.is_approved + })); + + console.log('Получено векторов:', vectors.length); + console.log('Пример вектора:', vectors[0]); + res.json({ vectors }); + } catch (error) { + console.error('Ошибка получения векторов:', error); + console.error('Детали ошибки:', { + code: error.code, + detail: error.detail, + hint: error.hint, + position: error.position + }); + res.status(500).json({ + error: 'Server error', + details: error.message, + code: error.code + }); + } +}); + +// Обработка CORS preflight запросов для админских роутов +router.options('/admin/*', (req, res) => { + setCorsHeaders(res); + res.sendStatus(200); +}); + +// Очистка кэша и данных +router.post('/admin/clear-cache', requireAdmin, async (req, res) => { + try { + setCorsHeaders(res); + + // Очищаем таблицы + await pool.query('TRUNCATE TABLE documents CASCADE'); + await pool.query('TRUNCATE TABLE chat_history CASCADE'); + await pool.query('TRUNCATE TABLE users CASCADE'); + + // Сбрасываем автоинкремент + await pool.query('ALTER SEQUENCE documents_id_seq RESTART WITH 1'); + await pool.query('ALTER SEQUENCE chat_history_id_seq RESTART WITH 1'); + await pool.query('ALTER SEQUENCE users_id_seq RESTART WITH 1'); + + // Реинициализируем векторное хранилище + await initVectorStore(); + + console.log('Кэш и данные очищены'); + res.json({ success: true }); + } catch (error) { + console.error('Ошибка очистки кэша:', error); + res.status(500).json({ + error: 'Server error', + details: error.message + }); + } +}); + +// Выход из системы +router.post('/signout', requireAuth, async (req, res) => { + try { + setCorsHeaders(res); + + // Уничтожаем сессию + req.session.destroy((err) => { + if (err) { + console.error('Ошибка при удалении сессии:', err); + return res.status(500).json({ error: 'Failed to destroy session' }); + } + + console.log('Сессия успешно завершена'); + res.json({ success: true }); + }); + } catch (error) { + console.error('Ошибка выхода:', error); + res.status(500).json({ error: 'Server error' }); + } +}); + +// Проверка сессии +router.get('/session', (req, res) => { + try { + setCorsHeaders(res); + + if (req.session?.authenticated && req.session?.siwe?.address) { + res.json({ + authenticated: true, + address: req.session.siwe.address + }); + } else { + res.json({ + authenticated: false + }); + } + } catch (error) { + console.error('Ошибка проверки сессии:', error); + res.status(500).json({ error: 'Server error' }); + } +}); + +// Создание нового пользователя +router.post('/users', async (req, res) => { + try { + setCorsHeaders(res); + + const { address } = req.body; + + // Проверяем существование пользователя + const existingUser = await pool.query( + 'SELECT * FROM users WHERE address = $1', + [address.toLowerCase()] + ); + + if (existingUser.rows.length > 0) { + return res.json({ user: existingUser.rows[0] }); + } + + // Создаем нового пользователя + const result = await pool.query( + 'INSERT INTO users (address) VALUES ($1) RETURNING *', + [address.toLowerCase()] + ); + + res.json({ user: result.rows[0] }); + } catch (error) { + console.error('Ошибка создания пользователя:', error); + res.status(500).json({ error: 'Server error' }); + } +}); + +// Создание необходимых таблиц при старте +async function initializeTables() { + try { + await pool.query(` + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + address VARCHAR(42) NOT NULL UNIQUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS chat_history ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + message TEXT, + response TEXT, + is_user BOOLEAN DEFAULT true, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); + `); + console.log('Таблицы успешно инициализированы'); + } catch (error) { + console.error('Ошибка инициализации таблиц:', error); + } +} + +// Вызываем инициализацию при старте +initializeTables(); + module.exports = router; \ No newline at end of file diff --git a/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json b/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json index 2777518..be1bed4 100644 --- a/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json +++ b/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json @@ -1 +1 @@ -{"cookie":{"originalMaxAge":2591999999,"expires":"2025-03-23T16:06:19.831Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"nonce":null,"__lastAccess":1740153979832,"siwe":{"domain":"127.0.0.1:5173","address":"0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B","statement":"Sign in with Ethereum to access DApp features and AI Assistant","uri":"http://127.0.0.1:5173","version":"1","nonce":"u7P3wT2kyPmGb4Z0I","issuedAt":"2025-02-21T16:05:49.561Z","chainId":11155111,"resources":["http://127.0.0.1:5173/api/chat","http://127.0.0.1:5173/api/contract"]},"authenticated":true} \ No newline at end of file +{"cookie":{"originalMaxAge":2591999999,"expires":"2025-03-24T09:38:02.826Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"nonce":null,"__lastAccess":1740217082827,"siwe":{"domain":"127.0.0.1:5173","address":"0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B","statement":"Sign in with Ethereum to access DApp features and AI Assistant","uri":"http://127.0.0.1:5173","version":"1","nonce":"3ZIxF8sn0dDIbwWZz","issuedAt":"2025-02-22T09:37:59.804Z","chainId":11155111,"resources":["http://127.0.0.1:5173/api/chat","http://127.0.0.1:5173/api/contract"]},"authenticated":true} \ No newline at end of file diff --git a/backend/yarn.lock b/backend/yarn.lock index cebe1a7..4c12f1d 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -195,9 +195,9 @@ integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== "@langchain/community@^0.3.31": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.3.31.tgz#6c7a52091a4f26fd044ce04b059dec083d1ab0d2" - integrity sha512-VsqZBQdJqJ8LeBjmVAAujpGQaSYjSXItb8uyWzoq/+fQkpq7UjWGnKWDkY65kBYPNDf6ShwjUdGiLm2yuyefzQ== + version "0.3.32" + resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.3.32.tgz#aa415ebfd51f10610f3abaa4127dae197f29cdc7" + integrity sha512-5AvGyjIFheXdBUSiIWNwc40rI8fXYiHV0UA3ncbBVu5fTwWur+mAQvl2ZsgyxBBKm4VuoCcuh6U6I7b1kiOYBQ== dependencies: "@langchain/openai" ">=0.2.0 <0.5.0" binary-extensions "^2.2.0" @@ -612,9 +612,9 @@ form-data "^4.0.0" "@types/node@*": - version "22.13.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.4.tgz#3fe454d77cd4a2d73c214008b3e331bfaaf5038a" - integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg== + version "22.13.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.5.tgz#23add1d71acddab2c6a4d31db89c0f98d330b511" + integrity sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg== dependencies: undici-types "~6.20.0" @@ -940,7 +940,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -call-bind-apply-helpers@^1.0.1: +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -1270,7 +1270,7 @@ es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-object-atoms@^1.0.0: +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== @@ -1584,22 +1584,22 @@ get-func-name@^2.0.0, get-func-name@^2.0.1, get-func-name@^2.0.2: integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" - integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: - call-bind-apply-helpers "^1.0.1" + call-bind-apply-helpers "^1.0.2" es-define-property "^1.0.1" es-errors "^1.3.0" - es-object-atoms "^1.0.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - get-proto "^1.0.0" + get-proto "^1.0.1" gopd "^1.2.0" has-symbols "^1.1.0" hasown "^2.0.2" math-intrinsics "^1.1.0" -get-proto@^1.0.0: +get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -2210,9 +2210,9 @@ once@^1.3.0: wrappy "1" openai@^4.77.0, openai@^4.85.2: - version "4.85.2" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.85.2.tgz#370702c56cd61c17c44c8c02c6a48229d5299903" - integrity sha512-ZQg3Q+K4A6M9dLFh5W36paZkZBQO+VbxMNJ1gUSyHsGiEWuXahdn02ermqNV68LhWQxdJQaWUFRAYpW/suTPWQ== + version "4.85.4" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.85.4.tgz#fdf9d3228967b87221a112993501fa33a98f5d18" + integrity sha512-Nki51PBSu+Aryo7WKbdXvfm0X/iKkQS2fq3O0Uqb/O3b4exOZFid2te1BZ52bbO5UwxQZ5eeHJDCTqtrJLPw0w== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" @@ -2800,9 +2800,9 @@ supports-color@^8.1.1: has-flag "^4.0.0" tinyglobby@^0.2.6: - version "0.2.11" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.11.tgz#9182cff655a0e272aad850d1a84c5e8e0f700426" - integrity sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g== + version "0.2.12" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5" + integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww== dependencies: fdir "^6.4.3" picomatch "^4.0.2" @@ -3083,9 +3083,9 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.5, zod-to-json-schema@^3.24.1: - version "3.24.2" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.2.tgz#0e24e4a963ab34cf4211ef5227e342c0c6eddb79" - integrity sha512-pNUqrcSxuuB3/+jBbU8qKUbTbDqYUaG1vf5cXFjbhGgoUuA1amO/y4Q8lzfOhHU8HNPK6VFJ18lBDKj3OHyDsg== + version "3.24.3" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz#5958ba111d681f8d01c5b6b647425c9b8a6059e7" + integrity sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A== zod@^3.22.3, zod@^3.22.4, zod@^3.24.1: version "3.24.2" diff --git a/frontend/index.html b/frontend/index.html index b6c12ba..c79cf37 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,8 +2,19 @@
+| Адрес | +Сообщение | +Ответ | +Дата | +Действия | +
|---|---|---|---|---|
| {{ shortenAddress(chat.address) }} | +{{ chat.message }} | +{{ JSON.parse(chat.response).content }} | +{{ formatDate(chat.created_at) }} | ++ + | +
| ID | +Адрес | +Дата регистрации | +
|---|---|---|
| {{ user.id }} | +{{ shortenAddress(user.address) }} | +{{ formatDate(user.created_at) }} | +
| Адрес | +Сообщение | +Ответ | +Дата | +
|---|---|---|---|
| {{ shortenAddress(chat.address) }} | +{{ chat.message }} | +{{ JSON.parse(chat.response).content }} | +{{ new Date(chat.created_at).toLocaleString() }} | +
Всего документов: {{ vectors.length }}
+Размерность эмбеддингов: {{ getEmbeddingStats }}
+| ID | +Содержание | +Метаданные | +Дата создания | +
|---|---|---|---|
| {{ vector.id }} | +
+
+
+ Q: {{ getQuestion(vector.content) }}
+ A: {{ getAnswer(vector.content) }}
+ |
+ + + | +{{ formatDate(vector.created) }} | +
Адрес кошелька: {{ address }}
+ ++ Текущая цена: {{ formatPrice(currentPrice) }} ETH +
+ + ++ Общая стоимость: {{ formatPrice(calculateTotalCost()) }} ETH +
+Адрес контракта: {{ contractAddress }}
+Начальная цена: {{ initialPrice }} ETH
+Владелец: {{ contractOwner }}
+Адрес кошелька: {{ address }}
- -- Текущая цена: {{ formatPrice(currentPrice) }} ETH -
- - -- Общая стоимость: {{ formatPrice(calculateTotalCost()) }} ETH -
-| Адрес | -Сообщение | -Ответ | -Дата | -
|---|---|---|---|
| {{ shortenAddress(chat.address) }} | -{{ chat.message }} | -{{ chat.response }} | -{{ formatDate(chat.created_at) }} | -
| ID | -Адрес | -Дата регистрации | -
|---|---|---|
| {{ user.id }} | -{{ shortenAddress(user.address) }} | -{{ formatDate(user.created_at) }} | -