first commit
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const generateToken = (userId) => {
|
||||
return jwt.sign({ userId }, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
|
||||
});
|
||||
};
|
||||
|
||||
const register = async (req, res) => {
|
||||
try {
|
||||
const { email, password, firstName, lastName, role = 'user' } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!email || !password || !firstName || !lastName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'All fields are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await User.findOne({ where: { email } });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'User already exists with this email'
|
||||
});
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const saltRounds = 12;
|
||||
const passwordHash = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// Create user
|
||||
const user = await User.create({
|
||||
email,
|
||||
password_hash: passwordHash,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
role
|
||||
});
|
||||
|
||||
// Generate token
|
||||
const token = generateToken(user.id);
|
||||
|
||||
logger.info(`New user registered: ${email}`);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
role: user.role
|
||||
},
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Registration error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const login = async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Email and password are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({ where: { email } });
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if (!user.is_active) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Account is deactivated'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login
|
||||
await user.update({ last_login: new Date() });
|
||||
|
||||
// Generate token
|
||||
const token = generateToken(user.id);
|
||||
|
||||
logger.info(`User logged in: ${email}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
role: user.role
|
||||
},
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getProfile = async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.userId, {
|
||||
attributes: { exclude: ['password_hash'] }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { user }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get profile error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateProfile = async (req, res) => {
|
||||
try {
|
||||
const { firstName, lastName, preferences } = req.body;
|
||||
const user = await User.findByPk(req.user.userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Update user
|
||||
const updateData = {};
|
||||
if (firstName) updateData.first_name = firstName;
|
||||
if (lastName) updateData.last_name = lastName;
|
||||
if (preferences) updateData.preferences = { ...user.preferences, ...preferences };
|
||||
|
||||
await user.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
role: user.role,
|
||||
preferences: user.preferences
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Update profile error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
login,
|
||||
getProfile,
|
||||
updateProfile
|
||||
};
|
||||
@@ -0,0 +1,356 @@
|
||||
const { Conversation, Message, Plan, User } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
const model1Service = require('../services/model1Service');
|
||||
const chatRouter = require('../services/chatRouter');
|
||||
|
||||
const createConversation = async (req, res) => {
|
||||
try {
|
||||
const { title } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const conversation = await Conversation.create({
|
||||
user_id: userId,
|
||||
title: title || 'New Conversation',
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
logger.info(`New conversation created: ${conversation.id}`);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: { conversation }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Create conversation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getConversations = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { page = 1, limit = 10, status } = req.query;
|
||||
|
||||
const whereClause = { user_id: userId };
|
||||
if (status) whereClause.status = status;
|
||||
|
||||
const conversations = await Conversation.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Message,
|
||||
limit: 1,
|
||||
order: [['created_at', 'DESC']]
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
conversations: conversations.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: conversations.count,
|
||||
pages: Math.ceil(conversations.count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get conversations error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getConversation = async (req, res) => {
|
||||
try {
|
||||
const { conversationId } = req.params;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const conversation = await Conversation.findOne({
|
||||
where: {
|
||||
id: conversationId,
|
||||
user_id: userId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Message,
|
||||
order: [['created_at', 'ASC']]
|
||||
},
|
||||
{
|
||||
model: Plan,
|
||||
order: [['created_at', 'DESC']]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Conversation not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { conversation }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get conversation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = async (req, res) => {
|
||||
try {
|
||||
const { conversationId, content, messageType = 'text' } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
// Verify conversation belongs to user
|
||||
const conversation = await Conversation.findOne({
|
||||
where: {
|
||||
id: conversationId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Conversation not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Create user message
|
||||
const userMessage = await Message.create({
|
||||
conversation_id: conversationId,
|
||||
role: 'user',
|
||||
content,
|
||||
message_type: messageType
|
||||
});
|
||||
|
||||
// Get conversation history for context
|
||||
const conversationHistory = await Message.findAll({
|
||||
where: { conversation_id: conversationId },
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: 20 // Last 20 messages for context
|
||||
});
|
||||
|
||||
// Create properly ordered conversation history for context
|
||||
const orderedHistory = conversationHistory.reverse().map(msg => ({
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
message_type: msg.message_type,
|
||||
timestamp: msg.created_at
|
||||
}));
|
||||
|
||||
// Route the message to determine response type
|
||||
const routingDecision = await chatRouter.routeMessage(content, {
|
||||
conversationId,
|
||||
userId,
|
||||
timestamp: new Date().toISOString(),
|
||||
conversationHistory: orderedHistory
|
||||
});
|
||||
|
||||
let assistantMessage;
|
||||
|
||||
if (routingDecision.responseType === 'plan_needed' && routingDecision.isEngineeringQuestion) {
|
||||
// Generate engineering plan using MODEL1
|
||||
try {
|
||||
const planData = await model1Service.generatePlan(content, {
|
||||
conversationId,
|
||||
userId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Save the plan to database
|
||||
const plan = await Plan.create({
|
||||
conversation_id: conversationId,
|
||||
title: planData.title,
|
||||
description: planData.description,
|
||||
steps: planData.steps,
|
||||
status: 'draft',
|
||||
tools_required: planData.toolsRequired || [],
|
||||
estimated_duration: planData.estimatedDuration,
|
||||
complexity_score: planData.complexityScore,
|
||||
metadata: {
|
||||
safetyConsiderations: planData.safetyConsiderations,
|
||||
qualityChecks: planData.qualityChecks,
|
||||
processingTime: planData.processingTime,
|
||||
tokensUsed: planData.tokensUsed,
|
||||
model: planData.model
|
||||
}
|
||||
});
|
||||
|
||||
// Create assistant message with plan
|
||||
assistantMessage = await Message.create({
|
||||
conversation_id: conversationId,
|
||||
plan_id: plan.id,
|
||||
role: 'assistant',
|
||||
content: planData.rawContent || `# ${planData.title}\n\n${planData.description}\n\n## Steps:\n${planData.steps.map((step, index) => `${index + 1}. ${step}`).join('\n')}`,
|
||||
message_type: 'plan',
|
||||
metadata: {
|
||||
modelType: 'MODEL1',
|
||||
processingTime: planData.processingTime,
|
||||
tokensUsed: planData.tokensUsed,
|
||||
routingDecision: routingDecision.reasoning
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`MODEL1 plan generated: ${plan.id} for conversation: ${conversationId}`);
|
||||
} catch (error) {
|
||||
logger.error('MODEL1 plan generation failed:', error);
|
||||
|
||||
// Fallback to simple response if plan generation fails
|
||||
const fallbackResponse = await chatRouter.generateSimpleResponse(content, {
|
||||
conversationHistory: orderedHistory
|
||||
});
|
||||
assistantMessage = await Message.create({
|
||||
conversation_id: conversationId,
|
||||
role: 'assistant',
|
||||
content: fallbackResponse,
|
||||
message_type: 'text',
|
||||
metadata: {
|
||||
error: error.message,
|
||||
modelType: 'FALLBACK',
|
||||
routingDecision: routingDecision.reasoning
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Generate simple conversational response
|
||||
const responseContent = routingDecision.response || await chatRouter.generateSimpleResponse(content, {
|
||||
conversationHistory: orderedHistory
|
||||
});
|
||||
|
||||
assistantMessage = await Message.create({
|
||||
conversation_id: conversationId,
|
||||
role: 'assistant',
|
||||
content: responseContent,
|
||||
message_type: 'text',
|
||||
metadata: {
|
||||
modelType: 'REASONAI',
|
||||
routingDecision: routingDecision.reasoning,
|
||||
responseType: routingDecision.responseType
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Simple response generated for conversation: ${conversationId}`, {
|
||||
responseType: routingDecision.responseType,
|
||||
reasoning: routingDecision.reasoning
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Message sent in conversation: ${conversationId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { userMessage, assistantMessage }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Send message error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateConversation = async (req, res) => {
|
||||
try {
|
||||
const { conversationId } = req.params;
|
||||
const { title, status } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const conversation = await Conversation.findOne({
|
||||
where: {
|
||||
id: conversationId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Conversation not found'
|
||||
});
|
||||
}
|
||||
|
||||
const updateData = {};
|
||||
if (title) updateData.title = title;
|
||||
if (status) updateData.status = status;
|
||||
|
||||
await conversation.update(updateData);
|
||||
|
||||
logger.info(`Conversation updated: ${conversationId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { conversation }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Update conversation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteConversation = async (req, res) => {
|
||||
try {
|
||||
const { conversationId } = req.params;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const conversation = await Conversation.findOne({
|
||||
where: {
|
||||
id: conversationId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Conversation not found'
|
||||
});
|
||||
}
|
||||
|
||||
await conversation.destroy();
|
||||
|
||||
logger.info(`Conversation deleted: ${conversationId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Conversation deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Delete conversation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createConversation,
|
||||
getConversations,
|
||||
getConversation,
|
||||
sendMessage,
|
||||
updateConversation,
|
||||
deleteConversation
|
||||
};
|
||||
@@ -0,0 +1,299 @@
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const pdf = require('pdf-parse');
|
||||
const { Document, sequelize } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const logger = require('../utils/logger');
|
||||
const embeddingService = require('../services/embeddingService');
|
||||
const graphRagService = require('../services/graphRagService');
|
||||
|
||||
// Configure multer for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const uploadPath = path.join(__dirname, '../../uploads');
|
||||
if (!fs.existsSync(uploadPath)) {
|
||||
fs.mkdirSync(uploadPath, { recursive: true });
|
||||
}
|
||||
cb(null, uploadPath);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024, // 10MB for testing
|
||||
fieldSize: 10 * 1024 * 1024, // 10MB for field values
|
||||
fieldNameSize: 100, // 100 bytes for field names
|
||||
files: 1 // Only 1 file at a time
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
const allowedTypes = ['.pdf', '.txt', '.doc', '.docx'];
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
|
||||
if (allowedTypes.includes(ext)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Invalid file type. Only PDF, TXT, DOC, DOCX files are allowed.'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const uploadDocument = async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'No file uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const { category, tags } = req.body;
|
||||
let extractedText = '';
|
||||
|
||||
// Extract text from PDF
|
||||
if (req.file.mimetype === 'application/pdf') {
|
||||
try {
|
||||
const dataBuffer = fs.readFileSync(req.file.path);
|
||||
const pdfData = await pdf(dataBuffer);
|
||||
extractedText = pdfData.text;
|
||||
} catch (error) {
|
||||
logger.error('PDF extraction error:', error);
|
||||
extractedText = 'Error extracting text from PDF';
|
||||
}
|
||||
} else if (req.file.mimetype === 'text/plain') {
|
||||
extractedText = fs.readFileSync(req.file.path, 'utf8');
|
||||
}
|
||||
|
||||
// Create document record
|
||||
const document = await Document.create({
|
||||
filename: req.file.filename,
|
||||
original_filename: req.file.originalname,
|
||||
file_path: req.file.path,
|
||||
file_type: req.file.mimetype,
|
||||
file_size: req.file.size,
|
||||
content: extractedText,
|
||||
extracted_text: extractedText,
|
||||
category: category || 'general',
|
||||
tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
|
||||
indexing_status: 'processing'
|
||||
});
|
||||
|
||||
// Generate and store embeddings (if text available)
|
||||
if (extractedText && extractedText.trim().length > 0) {
|
||||
try {
|
||||
const embedding = await embeddingService.embedText(extractedText.slice(0, 15000));
|
||||
await document.update({ embeddings: embedding, is_indexed: true, indexing_status: 'completed' });
|
||||
} catch (e) {
|
||||
logger.error('Embedding generation failed:', e);
|
||||
await document.update({ is_indexed: false, indexing_status: 'failed' });
|
||||
}
|
||||
} else {
|
||||
await document.update({ is_indexed: false, indexing_status: 'failed' });
|
||||
}
|
||||
|
||||
logger.info(`Document uploaded: ${document.id}`);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: { document }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Upload document error:', error);
|
||||
|
||||
// Clean up uploaded file if document creation failed
|
||||
if (req.file && fs.existsSync(req.file.path)) {
|
||||
fs.unlinkSync(req.file.path);
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDocuments = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, category, search, isIndexed } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
if (category) whereClause.category = category;
|
||||
if (isIndexed !== undefined) whereClause.is_indexed = isIndexed === 'true';
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ original_filename: { [Op.iLike]: `%${search}%` } },
|
||||
{ extracted_text: { [Op.iLike]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
const documents = await Document.findAndCountAll({
|
||||
where: whereClause,
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
documents: documents.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: documents.count,
|
||||
pages: Math.ceil(documents.count / parseInt(limit))
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get documents error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDocument = async (req, res) => {
|
||||
try {
|
||||
const { documentId } = req.params;
|
||||
|
||||
const document = await Document.findByPk(documentId);
|
||||
|
||||
if (!document) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Document not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { document }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get document error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const searchDocuments = async (req, res) => {
|
||||
try {
|
||||
const { query, category, limit = 10 } = req.query;
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Search query is required'
|
||||
});
|
||||
}
|
||||
|
||||
const whereClause = {
|
||||
is_indexed: true,
|
||||
...(category ? { category } : {})
|
||||
};
|
||||
|
||||
// Embed query and compute cosine similarity in JS for now
|
||||
const queryEmbedding = await embeddingService.embedText(query);
|
||||
|
||||
const candidates = await Document.findAll({
|
||||
where: whereClause,
|
||||
attributes: ['id', 'original_filename', 'extracted_text', 'embeddings', 'category', 'created_at']
|
||||
});
|
||||
|
||||
const scored = [];
|
||||
for (const doc of candidates) {
|
||||
const emb = doc.embeddings || [];
|
||||
const score = embeddingService.cosineSimilarity(queryEmbedding, emb);
|
||||
scored.push({
|
||||
id: doc.id,
|
||||
original_filename: doc.original_filename,
|
||||
snippet: (doc.extracted_text || '').slice(0, 300),
|
||||
category: doc.category,
|
||||
created_at: doc.created_at,
|
||||
score
|
||||
});
|
||||
}
|
||||
|
||||
scored.sort((a, b) => b.score - a.score);
|
||||
const top = scored.slice(0, parseInt(limit));
|
||||
|
||||
res.json({ success: true, data: { results: top } });
|
||||
} catch (error) {
|
||||
logger.error('Search documents error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const graphSearchDocuments = async (req, res) => {
|
||||
try {
|
||||
const { query, category } = req.query;
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({ success: false, error: 'Search query is required' });
|
||||
}
|
||||
|
||||
const result = await graphRagService.graphSearch({ query, category });
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
logger.error('Graph search error:', error);
|
||||
res.status(500).json({ success: false, error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDocument = async (req, res) => {
|
||||
try {
|
||||
const { documentId } = req.params;
|
||||
|
||||
const document = await Document.findByPk(documentId);
|
||||
|
||||
if (!document) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Document not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Delete physical file
|
||||
if (fs.existsSync(document.file_path)) {
|
||||
fs.unlinkSync(document.file_path);
|
||||
}
|
||||
|
||||
// Delete database record
|
||||
await document.destroy();
|
||||
|
||||
logger.info(`Document deleted: ${documentId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Document deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Delete document error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
uploadDocument,
|
||||
getDocuments,
|
||||
getDocument,
|
||||
searchDocuments,
|
||||
graphSearchDocuments,
|
||||
deleteDocument,
|
||||
upload // Export multer middleware
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
const { Feedback, Message, User, sequelize } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const submitFeedback = async (req, res) => {
|
||||
try {
|
||||
const { messageId, feedbackType, rating, comment, correctedContent } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
// Validate feedback type
|
||||
const validTypes = ['positive', 'negative', 'correction', 'suggestion'];
|
||||
if (!validTypes.includes(feedbackType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid feedback type'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate rating if provided
|
||||
if (rating && (rating < 1 || rating > 5)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Rating must be between 1 and 5'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify message exists if messageId provided
|
||||
if (messageId) {
|
||||
const message = await Message.findByPk(messageId);
|
||||
if (!message) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Message not found'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const feedback = await Feedback.create({
|
||||
user_id: userId,
|
||||
message_id: messageId,
|
||||
feedback_type: feedbackType,
|
||||
rating,
|
||||
comment,
|
||||
corrected_content: correctedContent,
|
||||
is_processed: false
|
||||
});
|
||||
|
||||
logger.info(`Feedback submitted: ${feedback.id} by user ${userId}`);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: { feedback }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Submit feedback error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getFeedback = async (req, res) => {
|
||||
try {
|
||||
const { feedbackId } = req.params;
|
||||
|
||||
const feedback = await Feedback.findByPk(feedbackId, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
attributes: ['id', 'email', 'first_name', 'last_name']
|
||||
},
|
||||
{
|
||||
model: Message,
|
||||
attributes: ['id', 'content', 'role']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!feedback) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Feedback not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { feedback }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get feedback error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getFeedbackList = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, feedbackType, isProcessed, userId } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
if (feedbackType) whereClause.feedback_type = feedbackType;
|
||||
if (isProcessed !== undefined) whereClause.is_processed = isProcessed === 'true';
|
||||
if (userId) whereClause.user_id = userId;
|
||||
|
||||
const feedback = await Feedback.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
attributes: ['id', 'email', 'first_name', 'last_name']
|
||||
},
|
||||
{
|
||||
model: Message,
|
||||
attributes: ['id', 'content', 'role']
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
feedback: feedback.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: feedback.count,
|
||||
pages: Math.ceil(feedback.count / parseInt(limit))
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get feedback list error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const processFeedback = async (req, res) => {
|
||||
try {
|
||||
const { feedbackId } = req.params;
|
||||
const { processingNotes } = req.body;
|
||||
|
||||
const feedback = await Feedback.findByPk(feedbackId);
|
||||
|
||||
if (!feedback) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Feedback not found'
|
||||
});
|
||||
}
|
||||
|
||||
await feedback.update({
|
||||
is_processed: true,
|
||||
processing_notes: processingNotes
|
||||
});
|
||||
|
||||
logger.info(`Feedback processed: ${feedbackId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { feedback }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Process feedback error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getFeedbackStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await Feedback.findAll({
|
||||
attributes: [
|
||||
'feedback_type',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
|
||||
[sequelize.fn('AVG', sequelize.col('rating')), 'avg_rating']
|
||||
],
|
||||
group: ['feedback_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const totalFeedback = await Feedback.count();
|
||||
const processedFeedback = await Feedback.count({ where: { is_processed: true } });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
stats,
|
||||
total: totalFeedback,
|
||||
processed: processedFeedback,
|
||||
unprocessed: totalFeedback - processedFeedback
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get feedback stats error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
submitFeedback,
|
||||
getFeedback,
|
||||
getFeedbackList,
|
||||
processFeedback,
|
||||
getFeedbackStats
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
const model1Service = require('../services/model1Service');
|
||||
const { Plan, Conversation } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const generatePlan = async (req, res) => {
|
||||
try {
|
||||
const { query, conversationId, context = {} } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Query is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify conversation belongs to user
|
||||
if (conversationId) {
|
||||
const conversation = await Conversation.findOne({
|
||||
where: {
|
||||
id: conversationId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Conversation not found'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate plan using MODEL1
|
||||
const planData = await model1Service.generatePlan(query, context);
|
||||
|
||||
// Save plan if conversationId provided
|
||||
let savedPlan = null;
|
||||
if (conversationId) {
|
||||
savedPlan = await model1Service.savePlan(planData, conversationId, userId);
|
||||
}
|
||||
|
||||
logger.info(`MODEL1 plan generated for user: ${userId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
plan: savedPlan || planData,
|
||||
processingTime: planData.processingTime,
|
||||
tokensUsed: planData.tokensUsed,
|
||||
model: planData.model
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('MODEL1 plan generation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const validatePlan = async (req, res) => {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const { feedback = [] } = req.body;
|
||||
|
||||
const result = await model1Service.validatePlan(planId, feedback);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('MODEL1 plan validation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getPlan = async (req, res) => {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { plan }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get plan error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updatePlan = async (req, res) => {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const { title, description, steps, status, approvalFeedback } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
const updateData = {};
|
||||
if (title) updateData.title = title;
|
||||
if (description) updateData.description = description;
|
||||
if (steps) updateData.steps = steps;
|
||||
if (status) updateData.status = status;
|
||||
if (approvalFeedback) updateData.approval_feedback = approvalFeedback;
|
||||
|
||||
await plan.update(updateData);
|
||||
|
||||
logger.info(`Plan updated: ${planId}, status: ${status}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { plan }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Update plan error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getModelStatus = async (req, res) => {
|
||||
try {
|
||||
const status = await model1Service.getModelStatus();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: status
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('MODEL1 status error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generatePlan,
|
||||
validatePlan,
|
||||
getPlan,
|
||||
updatePlan,
|
||||
getModelStatus
|
||||
};
|
||||
@@ -0,0 +1,265 @@
|
||||
const { ModelVersion, TrainingData } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const getModelStatus = async (req, res) => {
|
||||
try {
|
||||
const activeModels = await ModelVersion.findAll({
|
||||
where: { is_active: true },
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
const modelStatus = {
|
||||
MODEL1: null,
|
||||
QUERYMODEL: null
|
||||
};
|
||||
|
||||
activeModels.forEach(model => {
|
||||
if (model.model_type === 'MODEL1') {
|
||||
modelStatus.MODEL1 = {
|
||||
id: model.id,
|
||||
version: model.version,
|
||||
deployment_status: model.deployment_status,
|
||||
performance_metrics: model.performance_metrics,
|
||||
last_updated: model.updated_at
|
||||
};
|
||||
} else if (model.model_type === 'QUERYMODEL') {
|
||||
modelStatus.QUERYMODEL = {
|
||||
id: model.id,
|
||||
version: model.version,
|
||||
deployment_status: model.deployment_status,
|
||||
performance_metrics: model.performance_metrics,
|
||||
last_updated: model.updated_at
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { modelStatus }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get model status error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getModelVersions = async (req, res) => {
|
||||
try {
|
||||
const { modelType, page = 1, limit = 10 } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
if (modelType) whereClause.model_type = modelType;
|
||||
|
||||
const models = await ModelVersion.findAndCountAll({
|
||||
where: whereClause,
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
models: models.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: models.count,
|
||||
pages: Math.ceil(models.count / parseInt(limit))
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get model versions error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createModelVersion = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
modelName,
|
||||
modelType,
|
||||
baseModel,
|
||||
fineTuningMethod,
|
||||
hyperparameters = {}
|
||||
} = req.body;
|
||||
|
||||
if (!modelName || !modelType || !baseModel) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Model name, type, and base model are required'
|
||||
});
|
||||
}
|
||||
|
||||
const validModelTypes = ['MODEL1', 'QUERYMODEL'];
|
||||
if (!validModelTypes.includes(modelType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid model type'
|
||||
});
|
||||
}
|
||||
|
||||
// Deactivate current active model of same type
|
||||
await ModelVersion.update(
|
||||
{ is_active: false },
|
||||
{ where: { model_type: modelType, is_active: true } }
|
||||
);
|
||||
|
||||
const model = await ModelVersion.create({
|
||||
model_name: modelName,
|
||||
version: `v${Date.now()}`,
|
||||
model_type: modelType,
|
||||
base_model: baseModel,
|
||||
fine_tuning_method: fineTuningMethod,
|
||||
hyperparameters,
|
||||
deployment_status: 'training'
|
||||
});
|
||||
|
||||
logger.info(`New model version created: ${model.id}`);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: { model }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Create model version error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateModelVersion = async (req, res) => {
|
||||
try {
|
||||
const { modelId } = req.params;
|
||||
const {
|
||||
deploymentStatus,
|
||||
performanceMetrics,
|
||||
modelPath,
|
||||
trainingLog
|
||||
} = req.body;
|
||||
|
||||
const model = await ModelVersion.findByPk(modelId);
|
||||
|
||||
if (!model) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Model version not found'
|
||||
});
|
||||
}
|
||||
|
||||
const updateData = {};
|
||||
if (deploymentStatus) updateData.deployment_status = deploymentStatus;
|
||||
if (performanceMetrics) updateData.performance_metrics = performanceMetrics;
|
||||
if (modelPath) updateData.model_path = modelPath;
|
||||
if (trainingLog) updateData.training_log = trainingLog;
|
||||
|
||||
await model.update(updateData);
|
||||
|
||||
logger.info(`Model version updated: ${modelId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { model }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Update model version error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const activateModel = async (req, res) => {
|
||||
try {
|
||||
const { modelId } = req.params;
|
||||
|
||||
const model = await ModelVersion.findByPk(modelId);
|
||||
|
||||
if (!model) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Model version not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Deactivate other models of same type
|
||||
await ModelVersion.update(
|
||||
{ is_active: false },
|
||||
{ where: { model_type: model.model_type, is_active: true } }
|
||||
);
|
||||
|
||||
// Activate this model
|
||||
await model.update({
|
||||
is_active: true,
|
||||
deployment_status: 'deployed'
|
||||
});
|
||||
|
||||
logger.info(`Model activated: ${modelId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { model }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Activate model error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getTrainingData = async (req, res) => {
|
||||
try {
|
||||
const { modelVersionId, dataType, page = 1, limit = 10 } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
if (modelVersionId) whereClause.model_version_id = modelVersionId;
|
||||
if (dataType) whereClause.data_type = dataType;
|
||||
|
||||
const trainingData = await TrainingData.findAndCountAll({
|
||||
where: whereClause,
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
trainingData: trainingData.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: trainingData.count,
|
||||
pages: Math.ceil(trainingData.count / parseInt(limit))
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get training data error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getModelStatus,
|
||||
getModelVersions,
|
||||
createModelVersion,
|
||||
updateModelVersion,
|
||||
activateModel,
|
||||
getTrainingData
|
||||
};
|
||||
@@ -0,0 +1,350 @@
|
||||
const queryModelService = require('../services/queryModelService');
|
||||
const { Plan, ToolExecution, Conversation, Message } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const executePlan = async (req, res) => {
|
||||
try {
|
||||
const { planId, options = {} } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
if (!planId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Plan ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify plan belongs to user
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (plan.status !== 'approved') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Plan must be approved before execution'
|
||||
});
|
||||
}
|
||||
|
||||
// Execute plan using QUERYMODEL
|
||||
const result = await queryModelService.executePlan(planId, options);
|
||||
|
||||
logger.info(`QUERYMODEL execution completed for plan: ${planId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('QUERYMODEL execution error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const executeTool = async (req, res) => {
|
||||
try {
|
||||
const { planId, toolName, toolType, inputParameters } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
if (!planId || !toolName || !toolType || !inputParameters) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Plan ID, tool name, type, and input parameters are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify plan belongs to user
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Execute tool using QUERYMODEL
|
||||
const result = await queryModelService.executeTool(toolName, toolType, inputParameters, planId);
|
||||
|
||||
logger.info(`Tool executed: ${toolName} for plan: ${planId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { toolExecution: result }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Execute tool error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const orchestratePlan = async (req, res) => {
|
||||
try {
|
||||
const { planId, options = {} } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
if (!planId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Plan ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify plan belongs to user
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (plan.status !== 'approved') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Plan must be approved before orchestration'
|
||||
});
|
||||
}
|
||||
|
||||
// Get the original user query from the conversation
|
||||
const originalMessage = await Message.findOne({
|
||||
where: {
|
||||
conversation_id: plan.conversation_id,
|
||||
role: 'user'
|
||||
},
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
const originalQuery = originalMessage ? originalMessage.content : plan.title;
|
||||
|
||||
// Execute orchestration using QUERYMODEL
|
||||
const result = await queryModelService.executeOrchestrate({
|
||||
query: originalQuery,
|
||||
category: 'engineering',
|
||||
topK: options.topK || 5,
|
||||
generateReport: true
|
||||
});
|
||||
|
||||
logger.info(`QUERYMODEL orchestration completed for plan: ${planId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { toolExecution: result }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('QUERYMODEL orchestration error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getExecutionStatus = async (req, res) => {
|
||||
try {
|
||||
const { planId } = req.params;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Get tool executions for this plan
|
||||
const toolExecutions = await ToolExecution.findAll({
|
||||
where: {
|
||||
plan_id: planId
|
||||
},
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
plan,
|
||||
toolExecutions
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get execution status error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getToolExecutions = async (req, res) => {
|
||||
try {
|
||||
const { planId, toolType, status, page = 1, limit = 10 } = req.query;
|
||||
const userId = req.user.userId;
|
||||
|
||||
const whereClause = {};
|
||||
if (planId) whereClause.plan_id = planId;
|
||||
if (toolType) whereClause.tool_type = toolType;
|
||||
if (status) whereClause.status = status;
|
||||
|
||||
// Verify plan belongs to user if planId provided
|
||||
if (planId) {
|
||||
const plan = await Plan.findOne({
|
||||
where: {
|
||||
id: planId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
where: {
|
||||
user_id: userId
|
||||
},
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { count, rows: executions } = await ToolExecution.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Plan,
|
||||
where: {
|
||||
conversation: {
|
||||
user_id: userId
|
||||
}
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Conversation,
|
||||
attributes: ['id', 'title', 'user_id']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
executions,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get tool executions error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getModelStatus = async (req, res) => {
|
||||
try {
|
||||
const status = await queryModelService.getModelStatus();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: status
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('QUERYMODEL status error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
executePlan,
|
||||
executeTool,
|
||||
orchestratePlan,
|
||||
getExecutionStatus,
|
||||
getToolExecutions,
|
||||
getModelStatus
|
||||
};
|
||||
@@ -0,0 +1,250 @@
|
||||
const { ToolExecution, Plan, Document, sequelize } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
const queryModelService = require('../services/queryModelService');
|
||||
|
||||
const executeTool = async (req, res) => {
|
||||
try {
|
||||
const { planId, toolName, toolType, inputParameters } = req.body;
|
||||
|
||||
if (!planId || !toolName || !toolType || !inputParameters) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Plan ID, tool name, type, and input parameters are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify plan exists
|
||||
const plan = await Plan.findByPk(planId);
|
||||
if (!plan) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Plan not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Execute tool using QUERYMODEL service
|
||||
const toolExecution = await queryModelService.executeTool(
|
||||
toolName,
|
||||
toolType,
|
||||
inputParameters,
|
||||
planId
|
||||
);
|
||||
|
||||
logger.info(`Tool executed: ${toolName} for plan ${planId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { toolExecution }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Execute tool error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getToolExecutions = async (req, res) => {
|
||||
try {
|
||||
const { planId, toolType, status, page = 1, limit = 10 } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
if (planId) whereClause.plan_id = planId;
|
||||
if (toolType) whereClause.tool_type = toolType;
|
||||
if (status) whereClause.status = status;
|
||||
|
||||
const toolExecutions = await ToolExecution.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Plan,
|
||||
attributes: ['id', 'title', 'status']
|
||||
},
|
||||
{
|
||||
model: Document,
|
||||
attributes: ['id', 'filename', 'original_filename']
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
toolExecutions: toolExecutions.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: toolExecutions.count,
|
||||
pages: Math.ceil(toolExecutions.count / parseInt(limit))
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get tool executions error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getToolExecution = async (req, res) => {
|
||||
try {
|
||||
const { executionId } = req.params;
|
||||
|
||||
const toolExecution = await ToolExecution.findByPk(executionId, {
|
||||
include: [
|
||||
{
|
||||
model: Plan,
|
||||
attributes: ['id', 'title', 'status']
|
||||
},
|
||||
{
|
||||
model: Document,
|
||||
attributes: ['id', 'filename', 'original_filename']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!toolExecution) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Tool execution not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { toolExecution }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get tool execution error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getToolStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await ToolExecution.findAll({
|
||||
attributes: [
|
||||
'tool_name',
|
||||
'tool_type',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
|
||||
[sequelize.fn('AVG', sequelize.col('execution_time')), 'avg_execution_time'],
|
||||
[sequelize.fn('SUM', sequelize.col('tokens_used')), 'total_tokens']
|
||||
],
|
||||
group: ['tool_name', 'tool_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const totalExecutions = await ToolExecution.count();
|
||||
const successfulExecutions = await ToolExecution.count({ where: { status: 'completed' } });
|
||||
const failedExecutions = await ToolExecution.count({ where: { status: 'failed' } });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
stats,
|
||||
summary: {
|
||||
total: totalExecutions,
|
||||
successful: successfulExecutions,
|
||||
failed: failedExecutions,
|
||||
successRate: totalExecutions > 0 ? (successfulExecutions / totalExecutions) * 100 : 0
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get tool stats error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const retryToolExecution = async (req, res) => {
|
||||
try {
|
||||
const { executionId } = req.params;
|
||||
|
||||
const toolExecution = await ToolExecution.findByPk(executionId);
|
||||
|
||||
if (!toolExecution) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Tool execution not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (toolExecution.status === 'running') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Tool execution is already running'
|
||||
});
|
||||
}
|
||||
|
||||
// Reset execution status
|
||||
await toolExecution.update({
|
||||
status: 'pending',
|
||||
output_result: null,
|
||||
execution_time: null,
|
||||
error_message: null
|
||||
});
|
||||
|
||||
// Retry tool execution using QUERYMODEL service
|
||||
try {
|
||||
const retryExecution = await queryModelService.executeTool(
|
||||
toolExecution.tool_name,
|
||||
toolExecution.tool_type,
|
||||
toolExecution.input_parameters,
|
||||
toolExecution.plan_id
|
||||
);
|
||||
|
||||
// Update the original execution record with retry results
|
||||
await toolExecution.update({
|
||||
output_result: retryExecution.output_result,
|
||||
status: retryExecution.status,
|
||||
execution_time: retryExecution.execution_time,
|
||||
tokens_used: retryExecution.tokens_used,
|
||||
error_message: null
|
||||
});
|
||||
|
||||
logger.info(`Tool execution retried successfully: ${executionId}`);
|
||||
} catch (retryError) {
|
||||
logger.error(`Tool execution retry failed: ${executionId}`, retryError);
|
||||
|
||||
await toolExecution.update({
|
||||
status: 'failed',
|
||||
error_message: retryError.message
|
||||
});
|
||||
|
||||
throw retryError;
|
||||
}
|
||||
|
||||
logger.info(`Tool execution retried: ${executionId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { toolExecution }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Retry tool execution error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
executeTool,
|
||||
getToolExecutions,
|
||||
getToolExecution,
|
||||
getToolStats,
|
||||
retryToolExecution
|
||||
};
|
||||
Reference in New Issue
Block a user