import { escapeAttr, escapeHtml } from './citations.js'; export function createAssistantExporter(options) { options = options || {}; var exportCacheKey = ''; var exportCacheItems = null; function invalidate() { exportCacheKey = ''; exportCacheItems = null; } function exportAnswerPdf(state) { state = state || {}; if (!state.lastAnswer) { if (typeof options.showToast === 'function') options.showToast('No answer to export', 'error'); return; } var exportItems = collectExportItems(state.messages || [], state.lastAnswer, state.lastSources || []); var cacheKey = buildExportCacheKey(exportItems, state.lastGeneratedImageSrc || ''); if (exportCacheKey === cacheKey && exportCacheItems) { showPrintableExport(exportCacheItems, state.lastGeneratedImageSrc || ''); return; } exportCacheKey = cacheKey; exportCacheItems = exportItems; showPrintableExport(exportItems, state.lastGeneratedImageSrc || ''); } function showPrintableExport(items, imageSrc) { if (shouldUseInlineExport()) { writeInlineChatExport(items, imageSrc); return; } var doc = openExportWindow(); if (!doc) { if (typeof options.showToast === 'function') options.showToast('Allow popups to export PDF', 'error'); return; } writePrintableChatExport(doc, items, imageSrc); } function openExportWindow() { var doc = window.open('', '_blank', 'width=900,height=1100'); if (!doc) return null; doc.document.open(); doc.document.write('Preparing Clinical Assistant Export

Preparing PDF export...

'); doc.document.close(); return doc; } function writePrintableChatExport(doc, items, imageSrc) { if (!doc || doc.closed) return; var html = buildPrintableChatHtml(items, imageSrc, false); doc.document.open(); doc.document.write(html); doc.document.close(); try { var printBtn = doc.document.getElementById('assistant-export-print'); if (printBtn) printBtn.addEventListener('click', function () { doc.focus(); doc.print(); }); var closeBtn = doc.document.getElementById('assistant-export-close'); if (closeBtn) closeBtn.addEventListener('click', function () { try { doc.close(); } catch (e) { doc.location.href = '/'; } }); } catch (e) {} setTimeout(function () { try { doc.focus(); doc.print(); } catch (e) {} }, 500); } function writeInlineChatExport(items, imageSrc) { closeInlineExport(); var modal = document.createElement('div'); modal.id = 'assistant-export-modal'; modal.innerHTML = '
' + buildPrintableChatBody(items, imageSrc, true) + '
'; document.body.appendChild(modal); document.body.classList.add('assistant-export-open'); var printBtn = modal.querySelector('#assistant-export-print'); var closeBtn = modal.querySelector('#assistant-export-close'); if (printBtn) printBtn.addEventListener('click', async function () { printBtn.disabled = true; var originalText = printBtn.textContent; printBtn.textContent = 'Preparing export...'; try { var html = buildPrintableChatHtml(items, imageSrc, false); if (await printWithNativeBridge(html)) return; if (!(await saveInlineExport(html))) window.print(); } finally { printBtn.disabled = false; printBtn.textContent = originalText; } }); if (closeBtn) closeBtn.addEventListener('click', closeInlineExport); modal.addEventListener('click', function (event) { if (event.target === modal) closeInlineExport(); }); try { window.history.pushState({ assistantExport: true }, '', window.location.href); } catch (e) {} window.addEventListener('popstate', closeInlineExport, { once: true }); } function closeInlineExport() { var existing = document.getElementById('assistant-export-modal'); if (existing) existing.remove(); document.body.classList.remove('assistant-export-open'); } function buildPrintableChatHtml(items, imageSrc, inline) { return 'Clinical Assistant Export' + '' + '' + buildPrintableChatBody(items, imageSrc, inline) + ''; } function buildPrintableChatBody(items, imageSrc, inline) { items = Array.isArray(items) && items.length ? items : []; var imageHtml = imageSrc ? '

Generated Image

Generated clinical visual
' : ''; var sections = items.map(function (item, idx) { var sources = Array.isArray(item.sources) ? item.sources : []; var heading = item.heading || deriveExportHeading(item.question, idx); var summary = item.summary || ''; var answer = item.answer || ''; var citedNumbers = extractCitedSourceNumbers([summary, answer].filter(Boolean).join('\n\n')); var refs = renderExportRefs(sources, idx + 1, citedNumbers); return '
' + '

' + escapeHtml(heading) + '

' + '
Question: ' + escapeHtml(item.question || '') + '
' + (summary ? '

Summary

' + renderMarkdown(summary, sources, { citationLabel: 'number' }) + '
' : '') + '

Full Generated Answer

' + renderMarkdown(answer, sources, { citationLabel: 'number' }) + '
' + (refs ? '

References

    ' + refs + '
' : '') + '
'; }).join(''); return '

Clinical Assistant Export

' + '
Export generated ' + escapeHtml(new Date().toLocaleString()) + '
' + imageHtml + sections; } function renderMarkdown(md, sources, renderOptions) { if (typeof options.renderMarkdown === 'function') return options.renderMarkdown(md, sources, renderOptions); return escapeHtml(md); } return { exportAnswerPdf: exportAnswerPdf, invalidate: invalidate }; } function shouldUseInlineExport() { var isCapacitor = !!(window.Capacitor && (!window.Capacitor.isNativePlatform || window.Capacitor.isNativePlatform())); var isSmallTouch = window.matchMedia && window.matchMedia('(max-width: 700px), (pointer: coarse)').matches; return isCapacitor || isSmallTouch; } async function saveInlineExport(html) { var plugins = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins : null; if (!plugins || !plugins.Filesystem) return false; try { var name = 'clinical-assistant-export-' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19) + '.html'; var saved = await plugins.Filesystem.writeFile({ path: name, data: utf8ToBase64(html), directory: 'CACHE' }); if (plugins.Share && saved && saved.uri) { try { await plugins.Share.share({ title: 'Clinical Assistant Export', text: 'Open this export and use Print to save as PDF.', url: saved.uri, dialogTitle: 'Save or share export' }); } catch (e) { if (!isShareCancel(e)) throw e; } } if (typeof window.showToast === 'function') window.showToast('Export prepared', 'success'); return true; } catch (e) { if (typeof window.showToast === 'function') window.showToast(e.message || 'Export failed', 'error'); return false; } } async function printWithNativeBridge(html) { if (!window.NativePrint || typeof window.NativePrint.printHtml !== 'function') return false; try { window.NativePrint.printHtml('Clinical Assistant Export', utf8ToBase64(html)); return true; } catch (e) { if (typeof window.showToast === 'function') window.showToast(e.message || 'Native print failed', 'error'); return false; } } function utf8ToBase64(value) { var bytes = new TextEncoder().encode(String(value || '')); var binary = ''; for (var i = 0; i < bytes.length; i += 0x8000) { binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000)); } return btoa(binary); } function isShareCancel(error) { var message = String(error && (error.message || error.name) || '').toLowerCase(); return /cancel|abort|dismiss|user denied|share canceled/.test(message); } function exportWindowCss() { return 'body{font-family:Arial,sans-serif;color:#111827;line-height:1.55;margin:36px;max-width:820px}.assistant-export-actions{display:flex;gap:8px;justify-content:flex-end;margin-bottom:12px}.assistant-export-actions button{padding:8px 12px}h1{font-size:22px;margin:0 0 4px}h2{font-size:17px;margin-top:26px;border-bottom:1px solid #e5e7eb;padding-bottom:4px;break-after:avoid}h3{font-size:14px;margin:18px 0 8px;break-after:avoid}.meta,.question{font-size:12px;color:#6b7280;margin-bottom:12px}.answer{font-size:13px}.export-section{margin-top:20px;break-before:auto}.full-answer{page-break-before:auto}.export-image img{max-width:100%;border:1px solid #e5e7eb;border-radius:10px}.refs{font-size:12px;padding-left:20px;margin-top:8px;break-inside:auto}.refs li{margin:6px 0;break-inside:avoid}.assistant-cite{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 5px;margin:0 1px;border-radius:999px;background:#f3e8ff;color:#7c3aed;border:1px solid rgba(124,58,237,.22);font-size:9px;font-weight:800;line-height:16px;text-decoration:none;text-transform:uppercase;letter-spacing:.03em;vertical-align:baseline;white-space:nowrap;-webkit-print-color-adjust:exact;print-color-adjust:exact}.answer table{width:100%;border-collapse:collapse;table-layout:auto;margin:12px 0 18px;border:1px solid #e5e7eb;page-break-inside:auto}.answer th,.answer td{padding:7px 8px;border:1px solid #e5e7eb;text-align:left;vertical-align:top;word-break:normal;overflow-wrap:break-word}.answer th{background:#f9fafb;font-weight:700;-webkit-print-color-adjust:exact;print-color-adjust:exact}.answer th:last-child,.answer td:last-child{width:1%;white-space:nowrap}.answer tr{break-inside:avoid;page-break-inside:avoid}.answer thead{display:table-header-group}.answer tbody{display:table-row-group}.answer p{margin:8px 0}.answer ul,.answer ol{padding-left:20px}.answer li{margin:4px 0}@media print{.assistant-export-actions{display:none}body{margin:24mm}.export-section{break-inside:auto}.refs{break-before:avoid}}'; } function inlineExportCss() { return '#assistant-export-modal{position:fixed;inset:0;z-index:10000;background:rgba(15,23,42,.72);overflow:auto;padding:16px}#assistant-export-modal .assistant-export-sheet{box-sizing:border-box;background:white;color:#111827;font-family:Arial,sans-serif;line-height:1.55;max-width:860px;margin:0 auto 24px;padding:24px;border-radius:14px;box-shadow:0 24px 80px rgba(0,0,0,.35)}#assistant-export-modal .assistant-export-actions{display:flex;gap:8px;justify-content:flex-end;margin-bottom:12px}#assistant-export-modal .assistant-export-actions button{padding:8px 12px}#assistant-export-modal h1{font-size:22px;margin:0 0 4px}#assistant-export-modal h2{font-size:17px;margin-top:26px;border-bottom:1px solid #e5e7eb;padding-bottom:4px;break-after:avoid}#assistant-export-modal h3{font-size:14px;margin:18px 0 8px;break-after:avoid}#assistant-export-modal .meta,#assistant-export-modal .question{font-size:12px;color:#6b7280;margin-bottom:12px}#assistant-export-modal .answer{font-size:13px}#assistant-export-modal .export-section{margin-top:20px;break-before:auto}#assistant-export-modal .full-answer{page-break-before:auto}#assistant-export-modal .export-image img{max-width:100%;border:1px solid #e5e7eb;border-radius:10px}#assistant-export-modal .refs{font-size:12px;padding-left:20px;margin-top:8px;break-inside:auto}#assistant-export-modal .refs li{margin:6px 0;break-inside:avoid}#assistant-export-modal .assistant-cite{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 5px;margin:0 1px;border-radius:999px;background:#f3e8ff;color:#7c3aed;border:1px solid rgba(124,58,237,.22);font-size:9px;font-weight:800;line-height:16px;text-decoration:none;text-transform:uppercase;letter-spacing:.03em;vertical-align:baseline;white-space:nowrap;-webkit-print-color-adjust:exact;print-color-adjust:exact}#assistant-export-modal .answer table{width:100%;border-collapse:collapse;table-layout:auto;margin:12px 0 18px;border:1px solid #e5e7eb;page-break-inside:auto}#assistant-export-modal .answer th,#assistant-export-modal .answer td{padding:7px 8px;border:1px solid #e5e7eb;text-align:left;vertical-align:top;word-break:normal;overflow-wrap:break-word}#assistant-export-modal .answer th{background:#f9fafb;font-weight:700;-webkit-print-color-adjust:exact;print-color-adjust:exact}#assistant-export-modal .answer th:last-child,#assistant-export-modal .answer td:last-child{width:1%;white-space:nowrap}#assistant-export-modal .answer tr{break-inside:avoid;page-break-inside:avoid}#assistant-export-modal .answer thead{display:table-header-group}#assistant-export-modal .answer tbody{display:table-row-group}#assistant-export-modal .answer p{margin:8px 0}#assistant-export-modal .answer ul,#assistant-export-modal .answer ol{padding-left:20px}#assistant-export-modal .answer li{margin:4px 0}@media print{body.assistant-export-open>*:not(#assistant-export-modal){display:none!important}#assistant-export-modal{position:static!important;inset:auto!important;background:white!important;overflow:visible!important;padding:0!important}#assistant-export-modal .assistant-export-sheet{max-width:none!important;margin:0!important;padding:0!important;border-radius:0!important;box-shadow:none!important}#assistant-export-modal .assistant-export-actions{display:none!important}#assistant-export-modal .export-section{break-inside:auto}#assistant-export-modal .refs{break-before:avoid}}'; } function exportTableScrollCss() { return '.assistant-table-scroll{max-width:100%;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;margin:12px 0 18px;border:1px solid #e5e7eb;border-radius:10px;background:white}.answer .assistant-table-scroll table,#assistant-export-modal .answer .assistant-table-scroll table{width:max-content;min-width:100%;max-width:none;margin:0;border:0;border-radius:0}.answer .assistant-table-scroll th,.answer .assistant-table-scroll td,#assistant-export-modal .answer .assistant-table-scroll th,#assistant-export-modal .answer .assistant-table-scroll td{min-width:120px;overflow-wrap:normal;word-break:normal}.assistant-table-scroll::after{content:"Swipe table";display:block;position:sticky;left:0;bottom:0;padding:3px 9px;font-size:10px;font-weight:700;color:#6b7280;background:linear-gradient(90deg,rgba(255,255,255,.95),rgba(255,255,255,0));pointer-events:none}@media print{.assistant-table-scroll{overflow:visible;border:0}.assistant-table-scroll::after{display:none}.answer .assistant-table-scroll table,#assistant-export-modal .answer .assistant-table-scroll table{width:100%;max-width:100%}}'; } function collectExportItems(messages, lastAnswer, lastSources) { var items = []; var pendingQuestion = ''; (messages || []).forEach(function (m) { if (m.role === 'user') { pendingQuestion = m.content || pendingQuestion; return; } if (m.role !== 'assistant' || !m.content) return; if (isUtilityAssistantMessage(m.content)) return; items.push({ question: pendingQuestion || 'Clinical question', heading: deriveExportHeading(pendingQuestion, items.length), summary: '', answer: m.content, sources: Array.isArray(m.sources) && m.sources.length ? m.sources : lastSources }); pendingQuestion = ''; }); if (!items.length && lastAnswer) { items.push({ question: 'Clinical question', heading: 'Clinical Answer', summary: '', answer: lastAnswer, sources: lastSources }); } return items; } function renderExportRefs(sources, sectionNumber, citedNumbers) { var cited = citedNumbers && citedNumbers.length ? new Set(citedNumbers.map(String)) : null; return (sources || []).filter(function (s, idx) { var n = s.number || idx + 1; return !cited || cited.has(String(n)); }).map(function (s, idx) { var n = s.number || idx + 1; var title = s.title || s.resource || 'Untitled source'; var page = s.page || s.page_number || s.pageNumber; return '
  • [' + n + '] ' + escapeHtml(title) + (page ? ', page ' + escapeHtml(page) : '') + '.
  • '; }).join(''); } function extractCitedSourceNumbers(text) { var found = new Set(); String(text || '').replace(/\[(\d+(?:\s*,\s*\d+)*)\]/g, function (_, nums) { nums.split(',').forEach(function (n) { n = String(n || '').trim(); if (n) found.add(n); }); return _; }); return Array.from(found).sort(function (a, b) { return Number(a) - Number(b); }); } function deriveExportHeading(question, idx) { var text = String(question || '').replace(/\s+/g, ' ').trim(); if (!text || /^(what|which|when|why|how|and|also|what dose\??|dose\??)$/i.test(text)) return 'Clinical Question ' + (idx + 1); return text.replace(/[?!.]+$/, '').slice(0, 90); } function isUtilityAssistantMessage(content) { return /^I prepared the image prompt in the \*\*Image \/ Graph\*\* box/i.test(String(content || '')); } export function buildExportCacheKey(items, imageSrc) { return JSON.stringify({ items: (items || []).map(function (item) { return { q: item.question, a: item.answer, s: (item.sources || []).map(function (s) { return [s.number, s.title, s.page]; }) }; }), image: imageSrc ? '1' : '' }); }