UNPKG

voyageai-cli

Version:

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

118 lines (103 loc) 4.45 kB
'use strict'; /** * Render a chat session (or any markdown content) as PDF using Playwright. * * @param {object} normalized * @param {object} options * @param {string} [options.theme='dark'] - 'dark' | 'light' * @returns {Promise<{ content: Buffer, mimeType: string }>} */ async function renderPdf(normalized, options = {}) { if (normalized._context !== 'chat') { throw new Error(`PDF export currently supported for chat sessions only (got: ${normalized._context})`); } const html = buildChatHtml(normalized, options); const pdf = await htmlToPdf(html, options); return { content: pdf, mimeType: 'application/pdf' }; } /** * Build styled HTML from a chat session for PDF rendering. */ function buildChatHtml(chat, options = {}) { const theme = options.theme || 'dark'; const isDark = theme === 'dark'; const bg = isDark ? '#1e1e1e' : '#ffffff'; const fg = isDark ? '#e0e0e0' : '#1a1a1a'; const userBg = isDark ? '#2a2a2a' : '#f5f5f5'; const asstBg = isDark ? '#1a2a3a' : '#e8f4f8'; const accent = '#00D4AA'; const turns = (chat.turns || []).map((t) => { const isUser = t.role === 'user'; const roleName = isUser ? 'User' : 'vai'; const msgBg = isUser ? userBg : asstBg; const content = escapeHtml(t.content || '').replace(/\n/g, '<br/>'); let sources = ''; if (t.context && t.context.length > 0 && options.includeSources !== false) { const srcItems = t.context.map((s, i) => { const rel = s.score !== undefined ? ` (relevance: ${s.score})` : ''; return `<li style="font-size:12px;color:#888;">${escapeHtml(s.source || 'unknown')}${rel}</li>`; }).join(''); sources = `<ul style="margin:4px 0 0 16px;padding:0;">${srcItems}</ul>`; } let meta = ''; if (options.includeMetadata && t.metadata) { const parts = []; if (t.metadata.tokensUsed) parts.push(`${t.metadata.tokensUsed} tokens`); if (t.metadata.retrievalTimeMs) parts.push(`retrieval: ${t.metadata.retrievalTimeMs}ms`); if (t.metadata.generationTimeMs) parts.push(`generation: ${t.metadata.generationTimeMs}ms`); if (parts.length) { meta = `<div style="font-size:11px;color:#666;margin-top:4px;">${parts.join(' · ')}</div>`; } } return `<div style="background:${msgBg};padding:12px 16px;border-radius:8px;margin:8px 0;"> <div style="font-weight:bold;color:${accent};margin-bottom:6px;">${roleName}</div> <div>${content}</div> ${sources}${meta} </div>`; }).join(''); const sessionId = chat.sessionId || chat.id || ''; const headerParts = []; if (chat.startedAt) headerParts.push(`Date: ${chat.startedAt}`); if (chat.provider && chat.model) headerParts.push(`Provider: ${chat.provider} (${chat.model})`); else if (chat.model) headerParts.push(`Model: ${chat.model}`); if (chat.collection) headerParts.push(`Knowledge Base: ${chat.collection}`); headerParts.push(`Turns: ${(chat.turns || []).length}`); return `<!DOCTYPE html> <html><head><meta charset="utf-8"> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: ${bg}; color: ${fg}; margin: 40px; line-height: 1.6; } h1 { color: ${accent}; font-size: 24px; margin-bottom: 4px; } .meta { font-size: 13px; color: #888; margin-bottom: 24px; } .footer { font-size: 11px; color: #666; text-align: center; margin-top: 40px; border-top: 1px solid #333; padding-top: 12px; } </style> </head><body> <h1>Chat Session: ${escapeHtml(sessionId)}</h1> <div class="meta">${headerParts.join(' · ')}</div> <hr style="border:none;border-top:1px solid #333;margin:16px 0;"> ${turns} <div class="footer">Generated by vai · voyageai-cli</div> </body></html>`; } /** * Convert HTML to PDF using Playwright. */ async function htmlToPdf(html, options = {}) { const { chromium } = require('playwright'); const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); await page.setContent(html, { waitUntil: 'networkidle' }); const pdf = await page.pdf({ format: 'A4', margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' }, printBackground: true, }); return pdf; } finally { await browser.close(); } } function escapeHtml(str) { return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); } module.exports = { renderPdf, buildChatHtml, htmlToPdf };