ваше сообщение коммита

This commit is contained in:
2025-06-24 16:45:50 +03:00
parent 45cde76e85
commit 261a4ecb2a
17 changed files with 1209 additions and 155 deletions

View File

@@ -15,6 +15,7 @@ const messagesRoutes = require('./routes/messages');
const userTagsRoutes = require('./routes/userTags');
const tagsInitRoutes = require('./routes/tagsInit');
const tagsRoutes = require('./routes/tags');
const ragRoutes = require('./routes/rag'); // Новый роут для RAG-ассистента
// Проверка и создание директорий для хранения данных контрактов
const ensureDirectoriesExist = () => {
@@ -188,6 +189,7 @@ app.use('/api/messages', messagesRoutes);
app.use('/api/tags', tagsInitRoutes);
app.use('/api/tags', tagsRoutes);
app.use('/api/identities', identitiesRoutes);
app.use('/api/rag', ragRoutes); // Подключаем роут
const nonceStore = new Map(); // или любая другая реализация хранилища nonce

View File

@@ -351,9 +351,9 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
} else {
// Обычный пользователь — только в свой диалог
convResult = await db.getQuery()(
'SELECT * FROM conversations WHERE id = $1 AND user_id = $2',
[conversationId, userId]
);
'SELECT * FROM conversations WHERE id = $1 AND user_id = $2',
[conversationId, userId]
);
}
if (convResult.rows.length === 0) {
logger.warn('Conversation not found or access denied', { conversationId, userId });

View File

@@ -2,6 +2,8 @@ const express = require('express');
const router = express.Router();
const db = require('../db');
const { broadcastMessagesUpdate } = require('../wsHub');
const telegramBot = require('../services/telegramBot');
const emailBot = new (require('../services/emailBot'))();
// GET /api/messages?userId=123
router.get('/', async (req, res) => {
@@ -42,11 +44,100 @@ router.get('/', async (req, res) => {
router.post('/', async (req, res) => {
const { user_id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata } = req.body;
try {
const result = await db.getQuery()(
`INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata)
VALUES ($1,$2,$3,$4,$5,$6,NOW(),$7,$8,$9,$10,$11) RETURNING *`,
[user_id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata]
// Проверка наличия идентификатора для выбранного канала
if (channel === 'email') {
const emailIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'email']
);
if (emailIdentity.rows.length === 0) {
return res.status(400).json({ error: 'У пользователя не указан email. Сообщение не отправлено.' });
}
}
if (channel === 'telegram') {
const tgIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'telegram']
);
if (tgIdentity.rows.length === 0) {
return res.status(400).json({ error: 'У пользователя не привязан Telegram. Сообщение не отправлено.' });
}
}
if (channel === 'wallet' || channel === 'web3' || channel === 'web') {
const walletIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'wallet']
);
if (walletIdentity.rows.length === 0) {
return res.status(400).json({ error: 'У пользователя не привязан кошелёк. Сообщение не отправлено.' });
}
}
// 1. Проверяем, есть ли беседа для user_id
let conversationResult = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[user_id]
);
let conversation;
if (conversationResult.rows.length === 0) {
// 2. Если нет — создаём новую беседу
const title = `Чат с пользователем ${user_id}`;
const newConv = await db.getQuery()(
'INSERT INTO conversations (user_id, title, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[user_id, title]
);
conversation = newConv.rows[0];
} else {
conversation = conversationResult.rows[0];
}
// 3. Сохраняем сообщение с conversation_id
const result = await db.getQuery()(
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata)
VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),$8,$9,$10,$11,$12) RETURNING *`,
[user_id, conversation.id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata]
);
// 4. Если это исходящее сообщение для Telegram — отправляем через бота
if (channel === 'telegram' && direction === 'out') {
try {
console.log(`[messages.js] Попытка отправки сообщения в Telegram для user_id=${user_id}`);
// Получаем Telegram ID пользователя
const tgIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'telegram']
);
console.log(`[messages.js] Результат поиска Telegram ID:`, tgIdentity.rows);
if (tgIdentity.rows.length > 0) {
const telegramId = tgIdentity.rows[0].provider_id;
console.log(`[messages.js] Отправка сообщения в Telegram ID: ${telegramId}, текст: ${content}`);
const bot = await telegramBot.getBot();
try {
const sendResult = await bot.telegram.sendMessage(telegramId, content);
console.log(`[messages.js] Результат отправки в Telegram:`, sendResult);
} catch (sendErr) {
console.error(`[messages.js] Ошибка при отправке в Telegram:`, sendErr);
}
} else {
console.warn(`[messages.js] Не найден Telegram ID для user_id=${user_id}`);
}
} catch (err) {
console.error('[messages.js] Ошибка отправки сообщения в Telegram:', err);
}
}
// 5. Если это исходящее сообщение для Email — отправляем email
if (channel === 'email' && direction === 'out') {
try {
// Получаем email пользователя
const emailIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'email']
);
if (emailIdentity.rows.length > 0) {
const email = emailIdentity.rows[0].provider_id;
await emailBot.sendEmail(email, 'Новое сообщение', content);
}
} catch (err) {
console.error('[messages.js] Ошибка отправки email:', err);
}
}
broadcastMessagesUpdate();
res.json({ success: true, message: result.rows[0] });
} catch (e) {
@@ -123,4 +214,90 @@ router.get('/conversations', async (req, res) => {
}
});
// Массовая рассылка сообщения во все каналы пользователя
router.post('/broadcast', async (req, res) => {
const { user_id, content } = req.body;
if (!user_id || !content) {
return res.status(400).json({ error: 'user_id и content обязательны' });
}
try {
// Получаем все идентификаторы пользователя
const identitiesRes = await db.getQuery()(
'SELECT provider, provider_id FROM user_identities WHERE user_id = $1',
[user_id]
);
const identities = identitiesRes.rows;
// --- Найти или создать беседу (conversation) ---
let conversationResult = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[user_id]
);
let conversation;
if (conversationResult.rows.length === 0) {
const title = `Чат с пользователем ${user_id}`;
const newConv = await db.getQuery()(
'INSERT INTO conversations (user_id, title, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[user_id, title]
);
conversation = newConv.rows[0];
} else {
conversation = conversationResult.rows[0];
}
const results = [];
let sent = false;
// Email
const email = identities.find(i => i.provider === 'email')?.provider_id;
if (email) {
try {
await emailBot.sendEmail(email, 'Новое сообщение', content);
// Сохраняем в messages с conversation_id
await db.getQuery()(
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[user_id, conversation.id, 'admin', content, 'email', 'user', 'out', JSON.stringify({ broadcast: true })]
);
results.push({ channel: 'email', status: 'sent' });
sent = true;
} catch (err) {
results.push({ channel: 'email', status: 'error', error: err.message });
}
}
// Telegram
const telegram = identities.find(i => i.provider === 'telegram')?.provider_id;
if (telegram) {
try {
const bot = await telegramBot.getBot();
await bot.telegram.sendMessage(telegram, content);
await db.getQuery()(
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[user_id, conversation.id, 'admin', content, 'telegram', 'user', 'out', JSON.stringify({ broadcast: true })]
);
results.push({ channel: 'telegram', status: 'sent' });
sent = true;
} catch (err) {
results.push({ channel: 'telegram', status: 'error', error: err.message });
}
}
// Wallet/web3
const wallet = identities.find(i => i.provider === 'wallet')?.provider_id;
if (wallet) {
// Здесь можно реализовать отправку через web3, если нужно
await db.getQuery()(
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[user_id, conversation.id, 'admin', content, 'wallet', 'user', 'out', JSON.stringify({ broadcast: true })]
);
results.push({ channel: 'wallet', status: 'saved' });
sent = true;
}
if (!sent) {
return res.status(400).json({ error: 'У пользователя нет ни одного канала для рассылки.' });
}
res.json({ success: true, results });
} catch (e) {
res.status(500).json({ error: 'Broadcast error', details: e.message });
}
});
module.exports = router;

31
backend/routes/rag.js Normal file
View File

@@ -0,0 +1,31 @@
const express = require('express');
const router = express.Router();
const { ragAnswer, generateLLMResponse } = require('../services/ragService');
router.post('/answer', async (req, res) => {
const { tableId, question, userTags, product, systemPrompt, priority, date, rules, history, model, language } = req.body;
try {
const ragResult = await ragAnswer({ tableId, userQuestion: question, userTags, product });
const llmResponse = await generateLLMResponse({
userQuestion: question,
context: ragResult.context,
clarifyingAnswer: ragResult.clarifyingAnswer,
objectionAnswer: ragResult.objectionAnswer,
answer: ragResult.answer,
systemPrompt,
userTags: userTags?.join ? userTags.join(', ') : userTags,
product,
priority: priority || ragResult.priority,
date: date || ragResult.date,
rules,
history,
model,
language
});
res.json({ ...ragResult, llmResponse });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
module.exports = router;

View File

@@ -53,10 +53,19 @@ router.get('/:id', async (req, res, next) => {
router.post('/:id/columns', async (req, res, next) => {
try {
const tableId = req.params.id;
const { name, type, options, order } = req.body;
const { name, type, options, order, tagIds, purpose } = req.body;
let finalOptions = options;
// Собираем options
finalOptions = finalOptions || {};
if (type === 'tags' && Array.isArray(tagIds)) {
finalOptions.tagIds = tagIds;
}
if (purpose) {
finalOptions.purpose = purpose;
}
const result = await db.getQuery()(
'INSERT INTO user_columns (table_id, name, type, options, "order") VALUES ($1, $2, $3, $4, $5) RETURNING *',
[tableId, name, type, options ? JSON.stringify(options) : null, order || 0]
[tableId, name, type, finalOptions ? JSON.stringify(finalOptions) : null, order || 0]
);
res.json(result.rows[0]);
} catch (err) {

View File

@@ -52,30 +52,126 @@ router.put('/profile', requireAuth, async (req, res) => {
});
*/
// Получение списка пользователей с контактами
router.get('/', async (req, res, next) => {
// Получение списка пользователей с фильтрацией
router.get('/', requireAuth, async (req, res, next) => {
try {
const usersResult = await db.getQuery()('SELECT id, first_name, last_name, created_at, preferred_language FROM users ORDER BY id');
const users = usersResult.rows;
// Получаем все user_identities разом
const identitiesResult = await db.getQuery()('SELECT user_id, provider, provider_id FROM user_identities');
const identities = identitiesResult.rows;
// Группируем идентификаторы по user_id
const identityMap = {};
for (const id of identities) {
if (!identityMap[id.user_id]) identityMap[id.user_id] = {};
if (!identityMap[id.user_id][id.provider]) identityMap[id.user_id][id.provider] = id.provider_id;
const {
tagIds = '',
dateFrom = '',
dateTo = '',
contactType = 'all',
search = '',
newMessages = ''
} = req.query;
const adminId = req.user && req.user.id;
// --- Формируем условия ---
const where = [];
const params = [];
let idx = 1;
// Фильтр по дате
if (dateFrom) {
where.push(`DATE(u.created_at) >= $${idx++}`);
params.push(dateFrom);
}
// Собираем контакты
if (dateTo) {
where.push(`DATE(u.created_at) <= $${idx++}`);
params.push(dateTo);
}
// Фильтр по типу контакта
if (contactType !== 'all') {
where.push(`EXISTS (
SELECT 1 FROM user_identities ui
WHERE ui.user_id = u.id AND ui.provider = $${idx++}
)`);
params.push(contactType);
}
// Фильтр по поиску
if (search) {
where.push(`(
LOWER(u.first_name) LIKE $${idx} OR
LOWER(u.last_name) LIKE $${idx} OR
EXISTS (SELECT 1 FROM user_identities ui WHERE ui.user_id = u.id AND LOWER(ui.provider_id) LIKE $${idx})
)`);
params.push(`%${search.toLowerCase()}%`);
idx++;
}
// --- Основной SQL ---
let sql = `
SELECT u.id, u.first_name, u.last_name, u.created_at, u.preferred_language,
(SELECT provider_id FROM user_identities WHERE user_id = u.id AND provider = 'email' LIMIT 1) AS email,
(SELECT provider_id FROM user_identities WHERE user_id = u.id AND provider = 'telegram' LIMIT 1) AS telegram,
(SELECT provider_id FROM user_identities WHERE user_id = u.id AND provider = 'wallet' LIMIT 1) AS wallet
FROM users u
`;
// Фильтрация по тегам
if (tagIds) {
const tagIdArr = tagIds.split(',').map(Number).filter(Boolean);
if (tagIdArr.length > 0) {
sql += `
JOIN user_tags ut ON ut.user_id = u.id
WHERE ut.tag_id = ANY($${idx++})
GROUP BY u.id
HAVING COUNT(DISTINCT ut.tag_id) = $${idx++}
`;
params.push(tagIdArr);
params.push(tagIdArr.length);
}
} else if (where.length > 0) {
sql += ` WHERE ${where.join(' AND ')} `;
}
if (!tagIds) {
sql += ' ORDER BY u.id ';
}
// --- Выполняем запрос ---
const usersResult = await db.getQuery()(sql, params);
let users = usersResult.rows;
// --- Фильтрация по новым сообщениям ---
if (newMessages === 'yes' && adminId) {
// Получаем время последнего прочтения для каждого пользователя
const readRes = await db.getQuery()(
'SELECT user_id, last_read_at FROM admin_read_messages WHERE admin_id = $1',
[adminId]
);
const readMap = {};
for (const row of readRes.rows) {
readMap[row.user_id] = row.last_read_at;
}
// Получаем последнее сообщение для каждого пользователя
const msgRes = await db.getQuery()(
`SELECT user_id, MAX(created_at) as last_msg_at FROM messages GROUP BY user_id`
);
const msgMap = {};
for (const row of msgRes.rows) {
msgMap[row.user_id] = row.last_msg_at;
}
// Оставляем только тех, у кого есть новые сообщения
users = users.filter(u => {
const lastRead = readMap[u.id];
const lastMsg = msgMap[u.id];
return lastMsg && (!lastRead || new Date(lastMsg) > new Date(lastRead));
});
}
// --- Формируем ответ ---
const contacts = users.map(u => ({
id: u.id,
name: [u.first_name, u.last_name].filter(Boolean).join(' ') || null,
email: identityMap[u.id]?.email || null,
telegram: identityMap[u.id]?.telegram || null,
wallet: identityMap[u.id]?.wallet || null,
email: u.email || null,
telegram: u.telegram || null,
wallet: u.wallet || null,
created_at: u.created_at,
preferred_language: u.preferred_language || []
}));
res.json({ success: true, contacts });
} catch (error) {
logger.error('Error fetching contacts:', error);

View File

@@ -140,14 +140,30 @@ class EmailBotService {
const html = parsed.html || '';
// 1. Найти или создать пользователя
const { userId, role } = await identityService.findOrCreateUserWithRole('email', fromEmail);
// 2. Сохранить письмо и вложения в messages
// 1.1 Найти или создать беседу
let conversationResult = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[userId]
);
let conversation;
if (conversationResult.rows.length === 0) {
const title = `Чат с пользователем ${userId}`;
const newConv = await db.getQuery()(
'INSERT INTO conversations (user_id, title, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[userId, title]
);
conversation = newConv.rows[0];
} else {
conversation = conversationResult.rows[0];
}
// 2. Сохранять все сообщения с conversation_id
let hasAttachments = parsed.attachments && parsed.attachments.length > 0;
if (hasAttachments) {
for (const att of parsed.attachments) {
await db.getQuery()(
`INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9, $10, $11)`,
[userId, 'user', text, 'email', role, 'in',
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10, $11, $12)`,
[userId, conversation.id, 'user', text, 'email', role, 'in',
att.filename,
att.contentType,
att.size,
@@ -158,18 +174,18 @@ class EmailBotService {
}
} else {
await db.getQuery()(
`INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7)`,
[userId, 'user', text, 'email', role, 'in', JSON.stringify({ subject, html })]
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[userId, conversation.id, 'user', text, 'email', role, 'in', JSON.stringify({ subject, html })]
);
}
// 3. Получить ответ от ИИ
const aiResponse = await aiAssistant.getResponse(text, 'auto');
// 4. Сохранить ответ в БД
// 4. Сохранить ответ в БД с conversation_id
await db.getQuery()(
`INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7)`,
[userId, 'assistant', aiResponse, 'email', role, 'out', JSON.stringify({ subject, html })]
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[userId, conversation.id, 'assistant', aiResponse, 'email', role, 'out', JSON.stringify({ subject, html })]
);
// 5. Отправить ответ на email
await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);

