Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PDF Summarizer | AI-Powered Document Analysis</title> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <!-- Header --> | |
| <header class="header"> | |
| <div class="logo"> | |
| <i class="fas fa-file-pdf"></i> | |
| <h1>PDF Summarizer</h1> | |
| </div> | |
| <p class="tagline">Extract key insights from your documents in seconds</p> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="main-content"> | |
| <!-- Left Panel - Upload & Controls --> | |
| <div class="left-panel"> | |
| <div class="upload-section card"> | |
| <h2><i class="fas fa-upload"></i> Upload PDF</h2> | |
| <p class="description">Upload your PDF document to generate an AI-powered summary</p> | |
| <div class="upload-area" id="dropArea"> | |
| <i class="fas fa-cloud-upload-alt upload-icon"></i> | |
| <p class="upload-text">Drag & Drop your PDF file here</p> | |
| <p class="upload-subtext">or click to browse</p> | |
| <input type="file" id="pdfInput" accept=".pdf" hidden> | |
| <button class="btn btn-primary" id="browseBtn"> | |
| <i class="fas fa-folder-open"></i> Browse Files | |
| </button> | |
| <p class="file-info">Max file size: 16MB</p> | |
| </div> | |
| <div id="fileInfo" class="file-info-container" style="display: none;"> | |
| <div class="selected-file"> | |
| <i class="fas fa-file-pdf"></i> | |
| <span id="fileName"></span> | |
| <button class="btn-remove" id="removeFile"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Controls Section --> | |
| <div class="controls-section card"> | |
| <h2><i class="fas fa-sliders-h"></i> Summary Settings</h2> | |
| <div class="control-group"> | |
| <label for="summaryRatio"> | |
| <i class="fas fa-compress-alt"></i> Summary Ratio | |
| <span id="ratioValue">30%</span> | |
| </label> | |
| <input type="range" id="summaryRatio" min="10" max="70" value="30" step="5"> | |
| <div class="range-labels"> | |
| <span>Brief</span> | |
| <span>Detailed</span> | |
| </div> | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn btn-primary" id="summarizeBtn" disabled> | |
| <i class="fas fa-magic"></i> Generate Summary | |
| </button> | |
| <button class="btn btn-secondary" id="resetBtn"> | |
| <i class="fas fa-redo"></i> Reset | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Statistics Section --> | |
| <div class="stats-section card"> | |
| <h2><i class="fas fa-chart-bar"></i> Document Statistics</h2> | |
| <div id="statsContent"> | |
| <div class="stats-placeholder"> | |
| <i class="fas fa-chart-pie"></i> | |
| <p>Upload a PDF to view statistics</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel - Results --> | |
| <div class="right-panel"> | |
| <div class="results-section card"> | |
| <div class="results-header"> | |
| <h2><i class="fas fa-file-alt"></i> Summary</h2> | |
| <div class="actions" id="downloadActions" style="display: none;"> | |
| <button class="btn btn-download" id="downloadPdf"> | |
| <i class="fas fa-file-pdf"></i> Save as PDF | |
| </button> | |
| <button class="btn btn-download" id="downloadTxt"> | |
| <i class="fas fa-file-text"></i> Save as TXT | |
| </button> | |
| <button class="btn btn-copy" id="copyBtn"> | |
| <i class="far fa-copy"></i> Copy | |
| </button> | |
| </div> | |
| </div> | |
| <div class="results-content"> | |
| <div id="summaryPlaceholder"> | |
| <div class="placeholder-content"> | |
| <i class="fas fa-search"></i> | |
| <h3>No Summary Yet</h3> | |
| <p>Upload a PDF file and click "Generate Summary" to see the results here.</p> | |
| </div> | |
| </div> | |
| <div id="summaryContent" style="display: none;"> | |
| <div class="summary-header"> | |
| <h3 id="summaryTitle"></h3> | |
| <div class="summary-meta"> | |
| <span class="badge" id="compressionBadge"> | |
| <i class="fas fa-compress-alt"></i> <span id="compressionValue">0%</span> compressed | |
| </span> | |
| <span class="badge" id="lengthBadge"> | |
| <i class="fas fa-text-width"></i> <span id="wordCount">0</span> words | |
| </span> | |
| </div> | |
| </div> | |
| <div class="summary-text" id="summaryText"></div> | |
| </div> | |
| <div id="errorMessage" class="error-message" style="display: none;"> | |
| <i class="fas fa-exclamation-triangle"></i> | |
| <h3>Error Processing PDF</h3> | |
| <p id="errorText"></p> | |
| </div> | |
| </div> | |
| <div class="loader" id="loader" style="display: none;"> | |
| <div class="spinner"></div> | |
| <p>Analyzing your document...</p> | |
| </div> | |
| </div> | |
| <!-- Tips Section --> | |
| <div class="tips-section card"> | |
| <h2><i class="fas fa-lightbulb"></i> Tips for Better Results</h2> | |
| <ul class="tips-list"> | |
| <li><i class="fas fa-check-circle"></i> Use text-based PDFs for best results</li> | |
| <li><i class="fas fa-check-circle"></i> For scanned PDFs, use OCR software first</li> | |
| <li><i class="fas fa-check-circle"></i> Adjust summary ratio based on document length</li> | |
| <li><i class="fas fa-check-circle"></i> Longer documents may take more time to process</li> | |
| <li><i class="fas fa-check-circle"></i> Results are more accurate with well-structured documents</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="footer"> | |
| <p>© 2024 PDF Summarizer | Built with Flask & NLTK | <i class="fas fa-heart"></i> Made with passion</p> | |
| </footer> | |
| </div> | |
| <!-- Notification Toast --> | |
| <div id="toast" class="toast"></div> | |
| <script> | |
| // DOM Elements | |
| const pdfInput = document.getElementById('pdfInput'); | |
| const browseBtn = document.getElementById('browseBtn'); | |
| const dropArea = document.getElementById('dropArea'); | |
| const summarizeBtn = document.getElementById('summarizeBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const summaryRatio = document.getElementById('summaryRatio'); | |
| const ratioValue = document.getElementById('ratioValue'); | |
| const fileInfo = document.getElementById('fileInfo'); | |
| const fileName = document.getElementById('fileName'); | |
| const removeFile = document.getElementById('removeFile'); | |
| const summaryPlaceholder = document.getElementById('summaryPlaceholder'); | |
| const summaryContent = document.getElementById('summaryContent'); | |
| const summaryText = document.getElementById('summaryText'); | |
| const summaryTitle = document.getElementById('summaryTitle'); | |
| const compressionValue = document.getElementById('compressionValue'); | |
| const wordCount = document.getElementById('wordCount'); | |
| const downloadActions = document.getElementById('downloadActions'); | |
| const downloadPdf = document.getElementById('downloadPdf'); | |
| const downloadTxt = document.getElementById('downloadTxt'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| const errorText = document.getElementById('errorText'); | |
| const loader = document.getElementById('loader'); | |
| const statsContent = document.getElementById('statsContent'); | |
| const toast = document.getElementById('toast'); | |
| let currentFile = null; | |
| // Event Listeners | |
| browseBtn.addEventListener('click', () => pdfInput.click()); | |
| pdfInput.addEventListener('change', handleFileSelect); | |
| summarizeBtn.addEventListener('click', processPDF); | |
| resetBtn.addEventListener('click', resetAll); | |
| removeFile.addEventListener('click', removeSelectedFile); | |
| summaryRatio.addEventListener('input', updateRatioValue); | |
| downloadPdf.addEventListener('click', () => downloadSummary('pdf')); | |
| downloadTxt.addEventListener('click', () => downloadSummary('txt')); | |
| copyBtn.addEventListener('click', copySummary); | |
| // Drag and Drop functionality | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, unhighlight, false); | |
| }); | |
| dropArea.addEventListener('drop', handleDrop, false); | |
| // Functions | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| function highlight() { | |
| dropArea.classList.add('highlight'); | |
| } | |
| function unhighlight() { | |
| dropArea.classList.remove('highlight'); | |
| } | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const file = dt.files[0]; | |
| if (file && file.type === 'application/pdf') { | |
| handleFile(file); | |
| } else { | |
| showToast('Please drop a valid PDF file', 'error'); | |
| } | |
| } | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| handleFile(file); | |
| } | |
| } | |
| function handleFile(file) { | |
| if (file.size > 16 * 1024 * 1024) { | |
| showToast('File size exceeds 16MB limit', 'error'); | |
| return; | |
| } | |
| currentFile = file; | |
| fileName.textContent = file.name; | |
| fileInfo.style.display = 'block'; | |
| summarizeBtn.disabled = false; | |
| dropArea.style.display = 'none'; | |
| showToast('PDF uploaded successfully!', 'success'); | |
| } | |
| function removeSelectedFile() { | |
| currentFile = null; | |
| pdfInput.value = ''; | |
| fileInfo.style.display = 'none'; | |
| summarizeBtn.disabled = true; | |
| dropArea.style.display = 'flex'; | |
| } | |
| function updateRatioValue() { | |
| const value = summaryRatio.value; | |
| ratioValue.textContent = `${value}%`; | |
| } | |
| async function processPDF() { | |
| if (!currentFile) return; | |
| showLoader(true); | |
| hideError(); | |
| hideSummary(); | |
| const formData = new FormData(); | |
| formData.append('pdf_file', currentFile); | |
| formData.append('summary_ratio', summaryRatio.value / 100); | |
| try { | |
| const response = await fetch('/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| displaySummary(data); | |
| displayStatistics(data.statistics, data.compression_ratio, data.summary_length); | |
| showToast('Summary generated successfully!', 'success'); | |
| } else { | |
| showError(data.error || 'Failed to process PDF'); | |
| } | |
| } catch (error) { | |
| showError('Network error. Please try again.'); | |
| } finally { | |
| showLoader(false); | |
| } | |
| } | |
| function displaySummary(data) { | |
| summaryTitle.textContent = `Summary of ${data.filename}`; | |
| summaryText.textContent = data.summary; | |
| compressionValue.textContent = `${data.compression_ratio}%`; | |
| wordCount.textContent = data.summary_length; | |
| summaryPlaceholder.style.display = 'none'; | |
| summaryContent.style.display = 'block'; | |
| downloadActions.style.display = 'flex'; | |
| // Update badges based on compression | |
| const compressionBadge = document.getElementById('compressionBadge'); | |
| if (data.compression_ratio > 70) { | |
| compressionBadge.className = 'badge badge-high'; | |
| } else if (data.compression_ratio > 40) { | |
| compressionBadge.className = 'badge badge-medium'; | |
| } else { | |
| compressionBadge.className = 'badge badge-low'; | |
| } | |
| } | |
| function displayStatistics(stats, compressionRatio, summaryLength) { | |
| statsContent.innerHTML = ` | |
| <div class="stats-grid"> | |
| <div class="stat-item"> | |
| <div class="stat-icon"> | |
| <i class="fas fa-file"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <span class="stat-label">Pages</span> | |
| <span class="stat-value">${stats.page_count}</span> | |
| </div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-icon"> | |
| <i class="fas fa-paragraph"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <span class="stat-label">Sentences</span> | |
| <span class="stat-value">${stats.sentence_count}</span> | |
| </div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-icon"> | |
| <i class="fas fa-font"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <span class="stat-label">Words</span> | |
| <span class="stat-value">${stats.word_count}</span> | |
| </div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-icon"> | |
| <i class="fas fa-ruler"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <span class="stat-label">Avg. Word Length</span> | |
| <span class="stat-value">${stats.avg_word_length.toFixed(1)}</span> | |
| </div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-icon"> | |
| <i class="fas fa-compress-alt"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <span class="stat-label">Compression</span> | |
| <span class="stat-value">${compressionRatio}%</span> | |
| </div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-icon"> | |
| <i class="fas fa-text-height"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <span class="stat-label">Summary Words</span> | |
| <span class="stat-value">${summaryLength}</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| async function downloadSummary(format) { | |
| if (!summaryText.textContent) return; | |
| try { | |
| if (format === 'pdf') { | |
| const response = await fetch('/download', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| summary: summaryText.textContent, | |
| filename: currentFile.name | |
| }) | |
| }); | |
| if (response.ok) { | |
| const blob = await response.blob(); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = currentFile.name.replace('.pdf', '_summary.pdf'); | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| showToast('PDF downloaded successfully!', 'success'); | |
| } | |
| } else if (format === 'txt') { | |
| const response = await fetch('/export', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| summary: summaryText.textContent | |
| }) | |
| }); | |
| if (response.ok) { | |
| const blob = await response.blob(); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'summary.txt'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| showToast('Text file downloaded successfully!', 'success'); | |
| } | |
| } | |
| } catch (error) { | |
| showToast('Failed to download file', 'error'); | |
| } | |
| } | |
| function copySummary() { | |
| const text = summaryText.textContent; | |
| navigator.clipboard.writeText(text) | |
| .then(() => showToast('Summary copied to clipboard!', 'success')) | |
| .catch(() => showToast('Failed to copy text', 'error')); | |
| } | |
| function resetAll() { | |
| currentFile = null; | |
| pdfInput.value = ''; | |
| fileInfo.style.display = 'none'; | |
| summarizeBtn.disabled = true; | |
| dropArea.style.display = 'flex'; | |
| hideSummary(); | |
| hideError(); | |
| showLoader(false); | |
| statsContent.innerHTML = ` | |
| <div class="stats-placeholder"> | |
| <i class="fas fa-chart-pie"></i> | |
| <p>Upload a PDF to view statistics</p> | |
| </div> | |
| `; | |
| summaryRatio.value = 30; | |
| updateRatioValue(); | |
| showToast('All fields have been reset', 'info'); | |
| } | |
| function hideSummary() { | |
| summaryPlaceholder.style.display = 'flex'; | |
| summaryContent.style.display = 'none'; | |
| downloadActions.style.display = 'none'; | |
| } | |
| function showError(message) { | |
| errorText.textContent = message; | |
| errorMessage.style.display = 'block'; | |
| hideSummary(); | |
| } | |
| function hideError() { | |
| errorMessage.style.display = 'none'; | |
| } | |
| function showLoader(show) { | |
| loader.style.display = show ? 'flex' : 'none'; | |
| } | |
| function showToast(message, type = 'info') { | |
| toast.textContent = message; | |
| toast.className = `toast toast-${type} show`; | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Initialize | |
| updateRatioValue(); | |
| </script> | |
| </body> | |
| </html> |