"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = require("express"); const client_1 = require("@prisma/client"); const multer_1 = __importDefault(require("multer")); const XLSX = __importStar(require("xlsx")); const ai_service_1 = require("../services/ai.service"); const parser_service_1 = require("../services/parser.service"); const router = (0, express_1.Router)(); const prisma = new client_1.PrismaClient(); function getUserId(req) { if (!req.user?.userId) throw new Error('Unauthorized'); return req.user.userId; } const aiService = new ai_service_1.AIService(); const parserService = new parser_service_1.ParserService(prisma, aiService); const EXCEL_MIMES = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 'application/vnd.ms-excel', // .xls ]; function isExcelFile(mimetype) { return EXCEL_MIMES.includes(mimetype); } function parseExcelToText(buffer) { try { const workbook = XLSX.read(buffer, { type: 'buffer' }); const lines = []; for (const sheetName of workbook.SheetNames) { const sheet = workbook.Sheets[sheetName]; const data = XLSX.utils.sheet_to_json(sheet, { header: 1 }); lines.push(`=== Лист: ${sheetName} ===`); for (const row of data) { if (row && row.length > 0) { const rowStr = row .map(cell => (cell != null ? String(cell).trim() : '')) .filter(Boolean) .join(' | '); if (rowStr) lines.push(rowStr); } } } return lines.join('\n'); } catch (err) { console.error('Excel parse error:', err); return '[Не удалось прочитать Excel файл]'; } } // Configure multer for file uploads const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit fileFilter: (req, file, cb) => { if (file.mimetype === 'application/pdf' || file.mimetype.startsWith('text/') || isExcelFile(file.mimetype)) { cb(null, true); } else { cb(new Error('Разрешены только PDF, TXT и Excel (.xlsx, .xls)')); } }, }); // Create new chat session (user's own context) router.post('/sessions', async (req, res) => { try { const userId = getUserId(req); const session = await prisma.chatSession.create({ data: { userId, estimateId: req.body.estimateId || null, }, }); res.status(201).json(session); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(500).json({ error: 'Failed to create chat session' }); } }); // Get chat session by estimate ID (current user's session for this estimate) router.get('/sessions/by-estimate/:estimateId', async (req, res) => { try { const userId = getUserId(req); const session = await prisma.chatSession.findFirst({ where: { estimateId: req.params.estimateId, userId }, include: { messages: { orderBy: { createdAt: 'asc' } }, }, }); if (!session) { return res.status(404).json({ error: 'Session not found for this estimate' }); } res.json(session); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(500).json({ error: 'Failed to fetch session' }); } }); // Update chat session (e.g. link to estimate after creation); only owner router.patch('/sessions/:id', async (req, res) => { try { const userId = getUserId(req); const existing = await prisma.chatSession.findUnique({ where: { id: req.params.id }, }); if (!existing) return res.status(404).json({ error: 'Session not found' }); if (existing.userId !== userId) return res.status(403).json({ error: 'Access denied' }); const data = 'estimateId' in req.body ? { estimateId: req.body.estimateId || null } : {}; const session = await prisma.chatSession.update({ where: { id: req.params.id }, data, }); res.json(session); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: 'Failed to update session' }); } }); // Get chat session with messages; only owner router.get('/sessions/:id', async (req, res) => { try { const userId = getUserId(req); const session = await prisma.chatSession.findUnique({ where: { id: req.params.id }, include: { messages: { orderBy: { createdAt: 'asc' } }, }, }); if (!session) return res.status(404).json({ error: 'Session not found' }); if (session.userId !== userId) return res.status(403).json({ error: 'Access denied' }); res.json(session); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(500).json({ error: 'Failed to fetch session' }); } }); // Send message to chat router.post('/sessions/:id/messages', async (req, res) => { try { const userId = getUserId(req); const { content } = req.body; const sessionId = req.params.id; const session = await prisma.chatSession.findUnique({ where: { id: sessionId }, }); if (!session) return res.status(404).json({ error: 'Session not found' }); if (session.userId !== userId) return res.status(403).json({ error: 'Access denied' }); // Save user message const userMessage = await prisma.chatMessage.create({ data: { sessionId, role: 'user', content, }, }); // Get conversation history const history = await prisma.chatMessage.findMany({ where: { sessionId }, orderBy: { createdAt: 'asc' }, take: 20, // Last 20 messages for context }); // Process with AI and parser (with estimate context) const response = await parserService.processMessage(content, history, session.estimateId); // Save assistant message const assistantMessage = await prisma.chatMessage.create({ data: { sessionId, role: 'assistant', content: response.message, metadata: response.extractedData ? JSON.parse(JSON.stringify(response.extractedData)) : null, }, }); res.json({ userMessage, assistantMessage, extractedData: response.extractedData, needsClarification: response.needsClarification, clarificationQuestions: response.clarificationQuestions, }); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); console.error('Chat error:', error); res.status(500).json({ error: error.message || 'Failed to process message' }); } }); // Upload PDF/TZ file router.post('/sessions/:id/upload', upload.single('file'), async (req, res) => { try { const userId = getUserId(req); const sessionId = req.params.id; const file = req.file; if (!file) return res.status(400).json({ error: 'No file uploaded' }); const session = await prisma.chatSession.findUnique({ where: { id: sessionId }, }); if (!session) return res.status(404).json({ error: 'Session not found' }); if (session.userId !== userId) return res.status(403).json({ error: 'Access denied' }); // Extract text from file let textContent = ''; if (file.mimetype === 'application/pdf') { textContent = `[Содержимое PDF файла: ${file.originalname}]`; } else if (isExcelFile(file.mimetype)) { textContent = parseExcelToText(file.buffer); if (!textContent.trim()) { textContent = `[Excel файл пуст или не удалось прочитать: ${file.originalname}]`; } else { textContent = `Загружена смета/данные из Excel (${file.originalname}):\n\n${textContent}`; } } else { textContent = file.buffer.toString('utf-8'); } // Save file upload as user message const userMessage = await prisma.chatMessage.create({ data: { sessionId, role: 'user', content: `Загружен файл: ${file.originalname}\n\n${textContent}`, metadata: { fileType: file.mimetype, fileName: file.originalname, fileSize: file.size, }, }, }); // Process with AI const history = await prisma.chatMessage.findMany({ where: { sessionId }, orderBy: { createdAt: 'asc' }, take: 10, }); const response = await parserService.processMessage(textContent, history, session.estimateId); // Save assistant response const assistantMessage = await prisma.chatMessage.create({ data: { sessionId, role: 'assistant', content: response.message, metadata: response.extractedData ? JSON.parse(JSON.stringify(response.extractedData)) : null, }, }); res.json({ userMessage, assistantMessage, extractedData: response.extractedData, needsClarification: response.needsClarification, clarificationQuestions: response.clarificationQuestions, }); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); console.error('Upload error:', error); res.status(500).json({ error: error.message || 'Failed to process file' }); } }); // Delete chat session (only owner) router.delete('/sessions/:id', async (req, res) => { try { const userId = getUserId(req); const session = await prisma.chatSession.findUnique({ where: { id: req.params.id }, }); if (!session) return res.status(404).json({ error: 'Session not found' }); if (session.userId !== userId) return res.status(403).json({ error: 'Access denied' }); await prisma.chatSession.delete({ where: { id: req.params.id } }); res.status(204).send(); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: 'Failed to delete session' }); } }); exports.default = router; //# sourceMappingURL=chat.js.map