Spaces:
Sleeping
Sleeping
| // Configuration Axios | |
| axios.defaults.baseURL = window.location.origin.includes('hf.space') | |
| ? window.location.origin | |
| : 'http://localhost:8000'; | |
| console.log("API base URL:", axios.defaults.baseURL); | |
| // Variables globales | |
| let uploadedFiles = []; | |
| const MAX_FILE_SIZE_MB = 50; | |
| const SUPPORTED_TYPES = ['pdf', 'docx', 'pptx', 'xlsx', 'jpg', 'jpeg', 'png']; | |
| // Éléments DOM | |
| const elements = { | |
| fileInput: document.getElementById('fileInput'), | |
| dropArea: document.getElementById('dropArea'), | |
| fileList: document.getElementById('fileList'), | |
| uploadStatus: document.getElementById('uploadStatus'), | |
| summaryFile: document.getElementById('summaryFile'), | |
| captionFile: document.getElementById('captionFile'), | |
| qaFile: document.getElementById('qaFile'), | |
| summarizeBtn: document.getElementById('summarizeBtn'), | |
| captionBtn: document.getElementById('captionBtn'), | |
| askBtn: document.getElementById('askBtn'), | |
| questionInput: document.getElementById('questionInput'), | |
| summaryResult: document.getElementById('summaryResult'), | |
| captionResult: document.getElementById('captionResult'), | |
| answerResult: document.getElementById('answerResult'), | |
| imagePreview: document.getElementById('imagePreview'), | |
| summarySpinner: document.getElementById('summarySpinner'), | |
| captionSpinner: document.getElementById('captionSpinner'), | |
| qaSpinner: document.getElementById('qaSpinner') | |
| }; | |
| // Initialisation | |
| document.addEventListener('DOMContentLoaded', () => { | |
| initVideoBackground(); | |
| setupEventListeners(); | |
| updateFileSelectors(); | |
| }); | |
| function initVideoBackground() { | |
| const video = document.getElementById('bg-video'); | |
| if (video) { | |
| try { | |
| video.playbackRate = 0.8; | |
| video.muted = true; | |
| video.playsInline = true; | |
| video.play().catch(e => console.log("Video play error:", e)); | |
| } catch (e) { | |
| console.log("Video initialization error:", e); | |
| } | |
| video.onerror = () => { | |
| document.body.style.background = "linear-gradient(135deg, #4a6fa5 0%, #166088 100%)"; | |
| }; | |
| } | |
| } | |
| function setupEventListeners() { | |
| // Gestion de l'upload | |
| if (elements.dropArea) { | |
| elements.dropArea.addEventListener('click', () => elements.fileInput.click()); | |
| elements.dropArea.addEventListener('dragover', handleDragOver); | |
| elements.dropArea.addEventListener('dragleave', handleDragLeave); | |
| elements.dropArea.addEventListener('drop', handleDrop); | |
| } | |
| elements.fileInput.addEventListener('change', handleFileSelect); | |
| // Boutons d'action | |
| if (elements.summarizeBtn) { | |
| elements.summarizeBtn.addEventListener('click', summarizeDocument); | |
| } | |
| if (elements.captionBtn) { | |
| elements.captionBtn.addEventListener('click', generateCaption); | |
| } | |
| if (elements.askBtn) { | |
| elements.askBtn.addEventListener('click', answerQuestion); | |
| } | |
| } | |
| // Gestion des fichiers | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| elements.dropArea.style.borderColor = '#4a6fa5'; | |
| elements.dropArea.style.backgroundColor = 'rgba(74, 111, 165, 0.1)'; | |
| } | |
| function handleDragLeave() { | |
| elements.dropArea.style.borderColor = '#4a6fa5'; | |
| elements.dropArea.style.backgroundColor = 'rgba(74, 111, 165, 0.05)'; | |
| } | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| handleDragLeave(); | |
| if (e.dataTransfer.files.length) { | |
| elements.fileInput.files = e.dataTransfer.files; | |
| handleFiles(elements.fileInput.files); | |
| } | |
| } | |
| function handleFileSelect() { | |
| if (elements.fileInput.files.length) { | |
| handleFiles(elements.fileInput.files); | |
| } | |
| } | |
| async function handleFiles(files) { | |
| const validFiles = Array.from(files).filter(file => { | |
| const ext = file.name.split('.').pop().toLowerCase(); | |
| const isValidType = SUPPORTED_TYPES.includes(ext); | |
| const isValidSize = file.size <= MAX_FILE_SIZE_MB * 1024 * 1024; | |
| if (!isValidType) { | |
| showError(elements.uploadStatus, `Type non supporté: ${file.name}`); | |
| return false; | |
| } | |
| if (!isValidSize) { | |
| showError(elements.uploadStatus, `Fichier trop volumineux (max ${MAX_FILE_SIZE_MB}MB): ${file.name}`); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| if (!validFiles.length) return; | |
| showLoading(elements.uploadStatus, `Upload de ${validFiles.length} fichier(s)...`); | |
| const formData = new FormData(); | |
| validFiles.forEach(file => formData.append('files', file)); | |
| try { | |
| const response = await axios.post('/api/upload', formData, { | |
| headers: { 'Content-Type': 'multipart/form-data' }, | |
| timeout: 60000 | |
| }); | |
| uploadedFiles = [...uploadedFiles, ...response.data]; | |
| updateFileSelectors(); | |
| renderFileList(); | |
| showSuccess(elements.uploadStatus, `${validFiles.length} fichier(s) uploadé(s) avec succès !`); | |
| } catch (error) { | |
| handleUploadError(error); | |
| } finally { | |
| elements.fileInput.value = ''; | |
| } | |
| } | |
| function renderFileList() { | |
| if (!elements.fileList) return; | |
| if (uploadedFiles.length === 0) { | |
| elements.fileList.innerHTML = '<p class="empty-message">Aucun fichier uploadé</p>'; | |
| return; | |
| } | |
| elements.fileList.innerHTML = uploadedFiles.map(file => ` | |
| <div class="file-item" data-file-id="${file.file_id}"> | |
| <div class="file-info"> | |
| <span class="file-icon">${getFileIcon(file.file_type)}</span> | |
| <span class="file-name">${file.file_name}</span> | |
| <span class="file-type">${file.file_type.toUpperCase()}</span> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function updateFileSelectors() { | |
| const selectors = [ | |
| { element: elements.summaryFile, types: ['pdf', 'docx', 'pptx', 'xlsx'] }, | |
| { element: elements.captionFile, types: ['jpg', 'jpeg', 'png'] }, | |
| { element: elements.qaFile, types: ['pdf', 'docx', 'pptx', 'xlsx', 'jpg', 'jpeg', 'png'] } | |
| ]; | |
| selectors.forEach(({ element, types }) => { | |
| if (!element) return; | |
| const currentValue = element.value; | |
| element.innerHTML = element.id === 'qaFile' | |
| ? '<option value="">-- Aucun --</option>' | |
| : `<option value="">-- Choisir ${element.id.includes('summary') ? 'un document' : 'une image'} --</option>`; | |
| uploadedFiles | |
| .filter(file => types.includes(file.file_type.toLowerCase())) | |
| .forEach(file => { | |
| const option = new Option( | |
| `${file.file_name} (${file.file_type.toUpperCase()})`, | |
| file.file_id | |
| ); | |
| element.add(option); | |
| }); | |
| element.value = currentValue; | |
| }); | |
| } | |
| // Fonctions de traitement | |
| async function summarizeDocument() { | |
| if (!elements.summaryFile || !elements.summaryResult) return; | |
| const fileId = elements.summaryFile.value; | |
| if (!fileId) { | |
| showError(elements.summaryResult, 'Veuillez sélectionner un document'); | |
| return; | |
| } | |
| showLoading(elements.summaryResult, 'Génération du résumé en cours...'); | |
| toggleSpinner(elements.summarySpinner, true); | |
| disableButton(elements.summarizeBtn, true); | |
| try { | |
| const response = await axios.post('/api/summarize', { | |
| file_id: fileId, | |
| max_length: 200 | |
| }, { timeout: 120000 }); | |
| elements.summaryResult.innerHTML = ` | |
| <h3>Résumé du document</h3> | |
| <div class="summary-content">${response.data.summary}</div> | |
| `; | |
| } catch (error) { | |
| handleProcessingError(error, elements.summaryResult, 'résumé'); | |
| } finally { | |
| toggleSpinner(elements.summarySpinner, false); | |
| disableButton(elements.summarizeBtn, false); | |
| } | |
| } | |
| async function generateCaption() { | |
| if (!elements.captionFile || !elements.captionResult) return; | |
| const fileId = elements.captionFile.value; | |
| if (!fileId) { | |
| showError(elements.captionResult, 'Veuillez sélectionner une image'); | |
| return; | |
| } | |
| // Afficher l'aperçu | |
| const file = uploadedFiles.find(f => f.file_id === fileId); | |
| if (file && elements.imagePreview) { | |
| try { | |
| const response = await axios.get(`/api/file/${fileId}`, { responseType: 'blob' }); | |
| const url = URL.createObjectURL(response.data); | |
| elements.imagePreview.innerHTML = `<img src="${url}" alt="Image sélectionnée">`; | |
| } catch (error) { | |
| console.error("Error loading image preview:", error); | |
| } | |
| } | |
| showLoading(elements.captionResult, 'Analyse de l\'image en cours...'); | |
| toggleSpinner(elements.captionSpinner, true); | |
| disableButton(elements.captionBtn, true); | |
| try { | |
| const response = await axios.post('/api/caption', { file_id: fileId }, { timeout: 60000 }); | |
| elements.captionResult.innerHTML = ` | |
| <h3>Description de l'image</h3> | |
| <p>${response.data.caption}</p> | |
| `; | |
| } catch (error) { | |
| handleProcessingError(error, elements.captionResult, 'description'); | |
| } finally { | |
| toggleSpinner(elements.captionSpinner, false); | |
| disableButton(elements.captionBtn, false); | |
| } | |
| } | |
| async function answerQuestion() { | |
| if (!elements.questionInput || !elements.answerResult) return; | |
| const question = elements.questionInput.value.trim(); | |
| if (!question) { | |
| showError(elements.answerResult, 'Veuillez saisir une question'); | |
| return; | |
| } | |
| const fileId = elements.qaFile ? elements.qaFile.value : null; | |
| showLoading(elements.answerResult, 'Recherche de la réponse...'); | |
| toggleSpinner(elements.qaSpinner, true); | |
| disableButton(elements.askBtn, true); | |
| try { | |
| const response = await axios.post('/api/answer', { | |
| question, | |
| file_id: fileId || undefined | |
| }, { timeout: 120000 }); | |
| elements.answerResult.innerHTML = ` | |
| <h3>Réponse</h3> | |
| <div class="answer-content">${response.data.answer}</div> | |
| `; | |
| } catch (error) { | |
| handleProcessingError(error, elements.answerResult, 'réponse'); | |
| } finally { | |
| toggleSpinner(elements.qaSpinner, false); | |
| disableButton(elements.askBtn, false); | |
| } | |
| } | |
| // Fonctions utilitaires | |
| function getFileIcon(fileType) { | |
| const icons = { | |
| pdf: '📄', | |
| docx: '📝', | |
| pptx: '📊', | |
| xlsx: '📈', | |
| jpg: '🖼️', | |
| jpeg: '🖼️', | |
| png: '🖼️' | |
| }; | |
| return icons[fileType.toLowerCase()] || '📁'; | |
| } | |
| function showLoading(element, message) { | |
| if (!element) return; | |
| element.innerHTML = ` | |
| <div class="status-message"> | |
| <div class="loading-spinner"></div> | |
| ${message} | |
| </div> | |
| `; | |
| } | |
| function showSuccess(element, message) { | |
| if (!element) return; | |
| element.innerHTML = ` | |
| <div class="status-message success"> | |
| ✓ ${message} | |
| </div> | |
| `; | |
| if (element === elements.uploadStatus) { | |
| setTimeout(() => { | |
| element.innerHTML = ''; | |
| }, 3000); | |
| } | |
| } | |
| function showError(element, message) { | |
| if (!element) return; | |
| element.innerHTML = ` | |
| <div class="status-message error"> | |
| ✗ ${message} | |
| </div> | |
| `; | |
| } | |
| function handleUploadError(error) { | |
| let message = 'Échec de l\'upload'; | |
| if (error.response) { | |
| message += `: ${error.response.data.detail || error.response.statusText}`; | |
| } else if (error.code === 'ECONNABORTED') { | |
| message = 'Délai d\'attente dépassé (60s) - essayez avec des fichiers plus petits'; | |
| } else if (error.message.includes('Network Error')) { | |
| message = 'Impossible de se connecter au serveur - vérifiez votre connexion'; | |
| } else { | |
| message += `: ${error.message}`; | |
| } | |
| showError(elements.uploadStatus, message); | |
| } | |
| function handleProcessingError(error, element, action) { | |
| let message = `Échec de la génération du ${action}`; | |
| if (error.response) { | |
| message += `: ${error.response.data.detail || error.response.statusText}`; | |
| } else if (error.code === 'ECONNABORTED') { | |
| message = `Délai d'attente dépassé pour le ${action}`; | |
| } else { | |
| message += `: ${error.message}`; | |
| } | |
| showError(element, message); | |
| } | |
| function toggleSpinner(spinnerElement, show) { | |
| if (spinnerElement) { | |
| spinnerElement.style.display = show ? 'block' : 'none'; | |
| } | |
| } | |
| function disableButton(buttonElement, disable) { | |
| if (buttonElement) { | |
| buttonElement.disabled = disable; | |
| buttonElement.style.opacity = disable ? 0.7 : 1; | |
| buttonElement.style.cursor = disable ? 'not-allowed' : 'pointer'; | |
| } | |
| } | |
| function openTab(tabName) { | |
| // Masquer tous les onglets | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| // Désactiver tous les boutons | |
| document.querySelectorAll('.tab-button').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| // Activer l'onglet sélectionné | |
| const tab = document.getElementById(tabName); | |
| if (tab) tab.classList.add('active'); | |
| // Activer le bouton sélectionné | |
| event.currentTarget.classList.add('active'); | |
| } |