/** * 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 `
  • ${bolded}
  • `; }) .join(''); } else { highlightsContainer.classList.add('hidden'); } } else { $('#summaryContent').innerHTML = '

    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]) => `
    ${label} ${count}
    `).join(''); // Entity list listEl.innerHTML = entityData.entities .slice(0, 100) .map(ent => `
    ${ent.label} ${escapeHtml(ent.text)}
    ${ent.count > 1 ? `×${ent.count}` : ''}
    `).join(''); } function displaySentiment(sentData) { const overviewEl = $('#sentimentOverview'); if (!sentData) { overviewEl.innerHTML = '

    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 = `
    ${label}
    ${score >= 0 ? '+' : ''}${score.toFixed(3)}
    Positive ${posW}% Neutral ${neuW}% Negative ${negW}%
    `; // Sentence breakdown if (sentData.sentence_breakdown && sentData.sentence_breakdown.length > 0) { html += `

    Sentence-Level Breakdown (top ${Math.min(sentData.sentence_breakdown.length, 20)})

    ${sentData.sentence_breakdown.slice(0, 20).map(s => { let cls = 'sent-neutral'; if (s.compound >= 0.05) cls = 'sent-positive'; else if (s.compound <= -0.05) cls = 'sent-negative'; return `
    ${s.label} ${escapeHtml(s.text)}
    `; }).join('')}
    `; } overviewEl.innerHTML = html; } function displayMetadata(meta) { const metaEl = $('#metadataContent'); if (!meta) { metaEl.innerHTML = '

    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 = ` ${rows.filter(([, v]) => v && v !== 'None' && v !== 'null' && v !== '') .map(([k, v]) => ``) .join('')}
    ${k}${escapeHtml(String(v))}
    `; } // ===== Tabs ===== function initTabs() { $$('.tab').forEach(tab => { tab.addEventListener('click', () => { activateTab(tab.dataset.tab); }); }); } function activateTab(tabName) { $$('.tab').forEach(t => t.classList.remove('active')); $$('.tab-panel').forEach(p => p.classList.remove('active')); const tab = $(`.tab[data-tab="${tabName}"]`); const panel = $(`#panel${tabName.charAt(0).toUpperCase() + tabName.slice(1)}`); if (tab) tab.classList.add('active'); if (panel) panel.classList.add('active'); } // ===== Buttons ===== function initButtons() { // New upload $('#btnNewUpload').addEventListener('click', () => { resetAll(); showSection('upload'); }); // Back to upload (without full reset if possible, or just same as New) $('#btnBackToUpload').addEventListener('click', () => { // We reset anyway for now to avoid data conflicts, // but user specifically asked for "Back" resetAll(); showSection('upload'); }); // Cancel processing $('#btnCancelProcessing').addEventListener('click', () => { if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } showSection('upload'); showToast('Processing cancelled', 'info'); }); // Copy buttons $('#btnCopyText').addEventListener('click', () => { copyToClipboard($('#extractedText').textContent, '#btnCopyText'); }); $('#btnCopySummary').addEventListener('click', () => { copyToClipboard($('#summaryContent').textContent, '#btnCopySummary'); }); // Download button $('#btnDownloadText').addEventListener('click', () => { if (currentTaskId) { window.location.href = `/api/download/${currentTaskId}`; } else { showToast('No active document to download', 'error'); } }); } async function copyToClipboard(text, btnSelector) { try { await navigator.clipboard.writeText(text); const btn = $(btnSelector); btn.classList.add('copied'); const originalHTML = btn.innerHTML; btn.innerHTML = ` Copied!`; setTimeout(() => { btn.classList.remove('copied'); btn.innerHTML = originalHTML; }, 2000); } catch (e) { showToast('Failed to copy to clipboard', 'error'); } } // ===== UI Helpers ===== function showSection(sectionId) { [uploadSection, processingSection, resultsSection].forEach(s => s.classList.add('hidden')); if (sectionId === 'upload') { uploadSection.classList.remove('hidden'); } else if (sectionId === 'processing') { processingSection.classList.remove('hidden'); } else if (sectionId === 'results') { resultsSection.classList.remove('hidden'); } } function resetProcessingSteps() { ['stepExtract', 'stepSummary', 'stepEntities', 'stepSentiment'].forEach(id => { const el = $(`#${id}`); el.classList.remove('active', 'done'); el.querySelector('.step-status').textContent = 'âŗ'; }); } function updateStep(stepId, state) { const el = $(`#${stepId}`); el.classList.remove('active', 'done'); el.classList.add(state); el.querySelector('.step-status').textContent = state === 'done' ? '✅' : '⚡'; } function resetAll() { currentTaskId = null; if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } fileInput.value = ''; $('#extractedText').innerHTML = '

    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 = `${icons[type]}${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; }