Описание изменений
This commit is contained in:
5
backend/.gitignore
vendored
5
backend/.gitignore
vendored
@@ -26,4 +26,7 @@ yarn-error.log*
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
coverage.json
|
||||
coverage.json
|
||||
|
||||
# Sessions directory
|
||||
sessions/
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
AND LOWER(a.address) = LOWER(b.address);
|
||||
|
||||
ALTER TABLE chat_history
|
||||
ADD COLUMN IF NOT EXISTS is_approved BOOLEAN DEFAULT false;
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
{"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}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user