diff --git a/app.py b/app.py index 5884fb0..a6c0f46 100644 --- a/app.py +++ b/app.py @@ -39,27 +39,6 @@ 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: @@ -285,10 +264,15 @@ def run_extractor(): @app.route('/results/') def results(filename): try: - return send_file(os.path.join('results', filename)) + file_path = os.path.join('results', filename) + if os.path.exists(file_path): + return send_file(file_path) + else: + logger.error(f"File not found: {file_path}") + return jsonify({'error': f"File not found: {filename}"}), 404 except Exception as e: logger.error(f"Error serving file {filename}: {str(e)}") - return jsonify({'error': f"File not found: {filename}"}), 404 + return jsonify({'error': f"Error serving file: {filename}"}), 500 @app.route('/check-environment') def check_environment(): diff --git a/index.html b/index.html index 03657b1..6b237e0 100644 --- a/index.html +++ b/index.html @@ -62,25 +62,6 @@

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.

- - - @@ -136,6 +117,14 @@ + + +
Extraction Capabilities:
    @@ -165,8 +154,6 @@
- - \ No newline at end of file diff --git a/static/app.js b/static/app.js index 2f2e076..417d929 100644 --- a/static/app.js +++ b/static/app.js @@ -2,15 +2,6 @@ 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 @@ -48,143 +39,105 @@ function updateProgress(completedSteps) { } } -// PDF Preview Functions -async function loadPdfPreview(pdfFile) { - if (!pdfFile) { - hidePdfPreview(); - return; +// Load extracted images from the results folder +function loadExtractedImages() { + const imageGallery = document.getElementById('extracted-images-gallery'); + const imageContainer = document.getElementById('extracted-images-container'); + + // First try to load the index.html to get the list of images + fetch('/results/pictures/index.html') + .then(response => { + if (!response.ok) { + throw new Error('No extracted images found'); + } + return response.text(); + }) + .then(html => { + // Parse the HTML to extract image information + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const imageCards = doc.querySelectorAll('.picture-card'); + + if (imageCards.length === 0) { + imageGallery.innerHTML = '
No images were extracted from this page.
'; + return; + } + + let galleryHTML = ''; + imageCards.forEach((card, index) => { + const img = card.querySelector('img'); + const caption = card.querySelector('.picture-caption'); + const coords = card.querySelector('.picture-coords'); + + if (img) { + const imgSrc = img.getAttribute('src'); + const pictureId = index + 1; + const captionText = caption ? caption.textContent : ''; + const coordsText = coords ? coords.textContent : ''; + + galleryHTML += ` +
+
+ Extracted Image ${pictureId} +
+
+

Picture ${pictureId}

+ ${captionText ? `

${captionText}

` : ''} +

${coordsText}

