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:
parent
89c10b14a7
commit
86eb68c0e6
55
README.md
55
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
|
- **Rephrase**: Improve clarity and style of selected text
|
||||||
- **Analyze**: Detect inconsistencies and duplicates in documents
|
- **Analyze**: Detect inconsistencies and duplicates in documents
|
||||||
- **Advice**: Get constructive improvement suggestions
|
- **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
|
- Up to 10 iterations of progressive enhancement
|
||||||
- Real-time streaming feedback
|
- Real-time streaming feedback with section tracking
|
||||||
- Precision control for creative vs. conservative improvements
|
- Precision control for creative vs. conservative improvements (30-90%)
|
||||||
|
- Automatically avoids re-modifying recently enhanced sections
|
||||||
|
|
||||||
### User Interface
|
### User Interface
|
||||||
- Dark/Light theme with preference persistence
|
- Dark/Light theme with preference persistence
|
||||||
@ -95,10 +98,17 @@ npm start
|
|||||||
### Using AI Features
|
### Using AI Features
|
||||||
1. **Rephrase**: Select text → click "Rephrase" button
|
1. **Rephrase**: Select text → click "Rephrase" button
|
||||||
2. **Analysis**: Click "Inconsistencies", "Duplicates", or "Advice" buttons
|
2. **Analysis**: Click "Inconsistencies", "Duplicates", or "Advice" buttons
|
||||||
3. **Enhanced Mode**:
|
3. **Enhanced Mode (Smart Adaptive)**:
|
||||||
- Choose iterations (1-10) and precision (30-90%)
|
- Choose number of iterations (1-10)
|
||||||
|
- Select precision level (30-90%)
|
||||||
- Click "Enhanced Mode"
|
- 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
|
### Keyboard Shortcuts
|
||||||
- `Ctrl+S` - Save journal
|
- `Ctrl+S` - Save journal
|
||||||
@ -149,6 +159,39 @@ conception-assistant/
|
|||||||
| 70% | Conservative | Primarily based on existing content | Document refinement |
|
| 70% | Conservative | Primarily based on existing content | Document refinement |
|
||||||
| 90% | Very Precise | Only obvious deductions | Technical finalization |
|
| 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
|
## Technology Stack
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|||||||
19
app.js
19
app.js
@ -1,5 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { logger } = require('./utils/logger');
|
||||||
|
|
||||||
const indexRoutes = require('./routes/index');
|
const indexRoutes = require('./routes/index');
|
||||||
const apiRoutes = require('./routes/api');
|
const apiRoutes = require('./routes/api');
|
||||||
@ -15,13 +16,28 @@ app.use(express.urlencoded({ extended: true }));
|
|||||||
|
|
||||||
app.use('/assets', express.static(path.join(__dirname, 'assets')));
|
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 fs = require('fs');
|
||||||
const dataDir = path.join(__dirname, 'data');
|
const dataDir = path.join(__dirname, 'data');
|
||||||
if (!fs.existsSync(dataDir)) {
|
if (!fs.existsSync(dataDir)) {
|
||||||
fs.mkdirSync(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('/', indexRoutes);
|
||||||
app.use('/api', apiRoutes);
|
app.use('/api', apiRoutes);
|
||||||
app.use('/api/templates', templatesRoutes);
|
app.use('/api/templates', templatesRoutes);
|
||||||
@ -29,5 +45,6 @@ app.use('/api/export', exportRoutes);
|
|||||||
app.use('/api/ai', aiRoutes);
|
app.use('/api/ai', aiRoutes);
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
|
logger.systemStart(port);
|
||||||
console.log(`Server running on port ${port}`);
|
console.log(`Server running on port ${port}`);
|
||||||
});
|
});
|
||||||
@ -16,7 +16,7 @@
|
|||||||
--transition: all 0.3s ease;
|
--transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mode sombre */
|
/* Dark mode */
|
||||||
body.dark-theme {
|
body.dark-theme {
|
||||||
--primary-color: #ecf0f1;
|
--primary-color: #ecf0f1;
|
||||||
--secondary-color: #3498db;
|
--secondary-color: #3498db;
|
||||||
@ -664,7 +664,7 @@ section h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#journal-editor:empty::before {
|
#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);
|
color: var(--text-light);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|||||||
103
assets/js/app.js
103
assets/js/app.js
@ -14,11 +14,30 @@ class ConceptionAssistant {
|
|||||||
async init() {
|
async init() {
|
||||||
this.setupEditor();
|
this.setupEditor();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
this.setupMessageListener();
|
||||||
await this.loadJournalList();
|
await this.loadJournalList();
|
||||||
this.generateTOC();
|
this.generateTOC();
|
||||||
this.updateStatistics();
|
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() {
|
setupEditor() {
|
||||||
this.editor = document.getElementById('journal-editor');
|
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() {
|
generateTOC() {
|
||||||
// Save cursor position
|
// Save cursor position
|
||||||
const savedRange = this.saveSelection();
|
const savedRange = this.saveSelection();
|
||||||
@ -477,7 +509,8 @@ class ConceptionAssistant {
|
|||||||
|
|
||||||
// Ajouter l'élément avec indentation CSS
|
// Ajouter l'élément avec indentation CSS
|
||||||
const indent = (item.level - 1) * 1; // 1rem par niveau
|
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;
|
previousLevel = item.level;
|
||||||
}
|
}
|
||||||
@ -1100,10 +1133,14 @@ class ConceptionAssistant {
|
|||||||
progressFill.style.width = `${progressPercent}%`;
|
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 = `
|
const iterationHTML = `
|
||||||
<div style="background: var(--surface-color); border-left: 4px solid var(--primary-color); padding: 1rem; border-radius: 8px; margin: 0.5rem 0;">
|
<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)}
|
${this.formatAIResponse(data.explanation)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -1312,6 +1349,45 @@ class ConceptionAssistant {
|
|||||||
this.editor.contentEditable = true;
|
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') {
|
showNotification(message, type = 'success') {
|
||||||
const notification = document.createElement('div');
|
const notification = document.createElement('div');
|
||||||
notification.className = `notification ${type}`;
|
notification.className = `notification ${type}`;
|
||||||
@ -1402,9 +1478,26 @@ class ConceptionAssistant {
|
|||||||
// Render diagram
|
// Render diagram
|
||||||
mermaid.render(uniqueId + '-svg', mermaidCode).then(({ svg }) => {
|
mermaid.render(uniqueId + '-svg', mermaidCode).then(({ svg }) => {
|
||||||
mermaidDiv.innerHTML = svg;
|
mermaidDiv.innerHTML = svg;
|
||||||
}).catch(err => {
|
}).catch(async (err) => {
|
||||||
console.warn('Mermaid rendering error:', 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) {
|
} catch (error) {
|
||||||
console.warn('Mermaid processing error:', error);
|
console.warn('Mermaid processing error:', error);
|
||||||
|
|||||||
253
routes/ai.js
253
routes/ai.js
@ -1,6 +1,7 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const { logger } = require('../utils/logger');
|
||||||
require('dotenv').config({ path: './config/.env' });
|
require('dotenv').config({ path: './config/.env' });
|
||||||
|
|
||||||
// Mistral AI Configuration
|
// Mistral AI Configuration
|
||||||
@ -88,10 +89,15 @@ async function callMistralAPI(messages, temperature = null) {
|
|||||||
|
|
||||||
// POST /api/ai/rephrase - Rephrase text
|
// POST /api/ai/rephrase - Rephrase text
|
||||||
router.post('/rephrase', checkAIEnabled, async (req, res) => {
|
router.post('/rephrase', checkAIEnabled, async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { text, context = '' } = req.body;
|
const { text, context = '' } = req.body;
|
||||||
|
|
||||||
|
logger.aiRequest('rephrase', MISTRAL_MODEL, text.length);
|
||||||
|
|
||||||
if (!text || text.trim().length === 0) {
|
if (!text || text.trim().length === 0) {
|
||||||
|
logger.aiError('rephrase', new Error('Text to rephrase is required'));
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Text to rephrase is required'
|
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
|
const result = await callMistralAPI(messages, 0.2); // Precise rephrasing
|
||||||
|
|
||||||
|
logger.aiResponse('rephrase', true, Date.now() - startTime);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@ -130,7 +138,7 @@ router.post('/rephrase', checkAIEnabled, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Rephrasing error:', error);
|
logger.aiError('rephrase', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Error during rephrasing: ' + error.message
|
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
|
// POST /api/ai/check-inconsistencies - Check for inconsistencies
|
||||||
router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => {
|
router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { content } = req.body;
|
const { content } = req.body;
|
||||||
|
|
||||||
|
logger.aiRequest('check-inconsistencies', MISTRAL_MODEL, content.length);
|
||||||
|
|
||||||
if (!content || content.trim().length === 0) {
|
if (!content || content.trim().length === 0) {
|
||||||
|
logger.aiError('check-inconsistencies', new Error('Content to analyze is required'));
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Content to analyze is required'
|
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
|
const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis
|
||||||
|
|
||||||
|
logger.aiResponse('check-inconsistencies', true, Date.now() - startTime);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@ -179,7 +194,7 @@ router.post('/check-inconsistencies', checkAIEnabled, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Inconsistencies analysis error:', error);
|
logger.aiError('check-inconsistencies', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Error during analysis: ' + error.message
|
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
|
// POST /api/ai/check-duplications - Check for duplications
|
||||||
router.post('/check-duplications', checkAIEnabled, async (req, res) => {
|
router.post('/check-duplications', checkAIEnabled, async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { content } = req.body;
|
const { content } = req.body;
|
||||||
|
|
||||||
|
logger.aiRequest('check-duplications', MISTRAL_MODEL, content.length);
|
||||||
|
|
||||||
if (!content || content.trim().length === 0) {
|
if (!content || content.trim().length === 0) {
|
||||||
|
logger.aiError('check-duplications', new Error('Content to analyze is required'));
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Content to analyze is required'
|
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
|
const result = await callMistralAPI(messages, 0.1); // Precise and factual analysis
|
||||||
|
|
||||||
|
logger.aiResponse('check-duplications', true, Date.now() - startTime);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@ -229,7 +251,7 @@ router.post('/check-duplications', checkAIEnabled, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Duplications analysis error:', error);
|
logger.aiError('check-duplications', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Error during analysis: ' + error.message
|
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
|
// POST /api/ai/give-advice - Give advice
|
||||||
router.post('/give-advice', checkAIEnabled, async (req, res) => {
|
router.post('/give-advice', checkAIEnabled, async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { content, domain = 'general' } = req.body;
|
const { content, domain = 'general' } = req.body;
|
||||||
|
|
||||||
|
logger.aiRequest('give-advice', MISTRAL_MODEL, content.length);
|
||||||
|
|
||||||
if (!content || content.trim().length === 0) {
|
if (!content || content.trim().length === 0) {
|
||||||
|
logger.aiError('give-advice', new Error('Content to analyze is required'));
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Content to analyze is required'
|
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
|
const result = await callMistralAPI(messages, 0.4); // Balanced advice
|
||||||
|
|
||||||
|
logger.aiResponse('give-advice', true, Date.now() - startTime);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@ -283,7 +312,7 @@ router.post('/give-advice', checkAIEnabled, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Advice error:', error);
|
logger.aiError('give-advice', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Error during analysis: ' + error.message
|
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)
|
// POST /api/ai/liberty-mode - Enhanced mode (iterative generation)
|
||||||
router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { content, iterations = 1, precision = 70, focus = 'design' } = req.body;
|
const { content, iterations = 1, precision = 70, focus = 'design' } = req.body;
|
||||||
|
|
||||||
|
logger.aiRequest('liberty-mode', MISTRAL_MODEL, content.length);
|
||||||
|
|
||||||
if (!content || content.trim().length === 0) {
|
if (!content || content.trim().length === 0) {
|
||||||
|
logger.aiError('liberty-mode', new Error('Base content is required'));
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Base content is required'
|
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 maxIterations = Math.min(parseInt(iterations), 10); // Limit to 10 iterations
|
||||||
const precisionPercent = Math.min(Math.max(parseInt(precision), 10), 100); // Between 10% and 100%
|
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
|
// Configure streaming for real-time responses
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
'Content-Type': 'text/event-stream',
|
'Content-Type': 'text/event-stream',
|
||||||
@ -315,46 +351,115 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let currentContent = content;
|
let currentContent = content;
|
||||||
|
let modifiedSections = []; // Track sections that have been modified
|
||||||
|
|
||||||
for (let i = 0; i < maxIterations; i++) {
|
for (let i = 0; i < maxIterations; i++) {
|
||||||
try {
|
try {
|
||||||
const messages = [
|
const messages = [
|
||||||
{
|
{
|
||||||
role: 'system',
|
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:
|
PRECISION RULES:
|
||||||
- At ${precisionPercent}%: You can deduce and add content up to ${precisionPercent}% based on existing information
|
- 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
|
- 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:
|
INSTRUCTIONS:
|
||||||
1. Enrich ALL of the document consistently
|
1. **ANALYZE** all sections in the document
|
||||||
2. Add sections, details, explanations, conceptual diagrams
|
2. **CHOOSE** exactly ONE section to improve (based on criteria above)
|
||||||
3. Develop existing ideas with the allowed creativity
|
3. **IDENTIFY BOUNDARIES**: Determine where the chosen section starts and ends:
|
||||||
4. Maintain logical structure
|
- 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:
|
MANDATORY RESPONSE FORMAT - Use code blocks with triple backticks:
|
||||||
|
|
||||||
First, write your comment explaining the improvements inside a code block labeled "comment${i + 1}":
|
First, write your analysis and decision inside a code block labeled "comment${i + 1}":
|
||||||
- What sections were added
|
- **Analyzed Sections**: List all major sections found
|
||||||
- What was enhanced
|
- **Selected Section**: Which section you chose to improve (exact header name)
|
||||||
- Reasoning behind changes
|
- **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:
|
Example format:
|
||||||
First block: comment${i + 1}
|
\`\`\`comment${i + 1}
|
||||||
Second block: document
|
**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}
|
Focus: ${focus}
|
||||||
Precision: ${precisionPercent}%
|
Precision: ${precisionPercent}%
|
||||||
Iteration: ${i + 1}/${maxIterations}`
|
Iteration: ${i + 1}/${maxIterations}
|
||||||
|
Previously Modified: ${modifiedSections.length > 0 ? modifiedSections.join(', ') : 'None'}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'user',
|
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
|
// Extract code blocks
|
||||||
const commentRegex = /```comment\d+\s*([\s\S]*?)```/;
|
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 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 explanation = '';
|
||||||
let newMarkdown = currentContent; // Default, keep old content
|
let newMarkdown = currentContent; // Default, keep old content
|
||||||
|
let selectedSection = '';
|
||||||
|
|
||||||
if (commentMatch && commentMatch[1]) {
|
if (commentMatch && commentMatch[1]) {
|
||||||
explanation = commentMatch[1].trim();
|
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]) {
|
if (documentMatch && documentMatch[1]) {
|
||||||
@ -399,6 +521,7 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
const iterationData = {
|
const iterationData = {
|
||||||
iteration: i + 1,
|
iteration: i + 1,
|
||||||
explanation: explanation,
|
explanation: explanation,
|
||||||
|
selectedSection: selectedSection || 'Unknown',
|
||||||
markdown: newMarkdown,
|
markdown: newMarkdown,
|
||||||
completed: false
|
completed: false
|
||||||
};
|
};
|
||||||
@ -432,8 +555,10 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
res.write(`data: ${JSON.stringify(finalData)}\n\n`);
|
res.write(`data: ${JSON.stringify(finalData)}\n\n`);
|
||||||
res.end();
|
res.end();
|
||||||
|
|
||||||
|
logger.aiResponse('liberty-mode', true, Date.now() - startTime);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Enhanced mode error:', error);
|
logger.aiError('liberty-mode', error);
|
||||||
|
|
||||||
const errorData = {
|
const errorData = {
|
||||||
error: 'Error during generation: ' + error.message,
|
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
|
// GET /api/ai/status - AI status
|
||||||
router.get('/status', (req, res) => {
|
router.get('/status', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@ -3,6 +3,7 @@ const router = express.Router();
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
const { logger } = require('../utils/logger');
|
||||||
|
|
||||||
// Import export module
|
// Import export module
|
||||||
const exportRouter = require('./export');
|
const exportRouter = require('./export');
|
||||||
@ -140,30 +141,62 @@ function deteMd(id) {
|
|||||||
|
|
||||||
// GET /api/journals - Get all journals
|
// GET /api/journals - Get all journals
|
||||||
router.get('/journals', (req, res) => {
|
router.get('/journals', (req, res) => {
|
||||||
|
try {
|
||||||
|
logger.storageRead('get-all-journals', 'all');
|
||||||
|
const data = readMd();
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
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
|
// POST /api/journals - Create a new journal
|
||||||
router.post('/journals', (req, res) => {
|
router.post('/journals', (req, res) => {
|
||||||
|
try {
|
||||||
const { content } = req.body;
|
const { content } = req.body;
|
||||||
|
|
||||||
|
const result = createMd(content);
|
||||||
|
logger.storageWrite('create-journal', result.uuid);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
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
|
// GET /api/journals/:id - Get a specific journal
|
||||||
router.get('/journals/:id', (req, res) => {
|
router.get('/journals/:id', (req, res) => {
|
||||||
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
|
logger.storageRead('get-journal', id);
|
||||||
|
const data = readMd(id);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
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
|
// PUT /api/journals/:id - Update a journal
|
||||||
@ -175,11 +208,14 @@ router.put('/journals/:id', (req, res) => {
|
|||||||
let result;
|
let result;
|
||||||
if (content) {
|
if (content) {
|
||||||
// Complete content update
|
// Complete content update
|
||||||
|
logger.storageWrite('update-journal-content', id);
|
||||||
result = updateMd(id, content);
|
result = updateMd(id, content);
|
||||||
} else if (modifications) {
|
} else if (modifications) {
|
||||||
// Partial modifications (for future compatibility)
|
// Partial modifications (for future compatibility)
|
||||||
|
logger.storageWrite('update-journal-modifications', id);
|
||||||
result = modifMd(id, modifications);
|
result = modifMd(id, modifications);
|
||||||
} else {
|
} else {
|
||||||
|
logger.storageError('update-journal', new Error('Content or modifications required'));
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Content or modifications required'
|
error: 'Content or modifications required'
|
||||||
@ -191,6 +227,7 @@ router.put('/journals/:id', (req, res) => {
|
|||||||
data: result
|
data: result
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.storageError('update-journal', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message
|
error: error.message
|
||||||
@ -200,12 +237,23 @@ router.put('/journals/:id', (req, res) => {
|
|||||||
|
|
||||||
// DELETE /api/journals/:id - Delete a journal
|
// DELETE /api/journals/:id - Delete a journal
|
||||||
router.delete('/journals/:id', (req, res) => {
|
router.delete('/journals/:id', (req, res) => {
|
||||||
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
|
logger.storageWrite('delete-journal', id);
|
||||||
|
const data = deteMd(id);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
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
|
// Integrate export routes
|
||||||
|
|||||||
@ -4,12 +4,17 @@ const puppeteer = require('puppeteer');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const archiver = require('archiver');
|
const archiver = require('archiver');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { logger } = require('../utils/logger');
|
||||||
|
|
||||||
// POST /api/export/pdf - Generate PDF from markdown content
|
// POST /api/export/pdf - Generate PDF from markdown content
|
||||||
router.post('/pdf', async (req, res) => {
|
router.post('/pdf', async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
const { content, title = 'Design Journal' } = req.body;
|
const { content, title = 'Design Journal' } = req.body;
|
||||||
|
|
||||||
|
logger.exportOperation('pdf', 'application/pdf', content.length);
|
||||||
|
|
||||||
if (!content || content.trim() === '') {
|
if (!content || content.trim() === '') {
|
||||||
|
logger.error('EXPORT', 'PDF export failed: Content required');
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Content required to generate PDF'
|
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-Disposition', `attachment; filename="${sanitizeFilename(title)}.pdf"`);
|
||||||
res.setHeader('Content-Length', pdfBuffer.length);
|
res.setHeader('Content-Length', pdfBuffer.length);
|
||||||
|
|
||||||
|
logger.info('EXPORT', `PDF generated successfully in ${Date.now() - startTime}ms`, { size: pdfBuffer.length, title });
|
||||||
|
|
||||||
res.send(pdfBuffer);
|
res.send(pdfBuffer);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('PDF generation error:', error);
|
logger.error('EXPORT', 'PDF generation error', { error: error.message, title });
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
@ -513,9 +520,13 @@ function sanitizeFilename(filename) {
|
|||||||
|
|
||||||
// POST /api/export/web - Generate ZIP with HTML + CSS
|
// POST /api/export/web - Generate ZIP with HTML + CSS
|
||||||
router.post('/web', async (req, res) => {
|
router.post('/web', async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
const { content, title = 'Design Journal' } = req.body;
|
const { content, title = 'Design Journal' } = req.body;
|
||||||
|
|
||||||
|
logger.exportOperation('web-zip', 'application/zip', content.length);
|
||||||
|
|
||||||
if (!content || content.trim() === '') {
|
if (!content || content.trim() === '') {
|
||||||
|
logger.error('EXPORT', 'Web export failed: Content required');
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Content required to generate web export'
|
error: 'Content required to generate web export'
|
||||||
@ -661,8 +672,10 @@ body {
|
|||||||
// Finalize archive
|
// Finalize archive
|
||||||
await archive.finalize();
|
await archive.finalize();
|
||||||
|
|
||||||
|
logger.info('EXPORT', `Web ZIP generated successfully in ${Date.now() - startTime}ms`, { title });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Web export error:', error);
|
logger.error('EXPORT', 'Web export error', { error: error.message, title });
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Error during web export'
|
error: 'Error during web export'
|
||||||
|
|||||||
@ -127,9 +127,57 @@ router.post('/present', (req, res) => {
|
|||||||
// Render diagram
|
// Render diagram
|
||||||
mermaid.render(uniqueId + '-svg', mermaidCode).then(function(result) {
|
mermaid.render(uniqueId + '-svg', mermaidCode).then(function(result) {
|
||||||
mermaidDiv.innerHTML = result.svg;
|
mermaidDiv.innerHTML = result.svg;
|
||||||
}).catch(function(err) {
|
}).catch(async function(err) {
|
||||||
console.warn('Mermaid rendering error:', 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) {
|
} catch (error) {
|
||||||
console.warn('Mermaid processing error:', error);
|
console.warn('Mermaid processing error:', error);
|
||||||
|
|||||||
193
utils/logger.js
Normal file
193
utils/logger.js
Normal 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
|
||||||
|
};
|
||||||
@ -5,7 +5,7 @@ const { getFooter } = require('./footer');
|
|||||||
function getHead(){
|
function getHead(){
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@ -32,7 +32,7 @@ function getBody(){
|
|||||||
+ getFooter() +
|
+ getFooter() +
|
||||||
`
|
`
|
||||||
<!-- Bouton scroll to top -->
|
<!-- 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>
|
<script src="/assets/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user