Hanan-Alnakhal's picture
Update static/script.js
d0f502e verified
raw
history blame
14.1 kB
// ===== GLOBAL STATE =====
let currentResults = null;
let explanations = null;
let sessionId = null; // Store session ID
// ===== INITIALIZATION =====
document.addEventListener('DOMContentLoaded', () => {
initNavigation();
initFileUpload();
initScrollAnimations();
});
// ===== NAVIGATION =====
function initNavigation() {
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = link.getAttribute('href');
// Update active state
navLinks.forEach(l => l.classList.remove('active'));
link.classList.add('active');
// Smooth scroll
document.querySelector(target).scrollIntoView({
behavior: 'smooth',
block: 'start'
});
});
});
// Mobile menu toggle
const mobileToggle = document.querySelector('.mobile-menu-toggle');
const navMenu = document.querySelector('.nav-menu');
if (mobileToggle) {
mobileToggle.addEventListener('click', () => {
navMenu.classList.toggle('active');
});
}
}
// ===== SCROLL ANIMATIONS =====
function initScrollAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.about-card, .feature-card').forEach(card => {
card.style.opacity = '0';
card.style.transform = 'translateY(20px)';
card.style.transition = 'all 0.6s ease-out';
observer.observe(card);
});
}
// ===== FILE UPLOAD =====
function initFileUpload() {
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
if (!fileInput || !uploadArea) return;
// Click to upload
fileInput.addEventListener('change', handleFileSelect);
// Drag and drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
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');
}
// Store session ID and results
sessionId = data.session_id;
currentResults = data.results;
console.log('Session ID:', sessionId);
console.log('Results:', currentResults);
showToast(`✓ Found ${data.count} lab results!`, 'success');
// Generate explanations
await generateExplanations();
// Display results
displayResults();
// Scroll to results
setTimeout(() => {
document.getElementById('results').scrollIntoView({
behavior: 'smooth'
});
}, 500);
} catch (error) {
showToast(error.message, 'error');
resetUploadArea();
}
}
async function generateExplanations() {
showLoading('Generating AI explanations... This may take 30-60 seconds.');
try {
const response = await fetch('/api/explain', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_id: sessionId })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to generate explanations');
}
explanations = data.explanations;
console.log('Explanations:', explanations);
} catch (error) {
showToast('Error generating explanations: ' + error.message, 'error');
// Continue anyway to show results
} finally {
hideLoading();
}
}
function displayResults() {
const resultsSection = document.getElementById('results');
const container = document.getElementById('resultsContainer');
if (!currentResults || currentResults.length === 0) {
container.innerHTML = '<p class="empty-message">No results found</p>';
return;
}
// Show results section
resultsSection.classList.remove('hidden');
// Update summary stats
updateSummaryStats();
// Clear container
container.innerHTML = '';
// Create result cards
currentResults.forEach((result, index) => {
const card = createResultCard(result, index);
container.appendChild(card);
});
// Reset upload area
resetUploadArea();
}
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;
}
function createResultCard(result, index) {
const card = document.createElement('div');
card.className = 'result-card';
card.style.setProperty('--i', index + 1);
const statusClass = `status-${result.status}`;
const explanation = explanations && explanations[result.test_name]
? explanations[result.test_name]
: 'Explanation is being generated. Click "Ask Questions" to learn more about this result.';
card.innerHTML = `
<div class="result-header">
<div>
<h3 class="result-name">${escapeHtml(result.test_name)}</h3>
<p class="result-range">Reference: ${escapeHtml(result.reference_range || 'N/A')}</p>
</div>
<div class="result-value-container">
<div class="result-value">${escapeHtml(result.value)} ${escapeHtml(result.unit)}</div>
<span class="result-status ${statusClass}">${result.status}</span>
</div>
</div>
<div class="result-explanation">
<strong>💡 What does this mean?</strong>
<p>${escapeHtml(explanation)}</p>
</div>
`;
return card;
}
function resetUploadArea() {
document.getElementById('uploadArea').classList.remove('hidden');
document.getElementById('uploadProgress').classList.add('hidden');
document.getElementById('fileInput').value = '';
}
// ===== SUMMARY GENERATION =====
async function generateSummary() {
if (!sessionId) {
showToast('Please upload a lab report first', 'error');
return;
}
showLoading('Generating comprehensive summary...');
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();
if (!response.ok) {
throw new Error(data.error || 'Failed to generate summary');
}
// Show summary modal
showSummaryModal(data.summary, data.stats);
} catch (error) {
showToast('Error: ' + error.message, 'error');
} finally {
hideLoading();
}
}
function showSummaryModal(summary, stats) {
const modal = document.createElement('div');
modal.className = 'chat-modal';
modal.innerHTML = `
<div class="chat-modal-content">
<div class="chat-header">
<h3>📊 Complete Summary</h3>
<button class="chat-close" onclick="this.closest('.chat-modal').remove()">&times;</button>
</div>
<div class="chat-messages">
<div class="chat-message assistant">
<div class="chat-bubble">
<p>${escapeHtml(summary).replace(/\n/g, '<br><br>')}</p>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// Close on click outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
}
// ===== CHAT FUNCTIONALITY =====
function openChat() {
if (!sessionId) {
showToast('Please upload a lab report first', 'error');
return;
}
const modal = document.getElementById('chatModal');
modal.classList.remove('hidden');
document.getElementById('chatInput').focus();
}
function closeChat() {
document.getElementById('chatModal').classList.add('hidden');
}
function handleChatKeypress(event) {
if (event.key === 'Enter') {
sendChatMessage();
}
}
async function sendChatMessage() {
const input = document.getElementById('chatInput');
const question = input.value.trim();
if (!question) return;
if (!sessionId) {
showToast('Session expired. Please upload your report again.', 'error');
return;
}
// Clear input
input.value = '';
// Add user message
addChatMessage(question, 'user');
// Show 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();
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) {
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 welcome message if exists
const welcome = messagesContainer.querySelector('.chat-welcome');
if (welcome) {
welcome.remove();
}
const messageId = `msg-${Date.now()}`;
const messageDiv = document.createElement('div');
messageDiv.id = messageId;
messageDiv.className = `chat-message ${sender}`;
const bubbleClass = isLoading ? 'chat-bubble loading' : 'chat-bubble';
messageDiv.innerHTML = `<div class="${bubbleClass}">${escapeHtml(text)}</div>`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return messageId;
}
// ===== LOADING OVERLAY =====
function showLoading(message = 'Processing...') {
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.querySelector('p').textContent = message;
overlay.classList.remove('hidden');
}
}
function hideLoading() {
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.classList.add('hidden');
}
}
// ===== TOAST NOTIFICATIONS =====
function showToast(message, type = 'info') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast ${type} show`;
setTimeout(() => {
toast.classList.remove('show');
}, 4000);
}
// ===== UTILITY FUNCTIONS =====
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ===== SMOOTH SCROLL FOR ALL LINKS =====
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// ===== CLOSE MODALS ON ESC =====
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const chatModal = document.getElementById('chatModal');
if (chatModal && !chatModal.classList.contains('hidden')) {
closeChat();
}
}
});
// ===== CLOSE MODALS ON OUTSIDE CLICK =====
document.addEventListener('click', (e) => {
const chatModal = document.getElementById('chatModal');
if (e.target === chatModal) {
closeChat();
}
});