View File

@@ -0,0 +1,104 @@
const { OpenAIEmbeddings } = require('@langchain/openai');
const { HNSWLib } = require('@langchain/community/vectorstores/hnswlib');
const db = require('../db');
const { ChatOllama } = require('@langchain/ollama');
async function getTableData(tableId) {
const columns = (await db.getQuery()('SELECT * FROM user_columns WHERE table_id = $1', [tableId])).rows;
const rows = (await db.getQuery()('SELECT * FROM user_rows WHERE table_id = $1', [tableId])).rows;
const cellValues = (await db.getQuery()('SELECT * FROM user_cell_values WHERE row_id IN (SELECT id FROM user_rows WHERE table_id = $1)', [tableId])).rows;
const getColId = purpose => columns.find(col => col.options?.purpose === purpose)?.id;
const questionColId = getColId('question');
const answerColId = getColId('answer');
const userTagsColId = getColId('userTags');
const contextColId = getColId('context');
const productColId = getColId('product');
const priorityColId = getColId('priority');
const dateColId = getColId('date');
return rows.map(row => {
const cells = cellValues.filter(cell => cell.row_id === row.id);
return {
id: row.id,
question: cells.find(c => c.column_id === questionColId)?.value,
answer: cells.find(c => c.column_id === answerColId)?.value,
userTags: cells.find(c => c.column_id === userTagsColId)?.value,
context: cells.find(c => c.column_id === contextColId)?.value,
product: cells.find(c => c.column_id === productColId)?.value,
priority: cells.find(c => c.column_id === priorityColId)?.value,
date: cells.find(c => c.column_id === dateColId)?.value,
};
});
}
async function ragAnswer({ tableId, userQuestion, userTags = [], product = null }) {
const data = await getTableData(tableId);
const questions = data.map(row => row.question);
// Получаем embedding для всех вопросов
const embeddings = await new OpenAIEmbeddings().embedDocuments(questions);
// Создаём векторное хранилище
const vectorStore = await HNSWLib.fromTexts(questions, data, new OpenAIEmbeddings());
// Получаем embedding для вопроса пользователя
const [userEmbedding] = await new OpenAIEmbeddings().embedDocuments([userQuestion]);
// Ищем наиболее похожие вопросы (top-3)
const results = await vectorStore.similaritySearchVectorWithScore(userEmbedding, 3);
// Фильтруем по тегам/продукту, если нужно
let filtered = results.map(([row, score]) => ({ ...row, score }));
if (userTags.length) {
filtered = filtered.filter(row => row.userTags && userTags.some(tag => row.userTags.includes(tag)));
}
if (product) {
filtered = filtered.filter(row => row.product === product);
}
// Берём лучший результат
const best = filtered[0];
// Формируем ответ
return {
answer: best?.answer,
context: best?.context,
product: best?.product,
priority: best?.priority,
date: best?.date,
score: best?.score,
};
}
async function generateLLMResponse({ userQuestion, context, clarifyingAnswer, objectionAnswer, answer, systemPrompt, userTags, product, priority, date, rules, history, model, language }) {
// Подставляем значения в шаблон промта
let prompt = (systemPrompt || '')
.replace('{context}', context || '')
.replace('{clarifyingAnswer}', clarifyingAnswer || '')
.replace('{objectionAnswer}', objectionAnswer || '')
.replace('{answer}', answer || '')
.replace('{question}', userQuestion || '')
.replace('{userTags}', userTags || '')
.replace('{product}', product || '')
.replace('{priority}', priority || '')
.replace('{date}', date || '')
.replace('{rules}', rules || '')
.replace('{history}', history || '')
.replace('{model}', model || '')
.replace('{language}', language || '');
const chat = new ChatOllama({
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
model: process.env.OLLAMA_MODEL || 'qwen2.5',
system: prompt,
temperature: 0.7,
maxTokens: 1000,
timeout: 30000,
});
const response = await chat.invoke(`Вопрос пользователя: ${userQuestion}`);
return response.content;
}
module.exports = { ragAnswer, generateLLMResponse };

