abishekcodes's picture
Deployment Test
98abe61
// 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'));
});