+
+
+ `; + } + }); + + imageGallery.innerHTML = galleryHTML; + imageContainer.classList.remove('hidden'); + }) + .catch(error => { + console.log('No extracted images found:', error); + imageGallery.innerHTML = '
No images have been extracted yet. Run the image extraction first.
'; + }); +} + +// Open image in modal for better viewing +function openImageModal(imageSrc, caption, coords) { + // Create modal if it doesn't exist + let modal = document.getElementById('image-modal'); + if (!modal) { + modal = document.createElement('div'); + modal.id = 'image-modal'; + modal.className = 'image-modal'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); } - const previewContainer = document.getElementById('pdf-preview-container'); - const loadingDiv = document.getElementById('pdf-preview-loading'); - const canvas = document.getElementById('pdf-preview-canvas'); + // Set image and info + document.getElementById('modal-image').src = imageSrc; + document.getElementById('modal-caption').textContent = caption; + document.getElementById('modal-coords').textContent = coords; - // Show preview container and loading - previewContainer.classList.remove('hidden'); - loadingDiv.classList.remove('hidden'); - canvas.classList.add('hidden'); + // Show modal + modal.classList.add('active'); +} - 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 + ''; +// Close image modal +function closeImageModal() { + const modal = document.getElementById('image-modal'); + if (modal) { + modal.classList.remove('active'); } } -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'); @@ -219,6 +172,9 @@ window.addEventListener('DOMContentLoaded', function() { // Run an environment check on startup checkEnvironment(); + + // Try to load any existing extracted images + loadExtractedImages(); }); // Check the environment @@ -362,6 +318,13 @@ function pollTasks() { document.getElementById('image-container').classList.remove('hidden'); } + // Load extracted images if extractor completed + if (taskInfo.type === 'extractor') { + setTimeout(() => { + loadExtractedImages(); + }, 1000); // Small delay to ensure files are written + } + // Enable next step button and update progress if (taskInfo.type === 'analyzer') { document.getElementById('visualizer-btn').disabled = false; @@ -436,6 +399,11 @@ function runScript(script) { document.getElementById('image-container').classList.add('hidden'); } + // Hide extracted images if not running extractor + if (script !== 'extractor') { + document.getElementById('extracted-images-container').classList.add('hidden'); + } + // Determine endpoint let endpoint; switch(script) { diff --git a/static/styles.css b/static/styles.css index 650ed1f..506c0bb 100644 --- a/static/styles.css +++ b/static/styles.css @@ -72,7 +72,7 @@ h4 { color: var(--primary-dark); font-weight: 500; font-size: 1rem; - margin: 0 0 var(--spacing-sm) 0; + margin: 0 0 var(--spacing-md) 0; } p { @@ -279,60 +279,6 @@ 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); @@ -455,7 +401,7 @@ button:disabled { box-shadow: var(--shadow-md); } -/* Image container */ +/* Image container (for visualizer) */ #image-container { margin-top: var(--spacing-lg); text-align: center; @@ -478,6 +424,187 @@ button:disabled { box-shadow: var(--shadow-md); } +/* Extracted Images Gallery */ +#extracted-images-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); +} + +#extracted-images-container h4 { + color: var(--primary-dark); + margin: 0 0 var(--spacing-lg) 0; + font-size: 1rem; + font-weight: 500; + text-align: center; +} + +.images-gallery { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: var(--spacing-lg); + margin-top: var(--spacing-md); +} + +.extracted-image-card { + background: var(--surface-white); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + overflow: hidden; + transition: all 0.2s ease; + border: 1px solid var(--border-light); +} + +.extracted-image-card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.image-wrapper { + position: relative; + width: 100%; + height: 200px; + overflow: hidden; + background: var(--surface-light); + cursor: pointer; +} + +.image-wrapper img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + transition: transform 0.2s ease; +} + +.image-wrapper:hover img { + transform: scale(1.05); +} + +.image-info { + padding: var(--spacing-md); +} + +.image-info h4 { + margin: 0 0 var(--spacing-xs) 0; + font-size: 0.9rem; + font-weight: 600; + color: var(--primary-dark); +} + +.image-caption { + margin: 0 0 var(--spacing-xs) 0; + font-size: 0.8rem; + color: var(--text-medium); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.image-coords { + margin: 0; + font-size: 0.75rem; + color: var(--text-light); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.no-images { + text-align: center; + padding: var(--spacing-xl); + color: var(--text-light); + font-style: italic; + background: var(--surface-white); + border-radius: var(--radius-md); + border: 2px dashed var(--border-light); +} + +/* Image Modal */ +.image-modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(4px); +} + +.image-modal.active { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-lg); +} + +.modal-content { + background: var(--surface-white); + border-radius: var(--radius-lg); + max-width: 90vw; + max-height: 90vh; + overflow: auto; + position: relative; + box-shadow: var(--shadow-lg); +} + +.modal-close { + position: absolute; + top: var(--spacing-md); + right: var(--spacing-lg); + color: var(--text-light); + font-size: 2rem; + font-weight: bold; + cursor: pointer; + z-index: 1001; + background: var(--surface-white); + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-md); + transition: all 0.2s ease; +} + +.modal-close:hover { + color: var(--error); + transform: scale(1.1); +} + +#modal-image { + max-width: 100%; + max-height: 70vh; + object-fit: contain; + border-radius: var(--radius-md) var(--radius-md) 0 0; +} + +.modal-info { + padding: var(--spacing-lg); + border-top: 1px solid var(--border-light); +} + +.modal-info p { + margin: 0 0 var(--spacing-sm) 0; + font-size: 0.9rem; +} + +#modal-caption { + color: var(--text-dark); + font-weight: 500; +} + +#modal-coords { + color: var(--text-light); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.8rem; +} + /* Environment check */ #environment-check { margin-top: var(--spacing-lg); @@ -593,13 +720,21 @@ button:disabled { padding: var(--spacing-lg); } - .pdf-preview-controls { - flex-direction: column; - align-items: stretch; + .images-gallery { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: var(--spacing-md); } - .pdf-preview-info { - margin-left: 0; - text-align: center; + .image-wrapper { + height: 150px; + } + + .modal-content { + margin: var(--spacing-md); + max-width: calc(100vw - 32px); + } + + #modal-image { + max-height: 60vh; } } \ No newline at end of file