From 86eb68c0e6586c85e6dc2ff753c3b743c9dd1299 Mon Sep 17 00:00:00 2001 From: Muyue Date: Wed, 15 Oct 2025 10:21:54 +0200 Subject: [PATCH] feat: Enhanced mode v2 with Smart Adaptive strategy, comprehensive logging, and internationalization Major Features: - Enhanced Mode v2: Smart Adaptive section selection strategy * AI automatically selects ONE optimal section per iteration * Intelligent selection based on quality assessment and strategic importance * Section tracking to avoid duplicate modifications * Fixed header level preservation (## stays ##, ### stays ###) * Updated document code block format (document) - Mermaid Auto-Fix System * New POST /api/ai/fix-mermaid endpoint for automatic diagram correction * Automatic error detection in preview and presentation modes * Inter-window communication for seamless editor updates * AI-powered syntax error resolution - Comprehensive Logging System * Centralized logger utility (utils/logger.js) * API request/response middleware logging * AI operations: rephrase, check-inconsistencies, check-duplications, give-advice, liberty-mode, fix-mermaid * Storage operations: create, read, update, delete journals * Export operations: PDF and Web ZIP generation * Structured logging with timestamps, levels, categories, and metadata Improvements: - Table of Contents: Clean display with markdown formatting stripped from titles - Section badges: Fixed visibility with hardcoded blue background (#2563eb) - Internationalization: Complete English translation (lang, UI text, placeholders, comments) - Code cleanup: Removed all emojis from codebase Technical Changes: - routes/ai.js: Enhanced mode implementation, Mermaid fix endpoint, comprehensive logging - routes/api.js: Storage operation logging - routes/export.js: Export operation logging - routes/index.js: Presentation mode Mermaid auto-fix - assets/js/app.js: TOC markdown stripping, Mermaid error handling, message listeners - assets/css/style.css: Dark mode comment, English placeholders, header icon - views/page.js: English lang attribute, favicon, scroll-to-top button - views/header.js: Theme toggle icon - utils/logger.js: New centralized logging system - app.js: API request/response logging middleware --- README.md | 55 +++++++++- app.js | 19 +++- assets/css/style.css | 4 +- assets/js/app.js | 103 +++++++++++++++++- routes/ai.js | 253 +++++++++++++++++++++++++++++++++++++++---- routes/api.js | 86 +++++++++++---- routes/export.js | 17 ++- routes/index.js | 52 ++++++++- utils/logger.js | 193 +++++++++++++++++++++++++++++++++ views/page.js | 4 +- 10 files changed, 725 insertions(+), 61 deletions(-) create mode 100644 utils/logger.js diff --git a/README.md b/README.md index ca426c1..204684b 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,13 @@ A collaborative design documentation tool with AI-powered assistance for technic - **Rephrase**: Improve clarity and style of selected text - **Analyze**: Detect inconsistencies and duplicates in documents - **Advice**: Get constructive improvement suggestions -- **Enhanced Mode**: Iterative document enrichment with configurable precision (30-90%) +- **Enhanced Mode (Smart Adaptive)**: AI-driven iterative document enrichment + - AI automatically selects which section to improve in each iteration + - Smart section selection based on quality assessment, importance, and coverage - Up to 10 iterations of progressive enhancement - - Real-time streaming feedback - - Precision control for creative vs. conservative improvements + - Real-time streaming feedback with section tracking + - Precision control for creative vs. conservative improvements (30-90%) + - Automatically avoids re-modifying recently enhanced sections ### User Interface - Dark/Light theme with preference persistence @@ -95,10 +98,17 @@ npm start ### Using AI Features 1. **Rephrase**: Select text → click "Rephrase" button 2. **Analysis**: Click "Inconsistencies", "Duplicates", or "Advice" buttons -3. **Enhanced Mode**: - - Choose iterations (1-10) and precision (30-90%) +3. **Enhanced Mode (Smart Adaptive)**: + - Choose number of iterations (1-10) + - Select precision level (30-90%) - Click "Enhanced Mode" - - Watch real-time streaming improvements + - AI automatically analyzes the entire document + - At each iteration, AI selects ONE section to improve based on: + - Quality assessment (incomplete sections, lacking details) + - Strategic importance (introduction, architecture, APIs) + - Coverage (avoids recently modified sections) + - Watch real-time streaming with section badges showing which part is being enhanced + - Document updates automatically after each iteration ### Keyboard Shortcuts - `Ctrl+S` - Save journal @@ -149,6 +159,39 @@ conception-assistant/ | 70% | Conservative | Primarily based on existing content | Document refinement | | 90% | Very Precise | Only obvious deductions | Technical finalization | +### Smart Adaptive Section Selection +The Enhanced Mode uses an intelligent strategy to automatically choose which section to improve: + +**Selection Criteria (in priority order):** +1. **Quality Assessment**: Identifies sections that need improvement + - Incomplete content (too short, missing details) + - Vague or unclear explanations + - Lack of technical specifications + - Poor structure or organization + +2. **Strategic Importance**: Prioritizes critical document sections + - Introduction and overview sections + - Architecture and design decisions + - Core APIs and implementation details + - Critical workflows and processes + +3. **Coverage Tracking**: Ensures balanced enhancement + - Avoids sections recently modified + - Tracks modification history across iterations + - Ensures all sections receive attention + +4. **Progressive Enhancement**: Builds incrementally + - Each iteration improves ONE section only + - Maintains document coherence + - Preserves all other sections unchanged + +**Output Format Per Iteration:** +- **Analyzed Sections**: List of all major sections found in the document +- **Selected Section**: The specific section chosen for enhancement (with exact header name) +- **Selection Reason**: Explanation of why this section was prioritized +- **Improvements Made**: Detailed list of enhancements applied +- **Next Recommendations**: Suggestions for future iterations + ## Technology Stack ### Backend diff --git a/app.js b/app.js index c59a46b..38acfe9 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,6 @@ const express = require('express'); const path = require('path'); +const { logger } = require('./utils/logger'); const indexRoutes = require('./routes/index'); const apiRoutes = require('./routes/api'); @@ -15,13 +16,28 @@ app.use(express.urlencoded({ extended: true })); app.use('/assets', express.static(path.join(__dirname, 'assets'))); -// Créer le dossier data s'il n'existe pas +// Create data directory if it doesn't exist const fs = require('fs'); const dataDir = path.join(__dirname, 'data'); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir); + logger.info('SYSTEM', 'Created data directory', { path: dataDir }); } +// API request logging middleware +app.use((req, res, next) => { + const start = Date.now(); + + logger.apiRequest(req.method, req.path, req.body); + + res.on('finish', () => { + const duration = Date.now() - start; + logger.apiResponse(req.path, res.statusCode, duration); + }); + + next(); +}); + app.use('/', indexRoutes); app.use('/api', apiRoutes); app.use('/api/templates', templatesRoutes); @@ -29,5 +45,6 @@ app.use('/api/export', exportRoutes); app.use('/api/ai', aiRoutes); app.listen(port, () => { + logger.systemStart(port); console.log(`Server running on port ${port}`); }); \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css index 7555e29..91e4239 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -16,7 +16,7 @@ --transition: all 0.3s ease; } -/* Mode sombre */ +/* Dark mode */ body.dark-theme { --primary-color: #ecf0f1; --secondary-color: #3498db; @@ -664,7 +664,7 @@ section h2 { } #journal-editor:empty::before { - content: "Commencez à écrire votre journal de conception...\A\A# Titre de votre projet\A\A## Contexte et objectifs\A\ADécrivez ici le contexte de votre projet...\A\A## Architecture\A\A### Composants principaux\A\A### Technologies utilisées\A\A## Décisions de conception\A\A### Décision 1\A\A**Problème :**\A**Options considérées :**\A**Décision :**\A**Justification :**\A\A## Prochaines étapes\A\A- [ ] Tâche 1\A- [ ] Tâche 2"; + content: "Start writing your design journal...\A\A# Project Title\A\A## Context and Objectives\A\ADescribe your project context here...\A\A## Architecture\A\A### Main Components\A\A### Technologies Used\A\A## Design Decisions\A\A### Decision 1\A\A**Problem:**\A**Options Considered:**\A**Decision:**\A**Justification:**\A\A## Next Steps\A\A- [ ] Task 1\A- [ ] Task 2"; color: var(--text-light); font-style: italic; white-space: pre-wrap; diff --git a/assets/js/app.js b/assets/js/app.js index 3d0e755..6352359 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -14,11 +14,30 @@ class ConceptionAssistant { async init() { this.setupEditor(); this.setupEventListeners(); + this.setupMessageListener(); await this.loadJournalList(); this.generateTOC(); this.updateStatistics(); } + setupMessageListener() { + // Listen for messages from presentation mode window + window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'mermaid-fixed') { + // Update the document content with the fixed Mermaid diagram + const currentContent = this.editor.innerText; + const fixedContent = currentContent.replace(event.data.originalCode, event.data.fixedCode); + + if (fixedContent !== currentContent) { + this.editor.innerText = fixedContent; + this.generateTOC(); + this.saveState(true); + this.showNotification('Mermaid diagram fixed in document', 'success'); + } + } + }); + } + setupEditor() { this.editor = document.getElementById('journal-editor'); @@ -427,6 +446,19 @@ class ConceptionAssistant { } } + cleanMarkdownFromTitle(title) { + // Remove all markdown formatting markers from title + return title + .replace(/\*\*/g, '') // Remove bold ** + .replace(/\*/g, '') // Remove italic * + .replace(/__/g, '') // Remove bold __ + .replace(/_/g, '') // Remove italic _ + .replace(/`/g, '') // Remove code ` + .replace(/~~/g, '') // Remove strikethrough ~~ + .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links, keep text + .trim(); + } + generateTOC() { // Save cursor position const savedRange = this.saveSelection(); @@ -477,7 +509,8 @@ class ConceptionAssistant { // Ajouter l'élément avec indentation CSS const indent = (item.level - 1) * 1; // 1rem par niveau - tocHtml += `
  • ${item.title}`; + const cleanTitle = this.cleanMarkdownFromTitle(item.title); + tocHtml += `
  • ${cleanTitle}`; previousLevel = item.level; } @@ -1100,10 +1133,14 @@ class ConceptionAssistant { progressFill.style.width = `${progressPercent}%`; } - // Display this iteration's explanation + // Display this iteration's explanation with selected section + const sectionBadge = data.selectedSection + ? `${data.selectedSection}` + : ''; + const iterationHTML = `
    - Iteration ${data.iteration}

    + Iteration ${data.iteration}${sectionBadge}

    ${this.formatAIResponse(data.explanation)}
    `; @@ -1312,6 +1349,45 @@ class ConceptionAssistant { this.editor.contentEditable = true; } + async fixMermaidDiagram(mermaidCode, errorMessage) { + try { + this.showNotification('Fixing Mermaid diagram...', 'info'); + + const response = await fetch('/api/ai/fix-mermaid', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + mermaidCode: mermaidCode, + error: errorMessage + }) + }); + + const result = await response.json(); + + if (result.success && result.data.fixedCode) { + // Replace the erroneous Mermaid code in the editor + const currentContent = this.editor.innerText; + const fixedContent = currentContent.replace(mermaidCode, result.data.fixedCode); + + this.editor.innerText = fixedContent; + this.generateTOC(); + this.saveState(true); + + this.showNotification('Mermaid diagram fixed automatically', 'success'); + + // Return true to indicate successful fix + return true; + } else { + this.showNotification('Could not fix Mermaid diagram', 'error'); + return false; + } + } catch (error) { + console.error('Error fixing Mermaid:', error); + this.showNotification('Error fixing Mermaid diagram: ' + error.message, 'error'); + return false; + } + } + showNotification(message, type = 'success') { const notification = document.createElement('div'); notification.className = `notification ${type}`; @@ -1402,9 +1478,26 @@ class ConceptionAssistant { // Render diagram mermaid.render(uniqueId + '-svg', mermaidCode).then(({ svg }) => { mermaidDiv.innerHTML = svg; - }).catch(err => { + }).catch(async (err) => { console.warn('Mermaid rendering error:', err); - mermaidDiv.innerHTML = `
    Mermaid rendering error: ${err.message}
    `; + + // Automatically try to fix the Mermaid diagram + this.showNotification('Mermaid error detected. Attempting to fix...', 'warning'); + + const fixed = await this.fixMermaidDiagram(mermaidCode, err.message); + + if (fixed) { + // Diagram was fixed, re-toggle preview to show the corrected version + setTimeout(async () => { + // Exit preview mode + await this.togglePreview(); + // Re-enter preview mode to render fixed diagram + setTimeout(() => this.togglePreview(), 500); + }, 1000); + } else { + // Show error if fix failed + mermaidDiv.innerHTML = `
    Mermaid rendering error: ${err.message}\n\nAutomatic fix failed. Please check the syntax manually.
    `; + } }); } catch (error) { console.warn('Mermaid processing error:', error); diff --git a/routes/ai.js b/routes/ai.js index 6529c37..fff035d 100644 --- a/routes/ai.js +++ b/routes/ai.js @@ -1,6 +1,7 @@ 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 @@ -88,10 +89,15 @@ async function callMistralAPI(messages, temperature = null) { // 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' @@ -121,6 +127,8 @@ router.post('/rephrase', checkAIEnabled, async (req, res) => { const result = await callMistralAPI(messages, 0.2); // Precise rephrasing + logger.aiResponse('rephrase', true, Date.now() - startTime); + res.json({ success: true, data: { @@ -130,7 +138,7 @@ router.post('/rephrase', checkAIEnabled, async (req, res) => { }); } catch (error) { - console.error('Rephrasing error:', error); + logger.aiError('rephrase', error); res.status(500).json({ success: false, error: 'Error during rephrasing: ' + error.message @@ -140,10 +148,15 @@ router.post('/rephrase', checkAIEnabled, async (req, res) => { // 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' @@ -171,6 +184,8 @@ router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => { const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis + logger.aiResponse('check-inconsistencies', true, Date.now() - startTime); + res.json({ success: true, data: { @@ -179,7 +194,7 @@ router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => { }); } catch (error) { - console.error('Inconsistencies analysis error:', error); + logger.aiError('check-inconsistencies', error); res.status(500).json({ success: false, error: 'Error during analysis: ' + error.message @@ -189,10 +204,15 @@ router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => { // 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' @@ -221,6 +241,8 @@ router.post('/check-duplications', checkAIEnabled, async (req, res) => { const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis + logger.aiResponse('check-duplications', true, Date.now() - startTime); + res.json({ success: true, data: { @@ -229,7 +251,7 @@ router.post('/check-duplications', checkAIEnabled, async (req, res) => { }); } catch (error) { - console.error('Duplications analysis error:', error); + logger.aiError('check-duplications', error); res.status(500).json({ success: false, error: 'Error during analysis: ' + error.message @@ -239,10 +261,15 @@ router.post('/check-duplications', checkAIEnabled, async (req, res) => { // 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' @@ -275,6 +302,8 @@ router.post('/give-advice', checkAIEnabled, async (req, res) => { const result = await callMistralAPI(messages, 0.4); // Balanced advice + logger.aiResponse('give-advice', true, Date.now() - startTime); + res.json({ success: true, data: { @@ -283,7 +312,7 @@ router.post('/give-advice', checkAIEnabled, async (req, res) => { }); } catch (error) { - console.error('Advice error:', error); + logger.aiError('give-advice', error); res.status(500).json({ success: false, error: 'Error during analysis: ' + error.message @@ -293,10 +322,15 @@ router.post('/give-advice', checkAIEnabled, async (req, res) => { // 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' @@ -306,6 +340,8 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => { 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', @@ -315,46 +351,115 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => { }); 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". + content: `You are a technical design expert with "Enhanced Mode - Smart Adaptive Strategy". - MISSION: Improve and enrich the document respecting EXACTLY this precision level: ${precisionPercent}% + 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. Enrich ALL of the document consistently - 2. Add sections, details, explanations, conceptual diagrams - 3. Develop existing ideas with the allowed creativity - 4. Maintain logical structure + 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 comment explaining the improvements inside a code block labeled "comment${i + 1}": - - What sections were added - - What was enhanced - - Reasoning behind changes + 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 improved markdown document inside a code block labeled "document". + 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: - First block: comment${i + 1} - Second block: document + \`\`\`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}` + Iteration: ${i + 1}/${maxIterations} + Previously Modified: ${modifiedSections.length > 0 ? modifiedSections.join(', ') : 'None'}` }, { role: 'user', - content: `Document to improve (Iteration ${i + 1}):\n\n${currentContent}` + content: `Document to analyze and improve (Iteration ${i + 1}):\n\n${currentContent}` } ]; @@ -365,16 +470,33 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => { // Extract code blocks const commentRegex = /```comment\d+\s*([\s\S]*?)```/; - const documentRegex = /```document\s*([\s\S]*?)```/; + const documentRegex = /```document\s*([\s\S]*?)```document/; const commentMatch = result.match(commentRegex); - const documentMatch = result.match(documentRegex); + 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]) { @@ -399,6 +521,7 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => { const iterationData = { iteration: i + 1, explanation: explanation, + selectedSection: selectedSection || 'Unknown', markdown: newMarkdown, completed: false }; @@ -432,8 +555,10 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => { res.write(`data: ${JSON.stringify(finalData)}\n\n`); res.end(); + logger.aiResponse('liberty-mode', true, Date.now() - startTime); + } catch (error) { - console.error('Enhanced mode error:', error); + logger.aiError('liberty-mode', error); const errorData = { error: 'Error during generation: ' + error.message, @@ -445,6 +570,90 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => { } }); +// 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({ diff --git a/routes/api.js b/routes/api.js index 10af91d..521b9fb 100644 --- a/routes/api.js +++ b/routes/api.js @@ -3,6 +3,7 @@ const router = express.Router(); const fs = require('fs'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); +const { logger } = require('../utils/logger'); // Import export module const exportRouter = require('./export'); @@ -140,30 +141,62 @@ function deteMd(id) { // GET /api/journals - Get all journals router.get('/journals', (req, res) => { - res.json({ - success: true, - data: readMd() - }); + try { + logger.storageRead('get-all-journals', 'all'); + const data = readMd(); + res.json({ + success: true, + data: data + }); + } catch (error) { + logger.storageError('get-all-journals', error); + res.status(500).json({ + success: false, + error: error.message + }); + } }); // POST /api/journals - Create a new journal router.post('/journals', (req, res) => { - const { content } = req.body; + try { + const { content } = req.body; - res.status(201).json({ - success: true, - data: createMd(content) - }); + const result = createMd(content); + logger.storageWrite('create-journal', result.uuid); + + res.status(201).json({ + success: true, + data: result + }); + } catch (error) { + logger.storageError('create-journal', error); + res.status(500).json({ + success: false, + error: error.message + }); + } }); // GET /api/journals/:id - Get a specific journal router.get('/journals/:id', (req, res) => { - const { id } = req.params; + try { + const { id } = req.params; - res.json({ - success: true, - data: readMd(id) - }); + logger.storageRead('get-journal', id); + const data = readMd(id); + + res.json({ + success: true, + data: data + }); + } catch (error) { + logger.storageError('get-journal', error); + res.status(500).json({ + success: false, + error: error.message + }); + } }); // PUT /api/journals/:id - Update a journal @@ -175,11 +208,14 @@ router.put('/journals/:id', (req, res) => { let result; if (content) { // Complete content update + logger.storageWrite('update-journal-content', id); result = updateMd(id, content); } else if (modifications) { // Partial modifications (for future compatibility) + logger.storageWrite('update-journal-modifications', id); result = modifMd(id, modifications); } else { + logger.storageError('update-journal', new Error('Content or modifications required')); return res.status(400).json({ success: false, error: 'Content or modifications required' @@ -191,6 +227,7 @@ router.put('/journals/:id', (req, res) => { data: result }); } catch (error) { + logger.storageError('update-journal', error); res.status(500).json({ success: false, error: error.message @@ -200,12 +237,23 @@ router.put('/journals/:id', (req, res) => { // DELETE /api/journals/:id - Delete a journal router.delete('/journals/:id', (req, res) => { - const { id } = req.params; + try { + const { id } = req.params; - res.json({ - success: true, - data: deteMd(id) - }); + logger.storageWrite('delete-journal', id); + const data = deteMd(id); + + res.json({ + success: true, + data: data + }); + } catch (error) { + logger.storageError('delete-journal', error); + res.status(500).json({ + success: false, + error: error.message + }); + } }); // Integrate export routes diff --git a/routes/export.js b/routes/export.js index 850f231..1b151d5 100644 --- a/routes/export.js +++ b/routes/export.js @@ -4,12 +4,17 @@ const puppeteer = require('puppeteer'); const path = require('path'); const archiver = require('archiver'); const fs = require('fs'); +const { logger } = require('../utils/logger'); // POST /api/export/pdf - Generate PDF from markdown content router.post('/pdf', async (req, res) => { + const startTime = Date.now(); const { content, title = 'Design Journal' } = req.body; + logger.exportOperation('pdf', 'application/pdf', content.length); + if (!content || content.trim() === '') { + logger.error('EXPORT', 'PDF export failed: Content required'); return res.status(400).json({ success: false, error: 'Content required to generate PDF' @@ -90,10 +95,12 @@ router.post('/pdf', async (req, res) => { res.setHeader('Content-Disposition', `attachment; filename="${sanitizeFilename(title)}.pdf"`); res.setHeader('Content-Length', pdfBuffer.length); + logger.info('EXPORT', `PDF generated successfully in ${Date.now() - startTime}ms`, { size: pdfBuffer.length, title }); + res.send(pdfBuffer); } catch (error) { - console.error('PDF generation error:', error); + logger.error('EXPORT', 'PDF generation error', { error: error.message, title }); if (browser) { await browser.close(); @@ -513,9 +520,13 @@ function sanitizeFilename(filename) { // POST /api/export/web - Generate ZIP with HTML + CSS router.post('/web', async (req, res) => { + const startTime = Date.now(); const { content, title = 'Design Journal' } = req.body; + logger.exportOperation('web-zip', 'application/zip', content.length); + if (!content || content.trim() === '') { + logger.error('EXPORT', 'Web export failed: Content required'); return res.status(400).json({ success: false, error: 'Content required to generate web export' @@ -661,8 +672,10 @@ body { // Finalize archive await archive.finalize(); + logger.info('EXPORT', `Web ZIP generated successfully in ${Date.now() - startTime}ms`, { title }); + } catch (error) { - console.error('Web export error:', error); + logger.error('EXPORT', 'Web export error', { error: error.message, title }); res.status(500).json({ success: false, error: 'Error during web export' diff --git a/routes/index.js b/routes/index.js index f7ef40e..a5a2d49 100644 --- a/routes/index.js +++ b/routes/index.js @@ -127,9 +127,57 @@ router.post('/present', (req, res) => { // Render diagram mermaid.render(uniqueId + '-svg', mermaidCode).then(function(result) { mermaidDiv.innerHTML = result.svg; - }).catch(function(err) { + }).catch(async function(err) { console.warn('Mermaid rendering error:', err); - mermaidDiv.innerHTML = '
    Mermaid rendering error: ' + err.message + '
    '; + + // Show error message with auto-fix option + mermaidDiv.innerHTML = '
    ' + + '

    [!] Mermaid Diagram Error

    ' + + '

    Error: ' + err.message + '

    ' + + '

    Attempting to fix automatically...

    ' + + '
    ' + + '
    '; + + const statusDiv = document.getElementById('fix-status-' + uniqueId); + + try { + // Call AI API to fix Mermaid diagram + const response = await fetch('/api/ai/fix-mermaid', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + mermaidCode: mermaidCode, + error: err.message + }) + }); + + const result = await response.json(); + + if (result.success && result.data.fixedCode) { + statusDiv.innerHTML = '

    [OK] Diagram fixed! Please close this window and reopen presentation mode from the editor to see the corrected diagram.

    '; + + // Try to update parent window if available + if (window.opener && !window.opener.closed) { + try { + // Send message to parent window to update content + window.opener.postMessage({ + type: 'mermaid-fixed', + originalCode: mermaidCode, + fixedCode: result.data.fixedCode + }, '*'); + + statusDiv.innerHTML += '

    Document updated in editor. You can now close this window and reopen presentation mode.

    '; + } catch (e) { + console.warn('Could not update parent window:', e); + } + } + } else { + statusDiv.innerHTML = '

    [ERROR] Automatic fix failed. Please check the syntax in the editor.

    '; + } + } catch (error) { + console.error('Error fixing Mermaid:', error); + statusDiv.innerHTML = '

    [ERROR] Could not fix diagram automatically. Please check your syntax in the editor.

    '; + } }); } catch (error) { console.warn('Mermaid processing error:', error); diff --git a/utils/logger.js b/utils/logger.js new file mode 100644 index 0000000..c055da3 --- /dev/null +++ b/utils/logger.js @@ -0,0 +1,193 @@ +/** + * Centralized logging system for Conception Assistant + * Provides structured logging for all application events + */ + +const LOG_LEVELS = { + ERROR: 'ERROR', + WARN: 'WARN', + INFO: 'INFO', + DEBUG: 'DEBUG' +}; + +const LOG_CATEGORIES = { + API: 'API', + AI: 'AI', + UI: 'UI', + STORAGE: 'STORAGE', + EXPORT: 'EXPORT', + AUTH: 'AUTH', + SYSTEM: 'SYSTEM' +}; + +class Logger { + constructor() { + this.enabled = process.env.LOGGING_ENABLED !== 'false'; + this.logLevel = process.env.LOG_LEVEL || 'INFO'; + } + + log(level, category, message, metadata = {}) { + if (!this.enabled) return; + + const timestamp = new Date().toISOString(); + const logEntry = { + timestamp, + level, + category, + message, + ...metadata + }; + + const formattedMessage = `[${timestamp}] [${level}] [${category}] ${message}`; + + switch (level) { + case LOG_LEVELS.ERROR: + console.error(formattedMessage, metadata); + break; + case LOG_LEVELS.WARN: + console.warn(formattedMessage, metadata); + break; + case LOG_LEVELS.INFO: + console.info(formattedMessage, metadata); + break; + case LOG_LEVELS.DEBUG: + console.log(formattedMessage, metadata); + break; + default: + console.log(formattedMessage, metadata); + } + + return logEntry; + } + + // API Request logging + apiRequest(method, endpoint, payload = {}) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.API, `${method} ${endpoint}`, { + method, + endpoint, + payloadSize: JSON.stringify(payload).length + }); + } + + apiResponse(endpoint, statusCode, duration) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.API, `Response ${endpoint}`, { + endpoint, + statusCode, + duration: `${duration}ms` + }); + } + + apiError(endpoint, error) { + return this.log(LOG_LEVELS.ERROR, LOG_CATEGORIES.API, `API Error ${endpoint}`, { + endpoint, + error: error.message, + stack: error.stack + }); + } + + // AI Operations logging + aiRequest(operation, model, tokens = 0) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.AI, `AI Request: ${operation}`, { + operation, + model, + tokens + }); + } + + aiResponse(operation, success, duration) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.AI, `AI Response: ${operation}`, { + operation, + success, + duration: `${duration}ms` + }); + } + + aiError(operation, error) { + return this.log(LOG_LEVELS.ERROR, LOG_CATEGORIES.AI, `AI Error: ${operation}`, { + operation, + error: error.message + }); + } + + // UI Events logging + uiEvent(action, component, details = {}) { + return this.log(LOG_LEVELS.DEBUG, LOG_CATEGORIES.UI, `UI Event: ${action}`, { + action, + component, + ...details + }); + } + + // Storage operations + storageWrite(operation, journalId) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.STORAGE, `Storage Write: ${operation}`, { + operation, + journalId + }); + } + + storageRead(operation, journalId) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.STORAGE, `Storage Read: ${operation}`, { + operation, + journalId + }); + } + + storageError(operation, error) { + return this.log(LOG_LEVELS.ERROR, LOG_CATEGORIES.STORAGE, `Storage Error: ${operation}`, { + operation, + error: error.message + }); + } + + // Export operations + exportOperation(type, format, size) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.EXPORT, `Export: ${type}`, { + type, + format, + size: `${size} bytes` + }); + } + + // System events + systemStart(port) { + return this.log(LOG_LEVELS.INFO, LOG_CATEGORIES.SYSTEM, 'Application started', { + port, + nodeVersion: process.version, + platform: process.platform + }); + } + + systemError(message, error) { + return this.log(LOG_LEVELS.ERROR, LOG_CATEGORIES.SYSTEM, message, { + error: error.message, + stack: error.stack + }); + } + + // Generic logging methods + error(category, message, metadata = {}) { + return this.log(LOG_LEVELS.ERROR, category, message, metadata); + } + + warn(category, message, metadata = {}) { + return this.log(LOG_LEVELS.WARN, category, message, metadata); + } + + info(category, message, metadata = {}) { + return this.log(LOG_LEVELS.INFO, category, message, metadata); + } + + debug(category, message, metadata = {}) { + return this.log(LOG_LEVELS.DEBUG, category, message, metadata); + } +} + +// Export singleton instance +const logger = new Logger(); + +module.exports = { + logger, + LOG_LEVELS, + LOG_CATEGORIES +}; diff --git a/views/page.js b/views/page.js index 5cd8bb4..5da2316 100644 --- a/views/page.js +++ b/views/page.js @@ -5,7 +5,7 @@ const { getFooter } = require('./footer'); function getHead(){ return ` - + @@ -32,7 +32,7 @@ function getBody(){ + getFooter() + ` - +