const express = require('express'); const router = express.Router(); const https = require('https'); const { logger } = require('../utils/logger'); require('dotenv').config({ path: './config/.env' }); // Mistral AI Configuration const MISTRAL_API_KEY = process.env.MISTRAL_API_KEY; const MISTRAL_BASE_URL = process.env.MISTRAL_BASE_URL || 'https://api.mistral.ai/v1'; const MISTRAL_MODEL = process.env.MISTRAL_MODEL || 'mistral-large-latest'; const AI_ENABLED = process.env.AI_ENABLED === 'true'; // Verification middleware function checkAIEnabled(req, res, next) { if (!AI_ENABLED) { return res.status(503).json({ success: false, error: 'AI features are disabled' }); } if (!MISTRAL_API_KEY) { return res.status(500).json({ success: false, error: 'Mistral API key not configured' }); } next(); } // Create a custom HTTPS agent with longer timeouts const httpsAgent = new https.Agent({ keepAlive: true, timeout: 120000, // 120 seconds maxSockets: 5 }); // Function to call Mistral API with timeout handling async function callMistralAPI(messages, temperature = null) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 120000); // 120 seconds timeout try { const response = await fetch(`${MISTRAL_BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${MISTRAL_API_KEY}` }, body: JSON.stringify({ model: MISTRAL_MODEL, messages: messages, temperature: temperature !== null ? temperature : parseFloat(process.env.AI_TEMPERATURE) || 0.3, max_tokens: parseInt(process.env.AI_MAX_TOKENS) || 35000, top_p: parseFloat(process.env.AI_TOP_P) || 0.85 }), signal: controller.signal, agent: httpsAgent }); clearTimeout(timeoutId); if (!response.ok) { const error = await response.text(); throw new Error(`Mistral API Error: ${response.status} - ${error}`); } const data = await response.json(); return data.choices[0].message.content; } catch (error) { clearTimeout(timeoutId); console.error('Mistral API Error:', error); // Better error messages if (error.name === 'AbortError') { throw new Error('Request timeout: The API took too long to respond (>120s)'); } if (error.cause && error.cause.code === 'UND_ERR_HEADERS_TIMEOUT') { throw new Error('Connection timeout: Unable to connect to Mistral API. Check your network or API endpoint.'); } if (error.cause && error.cause.code === 'ECONNREFUSED') { throw new Error('Connection refused: Cannot reach Mistral API endpoint.'); } throw error; } } // POST /api/ai/rephrase - Rephrase text router.post('/rephrase', checkAIEnabled, async (req, res) => { const startTime = Date.now(); try { const { text, context = '' } = req.body; logger.aiRequest('rephrase', MISTRAL_MODEL, text.length); if (!text || text.trim().length === 0) { logger.aiError('rephrase', new Error('Text to rephrase is required')); return res.status(400).json({ success: false, error: 'Text to rephrase is required' }); } const messages = [ { role: 'system', content: `You are an assistant specialized in rephrasing technical and design texts. STRICT RULES: 1. Rephrase the text to improve clarity, style, and fluency 2. Preserve the technical level and all important details 3. Respond ONLY with the final rephrased text 4. No introduction, conclusion, explanation, or commentary 5. Do not start with "Here is", "The rephrased text" or other preamble 6. Start directly with the rephrased content ${context ? `Document context: ${context}` : ''}` }, { role: 'user', content: `Rephrase this text: "${text}"` } ]; const result = await callMistralAPI(messages, 0.2); // Precise rephrasing logger.aiResponse('rephrase', true, Date.now() - startTime); res.json({ success: true, data: { original: text, rephrased: result } }); } catch (error) { logger.aiError('rephrase', error); res.status(500).json({ success: false, error: 'Error during rephrasing: ' + error.message }); } }); // POST /api/ai/check-inconsistencies - Check for inconsistencies router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => { const startTime = Date.now(); try { const { content } = req.body; logger.aiRequest('check-inconsistencies', MISTRAL_MODEL, content.length); if (!content || content.trim().length === 0) { logger.aiError('check-inconsistencies', new Error('Content to analyze is required')); return res.status(400).json({ success: false, error: 'Content to analyze is required' }); } const messages = [ { role: 'system', content: `You are an expert in technical design document analysis. Analyze the following document and identify all potential inconsistencies: - Contradictions in information - Conflicting decisions - Inconsistencies in terminology - Logical issues in architecture or choices Respond directly with your detailed analysis of the inconsistencies found.` }, { role: 'user', content: `Analyze this document to detect inconsistencies:\n\n${content}` } ]; const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis logger.aiResponse('check-inconsistencies', true, Date.now() - startTime); res.json({ success: true, data: { analysis: result } }); } catch (error) { logger.aiError('check-inconsistencies', error); res.status(500).json({ success: false, error: 'Error during analysis: ' + error.message }); } }); // POST /api/ai/check-duplications - Check for duplications router.post('/check-duplications', checkAIEnabled, async (req, res) => { const startTime = Date.now(); try { const { content } = req.body; logger.aiRequest('check-duplications', MISTRAL_MODEL, content.length); if (!content || content.trim().length === 0) { logger.aiError('check-duplications', new Error('Content to analyze is required')); return res.status(400).json({ success: false, error: 'Content to analyze is required' }); } const messages = [ { role: 'system', content: `You are a content analysis expert. Analyze the following document to identify: - Repeated or redundant information - Sections that cover the same topic - Duplicated explanations - Concepts presented multiple times Suggest ways to eliminate these duplications while preserving important information. Respond directly with your analysis and suggestions.` }, { role: 'user', content: `Analyze this document to detect duplications:\n\n${content}` } ]; const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis logger.aiResponse('check-duplications', true, Date.now() - startTime); res.json({ success: true, data: { analysis: result } }); } catch (error) { logger.aiError('check-duplications', error); res.status(500).json({ success: false, error: 'Error during analysis: ' + error.message }); } }); // POST /api/ai/give-advice - Give advice router.post('/give-advice', checkAIEnabled, async (req, res) => { const startTime = Date.now(); try { const { content, domain = 'general' } = req.body; logger.aiRequest('give-advice', MISTRAL_MODEL, content.length); if (!content || content.trim().length === 0) { logger.aiError('give-advice', new Error('Content to analyze is required')); return res.status(400).json({ success: false, error: 'Content to analyze is required' }); } const messages = [ { role: 'system', content: `You are an expert consultant in design and technical architecture in the domain: ${domain}. Analyze the provided design document and give constructive advice to improve it. Focus on: - Documentation completeness - Clarity of explanations - Content organization - Domain best practices - Important missing points - Concrete improvement suggestions Be constructive and practical in your recommendations. Respond directly with your advice and improvement suggestions.` }, { role: 'user', content: `Analyze this design document and provide advice to improve it:\n\n${content}` } ]; const result = await callMistralAPI(messages, 0.4); // Balanced advice logger.aiResponse('give-advice', true, Date.now() - startTime); res.json({ success: true, data: { advice: result } }); } catch (error) { logger.aiError('give-advice', error); res.status(500).json({ success: false, error: 'Error during analysis: ' + error.message }); } }); // POST /api/ai/liberty-mode - Enhanced mode (iterative generation) router.post('/liberty-mode', checkAIEnabled, async (req, res) => { const startTime = Date.now(); try { const { content, iterations = 1, precision = 70, focus = 'design' } = req.body; logger.aiRequest('liberty-mode', MISTRAL_MODEL, content.length); if (!content || content.trim().length === 0) { logger.aiError('liberty-mode', new Error('Base content is required')); return res.status(400).json({ success: false, error: 'Base content is required' }); } const maxIterations = Math.min(parseInt(iterations), 10); // Limit to 10 iterations const precisionPercent = Math.min(Math.max(parseInt(precision), 10), 100); // Between 10% and 100% logger.info('AI', `Liberty mode started: ${maxIterations} iterations at ${precisionPercent}% precision`); // Configure streaming for real-time responses res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*' }); let currentContent = content; let modifiedSections = []; // Track sections that have been modified for (let i = 0; i < maxIterations; i++) { try { const messages = [ { role: 'system', content: `You are a technical design expert with "Enhanced Mode - Smart Adaptive Strategy". MISSION: Analyze the entire document and choose ONE section to improve in this iteration. SMART SECTION SELECTION CRITERIA: 1. **Quality Assessment**: Identify sections that need improvement (incomplete, vague, lacking details) 2. **Strategic Importance**: Prioritize critical sections (Introduction, Architecture, Core APIs) 3. **Coverage**: Avoid sections recently modified: ${modifiedSections.length > 0 ? modifiedSections.join(', ') : 'None yet'} 4. **Balance**: Ensure progressive document enhancement PRECISION RULES: - At ${precisionPercent}%: You can deduce and add content up to ${precisionPercent}% based on existing information - At ${100 - precisionPercent}%: You can create logical and relevant content even without explicit info in the text SECTION BOUNDARIES - CRITICAL UNDERSTANDING: A section is defined by its header and extends until: - The next header of EQUAL or HIGHER level appears - The end of the document is reached Example structure: ``` # Main Title (level 1) content... ## Section A (level 2) ← Section A starts here content A... ### Subsection A1 (level 3) ← Part of Section A content A1... ### Subsection A2 (level 3) ← Part of Section A content A2... ## Section B (level 2) ← Section A ends, Section B starts content B... ``` If you choose to modify "### Subsection A1": - Include ONLY the content from "### Subsection A1" until "### Subsection A2" - DO NOT include "### Subsection A2" or anything after it - DO NOT include "## Section B" or anything after it - The rest of the document (before and after) must remain EXACTLY as it was INSTRUCTIONS: 1. **ANALYZE** all sections in the document 2. **CHOOSE** exactly ONE section to improve (based on criteria above) 3. **IDENTIFY BOUNDARIES**: Determine where the chosen section starts and ends: - Starts: At the section's header (e.g., "### API Documentation") - Ends: Right BEFORE the next header of equal or higher level, OR at document end 4. **ENHANCE** only that chosen section with: - Additional technical details - Better explanations - Code examples if relevant - Mermaid diagrams if relevant (using \`\`\`mermaid blocks) - Best practices 5. **PRESERVE** everything else: - ALL content BEFORE the chosen section must remain unchanged - ALL content AFTER the chosen section must remain unchanged - ALL other sections at any level must remain unchanged 6. **MAINTAIN** document structure and formatting 7. **CRITICAL**: Keep the EXACT same header level for the modified section - If the original section uses ## (level 2), the modified section MUST use ## - If the original section uses ### (level 3), the modified section MUST use ### - DO NOT change header levels (## to ###, or ### to ##, etc.) 8. **VERIFY**: Before returning, check that the document contains ALL original sections MANDATORY RESPONSE FORMAT - Use code blocks with triple backticks: First, write your analysis and decision inside a code block labeled "comment${i + 1}": - **Analyzed Sections**: List all major sections found - **Selected Section**: Which section you chose to improve (exact header name) - **Selection Reason**: Why this section needs improvement most - **Improvements Made**: Detailed list of enhancements - **Next Recommendations**: Sections to consider for future iterations Then, write the complete markdown document inside a code block labeled "document". The document must include ALL sections, with ONLY the selected section modified. Example format: \`\`\`comment${i + 1} **Analyzed Sections**: Introduction, Architecture, API Documentation, Conclusion **Selected Section**: ### API Documentation **Selection Reason**: This section lacks implementation details and error handling **Improvements Made**: - Added authentication flow - Included error handling scenarios - Added code examples **Next Recommendations**: Consider enhancing Architecture section \`\`\` \`\`\`document [Full document with only API Documentation section improved] \`\`\`document Focus: ${focus} Precision: ${precisionPercent}% Iteration: ${i + 1}/${maxIterations} Previously Modified: ${modifiedSections.length > 0 ? modifiedSections.join(', ') : 'None'}` }, { role: 'user', content: `Document to analyze and improve (Iteration ${i + 1}):\n\n${currentContent}` } ]; // Temperature based on precision (more creative = higher temperature) const temperature = (100 - precisionPercent) / 100 * 0.8 + 0.1; // Between 0.1 and 0.9 const result = await callMistralAPI(messages, temperature); // Extract code blocks const commentRegex = /```comment\d+\s*([\s\S]*?)```/; const documentRegex = /```document\s*([\s\S]*?)```document/; const commentMatch = result.match(commentRegex); let documentMatch = result.match(documentRegex); // Fallback: if new format not found, try old format if (!documentMatch) { const oldDocumentRegex = /```document\s*([\s\S]*?)```/; documentMatch = result.match(oldDocumentRegex); } let explanation = ''; let newMarkdown = currentContent; // Default, keep old content let selectedSection = ''; if (commentMatch && commentMatch[1]) { explanation = commentMatch[1].trim(); // Extract selected section name from comment const sectionMatch = explanation.match(/\*\*Selected Section\*\*:?\s*(.+?)(?:\n|\*\*|$)/i); if (sectionMatch && sectionMatch[1]) { selectedSection = sectionMatch[1].trim(); // Add to modified sections tracker if (!modifiedSections.includes(selectedSection)) { modifiedSections.push(selectedSection); } } } if (documentMatch && documentMatch[1]) { newMarkdown = documentMatch[1].trim(); // Update for next iteration currentContent = newMarkdown; } // Fallback: if no code blocks found, try old format for compatibility if (!commentMatch && !documentMatch) { const parts = result.split('---SPLIT---'); if (parts.length >= 2) { explanation = parts[0].trim(); newMarkdown = parts[1].trim(); currentContent = newMarkdown; } else { explanation = result; } } // Send this iteration's response const iterationData = { iteration: i + 1, explanation: explanation, selectedSection: selectedSection || 'Unknown', markdown: newMarkdown, completed: false }; res.write(`data: ${JSON.stringify(iterationData)}\n\n`); // Small delay to allow client-side display await new Promise(resolve => setTimeout(resolve, 500)); } catch (iterationError) { console.error(`Iteration ${i + 1} error:`, iterationError); const errorData = { iteration: i + 1, error: `Iteration ${i + 1} error: ${iterationError.message}`, completed: true }; res.write(`data: ${JSON.stringify(errorData)}\n\n`); break; } } // End signal const finalData = { completed: true, totalIterations: maxIterations, finalMarkdown: currentContent }; res.write(`data: ${JSON.stringify(finalData)}\n\n`); res.end(); logger.aiResponse('liberty-mode', true, Date.now() - startTime); } catch (error) { logger.aiError('liberty-mode', error); const errorData = { error: 'Error during generation: ' + error.message, completed: true }; res.write(`data: ${JSON.stringify(errorData)}\n\n`); res.end(); } }); // POST /api/ai/fix-mermaid - Fix Mermaid diagram errors router.post('/fix-mermaid', checkAIEnabled, async (req, res) => { const startTime = Date.now(); try { const { mermaidCode, error } = req.body; logger.aiRequest('fix-mermaid', MISTRAL_MODEL, mermaidCode ? mermaidCode.length : 0); if (!mermaidCode || mermaidCode.trim().length === 0) { logger.aiError('fix-mermaid', new Error('Mermaid code is required')); return res.status(400).json({ success: false, error: 'Mermaid code is required' }); } if (!error || error.trim().length === 0) { logger.aiError('fix-mermaid', new Error('Error message is required')); return res.status(400).json({ success: false, error: 'Error message is required' }); } const messages = [ { role: 'system', content: `You are a Mermaid diagram syntax expert. MISSION: Fix the Mermaid diagram syntax error. STRICT RULES: 1. Analyze the error message carefully 2. Identify the syntax issue in the Mermaid code 3. Fix ONLY the syntax error while preserving the diagram's intent and content 4. Return ONLY the corrected Mermaid code 5. Do NOT add explanations, comments, or any text outside the code block 6. Do NOT wrap in \`\`\`mermaid - respond with raw Mermaid code only Common Mermaid syntax issues to watch for: - Invalid character sequences in node labels - Missing quotes around special characters - Incorrect bracket/parenthesis syntax for node shapes - Invalid keywords or operators - Line break issues (\n should be used carefully) - Special characters that need escaping: (, ), [, ], {, }, <, >, ", '` }, { role: 'user', content: `Fix this Mermaid diagram that has a syntax error. ERROR: ${error} MERMAID CODE: ${mermaidCode} Return ONLY the corrected Mermaid code without any explanation or markdown formatting.` } ]; const result = await callMistralAPI(messages, 0.1); // Very precise logger.aiResponse('fix-mermaid', true, Date.now() - startTime); res.json({ success: true, data: { originalCode: mermaidCode, fixedCode: result.trim(), error: error } }); } catch (error) { logger.aiError('fix-mermaid', error); res.status(500).json({ success: false, error: 'Error fixing Mermaid diagram: ' + error.message }); } }); // GET /api/ai/status - AI status router.get('/status', (req, res) => { res.json({ success: true, data: { enabled: AI_ENABLED, model: MISTRAL_MODEL, configured: !!MISTRAL_API_KEY } }); }); module.exports = router;