// ================================================================================ // CODE VED - ADVANCED MARKDOWN & MATH RENDERER (markdownRenderer.js) // Engineered by Divy Patel | Modular Architecture // // This module is the "Visual Engine" of the AI. It handles: // 1. Markdown parsing (via marked.js) // 2. Syntax Highlighting (via highlight.js) // 3. Complex Math Formulas (via MathJax) with pre/post-processing to prevent breaking // 4. Mermaid Diagrams rendering // 5. AI "Thinking Process" collapsible UI // 6. Code block copy buttons // ================================================================================ (function() { 'use strict'; // ---------------------------------------------------------------------------- // 1. INITIALIZATION & CONFIGURATION // ---------------------------------------------------------------------------- // Initialize Mermaid (Diagrams) function initMermaid() { if (typeof mermaid !== 'undefined') { mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose', fontFamily: 'Inter, sans-serif' }); } } // Configure Marked.js (Markdown Parser) function initMarked() { if (typeof marked === 'undefined') return; const renderer = new marked.Renderer(); // Custom Code Block Renderer (Handles Syntax Highlighting & Mermaid) renderer.code = function(code, language, isEscaped) { // Handle Mermaid Diagrams if (language === 'mermaid') { return `
${code}
`; } // Handle Syntax Highlighting let highlightedCode = code; const langClass = language ? `language-${language}` : ''; if (language && typeof hljs !== 'undefined' && hljs.getLanguage(language)) { try { highlightedCode = hljs.highlight(code, { language: language, ignoreIllegals: true }).value; } catch (e) { highlightedCode = hljs.highlightAuto(code).value; } } else if (typeof hljs !== 'undefined') { highlightedCode = hljs.highlightAuto(code).value; } // SVG Icons for Copy Button const copyIcon = ``; // Return formatted code block with a copy button return `
${highlightedCode}
`; }; // Apply the custom renderer to marked marked.use({ renderer: renderer, gfm: true, breaks: true, pedantic: false, mangle: false, headerIds: false }); } // ---------------------------------------------------------------------------- // 2. MATHJAX PRE-PROCESSING & POST-PROCESSING (The Secret Sauce) // ---------------------------------------------------------------------------- /* * WHY THIS IS NEEDED: * Markdown parsers (like marked) often break LaTeX math syntax. For example, * underscores (_) in math formulas get converted to italics (), and * asterisks (*) get converted to bold (). * To prevent this, we temporarily hide math blocks using unique placeholders, * parse the markdown, and then put the math blocks back! */ function preprocessMath(text) { const mathBlocks = {}; let counter = 0; function createPlaceholder(match) { const id = `@@MATHBLOCK_${counter++}@@`; mathBlocks[id] = match; return id; } // 1. Display Math: $$ ... $$ text = text.replace(/\$\$([\s\S]+?)\$\$/g, createPlaceholder); // 2. Display Math: \[ ... \] text = text.replace(/\\\[([\s\S]+?)\\\]/g, createPlaceholder); // 3. Inline Math: $ ... $ (Ensuring it doesn't match currency or empty spaces) text = text.replace(/(?thought\s*<\|channel\|>/gi, "\n") .replace(/<\|channel\|>answer\s*<\|channel\|>/gi, "\n\n") .replace(/<\|im_start\|>thought/gi, "\n") .replace(/<\|im_end\|>/gi, "\n\n"); const thinkRegex = /([\s\S]*?)(?:<\/think>|$)/i; const thinkMatch = normalizedText.match(thinkRegex); let thinkHtml = ''; let mainText = normalizedText; if (thinkMatch) { const thinkContent = thinkMatch[1].trim(); // Parse the thinking content as markdown too, for better readability const parsedThinkContent = typeof marked !== 'undefined' ? marked.parse(thinkContent) : thinkContent; thinkHtml = `
Thinking Process
${parsedThinkContent}
`; // Remove the think block from the main text mainText = normalizedText.replace(thinkRegex, '').trim(); } return { thinkHtml, mainText }; } // ---------------------------------------------------------------------------- // 4. POST-RENDERING TRIGGERS (MathJax & Mermaid) // ---------------------------------------------------------------------------- async function triggerMathJax(container) { if (window.MathJax && MathJax.typesetPromise) { try { // Clear previous math rendering in this container to prevent errors MathJax.typesetClear([container]); // Render new math await MathJax.typesetPromise([container]); } catch (err) { console.warn('MathJax Rendering Warning:', err); } } } async function triggerMermaid(container) { if (typeof mermaid === 'undefined') return; const diagrams = container.querySelectorAll('.mermaid'); if (diagrams.length === 0) return; try { // mermaid.run is the modern way to render specific nodes await mermaid.run({ nodes: diagrams }); } catch (err) { console.warn('Mermaid Rendering Warning:', err); // If mermaid fails, it might leave broken SVGs. We clean them up. diagrams.forEach(d => { if (!d.querySelector('svg')) { d.innerHTML = `⚠️ Invalid Mermaid Syntax`; } }); } } // ---------------------------------------------------------------------------- // 5. MAIN RENDERING ENGINE (Exposed to script.js) // ---------------------------------------------------------------------------- /** * The core function called by script.js to render AI responses. * @param {string} fullText - The raw text from the AI. * @param {boolean} isProcessing - True if the AI is still streaming. * @param {HTMLElement} container - The DOM element to inject the HTML into. */ async function parseAndRender(fullText, isProcessing, container) { if (!container) return; // Step 1: Extract and format "Thinking" blocks const { thinkHtml, mainText } = processThinkingBlocks(fullText); let finalHtml = thinkHtml; // Step 2: Process the main text if (mainText) { // 2a. Hide Math formulas so Markdown parser doesn't break them const { text: safeText, mathBlocks } = preprocessMath(mainText); // 2b. Parse Markdown to HTML let parsedHtml = ''; if (typeof marked !== 'undefined') { parsedHtml = marked.parse(safeText); } else { parsedHtml = safeText.replace(/\n/g, '
'); // Fallback } // 2c. Put Math formulas back parsedHtml = postprocessMath(parsedHtml, mathBlocks); // 2d. Wrap in a class for MathJax targeting finalHtml += `
${parsedHtml}
`; } // Step 3: Add blinking cursor if AI is still generating if (isProcessing) { finalHtml += ``; } // Step 4: Inject into DOM container.innerHTML = finalHtml; // Step 5: Trigger external renderers (MathJax & Mermaid) // We only trigger them fully when processing is done to save performance, // but we do a lightweight pass during streaming. if (!isProcessing) { await triggerMathJax(container); await triggerMermaid(container); } else { // Lightweight MathJax trigger during streaming (optional, prevents lag) // triggerMathJax(container); } } // ---------------------------------------------------------------------------- // 6. INITIALIZATION & EXPORT // ---------------------------------------------------------------------------- function init() { initMermaid(); initMarked(); console.log('[MarkdownRenderer] Engine initialized successfully.'); } // Expose the API to the global window object so script.js can use it window.MarkdownRenderer = { parseAndRender: parseAndRender, init: init }; // Auto-initialize when this script loads init(); })();