Spaces:
Runtime error
Runtime error
| // 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 = '<p style="text-align: center; color: var(--text-secondary);">No PII entities were detected.</p>'; | |
| redactBtn.disabled = true; | |
| return; | |
| } | |
| entities.forEach((entity, idx) => { | |
| const div = document.createElement('div'); | |
| div.className = 'entity-item'; | |
| div.innerHTML = ` | |
| <input type="checkbox" class="entity-checkbox" id="entity${idx}" checked> | |
| <label for="entity${idx}" class="entity-content"> | |
| <span class="entity-label">${entity.label}</span> | |
| <span>${entity.text}</span> | |
| </label> | |
| `; | |
| 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 = '<p>Loading preview...</p>'; | |
| const isPdf = (fileOrBlob.name && fileOrBlob.name.endsWith('.pdf')) || fileOrBlob.type === 'application/pdf'; | |
| if (!window.pdfjsLib) { | |
| container.innerHTML = `<p style="color: var(--accent-primary);">Error: PDF library did not load correctly.</p>`; | |
| return; | |
| } | |
| try { | |
| if (isPdf) { | |
| const arrayBuffer = await fileOrBlob.arrayBuffer(); | |
| await renderPdfPreview(arrayBuffer, container); | |
| } else { | |
| container.innerHTML = `<p>Preview for DOCX files is not supported.</p>`; | |
| } | |
| } catch (error) { | |
| container.innerHTML = `<p style="color: var(--accent-primary);">Could not render PDF preview. The file may be corrupt.</p>`; | |
| } | |
| }; | |
| // --- 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')); | |
| }); |