Files
geo/backend/dist/routes/chat.js
2026-02-04 00:11:19 +05:00

337 lines
13 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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