Hanan-Alnakhal's picture
Update static/script.js
182ca99 verified
// ===== GLOBAL STATE =====
let currentResults = null;
let explanations = null;
let sessionId = null;
// ===== SECTION NAVIGATION (CRITICAL - MISSING IN YOUR CODE) =====
function showSection(sectionId) {
console.log('πŸ”„ Showing section:', sectionId);
// Hide all sections
document.querySelectorAll('.section').forEach(section => {
section.classList.remove('active');
});
// Show target section
const targetSection = document.getElementById(sectionId);
if (targetSection) {
targetSection.classList.add('active');
targetSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
console.error('❌ Section not found:', sectionId);
}
}
// ===== INITIALIZATION =====
document.addEventListener('DOMContentLoaded', () => {
console.log('βœ… DOM loaded, initializing...');
initFileUpload();
// Show upload section by default
showSection('upload-section');
});
// ===== FILE UPLOAD =====
function initFileUpload() {
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
if (!fileInput || !uploadArea) {
console.error('❌ Upload elements not found');
return;
}
console.log('βœ… Upload initialized');
// Click to upload
fileInput.addEventListener('change', handleFileSelect);
// Drag and drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('border-secondary', 'bg-blue-100');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('border-secondary', 'bg-blue-100');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('border-secondary', 'bg-blue-100');
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 {
console.log('πŸ“€ Uploading file:', file.name);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
console.log('πŸ“₯ Upload response:', data);
if (!response.ok) {
throw new Error(data.error || 'Upload failed');
}
// Store session ID and results
sessionId = data.session_id;
currentResults = data.results;
console.log('βœ… Session ID:', sessionId);
console.log('βœ… Results:', currentResults.length);
showToast(`βœ“ Found ${data.count} lab results!`, 'success');
// Display results immediately with template explanations
console.log('🎨 Displaying template results...');
displayResults();
updateSummaryStats();
// Show stats and buttons
document.getElementById('summaryStats').classList.remove('hidden');
document.getElementById('generateSummaryBtn').classList.remove('hidden');
// Switch to results section
setTimeout(() => {
showSection('results-section');
}, 500);
// Fetch AI explanations in background
console.log('πŸ€– Fetching AI explanations...');
showToast('Generating AI explanations...', 'info');
await generateExplanations();
// Update display with AI explanations
console.log('πŸ”„ Updating with AI explanations...');
displayResults();
showToast('βœ“ AI explanations loaded!', 'success');
} catch (error) {
console.error('❌ Upload error:', error);
showToast(error.message, 'error');
resetUploadArea();
}
}
async function generateExplanations() {
try {
console.log('πŸ”„ Calling /api/explain with session:', sessionId);
const response = await fetch('/api/explain', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: sessionId })
});
const data = await response.json();
console.log('πŸ“₯ Explanation response:', data);
if (!response.ok) {
throw new Error(data.error || 'Failed to generate explanations');
}
explanations = data.explanations;
console.log('βœ… Explanations loaded:', Object.keys(explanations).length);
} catch (error) {
console.error('❌ Explanation error:', error);
showToast('Using basic explanations', 'info');
}
}
function displayResults() {
const container = document.getElementById('resultsContainer');
console.log('🎯 displayResults() called');
console.log('πŸ“Š Results:', currentResults?.length);
console.log('πŸ’¬ Explanations:', explanations ? Object.keys(explanations).length : 0);
if (!currentResults || currentResults.length === 0) {
console.log('❌ No results to display');
container.innerHTML = '<div class="flex flex-col items-center justify-center h-full text-slate-400 py-20"><div class="text-6xl mb-4 opacity-20">πŸ“Š</div><p>Upload a lab report to view results here</p></div>';
return;
}
console.log(`βœ… Displaying ${currentResults.length} results`);
// Clear container
container.innerHTML = '';
// Create result cards
currentResults.forEach((result, index) => {
console.log(`πŸ“ Card ${index + 1}:`, result.test_name, result.status);
const card = createResultCard(result, index);
container.appendChild(card);
});
// Reset upload area
resetUploadArea();
console.log('βœ… All cards rendered');
}
function createResultCard(result, index) {
const card = document.createElement('div');
card.className = 'bg-white rounded-xl shadow-md border border-slate-200 p-6 mb-4 hover:shadow-lg transition-shadow';
// Status colors
const statusColors = {
normal: { bg: 'bg-green-50', text: 'text-green-700', border: 'border-green-200' },
high: { bg: 'bg-red-50', text: 'text-red-700', border: 'border-red-200' },
low: { bg: 'bg-yellow-50', text: 'text-yellow-700', border: 'border-yellow-200' },
unknown: { bg: 'bg-slate-50', text: 'text-slate-700', border: 'border-slate-200' }
};
const colors = statusColors[result.status] || statusColors.unknown;
// Get explanation
let explanation = '';
if (explanations && explanations[result.test_name]) {
explanation = explanations[result.test_name];
console.log(` βœ… Using AI explanation for ${result.test_name}`);
} else {
console.log(` ⚠️ Using template for ${result.test_name}`);
// Template explanation
if (result.status === 'normal') {
explanation = `βœ… <strong>Good news!</strong> Your ${result.test_name} level of ${result.value} ${result.unit} is within the normal range (${result.reference_range}).<br><br>This indicates healthy levels. Keep up your current health habits!`;
} else if (result.status === 'high') {
explanation = `⚠️ Your ${result.test_name} level of ${result.value} ${result.unit} is <strong>above</strong> the normal range (${result.reference_range}).<br><br>This may require attention. Please consult your healthcare provider.`;
} else if (result.status === 'low') {
explanation = `⚠️ Your ${result.test_name} level of ${result.value} ${result.unit} is <strong>below</strong> the normal range (${result.reference_range}).<br><br>This may require attention. Please consult your healthcare provider.`;
} else {
explanation = `Your ${result.test_name} result is ${result.value} ${result.unit}.<br><br>Reference range: ${result.reference_range}`;
}
}
card.innerHTML = `
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="text-xl font-bold text-slate-800">${escapeHtml(result.test_name)}</h3>
<p class="text-sm text-slate-500">Reference: ${escapeHtml(result.reference_range || 'N/A')}</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-slate-800">${escapeHtml(result.value)} <span class="text-lg text-slate-500">${escapeHtml(result.unit)}</span></div>
<span class="inline-block mt-2 px-3 py-1 rounded-full text-sm font-medium ${colors.bg} ${colors.text} border ${colors.border}">
${result.status.toUpperCase()}
</span>
</div>
</div>
<div class="mt-4 p-4 bg-blue-50 rounded-lg border border-blue-100">
<div class="font-semibold text-blue-900 mb-2">πŸ’‘ What does this mean?</div>
<div class="text-sm text-slate-700 leading-relaxed">${explanation}</div>
</div>
`;
return card;
}
function updateSummaryStats() {
const stats = { normal: 0, high: 0, low: 0 };
currentResults.forEach(result => {
if (result.status in stats) {
stats[result.status]++;
}
});
document.getElementById('normalCount').textContent = stats.normal;
document.getElementById('highCount').textContent = stats.high;
document.getElementById('lowCount').textContent = stats.low;
console.log('πŸ“Š Stats updated:', stats);
}
function resetUploadArea() {
document.getElementById('uploadArea').classList.remove('hidden');
document.getElementById('uploadProgress').classList.add('hidden');
document.getElementById('fileInput').value = '';
}
// ===== CHAT FUNCTIONALITY =====
async function askQuestion() {
const input = document.getElementById('chatInput');
const question = input.value.trim();
if (!question) return;
if (!sessionId) {
showToast('Please upload a lab report first', 'error');
return;
}
console.log('πŸ’¬ Asking:', question);
// Clear input
input.value = '';
// Add user message
addChatMessage(question, 'user');
// Add loading message
const loadingId = addChatMessage('Thinking...', 'assistant', true);
try {
const response = await fetch('/api/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
question: question,
session_id: sessionId
})
});
const data = await response.json();
console.log('πŸ’¬ Answer:', data);
if (!response.ok) {
throw new Error(data.error || 'Failed to get answer');
}
// Remove loading message
document.getElementById(loadingId).remove();
// Add assistant response
addChatMessage(data.answer, 'assistant');
} catch (error) {
console.error('❌ Chat error:', error);
document.getElementById(loadingId).remove();
addChatMessage(`Sorry, I encountered an error: ${error.message}`, 'assistant');
showToast(error.message, 'error');
}
}
function addChatMessage(text, sender, isLoading = false) {
const messagesContainer = document.getElementById('chatMessages');
// Remove placeholder text if exists
const placeholder = messagesContainer.querySelector('.text-slate-400');
if (placeholder && placeholder.closest('.text-center')) {
placeholder.closest('.text-center').remove();
}
const messageId = `msg-${Date.now()}-${Math.random()}`;
const messageDiv = document.createElement('div');
messageDiv.id = messageId;
messageDiv.className = `mb-4 ${sender === 'user' ? 'text-right' : 'text-left'}`;
const bubbleClass = sender === 'user'
? 'inline-block bg-secondary text-white px-4 py-2 rounded-2xl rounded-tr-sm max-w-[80%]'
: 'inline-block bg-slate-100 text-slate-800 px-4 py-2 rounded-2xl rounded-tl-sm max-w-[80%]';
messageDiv.innerHTML = `<div class="${bubbleClass} ${isLoading ? 'animate-pulse' : ''}">${escapeHtml(text)}</div>`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return messageId;
}
// ===== SUMMARY GENERATION =====
async function generateSummary() {
if (!sessionId) {
showToast('Please upload a lab report first', 'error');
return;
}
console.log('πŸ“Š Generating summary...');
const summaryContent = document.getElementById('summaryContent');
summaryContent.innerHTML = '<div class="text-center py-10"><div class="inline-block animate-spin rounded-full h-8 w-8 border-4 border-blue-200 border-t-secondary"></div><p class="text-secondary mt-3">Generating summary...</p></div>';
try {
const response = await fetch('/api/summary', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: sessionId })
});
const data = await response.json();
console.log('πŸ“Š Summary:', data);
if (!response.ok) {
throw new Error(data.error || 'Failed to generate summary');
}
// Display summary
summaryContent.innerHTML = `
<div class="prose max-w-none">
<div class="text-slate-700 leading-relaxed whitespace-pre-wrap">${escapeHtml(data.summary)}</div>
</div>
`;
showToast('βœ“ Summary generated!', 'success');
} catch (error) {
console.error('❌ Summary error:', error);
summaryContent.innerHTML = '<div class="text-center text-slate-400 py-10"><p>Error generating summary. Please try again.</p></div>';
showToast('Error: ' + error.message, 'error');
}
}
// ===== TOAST NOTIFICATIONS =====
function showToast(message, type = 'info') {
const toast = document.getElementById('toast');
const colors = {
success: 'bg-green-600',
error: 'bg-red-600',
info: 'bg-blue-600'
};
toast.textContent = message;
toast.className = `fixed bottom-5 right-5 ${colors[type] || colors.info} text-white px-6 py-3 rounded-lg shadow-2xl z-50 transform transition-all duration-300`;
toast.style.transform = 'translateY(0)';
toast.style.opacity = '1';
setTimeout(() => {
toast.style.transform = 'translateY(100px)';
toast.style.opacity = '0';
}, 4000);
}
// ===== UTILITY FUNCTIONS =====
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}