voyageai-cli
Version:
CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search
118 lines (103 loc) • 4.45 kB
JavaScript
;
/**
* 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
module.exports = { renderPdf, buildChatHtml, htmlToPdf };