feat: Enhanced Mode v2 with Smart Adaptive strategy, section-based editing, and comprehensive logging
## Enhanced Mode v2 - Smart Section-Based Editing (routes/ai.js) ### Server-Side Section Replacement Architecture - Add cleanMarkdownFromTitle() to normalize section titles for matching - Add extractHeaders() to list all document sections for debugging - Add replaceSection() to surgically replace only modified sections - AI now returns ONLY the modified section, not entire document - Server automatically replaces section in original document - Automatic header level correction if AI changes ## to ### or vice versa - Section boundary detection based on header hierarchy ### Enhanced Prompt and Response Format - Modified prompt to explicitly request ONLY modified section - New closing format: document (mandatory) - Added fallback regex for old format with warnings - Explicit rules: keep exact header level (## stays ##) - Clear section boundary definition in prompt - Examples with proper formatting guidelines ### Comprehensive Logging System - Log all API requests with method, endpoint, payload size - Log AI responses with length and preview - Log section matching and replacement operations - Log header level corrections - Log section not found errors with available sections list - Track modified sections across iterations ## AI Button Mutex and Preview Mode Controls (assets/js/app.js) ### AI Button Mutex (Prevent API Overload) - Add disableAIButtons() to disable all AI buttons during operations - Add enableAIButtons() to re-enable after completion or error - Disable all AI buttons at start of any AI operation - Re-enable in finally blocks to ensure cleanup even on errors - Re-enable on validation failures (e.g., no text selected for rephrase) - Re-enable when user clicks Apply/Cancel in rephrase mode ### Preview Mode Button Restrictions - Disable Preview button during Enhanced Mode operation - Disable all AI buttons in preview mode (rephrase, inconsistencies, duplications, advice, liberty) - Disable Save and Load buttons in preview mode - Re-enable all buttons when returning to edit mode - Proper cleanup with finally blocks ## Mermaid Auto-Fix System - Complete Removal ### Removed from assets/js/app.js - Remove mermaidFixAttempts Set from constructor - Remove setupMessageListener() and postMessage handler - Remove fixMermaidDiagramBackground() function - Simplify Mermaid error display to inline messages only - Remove hash-based tracking mechanism ### Removed from routes/index.js (Present Mode) - Remove entire auto-fix fetch and retry logic - Remove status div updates and fix notifications - Remove postMessage to parent window - Simplify to display styled error message only ### Current Behavior - Preview mode: Shows inline error with simple message - Present mode: Shows styled error box with instructions - No automatic fix attempts - manual correction only ## Additional Improvements - Clean markdown formatting (##, **, etc.) from section titles in UI badges - Proper section title matching ignoring markdown syntax - Enhanced error handling with detailed logging - Better user feedback during Enhanced Mode iterations This release improves Enhanced Mode reliability, prevents API overload through button mutex, simplifies Mermaid error handling, and adds comprehensive logging for debugging.
This commit is contained in:
parent
86eb68c0e6
commit
e3debb3f71
191
assets/js/app.js
191
assets/js/app.js
@ -14,30 +14,11 @@ 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');
|
||||||
|
|
||||||
@ -928,9 +909,13 @@ class ConceptionAssistant {
|
|||||||
const selection = window.getSelection().toString().trim();
|
const selection = window.getSelection().toString().trim();
|
||||||
const fullContent = this.editor.innerText;
|
const fullContent = this.editor.innerText;
|
||||||
|
|
||||||
|
// Disable all AI buttons to prevent API overload
|
||||||
|
this.disableAIButtons();
|
||||||
|
|
||||||
// Check conditions based on action
|
// Check conditions based on action
|
||||||
if (action === 'rephrase' && !selection) {
|
if (action === 'rephrase' && !selection) {
|
||||||
this.showAIFeedback('Please select text to rephrase');
|
this.showAIFeedback('Please select text to rephrase');
|
||||||
|
this.enableAIButtons();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -993,8 +978,14 @@ class ConceptionAssistant {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
// Add event listeners for buttons
|
// Add event listeners for buttons
|
||||||
document.getElementById('validate-rephrase')?.addEventListener('click', () => this.validateRephrase());
|
document.getElementById('validate-rephrase')?.addEventListener('click', () => {
|
||||||
document.getElementById('cancel-rephrase')?.addEventListener('click', () => this.clearFeedback());
|
this.validateRephrase();
|
||||||
|
this.enableAIButtons();
|
||||||
|
});
|
||||||
|
document.getElementById('cancel-rephrase')?.addEventListener('click', () => {
|
||||||
|
this.clearFeedback();
|
||||||
|
this.enableAIButtons();
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'inconsistencies':
|
case 'inconsistencies':
|
||||||
@ -1016,6 +1007,14 @@ class ConceptionAssistant {
|
|||||||
// Save state before AI modifications
|
// Save state before AI modifications
|
||||||
this.saveState(true);
|
this.saveState(true);
|
||||||
|
|
||||||
|
// Disable preview button during enhanced mode
|
||||||
|
const previewBtn = document.getElementById('preview-toggle');
|
||||||
|
if (previewBtn) {
|
||||||
|
previewBtn.disabled = true;
|
||||||
|
previewBtn.style.opacity = '0.5';
|
||||||
|
previewBtn.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
|
||||||
const count = document.getElementById('liberty-repeat-count')?.value || 3;
|
const count = document.getElementById('liberty-repeat-count')?.value || 3;
|
||||||
const precision = document.getElementById('liberty-precision')?.value || 70;
|
const precision = document.getElementById('liberty-precision')?.value || 70;
|
||||||
|
|
||||||
@ -1034,12 +1033,24 @@ class ConceptionAssistant {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
// Use EventSource for streaming
|
// Use EventSource for streaming
|
||||||
await this.handleLibertyModeStreaming(fullContent, count, precision);
|
try {
|
||||||
|
await this.handleLibertyModeStreaming(fullContent, count, precision);
|
||||||
|
} finally {
|
||||||
|
// Re-enable preview button after completion (even on error)
|
||||||
|
if (previewBtn) {
|
||||||
|
previewBtn.disabled = false;
|
||||||
|
previewBtn.style.opacity = '1';
|
||||||
|
previewBtn.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('AI Error:', error);
|
console.error('AI Error:', error);
|
||||||
this.showAIFeedback(`<strong>Error</strong><br><br>An error occurred: ${error.message}<br><br>Check your connection and API configuration.`);
|
this.showAIFeedback(`<strong>Error</strong><br><br>An error occurred: ${error.message}<br><br>Check your connection and API configuration.`);
|
||||||
|
} finally {
|
||||||
|
// Re-enable all AI buttons after completion (even on error)
|
||||||
|
this.enableAIButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1324,6 +1335,44 @@ class ConceptionAssistant {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableAIButtons() {
|
||||||
|
// Disable all AI buttons to prevent API overload
|
||||||
|
const aiButtons = [
|
||||||
|
'activate-rephrase',
|
||||||
|
'check-inconsistencies',
|
||||||
|
'check-duplications',
|
||||||
|
'give-advice',
|
||||||
|
'liberty-mode'
|
||||||
|
];
|
||||||
|
aiButtons.forEach(buttonId => {
|
||||||
|
const btn = document.getElementById(buttonId);
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enableAIButtons() {
|
||||||
|
// Re-enable all AI buttons
|
||||||
|
const aiButtons = [
|
||||||
|
'activate-rephrase',
|
||||||
|
'check-inconsistencies',
|
||||||
|
'check-duplications',
|
||||||
|
'give-advice',
|
||||||
|
'liberty-mode'
|
||||||
|
];
|
||||||
|
aiButtons.forEach(buttonId => {
|
||||||
|
const btn = document.getElementById(buttonId);
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.style.opacity = '1';
|
||||||
|
btn.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ensureEditMode() {
|
ensureEditMode() {
|
||||||
// If in preview mode, force return to edit mode
|
// If in preview mode, force return to edit mode
|
||||||
if (this.isPreviewMode) {
|
if (this.isPreviewMode) {
|
||||||
@ -1349,45 +1398,6 @@ 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}`;
|
||||||
@ -1443,6 +1453,25 @@ class ConceptionAssistant {
|
|||||||
this.editor.style.border = '';
|
this.editor.style.border = '';
|
||||||
this.editor.style.borderRadius = '';
|
this.editor.style.borderRadius = '';
|
||||||
|
|
||||||
|
// Disable AI buttons and save/load buttons in preview mode
|
||||||
|
const disabledButtons = [
|
||||||
|
'activate-rephrase',
|
||||||
|
'check-inconsistencies',
|
||||||
|
'check-duplications',
|
||||||
|
'give-advice',
|
||||||
|
'liberty-mode',
|
||||||
|
'save-journal',
|
||||||
|
'load-journal'
|
||||||
|
];
|
||||||
|
disabledButtons.forEach(buttonId => {
|
||||||
|
const btn = document.getElementById(buttonId);
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Process Mermaid diagrams after rendering
|
// Process Mermaid diagrams after rendering
|
||||||
if (typeof mermaid !== 'undefined') {
|
if (typeof mermaid !== 'undefined') {
|
||||||
try {
|
try {
|
||||||
@ -1478,26 +1507,11 @@ 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(async (err) => {
|
}).catch((err) => {
|
||||||
console.warn('Mermaid rendering error:', err);
|
console.warn('Mermaid rendering error:', err);
|
||||||
|
|
||||||
// Automatically try to fix the Mermaid diagram
|
// Show error inline
|
||||||
this.showNotification('Mermaid error detected. Attempting to fix...', 'warning');
|
mermaidDiv.innerHTML = `<pre style="color: var(--accent-color); padding: 1rem; background: var(--background-color); border-radius: 6px; border-left: 4px solid var(--accent-color);">Mermaid rendering error: ${err.message}\n\nPlease check the diagram syntax in the editor.</pre>`;
|
||||||
|
|
||||||
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);
|
||||||
@ -1531,6 +1545,25 @@ class ConceptionAssistant {
|
|||||||
mainElement.classList.remove('preview-mode');
|
mainElement.classList.remove('preview-mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-enable AI buttons and save/load buttons
|
||||||
|
const enabledButtons = [
|
||||||
|
'activate-rephrase',
|
||||||
|
'check-inconsistencies',
|
||||||
|
'check-duplications',
|
||||||
|
'give-advice',
|
||||||
|
'liberty-mode',
|
||||||
|
'save-journal',
|
||||||
|
'load-journal'
|
||||||
|
];
|
||||||
|
enabledButtons.forEach(buttonId => {
|
||||||
|
const btn = document.getElementById(buttonId);
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.style.opacity = '1';
|
||||||
|
btn.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Change button
|
// Change button
|
||||||
previewBtn.innerHTML = 'Preview';
|
previewBtn.innerHTML = 'Preview';
|
||||||
previewBtn.classList.remove('secondary');
|
previewBtn.classList.remove('secondary');
|
||||||
|
|||||||
226
routes/ai.js
226
routes/ai.js
@ -36,6 +36,138 @@ const httpsAgent = new https.Agent({
|
|||||||
maxSockets: 5
|
maxSockets: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to clean markdown formatting from section titles
|
||||||
|
function cleanMarkdownFromTitle(title) {
|
||||||
|
if (!title) return title;
|
||||||
|
return title
|
||||||
|
.replace(/^#+\s*/g, '') // Remove leading # symbols
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to count headers in markdown content
|
||||||
|
function countHeaders(content) {
|
||||||
|
if (!content) return 0;
|
||||||
|
const lines = content.split('\n');
|
||||||
|
let count = 0;
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim().match(/^#{1,6}\s+/)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to extract all headers with their levels
|
||||||
|
function extractHeaders(content) {
|
||||||
|
if (!content) return [];
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const headers = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.trim().match(/^(#{1,6})\s+(.+)$/);
|
||||||
|
if (match) {
|
||||||
|
headers.push({
|
||||||
|
level: match[1].length,
|
||||||
|
title: match[2].trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to replace a section in the document
|
||||||
|
function replaceSection(originalContent, sectionTitle, newSectionContent) {
|
||||||
|
if (!originalContent || !sectionTitle || !newSectionContent) {
|
||||||
|
return originalContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = originalContent.split('\n');
|
||||||
|
let sectionStartIndex = -1;
|
||||||
|
let sectionEndIndex = -1;
|
||||||
|
let sectionLevel = -1;
|
||||||
|
let originalHeaderPrefix = '';
|
||||||
|
|
||||||
|
// Clean the search title
|
||||||
|
const cleanSectionTitle = cleanMarkdownFromTitle(sectionTitle);
|
||||||
|
|
||||||
|
// Find the section header - be more tolerant on matching
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const headerLevel = match[1].length;
|
||||||
|
const headerTitle = cleanMarkdownFromTitle(match[2].trim());
|
||||||
|
|
||||||
|
if (headerTitle === cleanSectionTitle) {
|
||||||
|
sectionStartIndex = i;
|
||||||
|
sectionLevel = headerLevel;
|
||||||
|
originalHeaderPrefix = match[1]; // Store original # count
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectionStartIndex === -1) {
|
||||||
|
// Section not found - return original content
|
||||||
|
logger.error('AI', `Section "${sectionTitle}" not found in document`);
|
||||||
|
logger.info('AI', `Available sections: ${extractHeaders(originalContent).map(h => cleanMarkdownFromTitle(h.title)).join(', ')}`);
|
||||||
|
return originalContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the header level in the new content if AI changed it
|
||||||
|
const newLines = newSectionContent.split('\n');
|
||||||
|
if (newLines.length > 0) {
|
||||||
|
const firstLineMatch = newLines[0].trim().match(/^(#{1,6})\s+(.+)$/);
|
||||||
|
if (firstLineMatch) {
|
||||||
|
const aiHeaderLevel = firstLineMatch[1].length;
|
||||||
|
if (aiHeaderLevel !== sectionLevel) {
|
||||||
|
// AI changed the header level - fix it!
|
||||||
|
logger.warn('AI', `AI changed header level from ${sectionLevel} to ${aiHeaderLevel} - fixing it`);
|
||||||
|
newLines[0] = newLines[0].replace(/^#{1,6}/, originalHeaderPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newSectionContent = newLines.join('\n');
|
||||||
|
|
||||||
|
// Find where the section ends (next header of equal or higher level)
|
||||||
|
for (let i = sectionStartIndex + 1; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
const match = line.match(/^(#{1,6})\s+/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const headerLevel = match[1].length;
|
||||||
|
if (headerLevel <= sectionLevel) {
|
||||||
|
// Found next section at same or higher level
|
||||||
|
sectionEndIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no end found, section goes to end of document
|
||||||
|
if (sectionEndIndex === -1) {
|
||||||
|
sectionEndIndex = lines.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the section
|
||||||
|
const before = lines.slice(0, sectionStartIndex);
|
||||||
|
const after = lines.slice(sectionEndIndex);
|
||||||
|
const newSection = newSectionContent.split('\n');
|
||||||
|
|
||||||
|
const result = [...before, ...newSection, ...after].join('\n');
|
||||||
|
|
||||||
|
logger.info('AI', `Replaced section "${sectionTitle}" (lines ${sectionStartIndex}-${sectionEndIndex})`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to call Mistral API with timeout handling
|
// Function to call Mistral API with timeout handling
|
||||||
async function callMistralAPI(messages, temperature = null) {
|
async function callMistralAPI(messages, temperature = null) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@ -377,23 +509,22 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
- The next header of EQUAL or HIGHER level appears
|
- The next header of EQUAL or HIGHER level appears
|
||||||
- The end of the document is reached
|
- The end of the document is reached
|
||||||
|
|
||||||
Example structure:
|
Example structure (using headers with hash symbols):
|
||||||
```
|
|
||||||
# Main Title (level 1)
|
Level 1 header: Main Title
|
||||||
content...
|
content...
|
||||||
|
|
||||||
## Section A (level 2) ← Section A starts here
|
Level 2 header: Section A ← Section A starts here
|
||||||
content A...
|
content A...
|
||||||
|
|
||||||
### Subsection A1 (level 3) ← Part of Section A
|
Level 3 header: Subsection A1 ← Part of Section A
|
||||||
content A1...
|
content A1...
|
||||||
|
|
||||||
### Subsection A2 (level 3) ← Part of Section A
|
Level 3 header: Subsection A2 ← Part of Section A
|
||||||
content A2...
|
content A2...
|
||||||
|
|
||||||
## Section B (level 2) ← Section A ends, Section B starts
|
Level 2 header: Section B ← Section A ends, Section B starts
|
||||||
content B...
|
content B...
|
||||||
```
|
|
||||||
|
|
||||||
If you choose to modify "### Subsection A1":
|
If you choose to modify "### Subsection A1":
|
||||||
- Include ONLY the content from "### Subsection A1" until "### Subsection A2"
|
- Include ONLY the content from "### Subsection A1" until "### Subsection A2"
|
||||||
@ -433,8 +564,8 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
- **Improvements Made**: Detailed list of enhancements
|
- **Improvements Made**: Detailed list of enhancements
|
||||||
- **Next Recommendations**: Sections to consider for future iterations
|
- **Next Recommendations**: Sections to consider for future iterations
|
||||||
|
|
||||||
Then, write the complete markdown document inside a code block labeled "document".
|
Then, write ONLY THE MODIFIED SECTION inside a code block labeled "document".
|
||||||
The document must include ALL sections, with ONLY the selected section modified.
|
DO NOT include the entire document - ONLY the section you improved.
|
||||||
|
|
||||||
Example format:
|
Example format:
|
||||||
\`\`\`comment${i + 1}
|
\`\`\`comment${i + 1}
|
||||||
@ -449,7 +580,35 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
\`\`\`document
|
\`\`\`document
|
||||||
[Full document with only API Documentation section improved]
|
### API Documentation
|
||||||
|
|
||||||
|
[Only the improved API Documentation section content here]
|
||||||
|
|
||||||
|
\`\`\`document
|
||||||
|
|
||||||
|
CRITICAL FORMATTING RULES - MANDATORY:
|
||||||
|
1. The document block MUST start with: \`\`\`document (three backticks + word "document")
|
||||||
|
2. The document block MUST end with: \`\`\`document (three backticks + word "document")
|
||||||
|
3. This closing tag is ESSENTIAL - without it, the content will be truncated
|
||||||
|
4. Return ONLY the modified section (from its header to the end of its content)
|
||||||
|
5. Include the section header (e.g., ### API Documentation)
|
||||||
|
6. **CRITICAL**: Keep EXACTLY the same header level (same number of #)
|
||||||
|
- If original is ### (3 hashes), your response MUST start with ###
|
||||||
|
- If original is ## (2 hashes), your response MUST start with ##
|
||||||
|
- DO NOT CHANGE: ### to ##### or ## to ###, etc.
|
||||||
|
7. Include ALL content of that section (including subsections if any)
|
||||||
|
8. DO NOT include any content from other sections
|
||||||
|
9. The server will automatically replace this section in the full document
|
||||||
|
|
||||||
|
EXAMPLE OF CORRECT FORMAT:
|
||||||
|
\`\`\`document
|
||||||
|
## My Section Title
|
||||||
|
|
||||||
|
Content here...
|
||||||
|
More content...
|
||||||
|
|
||||||
|
### Subsection if needed
|
||||||
|
Subsection content...
|
||||||
\`\`\`document
|
\`\`\`document
|
||||||
|
|
||||||
Focus: ${focus}
|
Focus: ${focus}
|
||||||
@ -468,6 +627,10 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
|
|
||||||
const result = await callMistralAPI(messages, temperature);
|
const result = await callMistralAPI(messages, temperature);
|
||||||
|
|
||||||
|
// Log raw AI response for debugging
|
||||||
|
logger.info('AI', `Raw AI response length: ${result.length} characters`);
|
||||||
|
logger.info('AI', `First 500 chars: ${result.substring(0, 500)}`);
|
||||||
|
|
||||||
// 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]*?)```document/;
|
const documentRegex = /```document\s*([\s\S]*?)```document/;
|
||||||
@ -475,10 +638,23 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
const commentMatch = result.match(commentRegex);
|
const commentMatch = result.match(commentRegex);
|
||||||
let documentMatch = result.match(documentRegex);
|
let documentMatch = result.match(documentRegex);
|
||||||
|
|
||||||
|
// Log what was matched
|
||||||
|
if (documentMatch) {
|
||||||
|
logger.info('AI', `Document block matched. Length: ${documentMatch[1].length} characters`);
|
||||||
|
logger.info('AI', `Document content preview: ${documentMatch[1].substring(0, 300)}...`);
|
||||||
|
} else {
|
||||||
|
logger.warn('AI', 'No document block found with new format, trying old format');
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback: if new format not found, try old format
|
// Fallback: if new format not found, try old format
|
||||||
if (!documentMatch) {
|
if (!documentMatch) {
|
||||||
const oldDocumentRegex = /```document\s*([\s\S]*?)```/;
|
// Try to find the last occurrence of ```document and take everything after it until the next ```
|
||||||
|
const oldDocumentRegex = /```document\s*([\s\S]*?)(?:```|$)/;
|
||||||
documentMatch = result.match(oldDocumentRegex);
|
documentMatch = result.match(oldDocumentRegex);
|
||||||
|
if (documentMatch) {
|
||||||
|
logger.info('AI', `Old format matched. Length: ${documentMatch[1].length} characters`);
|
||||||
|
logger.warn('AI', 'AI did not use the correct closing format ```document - using fallback');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let explanation = '';
|
let explanation = '';
|
||||||
@ -491,7 +667,7 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
// Extract selected section name from comment
|
// Extract selected section name from comment
|
||||||
const sectionMatch = explanation.match(/\*\*Selected Section\*\*:?\s*(.+?)(?:\n|\*\*|$)/i);
|
const sectionMatch = explanation.match(/\*\*Selected Section\*\*:?\s*(.+?)(?:\n|\*\*|$)/i);
|
||||||
if (sectionMatch && sectionMatch[1]) {
|
if (sectionMatch && sectionMatch[1]) {
|
||||||
selectedSection = sectionMatch[1].trim();
|
selectedSection = cleanMarkdownFromTitle(sectionMatch[1].trim());
|
||||||
// Add to modified sections tracker
|
// Add to modified sections tracker
|
||||||
if (!modifiedSections.includes(selectedSection)) {
|
if (!modifiedSections.includes(selectedSection)) {
|
||||||
modifiedSections.push(selectedSection);
|
modifiedSections.push(selectedSection);
|
||||||
@ -500,9 +676,27 @@ router.post('/liberty-mode', checkAIEnabled, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (documentMatch && documentMatch[1]) {
|
if (documentMatch && documentMatch[1]) {
|
||||||
newMarkdown = documentMatch[1].trim();
|
const modifiedSection = documentMatch[1].trim();
|
||||||
// Update for next iteration
|
|
||||||
currentContent = newMarkdown;
|
// Replace only the modified section in the original document
|
||||||
|
if (selectedSection) {
|
||||||
|
newMarkdown = replaceSection(currentContent, selectedSection, modifiedSection);
|
||||||
|
|
||||||
|
if (newMarkdown === currentContent) {
|
||||||
|
// Section replacement failed
|
||||||
|
logger.error('AI', `Failed to replace section "${selectedSection}" in document`);
|
||||||
|
explanation += '\n\n[ERROR: Could not find section to replace in document. Content was not updated.]';
|
||||||
|
} else {
|
||||||
|
// Section successfully replaced - update for next iteration
|
||||||
|
logger.info('AI', `Successfully replaced section "${selectedSection}"`);
|
||||||
|
currentContent = newMarkdown;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No section identified - keep original
|
||||||
|
logger.error('AI', 'No section identified for replacement');
|
||||||
|
newMarkdown = currentContent;
|
||||||
|
explanation += '\n\n[ERROR: No section identified. Content was not updated.]';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: if no code blocks found, try old format for compatibility
|
// Fallback: if no code blocks found, try old format for compatibility
|
||||||
|
|||||||
@ -127,57 +127,15 @@ 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(async function(err) {
|
}).catch(function(err) {
|
||||||
console.warn('Mermaid rendering error:', err);
|
console.warn('Mermaid rendering error:', err);
|
||||||
|
|
||||||
// Show error message with auto-fix option
|
// Show error message inline
|
||||||
mermaidDiv.innerHTML = '<div style="padding: 1.5rem; background: #fff3cd; border: 2px solid #ffc107; border-radius: 8px; margin: 1rem 0;">' +
|
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>' +
|
'<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: 0.5rem 0;"><strong>Error:</strong> ' + err.message + '</p>' +
|
||||||
'<p style="margin: 1rem 0 0.5rem 0; color: #666;">Attempting to fix automatically...</p>' +
|
'<p style="margin: 1rem 0 0.5rem 0; color: #666;">Please check the diagram syntax in the editor.</p>' +
|
||||||
'<div id="fix-status-' + uniqueId + '" style="margin-top: 0.5rem;"></div>' +
|
|
||||||
'</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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user