final_clone / static /js /files.js
abdullahalioo's picture
Upload 18 files
2f7626f verified
// 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
};