// Application State const AppState = { currentPdf: null, pdfs: [], deviceInfo: null, }; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { initializeTheme(); loadDeviceInfo(); initializeEventListeners(); loadPdfList(); }); // Theme Toggle function initializeTheme() { const savedTheme = localStorage.getItem('theme') || 'light'; document.body.setAttribute('data-theme', savedTheme); updateThemeIcon(savedTheme); document.getElementById('themeToggle').addEventListener('click', function() { const currentTheme = document.body.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.body.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); updateThemeIcon(newTheme); }); } function updateThemeIcon(theme) { const icon = document.getElementById('themeIcon'); if (icon) { icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } } // Load Device Info async function loadDeviceInfo() { try { const response = await fetch('/api/device-info'); const data = await response.json(); AppState.deviceInfo = data; updateDeviceStatus(data); } catch (error) { console.error('Error loading device info:', error); updateDeviceStatus({ device: 'unknown', cuda_available: false }); } } function updateDeviceStatus(info) { const badge = document.getElementById('deviceBadge'); const deviceName = document.getElementById('deviceName'); if (info.cuda_available) { badge.textContent = 'GPU'; badge.className = 'badge bg-success'; deviceName.textContent = info.device_name || 'CUDA Device'; } else { badge.textContent = 'CPU'; badge.className = 'badge bg-secondary'; deviceName.textContent = 'CPU Processing'; } } // Event Listeners function initializeEventListeners() { const uploadForm = document.getElementById('uploadForm'); uploadForm.addEventListener('submit', handleUpload); } // Handle File Upload async function handleUpload(e) { e.preventDefault(); const fileInput = document.getElementById('fileInput'); const files = fileInput.files; if (files.length === 0) { alert('Please select at least one PDF file'); return; } const extractionMode = document.querySelector('input[name="extractionMode"]:checked').value; // Show processing section document.getElementById('processingSection').style.display = 'block'; document.getElementById('resultsSection').style.display = 'none'; document.getElementById('emptyState').style.display = 'none'; const formData = new FormData(); for (let i = 0; i < files.length; i++) { formData.append('files[]', files[i]); } formData.append('extraction_mode', extractionMode); try { const response = await fetch('/api/upload', { method: 'POST', body: formData }); const data = await response.json(); if (data.error) { throw new Error(data.error); } // Hide processing section document.getElementById('processingSection').style.display = 'none'; // Reload PDF list and show results await loadPdfList(); // Show first PDF details if available if (data.results && data.results.length > 0) { const firstPdf = data.results[0]; if (!firstPdf.error) { showPdfDetails(firstPdf.stem); } } // Reset form fileInput.value = ''; } catch (error) { console.error('Upload error:', error); alert('Error processing files: ' + error.message); document.getElementById('processingSection').style.display = 'none'; } } // Load PDF List async function loadPdfList() { try { const response = await fetch('/api/pdf-list'); const data = await response.json(); AppState.pdfs = data.pdfs || []; renderPdfList(); if (AppState.pdfs.length > 0) { document.getElementById('resultsSection').style.display = 'block'; document.getElementById('emptyState').style.display = 'none'; } else { document.getElementById('resultsSection').style.display = 'none'; document.getElementById('emptyState').style.display = 'block'; } } catch (error) { console.error('Error loading PDF list:', error); } } // Render PDF List function renderPdfList() { const pdfList = document.getElementById('pdfList'); pdfList.innerHTML = ''; if (AppState.pdfs.length === 0) { pdfList.innerHTML = '
No PDFs processed yet
'; return; } AppState.pdfs.forEach((pdf, index) => { const item = document.createElement('div'); item.className = `list-group-item d-flex align-items-center justify-content-between ${index === 0 && !AppState.currentPdf ? 'active' : ''} ${AppState.currentPdf === pdf.stem ? 'active' : ''}`; const left = document.createElement('a'); left.href = '#'; left.className = 'flex-grow-1 text-decoration-none text-reset'; left.innerHTML = `
${pdf.stem}
`; left.addEventListener('click', function(e) { e.preventDefault(); // Update active state document.querySelectorAll('#pdfList .list-group-item').forEach(i => i.classList.remove('active')); item.classList.add('active'); showPdfDetails(pdf.stem); }); const delBtn = document.createElement('button'); delBtn.className = 'btn btn-sm btn-outline-danger ms-3'; delBtn.innerHTML = ''; delBtn.title = `Delete "${pdf.stem}"`; delBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const confirmed = confirm(`Delete processed outputs for "${pdf.stem}"? This cannot be undone.`); if (!confirmed) return; try { // Use form-encoded POST to the body endpoint for widest compatibility const resp = await fetch('/api/delete', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, body: new URLSearchParams({ stem: pdf.stem }).toString() }); const raw = await resp.text(); let res; try { res = JSON.parse(raw); } catch (_) { res = null; } if (!resp.ok || (res && res?.error)) { throw new Error((res && res?.error) || raw || 'Delete failed'); } // Refresh list and clear details if we deleted the active item if (AppState.currentPdf === pdf.stem) { AppState.currentPdf = null; const details = document.getElementById('pdfDetails'); if (details) { details.innerHTML = `
Deleted "${pdf.stem}" successfully.
`; } } await loadPdfList(); } catch (err) { console.error('Delete error:', err); alert('Failed to delete: ' + (err?.message || err)); } }); item.appendChild(left); item.appendChild(delBtn); pdfList.appendChild(item); }); } // Show PDF Details async function showPdfDetails(pdfStem) { AppState.currentPdf = pdfStem; // Update active state in list document.querySelectorAll('#pdfList .list-group-item').forEach((item, index) => { item.classList.remove('active'); const pdfStemFromItem = AppState.pdfs[index]?.stem; if (pdfStemFromItem === pdfStem) { item.classList.add('active'); } }); try { const response = await fetch(`/api/pdf-details/${encodeURIComponent(pdfStem)}`); const data = await response.json(); if (data.error) { throw new Error(data.error); } renderPdfDetails(data); } catch (error) { console.error('Error loading PDF details:', error); document.getElementById('pdfDetails').innerHTML = `
Error loading PDF details: ${error.message}
`; } } // Render PDF Details function renderPdfDetails(data) { const container = document.getElementById('pdfDetails'); let html = `
${data.stem}
${data.figures_count || 0}
Figures
${data.tables_count || 0}
Tables
${data.elements_count || 0}
Total Elements
${AppState.deviceInfo?.device === 'cuda' ? 'GPU' : 'CPU'}
Device
`; if (data.annotated_pdf) { html += ` Download Annotated PDF `; } if (data.markdown_path) { html += ` Download Markdown `; } html += `
`; // Figures Section if (data.figure_images && data.figure_images.length > 0) { html += `
Figures (${data.figure_images.length})
`; } // Tables Section if (data.table_images && data.table_images.length > 0) { html += `
Tables (${data.table_images.length})
`; } // Markdown Preview if (data.markdown_path) { html += `
Markdown Preview
Loading markdown...
`; } container.innerHTML = html; // Load markdown preview if available if (data.markdown_path) { loadMarkdownPreview(data.markdown_path); } // Wire delete button const deleteBtn = document.getElementById('deletePdfBtn'); if (deleteBtn) { deleteBtn.addEventListener('click', async () => { const confirmed = confirm(`Delete processed outputs for "${data.stem}"? This cannot be undone.`); if (!confirmed) return; try { // Use form-encoded POST to the body endpoint for widest compatibility const resp = await fetch('/api/delete', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, body: new URLSearchParams({ stem: data.stem }).toString() }); const raw = await resp.text(); let res; try { res = JSON.parse(raw); } catch (_) { res = null; } if (!resp.ok || (res && res.error)) { throw new Error((res && res.error) || raw || 'Delete failed'); } // Refresh list and clear details await loadPdfList(); document.getElementById('pdfDetails').innerHTML = `
Deleted "${data.stem}" successfully.
`; AppState.currentPdf = null; } catch (err) { console.error('Delete error:', err); alert('Failed to delete: ' + (err?.message || err)); } }); } } // Load Markdown Preview async function loadMarkdownPreview(markdownPath) { try { const response = await fetch(`/output/${markdownPath}`); const text = await response.text(); document.getElementById('markdownPreview').textContent = text; } catch (error) { console.error('Error loading markdown:', error); document.getElementById('markdownPreview').textContent = 'Error loading markdown content'; } }