voyageai-cli
Version:
CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search
117 lines (98 loc) • 4.11 kB
JavaScript
;
/**
* 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, '&').replace(/</g, '<').replace(/>/g, '>');
}
module.exports = { renderSvg, renderSvgAsync, renderMermaidToSvg, cleanSvg };