// ROOT CAUSE FIX: Wait for the DOM to be fully loaded before running any script logic. // This guarantees that libraries like pdf.js are available. document.addEventListener('DOMContentLoaded', () => { // --- PDF.js Worker Configuration --- // This is a critical step that must be run after the library is loaded. if (window.pdfjsLib) { pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.2.67/pdf.worker.min.js`; } else { console.error("PDF.js library is not loaded. Previews will fail."); } // --- DOM Elements --- const uploadContainer = document.getElementById('upload-container'); const uploadForm = document.getElementById('uploadForm'); const fileInput = document.getElementById('fileInput'); const resultsContainer = document.getElementById('results-container'); const entitiesList = document.getElementById('entitiesList'); const redactBtn = document.getElementById('redactBtn'); const loader = document.getElementById('loader'); const errorDiv = document.getElementById('error'); const originalPreview = document.getElementById('originalPreview'); const redactedPreview = document.getElementById('redactedPreview'); const tabOriginal = document.getElementById('tabOriginal'); const tabRedacted = document.getElementById('tabRedacted'); const dragArea = document.getElementById('dragArea'); const dragText = document.getElementById('dragText'); let detectedEntities = []; // This will store the full entity objects from the backend let uploadedFile = null; // --- State Management --- const showLoader = (message) => { loader.querySelector('p').textContent = message; loader.classList.remove('hidden'); uploadContainer.classList.add('hidden'); resultsContainer.classList.add('hidden'); errorDiv.classList.add('hidden'); }; const showError = (message, details = null) => { let errorMessage = message; if (details) { errorMessage += `\n\nDetails: ${JSON.stringify(details, null, 2)}`; } errorDiv.textContent = errorMessage; errorDiv.classList.remove('hidden'); loader.classList.add('hidden'); uploadContainer.classList.remove('hidden'); resultsContainer.classList.add('hidden'); }; const showResults = () => { resultsContainer.classList.remove('hidden'); loader.classList.add('hidden'); uploadContainer.classList.add('hidden'); }; // --- Drag and Drop --- if (dragArea) { ['dragenter', 'dragover'].forEach(eventName => { dragArea.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); dragArea.classList.add('dragover'); }); }); ['dragleave', 'drop'].forEach(eventName => { dragArea.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); dragArea.classList.remove('dragover'); }); }); dragArea.addEventListener('drop', e => { const files = e.dataTransfer.files; if (files.length) { fileInput.files = files; handleFileSelection(files[0]); } }); } fileInput.addEventListener('change', () => { if (fileInput.files.length) handleFileSelection(fileInput.files[0]); }); const handleFileSelection = (file) => { uploadedFile = file; dragText.textContent = `Selected: ${file.name}`; errorDiv.classList.add('hidden'); }; // --- API Calls --- uploadForm.addEventListener('submit', async (e) => { e.preventDefault(); if (!uploadedFile) { showError('Please select a file to analyze.'); return; } showLoader('Analyzing document...'); originalPreview.innerHTML = ''; redactedPreview.innerHTML = ''; switchToTab('original'); await showFilePreview(uploadedFile, originalPreview); const formData = new FormData(); formData.append('file', uploadedFile); try { const response = await fetch('/analyze', { method: 'POST', body: formData }); if (!response.ok) { const errData = await response.json().catch(() => ({ detail: 'Unknown server error.' })); throw new Error(errData.detail); } const data = await response.json(); // Store the full entity objects returned by the API detectedEntities = data.entities; displayEntities(detectedEntities); showResults(); } catch (err) { showError(err.message); } }); redactBtn.addEventListener('click', async () => { // ROOT CAUSE FIX: Ensure the FULL entity object (including 'location') is sent back. const selectedEntities = detectedEntities.filter((_, idx) => document.getElementById(`entity${idx}`).checked ); if (selectedEntities.length === 0) { errorDiv.textContent = 'Please select at least one entity to redact.'; errorDiv.classList.remove('hidden'); return; } showLoader('Redacting and preparing download...'); const formData = new FormData(); formData.append('file', uploadedFile); formData.append('entities_to_redact_json', JSON.stringify(selectedEntities)); try { const response = await fetch('/redact', { method: 'POST', body: formData }); if (!response.ok) { const errData = await response.json().catch(() => ({ detail: 'Redaction failed on the server.' })); throw new Error(errData.detail); } // FASTER DOWNLOAD: The server sends the file directly. // The browser will automatically handle the download prompt. const blob = await response.blob(); const url = URL.createObjectURL(blob); // Create a temporary link to trigger the download automatically const a = document.createElement('a'); a.href = url; a.download = `redacted_${uploadedFile.name}`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); // Clean up the object URL // Show the redacted preview after download starts await showFilePreview(blob, redactedPreview); switchToTab('redacted'); showResults(); } catch (err) { // Display detailed error from the backend if available showError(`Failed to redact document: ${err.message}`, err.detail || null); } }); // --- UI Rendering --- const displayEntities = (entities) => { entitiesList.innerHTML = ''; if (entities.length === 0) { entitiesList.innerHTML = '

No PII entities were detected.

'; redactBtn.disabled = true; return; } entities.forEach((entity, idx) => { const div = document.createElement('div'); div.className = 'entity-item'; div.innerHTML = ` `; entitiesList.appendChild(div); }); redactBtn.disabled = false; }; const renderPdfPreview = async (arrayBuffer, container) => { container.innerHTML = ''; const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const viewport = page.getViewport({ scale: 1.5 }); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; container.appendChild(canvas); await page.render({ canvasContext: context, viewport: viewport }).promise; } }; const showFilePreview = async (fileOrBlob, container) => { container.innerHTML = '

Loading preview...

'; const isPdf = (fileOrBlob.name && fileOrBlob.name.endsWith('.pdf')) || fileOrBlob.type === 'application/pdf'; if (!window.pdfjsLib) { container.innerHTML = `

Error: PDF library did not load correctly.

`; return; } try { if (isPdf) { const arrayBuffer = await fileOrBlob.arrayBuffer(); await renderPdfPreview(arrayBuffer, container); } else { container.innerHTML = `

Preview for DOCX files is not supported.

`; } } catch (error) { container.innerHTML = `

Could not render PDF preview. The file may be corrupt.

`; } }; // --- Tab Switching --- const switchToTab = (tabName) => { if (tabName === 'original') { tabOriginal.classList.add('active'); tabRedacted.classList.remove('active'); originalPreview.classList.remove('hidden'); redactedPreview.classList.add('hidden'); } else { tabRedacted.classList.add('active'); tabOriginal.classList.remove('active'); redactedPreview.classList.remove('hidden'); originalPreview.classList.add('hidden'); } }; tabOriginal.addEventListener('click', () => switchToTab('original')); tabRedacted.addEventListener('click', () => switchToTab('redacted')); });