Spaces:
Sleeping
Sleeping
| // File upload and management functionality | |
| // Initialize file upload functionality | |
| document.addEventListener('DOMContentLoaded', () => { | |
| if (document.body.classList.contains('chat-page')) { | |
| initializeFileUpload(); | |
| } | |
| }); | |
| function initializeFileUpload() { | |
| const fileInput = document.getElementById('fileInput'); | |
| const imageInput = document.getElementById('imageInput'); | |
| if (fileInput) { | |
| fileInput.addEventListener('change', handleFileSelect); | |
| } | |
| if (imageInput) { | |
| imageInput.addEventListener('change', handleFileSelect); | |
| } | |
| // Handle drag and drop | |
| const chatMessages = document.getElementById('chatMessages'); | |
| if (chatMessages) { | |
| chatMessages.addEventListener('dragover', handleDragOver); | |
| chatMessages.addEventListener('drop', handleFileDrop); | |
| chatMessages.addEventListener('dragenter', handleDragEnter); | |
| chatMessages.addEventListener('dragleave', handleDragLeave); | |
| } | |
| console.log('File upload initialized'); | |
| } | |
| function openFileUpload() { | |
| if (!window.currentConversation) { | |
| MainJS.showError('Please select a conversation first'); | |
| return; | |
| } | |
| const fileInput = document.getElementById('fileInput'); | |
| if (fileInput) { | |
| fileInput.click(); | |
| } | |
| } | |
| function openImageUpload() { | |
| if (!window.currentConversation) { | |
| MainJS.showError('Please select a conversation first'); | |
| return; | |
| } | |
| const imageInput = document.getElementById('imageInput'); | |
| if (imageInput) { | |
| imageInput.click(); | |
| } | |
| } | |
| function handleFileSelect(event) { | |
| const files = Array.from(event.target.files); | |
| if (files.length === 0) return; | |
| // Reset input value to allow selecting the same file again | |
| event.target.value = ''; | |
| files.forEach(file => { | |
| uploadFile(file); | |
| }); | |
| } | |
| function handleDragOver(event) { | |
| event.preventDefault(); | |
| event.dataTransfer.dropEffect = 'copy'; | |
| } | |
| function handleDragEnter(event) { | |
| event.preventDefault(); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| if (chatMessages) { | |
| chatMessages.classList.add('drag-over'); | |
| } | |
| } | |
| function handleDragLeave(event) { | |
| event.preventDefault(); | |
| // Only remove class if we're leaving the chat messages area entirely | |
| if (!event.currentTarget.contains(event.relatedTarget)) { | |
| const chatMessages = document.getElementById('chatMessages'); | |
| if (chatMessages) { | |
| chatMessages.classList.remove('drag-over'); | |
| } | |
| } | |
| } | |
| function handleFileDrop(event) { | |
| event.preventDefault(); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| if (chatMessages) { | |
| chatMessages.classList.remove('drag-over'); | |
| } | |
| if (!window.currentConversation) { | |
| MainJS.showError('Please select a conversation first'); | |
| return; | |
| } | |
| const files = Array.from(event.dataTransfer.files); | |
| files.forEach(file => { | |
| uploadFile(file); | |
| }); | |
| } | |
| async function uploadFile(file) { | |
| if (!window.currentConversation) { | |
| MainJS.showError('Please select a conversation first'); | |
| return; | |
| } | |
| // Validate file size (100MB limit) | |
| const maxSize = 100 * 1024 * 1024; // 100MB | |
| if (file.size > maxSize) { | |
| MainJS.showError(`File "${file.name}" is too large. Maximum size is 100MB.`); | |
| return; | |
| } | |
| // Validate file type | |
| const allowedExtensions = [ | |
| 'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'mp3', 'wav', 'ogg', 'm4a', | |
| 'mp4', 'avi', 'mov', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', | |
| 'zip', 'rar', '7z', 'apk', 'exe', 'dmg', 'deb', 'rpm' | |
| ]; | |
| const fileExtension = file.name.split('.').pop().toLowerCase(); | |
| if (!allowedExtensions.includes(fileExtension)) { | |
| MainJS.showError(`File type "${fileExtension}" is not allowed.`); | |
| return; | |
| } | |
| // Create progress UI | |
| const progressId = createProgressUI(file); | |
| try { | |
| // Create form data | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| formData.append('conversation_id', window.currentConversation); | |
| // Upload with progress tracking | |
| const response = await uploadWithProgress(formData, progressId); | |
| if (response.success) { | |
| updateProgressUI(progressId, 100, 'Upload complete'); | |
| MainJS.showSuccess(`File "${file.name}" uploaded successfully!`); | |
| // Remove progress UI after a delay | |
| setTimeout(() => { | |
| removeProgressUI(progressId); | |
| }, 2000); | |
| // Reload messages and conversations | |
| await loadMessages(window.currentConversation); | |
| await loadConversations(); | |
| } else { | |
| updateProgressUI(progressId, 0, 'Upload failed: ' + response.message); | |
| MainJS.showError('Failed to upload file: ' + response.message); | |
| // Remove progress UI after delay | |
| setTimeout(() => { | |
| removeProgressUI(progressId); | |
| }, 3000); | |
| } | |
| } catch (error) { | |
| console.error('Error uploading file:', error); | |
| updateProgressUI(progressId, 0, 'Upload failed'); | |
| MainJS.showError('Failed to upload file: ' + error.message); | |
| // Remove progress UI after delay | |
| setTimeout(() => { | |
| removeProgressUI(progressId); | |
| }, 3000); | |
| } | |
| } | |
| function uploadWithProgress(formData, progressId) { | |
| return new Promise((resolve, reject) => { | |
| const xhr = new XMLHttpRequest(); | |
| // Track upload progress | |
| xhr.upload.addEventListener('progress', (event) => { | |
| if (event.lengthComputable) { | |
| const percentComplete = (event.loaded / event.total) * 100; | |
| updateProgressUI(progressId, percentComplete, 'Uploading...'); | |
| } | |
| }); | |
| // Handle completion | |
| xhr.addEventListener('load', () => { | |
| try { | |
| const response = JSON.parse(xhr.responseText); | |
| resolve(response); | |
| } catch (error) { | |
| reject(new Error('Invalid response format')); | |
| } | |
| }); | |
| // Handle errors | |
| xhr.addEventListener('error', () => { | |
| reject(new Error('Network error during upload')); | |
| }); | |
| xhr.addEventListener('abort', () => { | |
| reject(new Error('Upload cancelled')); | |
| }); | |
| // Start upload | |
| xhr.open('POST', '/api/upload_file'); | |
| xhr.send(formData); | |
| }); | |
| } | |
| function createProgressUI(file) { | |
| const progressId = 'progress_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| if (!chatMessages) return progressId; | |
| const progressElement = document.createElement('div'); | |
| progressElement.id = progressId; | |
| progressElement.className = 'upload-progress'; | |
| const iconClass = MainJS.getFileIconClass(file.type); | |
| const iconColor = MainJS.getFileIconColor(file.type); | |
| progressElement.innerHTML = ` | |
| <div class="d-flex align-items-center mb-2"> | |
| <div class="file-icon ${iconColor} me-3"> | |
| <i class="${iconClass}"></i> | |
| </div> | |
| <div class="flex-grow-1"> | |
| <div class="fw-bold text-truncate">${MainJS.escapeHtml(file.name)}</div> | |
| <small class="text-muted">${MainJS.formatFileSize(file.size)}</small> | |
| </div> | |
| </div> | |
| <div class="progress mb-2" style="height: 4px;"> | |
| <div class="progress-bar bg-success" role="progressbar" style="width: 0%"></div> | |
| </div> | |
| <div class="progress-status small text-muted">Preparing upload...</div> | |
| `; | |
| chatMessages.appendChild(progressElement); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| return progressId; | |
| } | |
| function updateProgressUI(progressId, percent, status) { | |
| const progressElement = document.getElementById(progressId); | |
| if (!progressElement) return; | |
| const progressBar = progressElement.querySelector('.progress-bar'); | |
| const statusElement = progressElement.querySelector('.progress-status'); | |
| if (progressBar) { | |
| progressBar.style.width = percent + '%'; | |
| progressBar.setAttribute('aria-valuenow', percent); | |
| } | |
| if (statusElement) { | |
| statusElement.textContent = status; | |
| } | |
| // Change color based on status | |
| if (status.includes('failed') || status.includes('error')) { | |
| if (progressBar) { | |
| progressBar.classList.remove('bg-success'); | |
| progressBar.classList.add('bg-danger'); | |
| } | |
| if (statusElement) { | |
| statusElement.classList.add('text-danger'); | |
| } | |
| } else if (status.includes('complete')) { | |
| if (progressBar) { | |
| progressBar.classList.remove('bg-success'); | |
| progressBar.classList.add('bg-success'); | |
| } | |
| if (statusElement) { | |
| statusElement.classList.add('text-success'); | |
| } | |
| } | |
| } | |
| function removeProgressUI(progressId) { | |
| const progressElement = document.getElementById(progressId); | |
| if (progressElement) { | |
| progressElement.remove(); | |
| } | |
| } | |
| // File preview functionality | |
| function previewFile(fileUrl, fileName, fileType) { | |
| if (fileType.startsWith('image/')) { | |
| showImagePreview(fileUrl, fileName); | |
| } else if (fileType.startsWith('text/') || fileType.includes('pdf')) { | |
| window.open(fileUrl, '_blank'); | |
| } else { | |
| // For other file types, just download | |
| downloadFileFromUrl(fileUrl, fileName); | |
| } | |
| } | |
| function showImagePreview(imageUrl, fileName) { | |
| // Create modal for image preview | |
| const modal = document.createElement('div'); | |
| modal.className = 'modal fade'; | |
| modal.setAttribute('tabindex', '-1'); | |
| modal.innerHTML = ` | |
| <div class="modal-dialog modal-lg modal-dialog-centered"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title">${MainJS.escapeHtml(fileName)}</h5> | |
| <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
| </div> | |
| <div class="modal-body text-center"> | |
| <img src="${imageUrl}" alt="${MainJS.escapeHtml(fileName)}" class="img-fluid" style="max-height: 70vh;"> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
| <button type="button" class="btn btn-success" onclick="downloadFileFromUrl('${imageUrl}', '${fileName}')"> | |
| <i class="fas fa-download me-2"></i>Download | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| // Show modal | |
| const bsModal = new bootstrap.Modal(modal); | |
| bsModal.show(); | |
| // Remove modal from DOM when hidden | |
| modal.addEventListener('hidden.bs.modal', () => { | |
| modal.remove(); | |
| }); | |
| } | |
| function downloadFileFromUrl(url, fileName) { | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = fileName; | |
| link.style.display = 'none'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| // File type validation | |
| function validateFileType(file) { | |
| const allowedTypes = [ | |
| // Images | |
| 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', | |
| // Audio | |
| 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/m4a', | |
| // Video | |
| 'video/mp4', 'video/avi', 'video/quicktime', 'video/webm', | |
| // Documents | |
| 'application/pdf', | |
| 'application/msword', | |
| 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | |
| 'application/vnd.ms-excel', | |
| 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |
| 'application/vnd.ms-powerpoint', | |
| 'application/vnd.openxmlformats-officedocument.presentationml.presentation', | |
| // Archives | |
| 'application/zip', | |
| 'application/x-rar-compressed', | |
| 'application/x-7z-compressed', | |
| // Text | |
| 'text/plain', | |
| // Applications | |
| 'application/vnd.android.package-archive' | |
| ]; | |
| return allowedTypes.includes(file.type) || file.type === ''; | |
| } | |
| // File size formatting (already available in main.js, but keeping for reference) | |
| function 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]; | |
| } | |
| // Add CSS for drag and drop visual feedback | |
| const dragDropStyles = document.createElement('style'); | |
| dragDropStyles.textContent = ` | |
| .chat-messages.drag-over { | |
| border: 2px dashed #25d366; | |
| background-color: rgba(37, 211, 102, 0.1); | |
| } | |
| .chat-messages.drag-over::after { | |
| content: "Drop files here to upload"; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(37, 211, 102, 0.9); | |
| color: white; | |
| padding: 1rem 2rem; | |
| border-radius: 8px; | |
| font-weight: bold; | |
| z-index: 1000; | |
| pointer-events: none; | |
| } | |
| `; | |
| document.head.appendChild(dragDropStyles); | |
| // Export functions for global access | |
| window.FileJS = { | |
| openFileUpload, | |
| openImageUpload, | |
| uploadFile, | |
| previewFile, | |
| validateFileType, | |
| formatFileSize | |
| }; | |