diff --git a/public/js/assistant/export.js b/public/js/assistant/export.js
new file mode 100644
index 0000000..d202f20
--- /dev/null
+++ b/public/js/assistant/export.js
@@ -0,0 +1,154 @@
+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 || '');
+ var doc = openExportWindow();
+ if (!doc) {
+ if (typeof options.showToast === 'function') options.showToast('Allow popups to export PDF', 'error');
+ return;
+ }
+ if (exportCacheKey === cacheKey && exportCacheItems) {
+ writePrintableChatExport(doc, exportCacheItems, state.lastGeneratedImageSrc || '');
+ return;
+ }
+ exportCacheKey = cacheKey;
+ exportCacheItems = exportItems;
+ writePrintableChatExport(doc, exportItems, state.lastGeneratedImageSrc || '');
+ }
+
+ function openExportWindow() {
+ var doc = window.open('', '_blank', 'width=900,height=1100');
+ if (!doc) return null;
+ doc.document.open();
+ doc.document.write('
Preparing Clinical Assistant ExportPreparing PDF export...
');
+ doc.document.close();
+ return doc;
+ }
+
+ function writePrintableChatExport(doc, items, imageSrc) {
+ if (!doc || doc.closed) return;
+ items = Array.isArray(items) && items.length ? items : [];
+ var imageHtml = imageSrc ? 'Generated Image
' : '';
+ 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('');
+ var html = 'Clinical Assistant Export' +
+ '' +
+ 'Clinical Assistant Export
' +
+ 'Export generated ' + escapeHtml(new Date().toLocaleString()) + '
' +
+ imageHtml +
+ sections + '';
+ 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(); });
+ } catch (e) {}
+ setTimeout(function () { try { doc.focus(); doc.print(); } catch (e) {} }, 500);
+ }
+
+ 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 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' : '' });
+}
diff --git a/public/js/assistant/images.js b/public/js/assistant/images.js
new file mode 100644
index 0000000..6351a46
--- /dev/null
+++ b/public/js/assistant/images.js
@@ -0,0 +1,59 @@
+import { escapeAttr } from './citations.js';
+
+export function createAssistantImageStore() {
+ var generatedImages = {};
+ var generatedImageSeq = 0;
+
+ function renderGeneratedImage(src, alt) {
+ var id = 'img-' + (++generatedImageSeq);
+ generatedImages[id] = src;
+ return ' + ')
' +
+ '
';
+ }
+
+ function openImagePreview(id) {
+ var src = generatedImages[id];
+ if (!src) return;
+ closeImagePreview();
+ var modal = document.createElement('div');
+ modal.className = 'assistant-image-modal';
+ modal.innerHTML = ' + ')
';
+ document.body.appendChild(modal);
+ }
+
+ function closeImagePreview() {
+ document.querySelectorAll('.assistant-image-modal').forEach(function (el) { el.remove(); });
+ }
+
+ function clear() {
+ generatedImages = {};
+ closeImagePreview();
+ }
+
+ return {
+ renderGeneratedImage: renderGeneratedImage,
+ openImagePreview: openImagePreview,
+ closeImagePreview: closeImagePreview,
+ clear: clear
+ };
+}
+
+export function buildContextualImagePrompt(request, lastAnswer, lastSources) {
+ var prompt = String(request || '').trim();
+ if (!lastAnswer) return prompt;
+ var sourceText = (lastSources || []).slice(0, 6).map(function (s, idx) {
+ return '[' + (s.number || idx + 1) + '] ' + (s.title || s.resource || 'Source') + (s.page ? ', page ' + s.page : '');
+ }).join('\n');
+ return prompt + '\n\nUse this clinical answer as the required context. Do not switch topics or introduce unrelated scenes such as gardening. Create a medical teaching visual faithful to the answer.\n\nAnswer:\n' + String(lastAnswer || '').slice(0, 3000) + '\n\nSources:\n' + sourceText;
+}
+
+export function isImageRequest(text) {
+ text = String(text || '').trim();
+ return /^(image|photo|picture|visual|illustration|diagram|figure|flowchart|infographic)$/i.test(text) ||
+ /\b(show|see|display|view|image|photo|picture|visual|illustration|diagram|figure)\b/i.test(text) && text.split(/\s+/).length <= 6 ||
+ /\b(generate|create|draw|make|render)\b[\s\S]{0,80}\b(image|picture|illustration|visual|infographic|diagram|flowchart)\b/i.test(text) ||
+ /\b(generate|create|draw|make|render)\b[\s\S]{0,80}\b(an?|the)?\s*(algorithm|pathway|poster|teaching visual)\b/i.test(text);
+}
diff --git a/public/js/assistant/sources.js b/public/js/assistant/sources.js
new file mode 100644
index 0000000..832f38a
--- /dev/null
+++ b/public/js/assistant/sources.js
@@ -0,0 +1,47 @@
+import { escapeHtml } from './citations.js';
+
+export function renderSourcesList(sources) {
+ if (!sources || sources.length === 0) return 'No citations returned.
';
+ return sources.map(function (s, idx) {
+ var n = s.number || idx + 1;
+ var page = s.page || s.page_number || s.pageNumber;
+ var meta = [];
+ if (page) meta.push('page ' + page);
+ if (s.source_type === 'multimodal_page') meta.push('visual PDF page');
+ if (s.visual_caption_source) meta.push('caption: ' + s.visual_caption_source);
+ if (s.visual_kind || s.source_priority) meta.push(s.visual_kind || s.source_priority);
+ if (s.category) meta.push(s.category);
+ if (s.doc_type || s.type) meta.push(s.doc_type || s.type);
+ if (s.score != null) meta.push('score ' + Number(s.score).toFixed(3));
+ return '' +
+ '
[' + n + '] ' + escapeHtml(s.title || s.resource || 'Untitled source') + '' +
+ renderSourceBadges(s) +
+ '
' + escapeHtml(meta.join(' · ') || 'indexed source') + '
' +
+ (s.excerpt ? '
' + escapeHtml(cleanSourceExcerpt(s.excerpt).slice(0, 900)) + '
' : '') +
+ '
';
+ }).join('');
+}
+
+function renderSourceBadges(source) {
+ var badges = [];
+ if (source.source_priority) badges.push(source.source_priority);
+ if (source.visual_kind && badges.indexOf(source.visual_kind) === -1) badges.push(source.visual_kind);
+ if (source.visual_caption_source && badges.indexOf(source.visual_caption_source) === -1) badges.push(source.visual_caption_source);
+ if (source.category) badges.push(source.category);
+ if (source.source_boost && Number(source.source_boost) !== 1) badges.push(Number(source.source_boost).toFixed(2) + 'x');
+ if (!badges.length) return '';
+ return '' + badges.slice(0, 4).map(function (badge) {
+ return '' + escapeHtml(badge) + '';
+ }).join('') + '
';
+}
+
+function cleanSourceExcerpt(text) {
+ return String(text || '')
+ .replace(/^\[Page-image match\]\s*/i, '')
+ .replace(/
/gi, ' ')
+ .replace(/\*\*/g, '')
+ .replace(/\|\s*-{2,}\s*/g, ' ')
+ .replace(/\|/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+}
diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js
index be5225b..d13e771 100644
--- a/public/js/clinicalAssistant.js
+++ b/public/js/clinicalAssistant.js
@@ -5,6 +5,9 @@
// ============================================================
import { EMPTY_PROMPT_SETS } from './assistant/data.js';
import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/citations.js';
+import { renderSourcesList } from './assistant/sources.js';
+import { createAssistantExporter } from './assistant/export.js';
+import { buildContextualImagePrompt, createAssistantImageStore, isImageRequest } from './assistant/images.js';
import {
deleteSavedAssistantChat,
fetchAssistantChat,
@@ -26,13 +29,11 @@ import {
var lastSources = [];
var mermaidReady = false;
var dynamicExamples = [];
- var generatedImages = {};
- var generatedImageSeq = 0;
var lastGeneratedImageSrc = '';
- var exportCacheKey = '';
- var exportCacheItems = null;
var markdownRenderer = null;
var assistantBusy = false;
+ var exporter = createAssistantExporter({ renderMarkdown: renderMarkdown, showToast: window.showToast });
+ var imageStore = createAssistantImageStore();
document.addEventListener('tabChanged', function (e) {
if (e.detail && e.detail.tab === 'assistant') initIfNeeded();
@@ -120,8 +121,7 @@ import {
}
appendMessage('user', text);
- exportCacheKey = '';
- exportCacheItems = null;
+ exporter.invalidate();
if (input) input.value = '';
if (isImageRequest(text)) {
@@ -384,41 +384,7 @@ import {
function renderSources(sources) {
var wrap = document.getElementById('assistant-sources');
if (!wrap) return;
- if (!sources || sources.length === 0) {
- wrap.innerHTML = 'No citations returned.
';
- return;
- }
- wrap.innerHTML = sources.map(function (s, idx) {
- var n = s.number || idx + 1;
- var page = s.page || s.page_number || s.pageNumber;
- var meta = [];
- if (page) meta.push('page ' + page);
- if (s.source_type === 'multimodal_page') meta.push('visual PDF page');
- if (s.visual_caption_source) meta.push('caption: ' + s.visual_caption_source);
- if (s.visual_kind || s.source_priority) meta.push(s.visual_kind || s.source_priority);
- if (s.category) meta.push(s.category);
- if (s.doc_type || s.type) meta.push(s.doc_type || s.type);
- if (s.score != null) meta.push('score ' + Number(s.score).toFixed(3));
- return '' +
- '
[' + n + '] ' + escapeHtml(s.title || s.resource || 'Untitled source') + '' +
- renderSourceBadges(s) +
- '
' + escapeHtml(meta.join(' · ') || 'indexed source') + '
' +
- (s.excerpt ? '
' + escapeHtml(cleanSourceExcerpt(s.excerpt).slice(0, 900)) + '
' : '') +
- '
';
- }).join('');
- }
-
- function renderSourceBadges(source) {
- var badges = [];
- if (source.source_priority) badges.push(source.source_priority);
- if (source.visual_kind && badges.indexOf(source.visual_kind) === -1) badges.push(source.visual_kind);
- if (source.visual_caption_source && badges.indexOf(source.visual_caption_source) === -1) badges.push(source.visual_caption_source);
- if (source.category) badges.push(source.category);
- if (source.source_boost && Number(source.source_boost) !== 1) badges.push(Number(source.source_boost).toFixed(2) + 'x');
- if (!badges.length) return '';
- return '' + badges.slice(0, 4).map(function (badge) {
- return '' + escapeHtml(badge) + '';
- }).join('') + '
';
+ wrap.innerHTML = renderSourcesList(sources);
}
function renderEmbeddedBlocks(root) {
@@ -475,12 +441,11 @@ import {
var src = data.imageUrl || data.url || (data.base64 ? ('data:image/png;base64,' + data.base64) : '');
if (!src) throw new Error('No image returned');
lastGeneratedImageSrc = src;
- exportCacheKey = '';
- exportCacheItems = null;
- if (out) out.innerHTML = renderGeneratedImage(src, 'Generated clinical visual');
+ exporter.invalidate();
+ if (out) out.innerHTML = imageStore.renderGeneratedImage(src, 'Generated clinical visual');
if (fromChat) {
setBusy(false, 'Ready');
- var html = renderGeneratedImage(src, 'Generated clinical visual');
+ var html = imageStore.renderGeneratedImage(src, 'Generated clinical visual');
replaceLoadingMessage(loading, html, [], [], true);
}
})
@@ -492,16 +457,6 @@ import {
});
}
- function renderGeneratedImage(src, alt) {
- var id = 'img-' + (++generatedImageSeq);
- generatedImages[id] = src;
- return ' + ')
' +
- '
';
- }
-
function onAssistantDocumentClick(e) {
var loadBtn = e.target.closest('[data-assistant-load-chat]');
if (loadBtn) {
@@ -518,50 +473,25 @@ import {
var openBtn = e.target.closest('[data-assistant-open-image]');
if (openBtn) {
e.preventDefault();
- openImagePreview(openBtn.getAttribute('data-assistant-open-image'));
+ imageStore.openImagePreview(openBtn.getAttribute('data-assistant-open-image'));
return;
}
if (e.target.closest('.assistant-image-modal-close') || e.target.classList.contains('assistant-image-modal')) {
- closeImagePreview();
+ imageStore.closeImagePreview();
}
}
- function openImagePreview(id) {
- var src = generatedImages[id];
- if (!src) return;
- closeImagePreview();
- var modal = document.createElement('div');
- modal.className = 'assistant-image-modal';
- modal.innerHTML = ' + ')
';
- document.body.appendChild(modal);
- }
-
- function closeImagePreview() {
- document.querySelectorAll('.assistant-image-modal').forEach(function (el) { el.remove(); });
- }
-
function clearGeneratedImage() {
lastGeneratedImageSrc = '';
- generatedImages = {};
+ imageStore.clear();
var out = document.getElementById('assistant-visual-output');
if (out) out.innerHTML = '';
- closeImagePreview();
- exportCacheKey = '';
- exportCacheItems = null;
- }
-
- function buildContextualImagePrompt(request) {
- var prompt = String(request || '').trim();
- if (!lastAnswer) return prompt;
- var sourceText = lastSources.slice(0, 6).map(function (s, idx) {
- return '[' + (s.number || idx + 1) + '] ' + (s.title || s.resource || 'Source') + (s.page ? ', page ' + s.page : '');
- }).join('\n');
- return prompt + '\n\nUse this clinical answer as the required context. Do not switch topics or introduce unrelated scenes such as gardening. Create a medical teaching visual faithful to the answer.\n\nAnswer:\n' + lastAnswer.slice(0, 3000) + '\n\nSources:\n' + sourceText;
+ exporter.invalidate();
}
function prepareSidebarImagePrompt(request) {
var promptEl = document.getElementById('assistant-image-prompt');
- var prompt = buildContextualImagePrompt(request);
+ var prompt = buildContextualImagePrompt(request, lastAnswer, lastSources);
if (promptEl) {
promptEl.value = prompt;
promptEl.focus();
@@ -575,8 +505,7 @@ import {
lastAnswer = '';
lastSources = [];
lastGeneratedImageSrc = '';
- exportCacheKey = '';
- exportCacheItems = null;
+ exporter.invalidate();
var wrap = document.getElementById('assistant-messages');
if (wrap) {
wrap.innerHTML = renderEmptyState();
@@ -633,122 +562,12 @@ import {
}
function exportAnswerPdf() {
- if (!lastAnswer) { if (typeof showToast === 'function') showToast('No answer to export', 'error'); return; }
- var exportItems = collectExportItems();
- var cacheKey = buildExportCacheKey(exportItems, lastGeneratedImageSrc);
- var doc = openExportWindow();
- if (!doc) { if (typeof showToast === 'function') showToast('Allow popups to export PDF', 'error'); return; }
- if (exportCacheKey === cacheKey && exportCacheItems) {
- writePrintableChatExport(doc, exportCacheItems, lastGeneratedImageSrc);
- return;
- }
- exportCacheKey = cacheKey;
- exportCacheItems = exportItems;
- writePrintableChatExport(doc, exportItems, lastGeneratedImageSrc);
- }
-
- function openExportWindow() {
- var doc = window.open('', '_blank', 'width=900,height=1100');
- if (!doc) return null;
- doc.document.open();
- doc.document.write('Preparing Clinical Assistant ExportPreparing PDF export...
');
- doc.document.close();
- return doc;
- }
-
- function writePrintableChatExport(doc, items, imageSrc) {
- if (!doc || doc.closed) return;
- items = Array.isArray(items) && items.length ? items : collectExportItems();
- var imageHtml = imageSrc ? 'Generated Image
' : '';
- 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('');
- var html = 'Clinical Assistant Export' +
- '' +
- 'Clinical Assistant Export
' +
- 'Export generated ' + escapeHtml(new Date().toLocaleString()) + '
' +
- imageHtml +
- sections + '';
- 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(); });
- } catch (e) {}
- setTimeout(function () { try { doc.focus(); doc.print(); } catch (e) {} }, 500);
- }
-
- function collectExportItems() {
- 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 = '';
+ exporter.exportAnswerPdf({
+ messages: messages,
+ lastAnswer: lastAnswer,
+ lastSources: lastSources,
+ lastGeneratedImageSrc: lastGeneratedImageSrc
});
- 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 || ''));
}
function saveCurrentChat() {
@@ -859,7 +678,7 @@ import {
}
renderSources(lastSources);
var out = document.getElementById('assistant-visual-output');
- if (out) out.innerHTML = lastGeneratedImageSrc ? renderGeneratedImage(lastGeneratedImageSrc, 'Generated clinical visual') : '';
+ if (out) out.innerHTML = lastGeneratedImageSrc ? imageStore.renderGeneratedImage(lastGeneratedImageSrc, 'Generated clinical visual') : '';
}
function appendMessageNode(role, content, sources) {
@@ -884,28 +703,11 @@ import {
return String(first && first.content || 'Clinical assistant chat').replace(/\s+/g, ' ').trim().slice(0, 80);
}
- function cleanSourceExcerpt(text) {
- return String(text || '')
- .replace(/^\[Page-image match\]\s*/i, '')
- .replace(/
/gi, ' ')
- .replace(/\*\*/g, '')
- .replace(/\|\s*-{2,}\s*/g, ' ')
- .replace(/\|/g, ' ')
- .replace(/\s+/g, ' ')
- .trim();
- }
-
function imageForSavedChatPayload(src) {
src = String(src || '');
return /^https?:\/\//i.test(src) ? src : '';
}
- 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' : '' });
- }
-
function lastAssistantMessage(items) {
for (var i = items.length - 1; i >= 0; i--) if (items[i].role === 'assistant') return items[i].content;
return '';
@@ -920,14 +722,6 @@ import {
return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}
- function isImageRequest(text) {
- text = String(text || '').trim();
- return /^(image|photo|picture|visual|illustration|diagram|figure|flowchart|infographic)$/i.test(text) ||
- /\b(show|see|display|view|image|photo|picture|visual|illustration|diagram|figure)\b/i.test(text) && text.split(/\s+/).length <= 6 ||
- /\b(generate|create|draw|make|render)\b[\s\S]{0,80}\b(image|picture|illustration|visual|infographic|diagram|flowchart)\b/i.test(text) ||
- /\b(generate|create|draw|make|render)\b[\s\S]{0,80}\b(an?|the)?\s*(algorithm|pathway|poster|teaching visual)\b/i.test(text);
- }
-
function setBusy(isBusy, text, isError) {
assistantBusy = !!isBusy;
var status = document.getElementById('assistant-status');