Spaces:
Sleeping
Sleeping
Update static/chatbot.js
Browse files- static/chatbot.js +97 -7
static/chatbot.js
CHANGED
|
@@ -466,17 +466,13 @@ const Chatbot = (() => {
|
|
| 466 |
// Get Gemini AI-generated report if X-ray was uploaded
|
| 467 |
if (uploadedFiles.length) {
|
| 468 |
try {
|
| 469 |
-
|
| 470 |
-
reportBody.innerHTML = '<div class="report-loading"><span class="report-loading-text">Generating AI clinical report powered by Gemini...<br>This may take a moment.</span></div>';
|
| 471 |
-
reportOverlay.style.display = 'flex';
|
| 472 |
|
| 473 |
-
await botSay('<span class="loader-ring">Generating AI clinical report powered by Gemini...</span>', true, 400);
|
| 474 |
const formData = new FormData();
|
| 475 |
uploadedFiles.forEach(f => formData.append('images', f));
|
| 476 |
const resp = await fetch('/api/ai-report', { method: 'POST', body: formData });
|
| 477 |
if (resp.ok) {
|
| 478 |
const aiData = await resp.json();
|
| 479 |
-
// Extract the report from the response - it's keyed by filename
|
| 480 |
if (aiData.results) {
|
| 481 |
const firstKey = Object.keys(aiData.results)[0];
|
| 482 |
if (firstKey) {
|
|
@@ -484,8 +480,46 @@ const Chatbot = (() => {
|
|
| 484 |
}
|
| 485 |
}
|
| 486 |
}
|
| 487 |
-
|
| 488 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
}
|
| 490 |
|
| 491 |
// Build report HTML
|
|
@@ -615,6 +649,62 @@ const Chatbot = (() => {
|
|
| 615 |
btnTogglePanel.style.display = 'flex';
|
| 616 |
}
|
| 617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
// --- Event Listeners ---
|
| 619 |
function init() {
|
| 620 |
// Text input
|
|
|
|
| 466 |
// Get Gemini AI-generated report if X-ray was uploaded
|
| 467 |
if (uploadedFiles.length) {
|
| 468 |
try {
|
| 469 |
+
await botSay('<span class="loader-ring">Generating AI clinical report powered by Gemini... This may take a moment.</span>', true, 400);
|
|
|
|
|
|
|
| 470 |
|
|
|
|
| 471 |
const formData = new FormData();
|
| 472 |
uploadedFiles.forEach(f => formData.append('images', f));
|
| 473 |
const resp = await fetch('/api/ai-report', { method: 'POST', body: formData });
|
| 474 |
if (resp.ok) {
|
| 475 |
const aiData = await resp.json();
|
|
|
|
| 476 |
if (aiData.results) {
|
| 477 |
const firstKey = Object.keys(aiData.results)[0];
|
| 478 |
if (firstKey) {
|
|
|
|
| 480 |
}
|
| 481 |
}
|
| 482 |
}
|
| 483 |
+
|
| 484 |
+
// Show AI report as a chat message NOW, before "report ready"
|
| 485 |
+
if (geminiReport && geminiReport.report) {
|
| 486 |
+
let classSummaryHtml = '';
|
| 487 |
+
if (geminiReport.by_class) {
|
| 488 |
+
classSummaryHtml = Object.entries(geminiReport.by_class)
|
| 489 |
+
.map(([cls, count]) => `<span class="severity-badge severity-moderate" style="margin:2px 2px 4px;">${cls}: ${count}</span>`)
|
| 490 |
+
.join(' ');
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
const reportChatHtml = `
|
| 494 |
+
<div style="font-size:12px;line-height:1.7;max-height:420px;overflow-y:auto;padding-right:4px;">
|
| 495 |
+
<div style="font-weight:700;font-size:13px;color:var(--accent);border-bottom:1px solid var(--border);padding-bottom:6px;margin-bottom:10px;">
|
| 496 |
+
🤖 AI Clinical Report
|
| 497 |
+
<span style="font-size:10px;color:var(--text-muted);font-weight:400;margin-left:6px;">powered by Gemini</span>
|
| 498 |
+
</div>
|
| 499 |
+
${classSummaryHtml ? `<div style="margin-bottom:10px;display:flex;flex-wrap:wrap;gap:2px;align-items:center;"><strong style="margin-right:6px;font-size:11px;">Detections:</strong>${classSummaryHtml}<span style="color:var(--text-muted);font-size:11px;margin-left:6px;">(${geminiReport.total || 0} total)</span></div>` : ''}
|
| 500 |
+
<div>${geminiReport.report}</div>
|
| 501 |
+
</div>`;
|
| 502 |
+
|
| 503 |
+
addMessage(reportChatHtml, 'bot', true);
|
| 504 |
+
|
| 505 |
+
// PDF download button as a follow-up bot message
|
| 506 |
+
window._geminiReportHtml = geminiReport.report;
|
| 507 |
+
window._geminiByClass = geminiReport.by_class || {};
|
| 508 |
+
window._geminiTotal = geminiReport.total || 0;
|
| 509 |
+
const pdfBtnHtml = `<button onclick="window._downloadGeminiPDF()" style="
|
| 510 |
+
display:inline-flex;align-items:center;gap:6px;
|
| 511 |
+
padding:8px 16px;border-radius:8px;border:none;cursor:pointer;
|
| 512 |
+
background:var(--accent);color:#fff;font-weight:600;font-size:13px;
|
| 513 |
+
box-shadow:0 2px 8px rgba(0,0,0,.25);transition:opacity .2s;
|
| 514 |
+
" onmouseover="this.style.opacity='.85'" onmouseout="this.style.opacity='1'">
|
| 515 |
+
📥 Download Report as PDF
|
| 516 |
+
</button>`;
|
| 517 |
+
addMessage(pdfBtnHtml, 'bot', true);
|
| 518 |
+
} else if (geminiReport && geminiReport.error) {
|
| 519 |
+
await botSay(`⚠️ AI report note: ${geminiReport.error}`, false, 400);
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
} catch(e) { console.error('Gemini report error:', e); }
|
| 523 |
}
|
| 524 |
|
| 525 |
// Build report HTML
|
|
|
|
| 649 |
btnTogglePanel.style.display = 'flex';
|
| 650 |
}
|
| 651 |
|
| 652 |
+
// --- PDF Download ---
|
| 653 |
+
window._downloadGeminiPDF = function() {
|
| 654 |
+
const reportHtml = window._geminiReportHtml || '';
|
| 655 |
+
const byClass = window._geminiByClass || {};
|
| 656 |
+
const total = window._geminiTotal || 0;
|
| 657 |
+
|
| 658 |
+
const classBadges = Object.entries(byClass)
|
| 659 |
+
.map(([cls, count]) => `<span style="display:inline-block;padding:2px 8px;border-radius:12px;border:1px solid #0d9488;color:#0d9488;font-size:11px;font-weight:600;margin:2px;">${cls}: ${count}</span>`)
|
| 660 |
+
.join(' ');
|
| 661 |
+
|
| 662 |
+
const printWin = window.open('', '_blank', 'width=850,height=1000');
|
| 663 |
+
if (!printWin) { alert('Please allow popups to download the PDF.'); return; }
|
| 664 |
+
|
| 665 |
+
printWin.document.write(`<!DOCTYPE html><html><head>
|
| 666 |
+
<meta charset="utf-8">
|
| 667 |
+
<title>AI Clinical Report — Assnani Dental</title>
|
| 668 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
| 669 |
+
<style>
|
| 670 |
+
*{margin:0;padding:0;box-sizing:border-box}
|
| 671 |
+
body{font-family:'Inter',sans-serif;padding:40px;color:#1e293b;font-size:13px;line-height:1.7;background:#fff}
|
| 672 |
+
.header{border-bottom:3px solid #0d9488;padding-bottom:16px;margin-bottom:24px}
|
| 673 |
+
.header h1{font-size:22px;color:#0d9488;margin-bottom:4px}
|
| 674 |
+
.header p{color:#64748b;font-size:12px}
|
| 675 |
+
.detections{margin-bottom:20px;padding:12px;background:#f0fdfa;border-radius:8px;border:1px solid #99f6e4}
|
| 676 |
+
.detections strong{font-size:12px;margin-right:8px}
|
| 677 |
+
.report-content{font-size:13px;line-height:1.8}
|
| 678 |
+
.report-content h1,.report-content h2,.report-content h3{color:#0d9488;margin:16px 0 6px}
|
| 679 |
+
.report-content h1{font-size:16px} .report-content h2{font-size:15px} .report-content h3{font-size:14px}
|
| 680 |
+
.report-content strong{color:#0f172a}
|
| 681 |
+
.report-content table{width:100%;border-collapse:collapse;margin:12px 0}
|
| 682 |
+
.report-content th{background:#f0fdfa;color:#0d9488;padding:8px;border:1px solid #99f6e4;font-size:12px}
|
| 683 |
+
.report-content td{padding:7px 8px;border:1px solid #e2e8f0;font-size:12px}
|
| 684 |
+
.report-content tr:nth-child(even) td{background:#f8fafc}
|
| 685 |
+
.disclaimer{margin-top:32px;padding:12px;border:1px solid #e2e8f0;border-radius:8px;font-size:11px;color:#64748b;text-align:center}
|
| 686 |
+
@media print{
|
| 687 |
+
body{padding:20px}
|
| 688 |
+
.no-print{display:none}
|
| 689 |
+
@page{margin:1cm;size:A4}
|
| 690 |
+
}
|
| 691 |
+
</style>
|
| 692 |
+
</head><body>
|
| 693 |
+
<div class="header">
|
| 694 |
+
<h1>🦷 AI Clinical Dental Report</h1>
|
| 695 |
+
<p>Generated by Assnani AI • ${new Date().toLocaleDateString('en-US',{year:'numeric',month:'long',day:'numeric'})} • Powered by Gemini</p>
|
| 696 |
+
</div>
|
| 697 |
+
${classBadges ? `<div class="detections"><strong>Findings (${total} total):</strong>${classBadges}</div>` : ''}
|
| 698 |
+
<div class="report-content">${reportHtml}</div>
|
| 699 |
+
<div class="disclaimer">⚠️ This is an AI-assisted preliminary analysis. Final diagnosis must be verified by a licensed dental professional.</div>
|
| 700 |
+
<div class="no-print" style="margin-top:24px;text-align:center">
|
| 701 |
+
<button onclick="window.print()" style="padding:10px 28px;background:#0d9488;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;">🖨️ Save as PDF / Print</button>
|
| 702 |
+
</div>
|
| 703 |
+
</body></html>`);
|
| 704 |
+
printWin.document.close();
|
| 705 |
+
printWin.onload = () => { setTimeout(() => printWin.print(), 300); };
|
| 706 |
+
};
|
| 707 |
+
|
| 708 |
// --- Event Listeners ---
|
| 709 |
function init() {
|
| 710 |
// Text input
|