From 92de48359e11908cb4cb61fb42f56287c84cd15c Mon Sep 17 00:00:00 2001 From: pjmalandrino Date: Thu, 22 May 2025 14:59:24 +0200 Subject: [PATCH] Add pdf visualization on first part --- app.py | 21 +++++++ index.html | 21 +++++++ static/app.js | 146 ++++++++++++++++++++++++++++++++++++++++++++++ static/styles.css | 71 ++++++++++++++++++++++ 4 files changed, 259 insertions(+) diff --git a/app.py b/app.py index 60bc6c8..5884fb0 100644 --- a/app.py +++ b/app.py @@ -39,6 +39,27 @@ def index(): def serve_static(filename): return send_file(os.path.join('static', filename)) +@app.route('/') +def serve_pdf(filename): + """Serve PDF files from the current directory""" + try: + # Security check: only allow PDF files and prevent directory traversal + if not filename.endswith('.pdf') or '..' in filename or '/' in filename: + logger.warning(f"Blocked access to non-PDF or invalid file: {filename}") + return jsonify({"error": "Access denied"}), 403 + + # Check if file exists in current directory + file_path = Path(filename) + if not file_path.exists() or not file_path.is_file(): + logger.error(f"PDF file not found: {filename}") + return jsonify({"error": f"PDF file not found: {filename}"}), 404 + + logger.info(f"Serving PDF file: {filename}") + return send_file(filename, mimetype='application/pdf') + except Exception as e: + logger.error(f"Error serving PDF {filename}: {str(e)}") + return jsonify({"error": f"Error serving PDF: {str(e)}"}), 500 + @app.route('/pdf-files') def pdf_files(): try: diff --git a/index.html b/index.html index cc762a1..03657b1 100644 --- a/index.html +++ b/index.html @@ -62,6 +62,25 @@

Document Analysis

Extract comprehensive document structure from your PDF using advanced AI-powered analysis. This process identifies headers, paragraphs, images, tables, and other document elements with precise coordinate information.

