moazx's picture
update
443e99e
// 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 = '<div class="text-center text-muted p-3">No PDFs processed yet</div>';
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 = `
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-0">
<i class="fas fa-file-pdf me-2"></i>
${pdf.stem}
</h6>
</div>
`;
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 = '<i class="fas fa-trash-alt"></i>';
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 = `
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
Deleted "${pdf.stem}" successfully.
</div>
`;
}
}
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 = `
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle me-2"></i>
Error loading PDF details: ${error.message}
</div>
`;
}
}
// Render PDF Details
function renderPdfDetails(data) {
const container = document.getElementById('pdfDetails');
let html = `
<div class="card shadow-sm mb-4">
<div class="card-header bg-primary-custom text-white">
<h5 class="mb-0">
<i class="fas fa-file-pdf me-2"></i>
${data.stem}
</h5>
<button class="btn btn-sm btn-danger float-end" id="deletePdfBtn" title="Delete this processed PDF">
<i class="fas fa-trash-alt me-1"></i> Delete
</button>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-3">
<div class="stat-card">
<i class="fas fa-images fa-2x text-primary mb-2"></i>
<div class="stat-value">${data.figures_count || 0}</div>
<div class="stat-label">Figures</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<i class="fas fa-table fa-2x text-primary mb-2"></i>
<div class="stat-value">${data.tables_count || 0}</div>
<div class="stat-label">Tables</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<i class="fas fa-list fa-2x text-primary mb-2"></i>
<div class="stat-value">${data.elements_count || 0}</div>
<div class="stat-label">Total Elements</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<i class="fas fa-microchip fa-2x text-primary mb-2"></i>
<div class="stat-value">${AppState.deviceInfo?.device === 'cuda' ? 'GPU' : 'CPU'}</div>
<div class="stat-label">Device</div>
</div>
</div>
</div>
<div class="download-btn-group">
`;
if (data.annotated_pdf) {
html += `
<a href="/output/${data.annotated_pdf}" class="btn btn-primary" download>
<i class="fas fa-download me-2"></i>
Download Annotated PDF
</a>
`;
}
if (data.markdown_path) {
html += `
<a href="/output/${data.markdown_path}" class="btn btn-outline-primary" download>
<i class="fas fa-download me-2"></i>
Download Markdown
</a>
`;
}
html += `
</div>
</div>
</div>
`;
// Figures Section
if (data.figure_images && data.figure_images.length > 0) {
html += `
<div class="card shadow-sm mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-images me-2"></i>
Figures (${data.figure_images.length})
</h5>
</div>
<div class="card-body">
<div class="image-gallery">
`;
data.figure_images.forEach((imgPath, index) => {
const figure = data.figures[index] || {};
html += `
<div class="image-item">
<img src="/output/${imgPath}" alt="Figure ${index + 1}" loading="lazy">
<div class="image-caption">
<strong>Figure ${index + 1}</strong>
${figure.page ? `<br><small class="text-muted">Page ${figure.page}</small>` : ''}
</div>
</div>
`;
});
html += `
</div>
</div>
</div>
`;
}
// Tables Section
if (data.table_images && data.table_images.length > 0) {
html += `
<div class="card shadow-sm mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-table me-2"></i>
Tables (${data.table_images.length})
</h5>
</div>
<div class="card-body">
<div class="image-gallery">
`;
data.table_images.forEach((imgPath, index) => {
const table = data.tables[index] || {};
html += `
<div class="image-item">
<img src="/output/${imgPath}" alt="Table ${index + 1}" loading="lazy">
<div class="image-caption">
<strong>Table ${index + 1}</strong>
${table.page ? `<br><small class="text-muted">Page ${table.page}</small>` : ''}
</div>
</div>
`;
});
html += `
</div>
</div>
</div>
`;
}
// Markdown Preview
if (data.markdown_path) {
html += `
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-file-code me-2"></i>
Markdown Preview
</h5>
</div>
<div class="card-body">
<div class="markdown-preview" id="markdownPreview">
Loading markdown...
</div>
</div>
</div>
`;
}
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 = `
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
Deleted "${data.stem}" successfully.
</div>
`;
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';
}
}