/** * Application JavaScript pour l'interface web 4NK Notariat */ class NotaryApp { constructor() { this.apiUrl = 'http://localhost:8000'; this.currentDocument = null; this.documents = []; this.charts = {}; // Stockage des instances de graphiques this.init(); } init() { this.setupEventListeners(); this.loadDocuments(); this.loadStats(); this.checkSystemStatus(); } setupEventListeners() { // Navigation document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); this.showSection(link.dataset.section); }); }); // Upload form document.getElementById('upload-form').addEventListener('submit', (e) => { e.preventDefault(); this.uploadDocument(); }); // File input document.getElementById('file-input').addEventListener('change', (e) => { this.handleFileSelect(e.target.files[0]); }); // Drag and drop const uploadArea = document.getElementById('upload-area'); 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'); this.handleFileSelect(e.dataTransfer.files[0]); }); // Search and filters document.getElementById('search-documents').addEventListener('input', () => { this.filterDocuments(); }); document.getElementById('filter-status').addEventListener('change', () => { this.filterDocuments(); }); document.getElementById('filter-type').addEventListener('change', () => { this.filterDocuments(); }); } showSection(sectionName) { // Hide all sections document.querySelectorAll('.content-section').forEach(section => { section.style.display = 'none'; }); // Remove active class from nav links document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); // Show selected section document.getElementById(`${sectionName}-section`).style.display = 'block'; // Add active class to nav link document.querySelector(`[data-section="${sectionName}"]`).classList.add('active'); // Load section-specific data if (sectionName === 'documents') { this.loadDocuments(); } else if (sectionName === 'stats') { this.loadStats(); } } handleFileSelect(file) { if (!file) return; // Validate file type const allowedTypes = [ 'application/pdf', 'image/jpeg', 'image/png', 'image/tiff', 'image/heic' ]; if (!allowedTypes.includes(file.type)) { this.showAlert('Type de fichier non supporté', 'danger'); return; } // Store file for preview this.selectedFile = file; // Update UI with preview this.showFilePreview(file); } showFilePreview(file) { const uploadArea = document.getElementById('upload-area'); if (file.type.startsWith('image/')) { // Preview for images const reader = new FileReader(); reader.onload = (e) => { uploadArea.innerHTML = `
Aperçu
${file.name}

${this.formatFileSize(file.size)}

Type: ${file.type}

`; }; reader.readAsDataURL(file); } else if (file.type === 'application/pdf') { // Preview for PDF with PDF.js this.showPDFPreview(file, uploadArea); } else { // Generic preview for other files uploadArea.innerHTML = `
${file.name}

${this.formatFileSize(file.size)}

Type: ${file.type}

`; } } showPDFPreview(file, container) { // Create PDF preview using PDF.js const reader = new FileReader(); reader.onload = (e) => { const arrayBuffer = e.target.result; // Load PDF.js if not already loaded if (typeof pdfjsLib === 'undefined') { this.loadPDFJS().then(() => { this.renderPDFPreview(arrayBuffer, file, container); }); } else { this.renderPDFPreview(arrayBuffer, file, container); } }; reader.readAsArrayBuffer(file); } loadPDFJS() { return new Promise((resolve, reject) => { if (typeof pdfjsLib !== 'undefined') { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'; script.onload = () => { pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; resolve(); }; script.onerror = reject; document.head.appendChild(script); }); } async renderPDFPreview(arrayBuffer, file, container) { try { const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; const page = await pdf.getPage(1); const scale = 0.5; const viewport = page.getViewport({ scale }); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: context, viewport: viewport }).promise; container.innerHTML = `
Page 1 sur ${pdf.numPages}
${file.name}

${this.formatFileSize(file.size)}

Type: ${file.type}

`; // Replace the canvas in the container const canvasContainer = container.querySelector('canvas'); canvasContainer.parentNode.replaceChild(canvas, canvasContainer); } catch (error) { console.error('Erreur aperçu PDF:', error); // Fallback to icon if PDF preview fails container.innerHTML = `
Aperçu PDF non disponible
${file.name}

${this.formatFileSize(file.size)}

Type: ${file.type}

`; } } showDocumentPreview(uploadResult, file) { // Create a preview modal or section let previewContainer = document.getElementById('upload-preview'); if (!previewContainer) { // Create preview container if it doesn't exist const uploadSection = document.getElementById('upload-section'); const previewDiv = document.createElement('div'); previewDiv.id = 'upload-preview'; previewDiv.className = 'mt-4'; uploadSection.appendChild(previewDiv); previewContainer = document.getElementById('upload-preview'); } let previewContent = ''; if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = (e) => { previewContent = `
Aperçu du document uploadé
Aperçu
Informations du document
  • Nom: ${file.name}
  • Taille: ${this.formatFileSize(file.size)}
  • Type: ${file.type}
  • ID Document: ${uploadResult.document_id}
  • Statut: ${uploadResult.status}
`; previewContainer.innerHTML = previewContent; }; reader.readAsDataURL(file); } else if (file.type === 'application/pdf') { // PDF preview after upload this.showPDFPreviewAfterUpload(file, uploadResult, previewContainer); } else { previewContent = `
Aperçu du document uploadé
Informations du document
  • Nom: ${file.name}
  • Taille: ${this.formatFileSize(file.size)}
  • Type: ${file.type}
  • ID Document: ${uploadResult.document_id}
  • Statut: ${uploadResult.status}
`; previewContainer.innerHTML = previewContent; } } showPDFPreviewAfterUpload(file, uploadResult, container) { const reader = new FileReader(); reader.onload = (e) => { const arrayBuffer = e.target.result; // Load PDF.js if not already loaded if (typeof pdfjsLib === 'undefined') { this.loadPDFJS().then(() => { this.renderPDFPreviewAfterUpload(arrayBuffer, file, uploadResult, container); }); } else { this.renderPDFPreviewAfterUpload(arrayBuffer, file, uploadResult, container); } }; reader.readAsArrayBuffer(file); } async renderPDFPreviewAfterUpload(arrayBuffer, file, uploadResult, container) { try { const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; const page = await pdf.getPage(1); const scale = 0.3; const viewport = page.getViewport({ scale }); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: context, viewport: viewport }).promise; const previewContent = `
Aperçu du document uploadé
Page 1 sur ${pdf.numPages}
Informations du document
  • Nom: ${file.name}
  • Taille: ${this.formatFileSize(file.size)}
  • Type: ${file.type}
  • Pages: ${pdf.numPages}
  • ID Document: ${uploadResult.document_id}
  • Statut: ${uploadResult.status}
`; container.innerHTML = previewContent; // Replace the canvas in the container const canvasContainer = container.querySelector('canvas'); canvasContainer.parentNode.replaceChild(canvas, canvasContainer); } catch (error) { console.error('Erreur aperçu PDF après upload:', error); // Fallback to icon if PDF preview fails container.innerHTML = `
Aperçu du document uploadé
Informations du document
  • Nom: ${file.name}
  • Taille: ${this.formatFileSize(file.size)}
  • Type: ${file.type}
  • ID Document: ${uploadResult.document_id}
  • Statut: ${uploadResult.status}
`; } } hideDocumentPreview() { const previewContainer = document.getElementById('upload-preview'); if (previewContainer) { previewContainer.innerHTML = ''; } } clearFile() { // Clear selected file this.selectedFile = null; // Clear file input document.getElementById('file-input').value = ''; // Reset upload area document.getElementById('upload-area').innerHTML = `
Glissez-déposez votre document ici

ou cliquez pour sélectionner un fichier

`; // Hide document preview this.hideDocumentPreview(); // Re-setup event listeners document.getElementById('file-input').addEventListener('change', (e) => { this.handleFileSelect(e.target.files[0]); }); } async uploadDocument() { const file = this.selectedFile || document.getElementById('file-input')?.files[0]; if (!file) { this.showAlert('Veuillez sélectionner un fichier', 'warning'); return; } const formData = new FormData(); formData.append('file', file); formData.append('id_dossier', document.getElementById('id-dossier').value); formData.append('etude_id', document.getElementById('etude-id').value); formData.append('utilisateur_id', document.getElementById('utilisateur-id').value); formData.append('source', 'upload'); const typeDocument = document.getElementById('type-document').value; if (typeDocument) { formData.append('type_document_attendu', typeDocument); } try { this.showProgress(true); this.updateProgress(0, 'Envoi du document...'); const response = await fetch(`${this.apiUrl}/api/notary/upload`, { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`Erreur HTTP: ${response.status}`); } const result = await response.json(); this.currentDocument = result; this.updateProgress(25, 'Document reçu, traitement en cours...'); // Show document preview after successful upload this.showDocumentPreview(result, file); // Poll for status updates this.pollDocumentStatus(result.document_id); } catch (error) { console.error('Erreur upload:', error); this.showAlert(`Erreur lors de l'upload: ${error.message}`, 'danger'); this.showProgress(false); } } async pollDocumentStatus(documentId) { const maxAttempts = 60; // 5 minutes max let attempts = 0; const poll = async () => { try { const response = await fetch(`${this.apiUrl}/api/notary/document/${documentId}/status`); const status = await response.json(); this.updateProgress( status.progress || 0, status.current_step || 'Traitement en cours...' ); if (status.status === 'completed') { this.updateProgress(100, 'Traitement terminé!'); setTimeout(() => { this.showProgress(false); this.loadDocuments(); this.showAlert('Document traité avec succès!', 'success'); this.showSection('documents'); }, 1000); } else if (status.status === 'error') { this.showProgress(false); this.showAlert('Erreur lors du traitement', 'danger'); } else if (attempts < maxAttempts) { attempts++; setTimeout(poll, 5000); // Poll every 5 seconds } else { this.showProgress(false); this.showAlert('Timeout du traitement', 'warning'); } } catch (error) { console.error('Erreur polling:', error); this.showProgress(false); this.showAlert('Erreur de communication', 'danger'); } }; poll(); } showProgress(show) { const container = document.querySelector('.progress-container'); container.style.display = show ? 'block' : 'none'; } updateProgress(percent, text) { const progressBar = document.querySelector('.progress-bar'); const progressText = document.getElementById('progress-text'); progressBar.style.width = `${percent}%`; progressText.textContent = text; } async loadDocuments() { try { const response = await fetch(`${this.apiUrl}/api/notary/documents`); const data = await response.json(); this.documents = data.documents || []; this.renderDocuments(); } catch (error) { console.error('Erreur chargement documents:', error); this.showAlert('Erreur lors du chargement des documents', 'danger'); } } renderDocuments() { const container = document.getElementById('documents-list'); if (this.documents.length === 0) { container.innerHTML = `
Aucun document trouvé

Commencez par uploader un document

`; return; } const html = this.documents.map(doc => `
${doc.filename || 'Document'}
ID: ${doc.document_id}
${this.getStatusText(doc.status)}
${new Date(doc.created_at).toLocaleDateString()}
`).join(''); container.innerHTML = html; } getStatusClass(status) { const classes = { 'queued': 'bg-warning', 'processing': 'bg-info', 'completed': 'bg-success', 'error': 'bg-danger' }; return classes[status] || 'bg-secondary'; } getStatusText(status) { const texts = { 'queued': 'En attente', 'processing': 'En cours', 'completed': 'Terminé', 'error': 'Erreur' }; return texts[status] || status; } filterDocuments() { const search = document.getElementById('search-documents').value.toLowerCase(); const statusFilter = document.getElementById('filter-status').value; const typeFilter = document.getElementById('filter-type').value; const filtered = this.documents.filter(doc => { const matchesSearch = !search || doc.filename?.toLowerCase().includes(search) || doc.document_id.toLowerCase().includes(search); const matchesStatus = !statusFilter || doc.status === statusFilter; const matchesType = !typeFilter || doc.type === typeFilter; return matchesSearch && matchesStatus && matchesType; }); // Re-render with filtered documents const originalDocuments = this.documents; this.documents = filtered; this.renderDocuments(); this.documents = originalDocuments; } async viewDocument(documentId) { try { const response = await fetch(`${this.apiUrl}/api/notary/document/${documentId}/analysis`); const analysis = await response.json(); this.showAnalysisModal(analysis); } catch (error) { console.error('Erreur chargement analyse:', error); this.showAlert('Erreur lors du chargement de l\'analyse', 'danger'); } } showAnalysisModal(analysis) { const content = document.getElementById('analysis-content'); content.innerHTML = `
Informations Générales
Type détecté: ${analysis.type_detecte}
Confiance classification: ${(analysis.confiance_classification * 100).toFixed(1)}%
Score de vraisemblance: ${(analysis.score_vraisemblance * 100).toFixed(1)}%
Entités Extraites
${this.renderEntities(analysis.entites_extraites)}
Vérifications Externes
${this.renderVerifications(analysis.verifications_externes)}
Avis de Synthèse
${analysis.avis_synthese}
Recommandations
`; const modal = new bootstrap.Modal(document.getElementById('analysisModal')); modal.show(); } renderEntities(entities) { let html = ''; for (const [type, items] of Object.entries(entities)) { if (Array.isArray(items) && items.length > 0) { html += `
${type.charAt(0).toUpperCase() + type.slice(1)}
`; items.forEach(item => { html += `
${JSON.stringify(item, null, 2)}
`; }); } } return html || '

Aucune entité extraite

'; } renderVerifications(verifications) { let html = ''; for (const [service, result] of Object.entries(verifications)) { const statusClass = result.status === 'verified' ? 'success' : result.status === 'error' ? 'error' : 'warning'; html += `
${service}: ${result.status} ${result.details ? `
${JSON.stringify(result.details)}` : ''}
`; } return html || '

Aucune vérification effectuée

'; } async loadStats() { try { const response = await fetch(`${this.apiUrl}/api/notary/stats`); const stats = await response.json(); document.getElementById('total-documents').textContent = stats.documents_traites || 0; document.getElementById('processing-documents').textContent = stats.documents_en_cours || 0; document.getElementById('success-rate').textContent = `${((stats.taux_reussite || 0) * 100).toFixed(1)}%`; document.getElementById('avg-time').textContent = `${stats.temps_moyen_traitement || 0}s`; this.renderCharts(stats); } catch (error) { console.error('Erreur chargement stats:', error); } } renderCharts(stats) { // Détruire les graphiques existants if (this.charts.documentTypes) { this.charts.documentTypes.destroy(); } if (this.charts.timeline) { this.charts.timeline.destroy(); } // Document types chart const typesCtx = document.getElementById('document-types-chart'); if (typesCtx) { this.charts.documentTypes = new Chart(typesCtx, { type: 'doughnut', data: { labels: Object.keys(stats.types_documents || {}), datasets: [{ data: Object.values(stats.types_documents || {}), backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40' ] }] }, options: { responsive: true, plugins: { legend: { position: 'bottom' } } } }); } // Timeline chart const timelineCtx = document.getElementById('timeline-chart'); if (timelineCtx) { this.charts.timeline = new Chart(timelineCtx, { type: 'line', data: { labels: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun'], datasets: [{ label: 'Documents traités', data: [12, 19, 3, 5, 2, 3], borderColor: '#007bff', backgroundColor: 'rgba(0, 123, 255, 0.1)', tension: 0.4 }] }, options: { responsive: true, scales: { y: { beginAtZero: true } } } }); } } async checkSystemStatus() { try { // Check API const response = await fetch(`${this.apiUrl}/api/health`); const health = await response.json(); document.getElementById('api-status').textContent = 'Connecté'; document.getElementById('api-status').className = 'badge bg-success'; // Check LLM (simplified) document.getElementById('llm-status').textContent = 'Disponible'; document.getElementById('llm-status').className = 'badge bg-success'; // Check external APIs (simplified) document.getElementById('external-apis-status').textContent = 'OK'; document.getElementById('external-apis-status').className = 'badge bg-success'; } catch (error) { console.error('Erreur vérification statut:', error); document.getElementById('api-status').textContent = 'Erreur'; document.getElementById('api-status').className = 'badge bg-danger'; } } showAlert(message, type = 'info') { const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type} alert-dismissible fade show`; alertDiv.innerHTML = ` ${message} `; // Insert at the top of main content const mainContent = document.querySelector('.main-content'); mainContent.insertBefore(alertDiv, mainContent.firstChild); // Auto-dismiss after 5 seconds setTimeout(() => { if (alertDiv.parentNode) { alertDiv.remove(); } }, 5000); } 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]; } async downloadDocument(documentId) { // Implementation for document download this.showAlert('Fonctionnalité de téléchargement en cours de développement', 'info'); } downloadReport() { // Implementation for report download this.showAlert('Fonctionnalité de téléchargement de rapport en cours de développement', 'info'); } } // Global functions function testConnection() { app.checkSystemStatus(); app.showAlert('Test de connexion effectué', 'info'); } // L'application est maintenant initialisée dans index.html