View File

@@ -267,8 +267,23 @@ async function getBot() {
const telegramId = ctx.from.id.toString();
// 1. Найти или создать пользователя
const { userId, role } = await identityService.findOrCreateUserWithRole('telegram', telegramId);
// 2. Сохранить входящее сообщение в messages
// 1.1 Найти или создать беседу
let conversationResult = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[userId]
);
let conversation;
if (conversationResult.rows.length === 0) {
const title = `Чат с пользователем ${userId}`;
const newConv = await db.getQuery()(
'INSERT INTO conversations (user_id, title, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[userId, title]
);
conversation = newConv.rows[0];
} else {
conversation = conversationResult.rows[0];
}
// 2. Сохранять все сообщения с conversation_id
let content = text;
let attachmentMeta = {};
// Проверяем вложения (фото, документ, аудио, видео)
@@ -310,9 +325,9 @@ async function getBot() {
}
// Сохраняем сообщение в БД
await db.getQuery()(
`INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9, $10)`,
[userId, 'user', content, 'telegram', role, 'in',
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10, $11)`,
[userId, conversation.id, 'user', content, 'telegram', role, 'in',
attachmentMeta.attachment_filename || null,
attachmentMeta.attachment_mimetype || null,
attachmentMeta.attachment_size || null,
@@ -322,11 +337,11 @@ async function getBot() {
// 3. Получить ответ от ИИ
const aiResponse = await aiAssistant.getResponse(content, 'auto');
// 4. Сохранить ответ в БД
// 4. Сохранить ответ в БД с conversation_id
await db.getQuery()(
`INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
[userId, 'assistant', aiResponse, 'telegram', role, 'out']
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
[userId, conversation.id, 'assistant', aiResponse, 'telegram', role, 'out']
);
// 5. Отправить ответ пользователю
await ctx.reply(aiResponse);