first commit

This commit is contained in:
2025-11-06 11:08:59 +01:00
commit 3c5117c2c3
85 changed files with 13275 additions and 0 deletions
+214
View File
@@ -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
};
+356
View File
@@ -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
};
+299
View File
@@ -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
};
+220
View File
@@ -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
};
+198
View File
@@ -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
};
+265
View File
@@ -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
};
+350
View File
@@ -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
};
+250
View File
@@ -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
};