// ===== GLOBAL STATE ===== let currentResults = null; let explanations = null; // ===== INITIALIZATION ===== document.addEventListener('DOMContentLoaded', () => { // Navigation is now handled via onclick="showSection", but we init other listeners initFileUpload(); initScrollAnimations(); initEnterKey(); }); // ===== NAVIGATION (Compatible with Tailwind HTML) ===== function showSection(sectionId) { // Hide all sections document.querySelectorAll('.section').forEach(sec => { sec.classList.remove('active'); sec.style.display = 'none'; }); // Show target section const target = document.getElementById(sectionId); if (target) { target.style.display = 'block'; // Small timeout to allow display:block to apply before adding active class for animation setTimeout(() => { target.classList.add('active'); }, 10); // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); } } // ===== SCROLL ANIMATIONS ===== function initScrollAnimations() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.remove('opacity-0', 'translate-y-4'); entry.target.classList.add('opacity-100', 'translate-y-0'); } }); }, { threshold: 0.1 }); // Add animation classes to cards document.querySelectorAll('.feature-card, .upload-card').forEach(card => { card.classList.add('transition-all', 'duration-700', 'opacity-0', 'translate-y-4'); observer.observe(card); }); } // ===== FILE UPLOAD ===== function initFileUpload() { const fileInput = document.getElementById('fileInput'); const uploadArea = document.getElementById('uploadArea'); if (!fileInput || !uploadArea) return; // Click to upload (handled by onclick in HTML, but keeping listener just in case) fileInput.addEventListener('change', handleFileSelect); // Drag and drop Visuals uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); // Add Tailwind classes for drag state uploadArea.classList.add('border-secondary', 'bg-blue-50', 'scale-[1.02]'); }); uploadArea.addEventListener('dragleave', () => { // Remove Tailwind classes uploadArea.classList.remove('border-secondary', 'bg-blue-50', 'scale-[1.02]'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('border-secondary', 'bg-blue-50', 'scale-[1.02]'); const file = e.dataTransfer.files[0]; if (file && file.type === 'application/pdf') { uploadFile(file); } else { showToast('Please upload a PDF file', 'error'); } }); } function handleFileSelect(e) { const file = e.target.files[0]; if (file) { uploadFile(file); } } async function uploadFile(file) { const formData = new FormData(); formData.append('file', file); // Show progress document.getElementById('uploadArea').classList.add('hidden'); document.getElementById('uploadProgress').classList.remove('hidden'); try { const response = await fetch('/api/upload', { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Upload failed'); } currentResults = data.results; showToast(`✓ Found ${data.count} lab results!`, 'success'); // Generate explanations await generateExplanations(); // Display results displayResults(); // Switch to results tab automatically showSection('results-section'); } catch (error) { showToast(error.message, 'error'); resetUploadArea(); } } async function generateExplanations() { // Optional: show a mini toast or loading indicator try { const response = await fetch('/api/explain', { method: 'POST' }); const data = await response.json(); if (!response.ok) throw new Error(data.error); explanations = data.explanations; } catch (error) { console.warn('Auto-explanation generation failed:', error); // We continue anyway, results will just say "Loading..." or show basic info } } function displayResults() { const container = document.getElementById('resultsContainer'); if (!currentResults || currentResults.length === 0) { container.innerHTML = '
Ref Range: ${escapeHtml(result.reference_range || 'N/A')}
Insight: ${escapeHtml(explanation)}
Failed to generate summary.
'; } } // ===== CHAT FUNCTIONALITY ===== function initEnterKey() { const input = document.getElementById('chatInput'); if (input) { input.addEventListener('keypress', (e) => { if (e.key === 'Enter') askQuestion(); }); } } async function askQuestion() { if (!currentResults) { showToast('Please upload a lab report first', 'error'); return; } const input = document.getElementById('chatInput'); const question = input.value.trim(); if (!question) return; // Clear input input.value = ''; // Add user message addChatMessage(question, 'user'); // Show loading const loadingId = addChatMessage('Analyzing...', 'assistant', true); try { const response = await fetch('/api/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question }) }); const data = await response.json(); if (!response.ok) throw new Error(data.error); // Remove loading const loadingEl = document.getElementById(loadingId); if(loadingEl) loadingEl.remove(); // Add response addChatMessage(data.answer, 'assistant'); } catch (error) { const loadingEl = document.getElementById(loadingId); if(loadingEl) loadingEl.remove(); showToast(error.message, 'error'); } } function addChatMessage(text, sender, isLoading = false) { const container = document.getElementById('chatMessages'); const wrapper = document.createElement('div'); // Flex alignment based on sender wrapper.className = `flex w-full mb-4 ${sender === 'user' ? 'justify-end' : 'justify-start'}`; const bubble = document.createElement('div'); // Bubble Styling const baseStyle = "max-w-[85%] rounded-2xl px-5 py-3 shadow-sm text-sm leading-relaxed"; const userStyle = "bg-secondary text-white rounded-br-none"; // Blue bubble const aiStyle = "bg-slate-100 text-slate-800 rounded-tl-none border border-slate-200"; // Grey bubble bubble.className = `${baseStyle} ${sender === 'user' ? userStyle : aiStyle} ${isLoading ? 'animate-pulse' : ''}`; bubble.innerHTML = escapeHtml(text).replace(/\n/g, '