UNPKG

voyageai-cli

Version:

CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search

117 lines (98 loc) 4.11 kB
'use strict'; /** * Render an SVG string for export. * In CLI context, this generates SVG from Mermaid via Playwright. * In Electron/browser context, this serializes and cleans the DOM SVG. * * @param {object} normalized - Normalized data (must have _context = 'workflow' or provide _svgContent) * @param {object} options * @param {string} [options.background='transparent'] - 'transparent' | 'dark' | 'light' * @param {boolean} [options.includeWatermark=true] * @param {boolean} [options.fitToContent=true] * @returns {{ content: string, mimeType: string }} */ function renderSvg(normalized, options = {}) { // If raw SVG content is provided (from browser/Electron capture), clean and return it if (normalized._svgContent) { const cleaned = cleanSvg(normalized._svgContent, options); return { content: cleaned, mimeType: 'image/svg+xml' }; } // For CLI: generate SVG from Mermaid (async path handled by renderSvgAsync) throw new Error('SVG export requires either _svgContent (browser) or use renderSvgAsync (CLI)'); } /** * Async SVG render — generates SVG from Mermaid using Playwright. * @param {object} normalized * @param {object} options * @returns {Promise<{ content: string, mimeType: string }>} */ async function renderSvgAsync(normalized, options = {}) { if (normalized._svgContent) { return renderSvg(normalized, options); } // Generate Mermaid source, then render via Playwright const { workflowToMermaid } = require('./mermaid-export'); if (normalized._context !== 'workflow' && normalized._context !== 'benchmark') { throw new Error(`SVG export not supported for context: ${normalized._context}`); } const mermaidSrc = normalized._context === 'workflow' ? workflowToMermaid(normalized, options) : null; if (!mermaidSrc) { throw new Error('No Mermaid source available for SVG rendering'); } const svg = await renderMermaidToSvg(mermaidSrc, options); return { content: svg, mimeType: 'image/svg+xml' }; } /** * Render Mermaid syntax to SVG using Playwright. */ async function renderMermaidToSvg(mermaidSrc, options = {}) { const { chromium } = require('playwright'); const background = options.background || 'transparent'; const bgColor = background === 'dark' ? '#1e1e1e' : background === 'light' ? '#ffffff' : 'transparent'; const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); const html = `<!DOCTYPE html> <html><head> <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script> </head><body style="margin:0;background:${bgColor};"> <pre class="mermaid">${escapeHtml(mermaidSrc)}</pre> <script>mermaid.initialize({ startOnLoad: true, theme: '${options.theme || 'dark'}' });</script> </body></html>`; await page.setContent(html, { waitUntil: 'networkidle' }); await page.waitForSelector('svg', { timeout: 10000 }); const svg = await page.evaluate(() => { const el = document.querySelector('svg'); // Remove interactive attributes el.removeAttribute('style'); const scripts = el.querySelectorAll('script'); scripts.forEach(s => s.remove()); return el.outerHTML; }); const cleaned = cleanSvg(svg, options); return cleaned; } finally { await browser.close(); } } /** * Clean an SVG string: remove scripts, event handlers, optionally add watermark. */ function cleanSvg(svgStr, options = {}) { // Strip event handlers let svg = svgStr.replace(/\s+on\w+="[^"]*"/g, ''); // Strip <script> tags svg = svg.replace(/<script[\s\S]*?<\/script>/gi, ''); if (options.includeWatermark !== false) { // Insert watermark before closing </svg> const watermark = '<text x="99%" y="99%" text-anchor="end" font-size="10" fill="#666" opacity="0.5">Generated by vai</text>'; svg = svg.replace('</svg>', `${watermark}</svg>`); } return svg; } function escapeHtml(str) { return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); } module.exports = { renderSvg, renderSvgAsync, renderMermaidToSvg, cleanSvg };