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
This commit is contained in:
Augustin ROUX 2025-10-15 10:21:54 +02:00
parent 89c10b14a7
commit 86eb68c0e6
10 changed files with 725 additions and 61 deletions

View File

@ -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

19
app.js
View File

@ -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}`);
});

View File

@ -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;

View File

@ -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 += `<li style="margin-left: ${indent}rem;"><a href="#${item.id}" onclick="app.scrollToHeading('${item.title.replace(/'/g, "\\'")}'); return false;">${item.title}</a>`;
const cleanTitle = this.cleanMarkdownFromTitle(item.title);
tocHtml += `<li style="margin-left: ${indent}rem;"><a href="#${item.id}" onclick="app.scrollToHeading('${item.title.replace(/'/g, "\\'")}'); return false;">${cleanTitle}</a>`;
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
? `<span style="display: inline-block; background: #2563eb; color: #ffffff; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.85rem; margin-left: 0.5rem; font-weight: 500;">${data.selectedSection}</span>`
: '';
const iterationHTML = `
<div style="background: var(--surface-color); border-left: 4px solid var(--primary-color); padding: 1rem; border-radius: 8px; margin: 0.5rem 0;">
<strong>Iteration ${data.iteration}</strong><br><br>
<strong>Iteration ${data.iteration}</strong>${sectionBadge}<br><br>
${this.formatAIResponse(data.explanation)}
</div>
`;
@ -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 = `<pre style="color: var(--accent-color); padding: 1rem; background: var(--background-color); border-radius: 6px;">Mermaid rendering error: ${err.message}</pre>`;
// 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 = `<pre style="color: var(--accent-color); padding: 1rem; background: var(--background-color); border-radius: 6px;">Mermaid rendering error: ${err.message}\n\nAutomatic fix failed. Please check the syntax manually.</pre>`;
}
});
} catch (error) {
console.warn('Mermaid processing error:', error);

View File

@ -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({

View File

@ -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) => {
try {
logger.storageRead('get-all-journals', 'all');
const data = readMd();
res.json({
success: true,
data: readMd()
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) => {
try {
const { content } = req.body;
const result = createMd(content);
logger.storageWrite('create-journal', result.uuid);
res.status(201).json({
success: true,
data: createMd(content)
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) => {
try {
const { id } = req.params;
logger.storageRead('get-journal', id);
const data = readMd(id);
res.json({
success: true,
data: readMd(id)
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) => {
try {
const { id } = req.params;
logger.storageWrite('delete-journal', id);
const data = deteMd(id);
res.json({
success: true,
data: deteMd(id)
data: data
});
} catch (error) {
logger.storageError('delete-journal', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// Integrate export routes

View File

@ -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'

View File

@ -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 = '<pre style="color: red; padding: 1rem; background: #ffe6e6; border-radius: 6px;">Mermaid rendering error: ' + err.message + '</pre>';
// Show error message with auto-fix option
mermaidDiv.innerHTML = '<div style="padding: 1.5rem; background: #fff3cd; border: 2px solid #ffc107; border-radius: 8px; margin: 1rem 0;">' +
'<h4 style="margin-top: 0; color: #856404;">[!] Mermaid Diagram Error</h4>' +
'<p style="margin: 0.5rem 0;"><strong>Error:</strong> ' + err.message + '</p>' +
'<p style="margin: 1rem 0 0.5rem 0; color: #666;">Attempting to fix automatically...</p>' +
'<div id="fix-status-' + uniqueId + '" style="margin-top: 0.5rem;"></div>' +
'</div>';
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 = '<p style="color: #28a745; font-weight: 500;">[OK] Diagram fixed! Please close this window and reopen presentation mode from the editor to see the corrected diagram.</p>';
// 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 += '<p style="color: #28a745; margin-top: 0.5rem;">Document updated in editor. You can now close this window and reopen presentation mode.</p>';
} catch (e) {
console.warn('Could not update parent window:', e);
}
}
} else {
statusDiv.innerHTML = '<p style="color: #dc3545;">[ERROR] Automatic fix failed. Please check the syntax in the editor.</p>';
}
} catch (error) {
console.error('Error fixing Mermaid:', error);
statusDiv.innerHTML = '<p style="color: #dc3545;">[ERROR] Could not fix diagram automatically. Please check your syntax in the editor.</p>';
}
});
} catch (error) {
console.warn('Mermaid processing error:', error);

193
utils/logger.js Normal file
View File

@ -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
};

View File

@ -5,7 +5,7 @@ const { getFooter } = require('./footer');
function getHead(){
return `
<!DOCTYPE html>
<html lang="fr">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -32,7 +32,7 @@ function getBody(){
+ getFooter() +
`
<!-- Bouton scroll to top -->
<button id="scroll-to-top" class="scroll-to-top" title="Retour en haut"></button>
<button id="scroll-to-top" class="scroll-to-top" title="Back to top"></button>
<script src="/assets/js/app.js"></script>
</body>
</html>