+ + + @@ -146,6 +165,8 @@ + + \ No newline at end of file diff --git a/static/app.js b/static/app.js index fd94935..2f2e076 100644 --- a/static/app.js +++ b/static/app.js @@ -2,6 +2,15 @@ const activeTasks = {}; let pollingInterval = null; +// PDF preview variables +let currentPdf = null; +let currentPageNum = 1; +let totalPages = 0; +let renderScale = 1.0; + +// Initialize PDF.js +pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; + // Tab switching functionality function switchTab(tabName) { // Hide all tab contents @@ -39,6 +48,143 @@ function updateProgress(completedSteps) { } } +// PDF Preview Functions +async function loadPdfPreview(pdfFile) { + if (!pdfFile) { + hidePdfPreview(); + return; + } + + const previewContainer = document.getElementById('pdf-preview-container'); + const loadingDiv = document.getElementById('pdf-preview-loading'); + const canvas = document.getElementById('pdf-preview-canvas'); + + // Show preview container and loading + previewContainer.classList.remove('hidden'); + loadingDiv.classList.remove('hidden'); + canvas.classList.add('hidden'); + + try { + // Load the PDF + const loadingTask = pdfjsLib.getDocument(`/${pdfFile}`); + currentPdf = await loadingTask.promise; + totalPages = currentPdf.numPages; + + // Update page info + updatePageInfo(); + updatePageControls(); + + // Render the current page + await renderPage(); + + // Hide loading, show canvas + loadingDiv.classList.add('hidden'); + canvas.classList.remove('hidden'); + + } catch (error) { + console.error('Error loading PDF preview:', error); + loadingDiv.innerHTML = 'Error loading PDF preview: ' + error.message + ''; + } +} + +async function renderPage() { + if (!currentPdf) return; + + const canvas = document.getElementById('pdf-preview-canvas'); + const ctx = canvas.getContext('2d'); + + try { + // Get the page + const page = await currentPdf.getPage(currentPageNum); + + // Calculate scale to fit container width (max 600px) + const viewport = page.getViewport({ scale: 1.0 }); + const maxWidth = 600; + renderScale = Math.min(maxWidth / viewport.width, 1.5); + + const scaledViewport = page.getViewport({ scale: renderScale }); + + // Set canvas dimensions + canvas.height = scaledViewport.height; + canvas.width = scaledViewport.width; + + // Render the page + const renderContext = { + canvasContext: ctx, + viewport: scaledViewport + }; + + await page.render(renderContext).promise; + + // Update zoom info + document.getElementById('zoom-info').textContent = Math.round(renderScale * 100) + '%'; + + } catch (error) { + console.error('Error rendering page:', error); + } +} + +function updatePageInfo() { + document.getElementById('page-info').textContent = `Page ${currentPageNum} of ${totalPages}`; +} + +function updatePageControls() { + document.getElementById('prev-page-btn').disabled = currentPageNum <= 1; + document.getElementById('next-page-btn').disabled = currentPageNum >= totalPages; +} + +async function changePage(delta) { + const newPage = currentPageNum + delta; + if (newPage >= 1 && newPage <= totalPages) { + currentPageNum = newPage; + + // Update the page number input + document.getElementById('page_num').value = currentPageNum; + + updatePageInfo(); + updatePageControls(); + await renderPage(); + } +} + +async function refreshPreview() { + const pdfFile = document.getElementById('pdf_file').value; + if (pdfFile) { + await loadPdfPreview(pdfFile); + } +} + +function hidePdfPreview() { + document.getElementById('pdf-preview-container').classList.add('hidden'); + currentPdf = null; + totalPages = 0; + currentPageNum = 1; +} + +// Event listeners for PDF selection and page changes +document.addEventListener('DOMContentLoaded', function() { + // PDF file selection change + document.getElementById('pdf_file').addEventListener('change', async function() { + const selectedFile = this.value; + if (selectedFile) { + await loadPdfPreview(selectedFile); + } else { + hidePdfPreview(); + } + }); + + // Page number input change + document.getElementById('page_num').addEventListener('change', async function() { + const pageNum = parseInt(this.value); + if (pageNum >= 1 && pageNum <= totalPages && pageNum !== currentPageNum) { + currentPageNum = pageNum; + updatePageInfo(); + updatePageControls(); + await renderPage(); + } + }); +}); + // Load PDF files on page load window.addEventListener('DOMContentLoaded', function() { const pdfStatus = document.getElementById('pdf-load-status'); diff --git a/static/styles.css b/static/styles.css index d360877..650ed1f 100644 --- a/static/styles.css +++ b/static/styles.css @@ -68,6 +68,13 @@ h3 { margin: 0 0 var(--spacing-sm) 0; } +h4 { + color: var(--primary-dark); + font-weight: 500; + font-size: 1rem; + margin: 0 0 var(--spacing-sm) 0; +} + p { color: var(--text-medium); margin: 0 0 var(--spacing-lg) 0; @@ -272,6 +279,60 @@ label:has(input[type="checkbox"]) { display: block; } +/* PDF Preview Section */ +.pdf-preview-container { + margin-top: var(--spacing-lg); + padding: var(--spacing-lg); + background: var(--surface-light); + border-radius: var(--radius-md); + border: 1px solid var(--border-light); +} + +.pdf-preview-controls { + display: flex; + gap: var(--spacing-md); + margin-bottom: var(--spacing-md); + align-items: center; +} + +.pdf-preview-controls button { + padding: var(--spacing-xs) var(--spacing-md); + font-size: 0.8rem; + background: var(--text-medium); +} + +.pdf-preview-controls button:hover { + background: var(--text-dark); +} + +.pdf-preview-controls button:disabled { + background: var(--border-light); + color: var(--text-light); +} + +.pdf-preview-info { + font-size: 0.8rem; + color: var(--text-medium); + margin-left: auto; +} + +#pdf-preview-canvas { + max-width: 100%; + border: 1px solid var(--border-light); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-sm); + background: white; + display: block; + margin: 0 auto; +} + +.pdf-preview-loading { + text-align: center; + padding: var(--spacing-xl); + color: var(--text-medium); + font-size: 0.9rem; +} + /* Buttons */ button { padding: var(--spacing-md) var(--spacing-lg); @@ -531,4 +592,14 @@ button:disabled { .tab-content { padding: var(--spacing-lg); } + + .pdf-preview-controls { + flex-direction: column; + align-items: stretch; + } + + .pdf-preview-info { + margin-left: 0; + text-align: center; + } } \ No newline at end of file