/** * Shared rendering utilities used by the Rendering node and output views. * Pure functions only; no React or node-specific logic. */ /** Extract ... blocks from HTML/markdown; return main (with blocks removed) and think for collapsible. */ export function parseThinkSections(html: string): { main: string; think: string } { const thinkRegex = /([\s\S]*?)<\/think>/gi const thinkParts: string[] = [] let match while ((match = thinkRegex.exec(html)) !== null) thinkParts.push(match[1].trim()) const think = thinkParts.join('\n\n') const main = html.replace(thinkRegex, '').replace(/\n{3,}/g, '\n\n').trim() return { main, think } } /** Process SVG HTML for viewport display (aspect ratio, fill container). */ export function processSvgDisplay(html: string): string { let out = html out = out.replace(/\bpreserveAspectRatio\s*=\s*["']none["']/gi, 'preserveAspectRatio="xMidYMid meet"') out = out.replace(/\bwidth\s*=\s*["'][^"']*["']/i, 'width="100%"') out = out.replace(/\bheight\s*=\s*["'][^"']*["']/i, 'height="100%"') out = out.replace(/\bstyle\s*=\s*["']([^"']*)["']/i, (_, style) => { const overridden = style.replace(/\b(width|height):[^;]+/gi, '$1:100%') return `style="${overridden}"` }) return out } /** Strip Nunjucks/template syntax for raw view. */ export function stripTemplateSyntax(text: string): string { return text .replace(/\{%[\s\S]*?%\}/g, '') .replace(/\{\{[\s\S]*?\}\}/g, '') .replace(/\{#[\s\S]*?#\}/g, '') .replace(/(\r?\n)\s*(\r?\n)/g, '$1$2') .replace(/^\s*\n|\n\s*$/g, (m) => (m === '\n' ? '\n' : '')) .trim() }