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 += `
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 = '
Error: ' + err.message + '
' + + 'Attempting to fix automatically...
' + + '' + + '[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() + ` - +