document.addEventListener('DOMContentLoaded', () => { // Elements const apiStatus = document.getElementById('api-status'); const statusDot = apiStatus.querySelector('.status-dot'); const statusText = apiStatus.querySelector('.status-text'); const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); const browseBtn = document.getElementById('browse-btn'); const textInput = document.getElementById('text-input'); const processBtn = document.getElementById('process-btn'); const clearBtn = document.getElementById('clear-btn'); const resultsPanel = document.getElementById('results-panel'); const outputText = document.getElementById('output-text'); const legend = document.getElementById('legend'); const copyBtn = document.getElementById('copy-btn'); const downloadBtn = document.getElementById('download-btn'); let currentFile = null; let currentDeidentifiedText = ''; // Init checkApiStatus(); // API Health Check async function checkApiStatus() { try { const resp = await fetch('/health'); const data = await resp.json(); if (data.status === 'healthy') { statusDot.classList.add('healthy'); statusText.textContent = 'Backend Online'; } } catch (e) { statusDot.classList.remove('healthy'); statusText.textContent = 'Backend Offline'; } } // Drag & Drop ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evt => { dropZone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); }); }); dropZone.addEventListener('dragover', () => dropZone.classList.add('drag-over')); dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over')); dropZone.addEventListener('drop', e => { dropZone.classList.remove('drag-over'); const files = e.dataTransfer.files; if (files.length > 0) handleFile(files[0]); }); browseBtn.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', e => { if (e.target.files.length > 0) handleFile(e.target.files[0]); }); function handleFile(file) { const ext = file.name.split('.').pop().toLowerCase(); if (ext !== 'txt' && ext !== 'pdf' && ext !== 'docx') { alert('Please upload a .txt, .pdf, or .docx file.'); return; } currentFile = file; textInput.value = `File selected: ${file.name}`; textInput.disabled = true; // Show immediate indicator for PDF if (ext === 'pdf') { textInput.value += "\nNote: Text will be extracted upon processing."; } else { // Pre-read txt files for preview const reader = new FileReader(); reader.onload = (e) => { textInput.value = e.target.result; textInput.disabled = false; }; reader.readAsText(file); } } // Processing processBtn.addEventListener('click', async () => { const text = textInput.value.trim(); if (!text && !currentFile) return; // UI State processBtn.disabled = true; processBtn.querySelector('.btn-text').textContent = 'Processing...'; processBtn.querySelector('.loader').hidden = false; try { let response; if (currentFile && (currentFile.name.endsWith('.pdf') || textInput.disabled)) { // Upload file const formData = new FormData(); formData.append('file', currentFile); response = await fetch('/deidentify/file', { method: 'POST', body: formData }); } else { // Process text response = await fetch('/deidentify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) }); } if (!response.ok) { const err = await response.json(); throw new Error(err.detail || 'API Error'); } const result = await response.json(); displayResults(result); } catch (error) { alert('Failed to process: ' + error.message); } finally { processBtn.disabled = false; processBtn.querySelector('.btn-text').textContent = 'De-identify PHI'; processBtn.querySelector('.loader').hidden = true; } }); function displayResults(data) { resultsPanel.hidden = false; // Render de-identified text with highlights // To make it interactive, we replace tokens with styled spans let displayHtml = data.deidentified; // Gather unique labels for the legend const labels = new Set(); data.entities.forEach(ent => labels.add(ent.label)); // Update Legend legend.innerHTML = ''; labels.forEach(label => { const item = document.createElement('div'); item.className = 'legend-item'; item.innerHTML = `${label}`; legend.appendChild(item); }); // Simple masking visualization // Find entities in the deidentified text (e.g., [PER]) and highlight them const protectedLabels = Array.from(labels).map(l => `\\[${l}\\]`).join('|'); if (protectedLabels) { const regex = new RegExp(`(${protectedLabels})`, 'g'); displayHtml = displayHtml.replace(regex, '$1'); } outputText.innerHTML = displayHtml; // Prepare for download currentDeidentifiedText = data.deidentified; downloadBtn.disabled = false; } clearBtn.addEventListener('click', () => { textInput.value = ''; resultsPanel.hidden = true; fileInput.value = ''; currentFile = null; textInput.disabled = false; }); copyBtn.addEventListener('click', () => { navigator.clipboard.writeText(outputText.innerText); const originalIcon = copyBtn.innerHTML; copyBtn.innerHTML = ''; setTimeout(() => copyBtn.innerHTML = originalIcon, 2000); }); // Download logic downloadBtn.addEventListener('click', () => { if (!currentDeidentifiedText) return; const blob = new Blob([currentDeidentifiedText], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `deidentified_${new Date().getTime()}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); });