Spaces:
Paused
Paused
| /** | |
| * File Upload Handler for Legal Dashboard | |
| * Manages document uploads, OCR processing, and real-time progress | |
| */ | |
| class FileUploadHandler { | |
| constructor() { | |
| this.uploadEndpoint = '/api/ocr/upload'; | |
| this.processEndpoint = '/api/ocr/process'; | |
| this.maxFileSize = 10 * 1024 * 1024; // 10MB | |
| this.allowedTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/tiff']; | |
| this.currentUpload = null; | |
| this.uploadQueue = []; | |
| this.initializeEventListeners(); | |
| } | |
| initializeEventListeners() { | |
| // File input change | |
| const fileInput = document.getElementById('documentUpload'); | |
| if (fileInput) { | |
| fileInput.addEventListener('change', (e) => this.handleFileSelection(e)); | |
| } | |
| // Upload button | |
| const uploadBtn = document.getElementById('uploadButton'); | |
| if (uploadBtn) { | |
| uploadBtn.addEventListener('click', () => this.startUpload()); | |
| } | |
| // Drag and drop | |
| const dropZone = document.getElementById('uploadDropZone'); | |
| if (dropZone) { | |
| dropZone.addEventListener('dragover', (e) => this.handleDragOver(e)); | |
| dropZone.addEventListener('drop', (e) => this.handleDrop(e)); | |
| dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e)); | |
| } | |
| } | |
| handleFileSelection(event) { | |
| const files = event.target.files; | |
| if (files.length > 0) { | |
| this.validateAndQueueFiles(files); | |
| } | |
| } | |
| handleDragOver(event) { | |
| event.preventDefault(); | |
| event.currentTarget.classList.add('drag-over'); | |
| } | |
| handleDragLeave(event) { | |
| event.preventDefault(); | |
| event.currentTarget.classList.remove('drag-over'); | |
| } | |
| handleDrop(event) { | |
| event.preventDefault(); | |
| event.currentTarget.classList.remove('drag-over'); | |
| const files = event.dataTransfer.files; | |
| if (files.length > 0) { | |
| this.validateAndQueueFiles(files); | |
| } | |
| } | |
| validateAndQueueFiles(files) { | |
| const validFiles = []; | |
| const errors = []; | |
| for (let file of files) { | |
| // Check file size | |
| if (file.size > this.maxFileSize) { | |
| errors.push(`${file.name}: File too large (max 10MB)`); | |
| continue; | |
| } | |
| // Check file type | |
| if (!this.allowedTypes.includes(file.type)) { | |
| errors.push(`${file.name}: Unsupported file type`); | |
| continue; | |
| } | |
| validFiles.push(file); | |
| } | |
| // Show errors if any | |
| if (errors.length > 0) { | |
| this.showErrors(errors); | |
| } | |
| // Queue valid files | |
| if (validFiles.length > 0) { | |
| this.uploadQueue.push(...validFiles); | |
| this.updateUploadQueue(); | |
| } | |
| } | |
| updateUploadQueue() { | |
| const queueContainer = document.getElementById('uploadQueue'); | |
| if (!queueContainer) return; | |
| if (this.uploadQueue.length === 0) { | |
| queueContainer.innerHTML = '<p class="no-files">هیچ فایلی برای آپلود انتخاب نشده</p>'; | |
| return; | |
| } | |
| const queueHTML = this.uploadQueue.map((file, index) => ` | |
| <div class="queue-item" data-index="${index}"> | |
| <div class="file-info"> | |
| <span class="file-name">${file.name}</span> | |
| <span class="file-size">${this.formatFileSize(file.size)}</span> | |
| </div> | |
| <div class="file-actions"> | |
| <button class="remove-file" onclick="fileUploadHandler.removeFromQueue(${index})"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `).join(''); | |
| queueContainer.innerHTML = queueHTML; | |
| } | |
| removeFromQueue(index) { | |
| this.uploadQueue.splice(index, 1); | |
| this.updateUploadQueue(); | |
| } | |
| async startUpload() { | |
| if (this.uploadQueue.length === 0) { | |
| this.showToast('لطفاً فایلی برای آپلود انتخاب کنید', 'warning'); | |
| return; | |
| } | |
| if (this.currentUpload) { | |
| this.showToast('آپلود در حال انجام است، لطفاً صبر کنید', 'warning'); | |
| return; | |
| } | |
| this.currentUpload = true; | |
| this.showUploadProgress(); | |
| try { | |
| for (let i = 0; i < this.uploadQueue.length; i++) { | |
| const file = this.uploadQueue[i]; | |
| await this.uploadFile(file, i + 1, this.uploadQueue.length); | |
| } | |
| this.showToast('تمام فایلها با موفقیت آپلود شدند', 'success'); | |
| this.refreshDocumentsList(); | |
| } catch (error) { | |
| this.showToast(`خطا در آپلود: ${error.message}`, 'error'); | |
| } finally { | |
| this.currentUpload = false; | |
| this.hideUploadProgress(); | |
| this.uploadQueue = []; | |
| this.updateUploadQueue(); | |
| } | |
| } | |
| async uploadFile(file, current, total) { | |
| return new Promise((resolve, reject) => { | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| formData.append('filename', file.name); | |
| const xhr = new XMLHttpRequest(); | |
| // Progress tracking | |
| xhr.upload.addEventListener('progress', (e) => { | |
| if (e.lengthComputable) { | |
| const percentComplete = (e.loaded / e.total) * 100; | |
| this.updateProgress(percentComplete, current, total); | |
| } | |
| }); | |
| // Response handling | |
| xhr.addEventListener('load', () => { | |
| if (xhr.status === 200) { | |
| try { | |
| const response = JSON.parse(xhr.responseText); | |
| this.handleUploadSuccess(response, file); | |
| resolve(response); | |
| } catch (error) { | |
| reject(new Error('Invalid response format')); | |
| } | |
| } else { | |
| reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`)); | |
| } | |
| }); | |
| xhr.addEventListener('error', () => { | |
| reject(new Error('Network error during upload')); | |
| }); | |
| xhr.addEventListener('abort', () => { | |
| reject(new Error('Upload cancelled')); | |
| }); | |
| // Start upload | |
| xhr.open('POST', this.uploadEndpoint); | |
| xhr.send(formData); | |
| }); | |
| } | |
| handleUploadSuccess(response, file) { | |
| // Update dashboard stats | |
| this.updateDashboardStats(); | |
| // Show success message | |
| this.showToast(`${file.name} با موفقیت آپلود شد`, 'success'); | |
| // Process OCR if needed | |
| if (response.document_id) { | |
| this.processOCR(response.document_id, file.name); | |
| } | |
| } | |
| async processOCR(documentId, fileName) { | |
| try { | |
| const response = await fetch(this.processEndpoint, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| document_id: documentId, | |
| filename: fileName | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`OCR processing failed: ${response.status}`); | |
| } | |
| const result = await response.json(); | |
| this.showOCRResult(result, fileName); | |
| } catch (error) { | |
| this.showToast(`خطا در پردازش OCR: ${error.message}`, 'error'); | |
| } | |
| } | |
| showOCRResult(result, fileName) { | |
| const ocrResultsContainer = document.getElementById('ocrResults'); | |
| if (!ocrResultsContainer) return; | |
| const resultHTML = ` | |
| <div class="ocr-result"> | |
| <h4>نتایج OCR - ${fileName}</h4> | |
| <div class="ocr-content"> | |
| <p><strong>کیفیت:</strong> ${(result.quality || 0).toFixed(2)}%</p> | |
| <p><strong>متن استخراج شده:</strong></p> | |
| <div class="extracted-text"> | |
| ${result.text || 'متنی استخراج نشد'} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| ocrResultsContainer.insertAdjacentHTML('afterbegin', resultHTML); | |
| } | |
| updateProgress(percent, current, total) { | |
| const progressBar = document.getElementById('uploadProgressBar'); | |
| const progressText = document.getElementById('uploadProgressText'); | |
| if (progressBar) { | |
| progressBar.style.width = `${percent}%`; | |
| } | |
| if (progressText) { | |
| progressText.textContent = `آپلود فایل ${current} از ${total} (${Math.round(percent)}%)`; | |
| } | |
| } | |
| showUploadProgress() { | |
| const progressContainer = document.getElementById('uploadProgress'); | |
| if (progressContainer) { | |
| progressContainer.style.display = 'block'; | |
| } | |
| } | |
| hideUploadProgress() { | |
| const progressContainer = document.getElementById('uploadProgress'); | |
| if (progressContainer) { | |
| progressContainer.style.display = 'none'; | |
| } | |
| } | |
| updateDashboardStats() { | |
| // Trigger dashboard stats refresh | |
| if (typeof loadDashboardStats === 'function') { | |
| loadDashboardStats(); | |
| } | |
| } | |
| refreshDocumentsList() { | |
| // Trigger documents list refresh | |
| if (typeof loadDocumentsList === 'function') { | |
| loadDocumentsList(); | |
| } | |
| } | |
| showErrors(errors) { | |
| const errorMessage = errors.join('\n'); | |
| this.showToast(errorMessage, 'error'); | |
| } | |
| showToast(message, type = 'info') { | |
| if (typeof showToast === 'function') { | |
| showToast(message, type); | |
| } else { | |
| console.log(`${type.toUpperCase()}: ${message}`); | |
| } | |
| } | |
| formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| } | |
| // Initialize file upload handler | |
| const fileUploadHandler = new FileUploadHandler(); |