Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Invoice PDF Converter</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 20px; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 40px; | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.1); | |
| max-width: 600px; | |
| width: 100%; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| .header h1 { | |
| color: #333; | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| color: #666; | |
| font-size: 1.1em; | |
| } | |
| .upload-area { | |
| border: 3px dashed #667eea; | |
| border-radius: 15px; | |
| padding: 40px 20px; | |
| text-align: center; | |
| transition: all 0.3s ease; | |
| margin-bottom: 30px; | |
| cursor: pointer; | |
| } | |
| .upload-area:hover { | |
| border-color: #764ba2; | |
| background: rgba(102, 126, 234, 0.05); | |
| } | |
| .upload-area.dragover { | |
| border-color: #764ba2; | |
| background: rgba(102, 126, 234, 0.1); | |
| } | |
| .upload-icon { | |
| font-size: 4em; | |
| color: #667eea; | |
| margin-bottom: 20px; | |
| } | |
| .upload-text { | |
| font-size: 1.2em; | |
| color: #333; | |
| margin-bottom: 10px; | |
| } | |
| .upload-subtext { | |
| color: #666; | |
| font-size: 0.9em; | |
| } | |
| #file-input { | |
| display: none; | |
| } | |
| .processing { | |
| text-align: center; | |
| margin: 30px 0; | |
| display: none; | |
| } | |
| .loader { | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid #667eea; | |
| border-radius: 50%; | |
| width: 50px; | |
| height: 50px; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 20px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .processing-text { | |
| color: #666; | |
| font-size: 1.1em; | |
| } | |
| .result-section { | |
| display: none; | |
| margin-top: 30px; | |
| } | |
| .result-header { | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| color: white; | |
| padding: 15px; | |
| border-radius: 10px 10px 0 0; | |
| font-size: 1.2em; | |
| font-weight: bold; | |
| } | |
| .result-content { | |
| border: 2px solid #667eea; | |
| border-top: none; | |
| border-radius: 0 0 10px 10px; | |
| padding: 20px; | |
| background: #f9f9f9; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .result-content pre { | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9em; | |
| line-height: 1.4; | |
| } | |
| .buttons-section { | |
| display: none; | |
| margin-top: 30px; | |
| text-align: center; | |
| } | |
| .button-group { | |
| display: flex; | |
| gap: 20px; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| .btn { | |
| padding: 15px 30px; | |
| border: none; | |
| border-radius: 50px; | |
| font-size: 1.1em; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| text-decoration: none; | |
| display: inline-block; | |
| } | |
| .btn-json { | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| color: white; | |
| } | |
| .btn-excel { | |
| background: linear-gradient(135deg, #56ab2f, #a8e6cf); | |
| color: white; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.2); | |
| } | |
| .error-message { | |
| background: #ff4757; | |
| color: white; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin-top: 20px; | |
| display: none; | |
| } | |
| .success-message { | |
| background: #2ed573; | |
| color: white; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin-top: 20px; | |
| display: none; | |
| } | |
| .file-info { | |
| background: #e3f2fd; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin-top: 20px; | |
| display: none; | |
| } | |
| .file-info strong { | |
| color: #1976d2; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>📄 Invoice Converter</h1> | |
| <p>Upload your PDF invoice and convert to JSON or Excel</p> | |
| </div> | |
| <div class="upload-area" id="upload-area"> | |
| <div class="upload-icon">📁</div> | |
| <div class="upload-text">Click to upload or drag and drop</div> | |
| <div class="upload-subtext">PDF files only (Max 50MB)</div> | |
| <input type="file" id="file-input" accept=".pdf"> | |
| </div> | |
| <div class="file-info" id="file-info"> | |
| <strong>Selected file:</strong> <span id="file-name"></span> | |
| </div> | |
| <div class="processing" id="processing"> | |
| <div class="loader"></div> | |
| <div class="processing-text">Processing your invoice...</div> | |
| </div> | |
| <div class="result-section" id="result-section"> | |
| <div class="result-header"> | |
| 📋 Extracted Invoice Data | |
| </div> | |
| <div class="result-content"> | |
| <pre id="result-content"></pre> | |
| </div> | |
| </div> | |
| <div class="buttons-section" id="buttons-section"> | |
| <div class="button-group"> | |
| <button class="btn btn-json" id="convert-json"> | |
| 📄 Convert to JSON | |
| </button> | |
| <button class="btn btn-excel" id="convert-excel"> | |
| 📊 Convert to Excel | |
| </button> | |
| </div> | |
| </div> | |
| <div class="error-message" id="error-message"></div> | |
| <div class="success-message" id="success-message"></div> | |
| </div> | |
| <script> | |
| let currentInvoiceData = null; | |
| const uploadArea = document.getElementById('upload-area'); | |
| const fileInput = document.getElementById('file-input'); | |
| const fileInfo = document.getElementById('file-info'); | |
| const fileName = document.getElementById('file-name'); | |
| const processing = document.getElementById('processing'); | |
| const resultSection = document.getElementById('result-section'); | |
| const resultContent = document.getElementById('result-content'); | |
| const buttonsSection = document.getElementById('buttons-section'); | |
| const errorMessage = document.getElementById('error-message'); | |
| const successMessage = document.getElementById('success-message'); | |
| const convertJsonBtn = document.getElementById('convert-json'); | |
| const convertExcelBtn = document.getElementById('convert-excel'); | |
| uploadArea.addEventListener('click', () => fileInput.click()); | |
| uploadArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.add('dragover'); | |
| }); | |
| uploadArea.addEventListener('dragleave', () => { | |
| uploadArea.classList.remove('dragover'); | |
| }); | |
| uploadArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('dragover'); | |
| const files = e.dataTransfer.files; | |
| if (files.length > 0) { | |
| handleFile(files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length > 0) { | |
| handleFile(e.target.files[0]); | |
| } | |
| }); | |
| function hideMessages() { | |
| errorMessage.style.display = 'none'; | |
| successMessage.style.display = 'none'; | |
| } | |
| function showError(message) { | |
| hideMessages(); | |
| errorMessage.textContent = message; | |
| errorMessage.style.display = 'block'; | |
| } | |
| function showSuccess(message) { | |
| hideMessages(); | |
| successMessage.textContent = message; | |
| successMessage.style.display = 'block'; | |
| } | |
| function handleFile(file) { | |
| if (!file.name.toLowerCase().endsWith('.pdf')) { | |
| showError('Please select a PDF file only.'); | |
| return; | |
| } | |
| if (file.size > 50 * 1024 * 1024) { | |
| showError('File size must be less than 50MB.'); | |
| return; | |
| } | |
| fileName.textContent = file.name; | |
| fileInfo.style.display = 'block'; | |
| hideMessages(); | |
| resultSection.style.display = 'none'; | |
| buttonsSection.style.display = 'none'; | |
| processing.style.display = 'block'; | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| fetch('/upload', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| processing.style.display = 'none'; | |
| if (data.success) { | |
| currentInvoiceData = data.data; | |
| resultContent.textContent = JSON.stringify(data.data, null, 2); | |
| resultSection.style.display = 'block'; | |
| buttonsSection.style.display = 'block'; | |
| showSuccess('Invoice processed successfully!'); | |
| } else { | |
| showError(data.error || 'Failed to process the invoice.'); | |
| } | |
| }) | |
| .catch(error => { | |
| processing.style.display = 'none'; | |
| showError('Network error occurred. Please try again.'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| convertJsonBtn.addEventListener('click', () => { | |
| if (!currentInvoiceData) return; | |
| fetch('/convert_json', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| invoice_data: currentInvoiceData | |
| }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| const blob = new Blob([data.json_data], { type: 'application/json' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = data.filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| showSuccess('JSON file downloaded successfully!'); | |
| } else { | |
| showError(data.error || 'Failed to convert to JSON.'); | |
| } | |
| }) | |
| .catch(error => { | |
| showError('Network error occurred. Please try again.'); | |
| console.error('Error:', error); | |
| }); | |
| }); | |
| convertExcelBtn.addEventListener('click', () => { | |
| if (!currentInvoiceData) return; | |
| fetch('/convert_excel', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| invoice_data: currentInvoiceData | |
| }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| const binaryString = atob(data.excel_data); | |
| const bytes = new Uint8Array(binaryString.length); | |
| for (let i = 0; i < binaryString.length; i++) { | |
| bytes[i] = binaryString.charCodeAt(i); | |
| } | |
| const blob = new Blob([bytes], { | |
| type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | |
| }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = data.filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| showSuccess('Excel file downloaded successfully!'); | |
| } else { | |
| showError(data.error || 'Failed to convert to Excel.'); | |
| } | |
| }) | |
| .catch(error => { | |
| showError('Network error occurred. Please try again.'); | |
| console.error('Error:', error); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |