/**
* Alldocex - Intelligent Document Processing
* Frontend application logic
*/
// ===== State =====
let currentTaskId = null;
let pollInterval = null;
// ===== DOM Elements =====
const $ = (sel) => document.querySelector(sel);
const $$ = (sel) => document.querySelectorAll(sel);
const dropZone = $('#dropZone');
const fileInput = $('#fileInput');
const uploadSection = $('#uploadSection');
const processingSection = $('#processingSection');
const resultsSection = $('#resultsSection');
const toastContainer = $('#toastContainer');
const btnExtractUrl = $('#btnExtractUrl');
const urlInput = $('#urlInput');
// ===== Init =====
document.addEventListener('DOMContentLoaded', () => {
initUpload();
initTabs();
initButtons();
});
// ===== Health Check =====
// ===== Upload =====
function initUpload() {
// Click to upload
dropZone.addEventListener('click', () => fileInput.click());
// File selected
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
// URL input
btnExtractUrl.addEventListener('click', () => {
const url = urlInput.value.trim();
if (url) {
handleUrl(url);
} else {
showToast('Please enter a valid URL', 'error');
}
});
// Drag and drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
if (e.dataTransfer.files.length > 0) {
handleFile(e.dataTransfer.files[0]);
}
});
// Format badge filters
$$('.format-badge').forEach(badge => {
badge.addEventListener('click', (e) => {
e.stopPropagation(); // Don't trigger the main dropZone click
const format = badge.textContent.trim().toLowerCase();
openFilteredPicker(format);
});
});
}
function openFilteredPicker(format) {
const defaultAccept = fileInput.accept;
// Map of extensions
const extMap = {
pdf: '.pdf',
docx: '.docx',
png: '.png',
jpg: '.jpg,.jpeg',
jpeg: '.jpg,.jpeg',
tiff: '.tiff',
bmp: '.bmp',
webp: '.webp'
};
if (extMap[format]) {
fileInput.accept = extMap[format];
}
fileInput.click();
// Reset accept after a short delay so the main zone works normally
setTimeout(() => {
fileInput.accept = defaultAccept;
}, 500);
}
async function handleFile(file) {
// Validate extension
const validExts = ['pdf', 'docx', 'png', 'jpg', 'jpeg', 'tiff', 'bmp', 'webp'];
const ext = file.name.split('.').pop().toLowerCase();
if (!validExts.includes(ext)) {
showToast(`Unsupported file type: .${ext}`, 'error');
return;
}
// Validate size (20MB)
if (file.size > 20 * 1024 * 1024) {
showToast('File too large. Maximum size: 20MB', 'error');
return;
}
// Show processing UI
showSection('processing');
resetProcessingSteps();
// Upload
const formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.detail || 'Upload failed');
}
const data = await res.json();
currentTaskId = data.file_id;
// Start polling for results
updateStep('stepExtract', 'active');
startPolling(data.file_id);
} catch (e) {
showToast(e.message || 'Upload failed', 'error');
showSection('upload');
}
}
async function handleUrl(url) {
if (!url.startsWith('http')) {
showToast('URL must start with http:// or https://', 'error');
return;
}
try {
resetAll();
showSection('processing');
updateStep('stepExtract', 'active');
const response = await fetch('/api/extract/url', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to start URL extraction');
}
const data = await response.json();
currentTaskId = data.file_id;
// Polling results
startPolling(data.file_id);
} catch (error) {
showSection('upload');
showToast(error.message, 'error');
}
}
// ===== Polling =====
function startPolling(taskId) {
if (pollInterval) clearInterval(pollInterval);
pollInterval = setInterval(async () => {
try {
const res = await fetch(`/api/status/${taskId}`);
const data = await res.json();
if (data.status === 'processing') {
// Update steps based on available data
if (data.extraction) {
updateStep('stepExtract', 'done');
updateStep('stepSummary', 'active');
}
if (data.summary) {
updateStep('stepSummary', 'done');
updateStep('stepEntities', 'active');
}
if (data.entities) {
updateStep('stepEntities', 'done');
updateStep('stepSentiment', 'active');
}
if (data.sentiment) {
updateStep('stepSentiment', 'done');
}
}
if (data.status === 'completed' || data.status === 'error') {
clearInterval(pollInterval);
pollInterval = null;
// Mark all steps as done
updateStep('stepExtract', 'done');
updateStep('stepSummary', 'done');
updateStep('stepEntities', 'done');
updateStep('stepSentiment', 'done');
// Short delay to show completion
setTimeout(() => {
if (data.status === 'error' && !data.extraction) {
showToast(data.error_message || 'Processing failed', 'error');
showSection('upload');
} else {
displayResults(data);
showSection('results');
}
}, 600);
}
} catch (e) {
clearInterval(pollInterval);
pollInterval = null;
showToast('Lost connection to server', 'error');
showSection('upload');
}
}, 800);
}
// ===== Display Results =====
function displayResults(data) {
// File info bar
const typeIcons = { pdf: 'đ', docx: 'đ', image: 'đŧī¸' };
$('#fileTypeIcon').textContent = typeIcons[data.file_type] || 'đ';
$('#fileName').textContent = data.filename;
const meta = data.extraction?.metadata;
const parts = [data.file_type.toUpperCase()];
if (meta?.word_count) parts.push(`${meta.word_count.toLocaleString()} words`);
if (meta?.page_count) parts.push(`${meta.page_count} pages`);
$('#fileMeta').textContent = parts.join(' âĸ ');
const timeSeconds = (data.processing_time_ms / 1000).toFixed(1);
$('#processingTime').textContent = `âą ${timeSeconds}s`;
// Fallback parser in case CDN fails or is blocked
const parseMarkdown = (text) => {
if (!text) return '';
if (window.marked && window.marked.parse) {
return window.marked.parse(text);
} else if (window.marked) {
return window.marked(text);
}
// Very basic fallback if marked fails to load
return escapeHtml(text).replace(/\n/g, '
').replace(/\*\*(.*?)\*\*/g, '$1');
};
// Extracted Text
const textEl = $('#extractedText');
if (data.extraction?.raw_text) {
textEl.innerHTML = parseMarkdown(data.extraction.raw_text);
} else {
textEl.innerHTML = `
${data.extraction?.error_message || 'No text extracted.'}
`; } // Summary if (data.summary) { $('#summaryContent').innerHTML = parseMarkdown(data.summary.summary || 'Summary generation failed.'); $('#summaryStats').classList.remove('hidden'); $('#statOriginalLen').textContent = data.summary.original_length.toLocaleString(); $('#statSummaryLen').textContent = data.summary.summary_length.toLocaleString(); const pct = Math.round((1 - data.summary.compression_ratio) * 100); $('#statCompression').textContent = `${pct}%`; $('#statAlgorithm').textContent = data.summary.algorithm; // Render Key Highlights const highlightsContainer = $('#keyHighlightsContainer'); const highlightsList = $('#highlightsList'); if (data.summary.key_points && data.summary.key_points.length > 0) { highlightsContainer.classList.remove('hidden'); highlightsList.innerHTML = data.summary.key_points .map(point => { let escaped = escapeHtml(point); let bolded = escaped.replace(/\*\*(.*?)\*\*/g, '$1'); return `Summarization not available.
'; $('#summaryStats').classList.add('hidden'); $('#keyHighlightsContainer').classList.add('hidden'); } // Entities displayEntities(data.entities); // Sentiment displaySentiment(data.sentiment); // Metadata displayMetadata(data.extraction?.metadata); // Activate first tab activateTab('extracted'); } function displayEntities(entityData) { const catEl = $('#entityCategories'); const listEl = $('#entityList'); const countEl = $('#entityCount'); if (!entityData || entityData.entities.length === 0) { catEl.innerHTML = 'No entities detected in this document.
'; listEl.innerHTML = ''; countEl.textContent = '0 entities found'; return; } countEl.textContent = `${entityData.total_entities} entities found`; // Category badges const catColors = { PERSON: '#ec4899', ORG: '#3b82f6', GPE: '#10b981', DATE: '#f59e0b', MONEY: '#8b5cf6', EVENT: '#06b6d4', PRODUCT: '#fb923c', LAW: '#a855f7', NORP: '#f472b6', EMAIL: '#06b6d4', PHONE: '#3b82f6', URL: '#10b981', TIME: '#f59e0b', PERCENT: '#8b5cf6', CARDINAL: '#94a3b8', }; catEl.innerHTML = Object.entries(entityData.entity_counts) .sort((a, b) => b[1] - a[1]) .map(([label, count]) => `Sentiment analysis not available.
'; return; } const score = sentData.overall_compound; const label = sentData.overall_label; const posW = Math.round(sentData.overall_positive * 100); const neuW = Math.round(sentData.overall_neutral * 100); const negW = Math.round(sentData.overall_negative * 100); // Label color let labelColor; if (score >= 0.05) labelColor = 'var(--accent-green)'; else if (score <= -0.05) labelColor = 'var(--accent-red)'; else labelColor = 'var(--text-muted)'; let html = `No metadata available.
'; return; } const rows = [ ['Title', meta.title], ['Author', meta.author], ['File Type', meta.file_type], ['Page Count', meta.page_count], ['Word Count', meta.word_count?.toLocaleString()], ['Character Count', meta.character_count?.toLocaleString()], ['Created', meta.creation_date], ['Modified', meta.modification_date], ]; // Add extra metadata if (meta.extra) { for (const [key, value] of Object.entries(meta.extra)) { if (value && value !== '' && value !== 0 && value !== false) { const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); rows.push([label, String(value)]); } } } metaEl.innerHTML = `| ${k} | ${escapeHtml(String(v))} |
No text extracted yet.
'; $('#summaryContent').innerHTML = 'No summary available.
'; $('#summaryStats').classList.add('hidden'); $('#keyHighlightsContainer').classList.add('hidden'); $('#highlightsList').innerHTML = ''; $('#entityCategories').innerHTML = 'No entities detected.
'; $('#entityList').innerHTML = ''; $('#sentimentOverview').innerHTML = 'No sentiment data available.
'; $('#metadataContent').innerHTML = 'No metadata available.
'; } function showToast(message, type = 'info') { const icons = { info: 'âšī¸', error: 'â', success: 'â ' }; const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = `${escapeHtml(message)}`; toastContainer.appendChild(toast); setTimeout(() => { if (toast.parentNode) toast.remove(); }, 4000); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }