| <!DOCTYPE html> |
| <html lang="fr"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Gestion des Analyses - TextAnalyzer AI</title> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.13.0/cdn.js" defer></script> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| min-height: 100vh; |
| color: #333; |
| } |
| |
| .container { |
| max-width: 1400px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| .header { |
| background: rgba(255, 255, 255, 0.95); |
| backdrop-filter: blur(20px); |
| border-radius: 20px; |
| padding: 30px; |
| margin-bottom: 30px; |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); |
| text-align: center; |
| } |
| |
| .header h1 { |
| font-size: 2.5rem; |
| font-weight: 700; |
| background: linear-gradient(135deg, #667eea, #764ba2); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| margin-bottom: 10px; |
| } |
| |
| .header p { |
| color: #666; |
| font-size: 1.1rem; |
| } |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| gap: 20px; |
| margin-bottom: 30px; |
| } |
| |
| .stat-card { |
| background: rgba(255, 255, 255, 0.95); |
| backdrop-filter: blur(20px); |
| border-radius: 15px; |
| padding: 25px; |
| text-align: center; |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); |
| transition: transform 0.3s ease; |
| } |
| |
| .stat-card:hover { |
| transform: translateY(-5px); |
| } |
| |
| .stat-number { |
| font-size: 2.5rem; |
| font-weight: 700; |
| color: #667eea; |
| margin-bottom: 10px; |
| } |
| |
| .stat-label { |
| color: #666; |
| font-weight: 500; |
| } |
| |
| .controls { |
| background: rgba(255, 255, 255, 0.95); |
| backdrop-filter: blur(20px); |
| border-radius: 15px; |
| padding: 20px; |
| margin-bottom: 20px; |
| display: flex; |
| gap: 15px; |
| flex-wrap: wrap; |
| align-items: center; |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); |
| } |
| |
| .search-box { |
| flex: 1; |
| min-width: 300px; |
| } |
| |
| .search-box input { |
| width: 100%; |
| padding: 12px 15px; |
| border: 2px solid #e0e7ff; |
| border-radius: 10px; |
| font-size: 1rem; |
| transition: all 0.3s ease; |
| } |
| |
| .search-box input:focus { |
| outline: none; |
| border-color: #667eea; |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); |
| } |
| |
| .filter-buttons { |
| display: flex; |
| gap: 10px; |
| flex-wrap: wrap; |
| } |
| |
| .filter-btn { |
| padding: 10px 20px; |
| border: none; |
| border-radius: 25px; |
| font-weight: 500; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| background: #f1f5f9; |
| color: #64748b; |
| } |
| |
| .filter-btn.active { |
| background: #667eea; |
| color: white; |
| } |
| |
| .refresh-btn { |
| background: #10b981; |
| color: white; |
| border: none; |
| padding: 12px 24px; |
| border-radius: 10px; |
| cursor: pointer; |
| font-weight: 500; |
| transition: all 0.3s ease; |
| } |
| |
| .refresh-btn:hover { |
| background: #059669; |
| transform: translateY(-2px); |
| } |
| |
| .analyses-grid { |
| display: grid; |
| gap: 20px; |
| } |
| |
| .analysis-card { |
| background: rgba(255, 255, 255, 0.95); |
| backdrop-filter: blur(20px); |
| border-radius: 15px; |
| padding: 25px; |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); |
| transition: all 0.3s ease; |
| } |
| |
| .analysis-card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); |
| } |
| |
| .analysis-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: flex-start; |
| margin-bottom: 20px; |
| flex-wrap: wrap; |
| gap: 15px; |
| } |
| |
| .analysis-info h3 { |
| font-size: 1.3rem; |
| font-weight: 600; |
| color: #333; |
| margin-bottom: 5px; |
| } |
| |
| .analysis-meta { |
| color: #666; |
| font-size: 0.9rem; |
| display: flex; |
| flex-wrap: wrap; |
| gap: 15px; |
| } |
| |
| .meta-item { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| } |
| |
| .analysis-badges { |
| display: flex; |
| gap: 10px; |
| flex-wrap: wrap; |
| } |
| |
| .badge { |
| padding: 6px 12px; |
| border-radius: 20px; |
| font-size: 0.8rem; |
| font-weight: 500; |
| } |
| |
| .badge.deepthink { |
| background: #fef3c7; |
| color: #92400e; |
| } |
| |
| .badge.standard { |
| background: #dbeafe; |
| color: #1d4ed8; |
| } |
| |
| .analysis-content { |
| display: grid; |
| gap: 20px; |
| } |
| |
| .content-section { |
| border: 1px solid #e5e7eb; |
| border-radius: 10px; |
| overflow: hidden; |
| } |
| |
| .content-header { |
| background: #f9fafb; |
| padding: 15px; |
| font-weight: 600; |
| color: #374151; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .content-body { |
| padding: 20px; |
| max-height: 300px; |
| overflow-y: auto; |
| line-height: 1.6; |
| } |
| |
| .image-section { |
| text-align: center; |
| margin-bottom: 20px; |
| } |
| |
| .analysis-image { |
| max-width: 100%; |
| max-height: 200px; |
| border-radius: 10px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| cursor: pointer; |
| transition: transform 0.3s ease; |
| } |
| |
| .analysis-image:hover { |
| transform: scale(1.05); |
| } |
| |
| .consignes-section { |
| background: #f0f9ff; |
| border-left: 4px solid #0ea5e9; |
| padding: 15px; |
| border-radius: 8px; |
| margin-bottom: 20px; |
| } |
| |
| .consignes-section h4 { |
| color: #0c4a6e; |
| margin-bottom: 10px; |
| } |
| |
| .toggle-btn { |
| background: #667eea; |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 0.9rem; |
| transition: all 0.3s ease; |
| } |
| |
| .toggle-btn:hover { |
| background: #5a67d8; |
| } |
| |
| .loading { |
| text-align: center; |
| padding: 50px; |
| color: #666; |
| } |
| |
| .spinner { |
| border: 4px solid #f3f4f6; |
| border-left: 4px solid #667eea; |
| border-radius: 50%; |
| width: 40px; |
| height: 40px; |
| animation: spin 1s linear infinite; |
| margin: 0 auto 20px; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| .no-data { |
| text-align: center; |
| padding: 50px; |
| color: #666; |
| } |
| |
| .error { |
| background: #fef2f2; |
| color: #dc2626; |
| padding: 15px; |
| border-radius: 10px; |
| margin-bottom: 20px; |
| border-left: 4px solid #dc2626; |
| } |
| |
| .back-button { |
| position: fixed; |
| top: 20px; |
| left: 20px; |
| background: rgba(255, 255, 255, 0.9); |
| color: #667eea; |
| border: 2px solid #667eea; |
| padding: 12px 20px; |
| border-radius: 25px; |
| text-decoration: none; |
| font-weight: 600; |
| transition: all 0.3s ease; |
| backdrop-filter: blur(10px); |
| } |
| |
| .back-button:hover { |
| background: #667eea; |
| color: white; |
| transform: translateY(-2px); |
| } |
| |
| |
| .modal { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0, 0, 0, 0.8); |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| z-index: 1000; |
| } |
| |
| .modal-content { |
| background: white; |
| border-radius: 15px; |
| max-width: 90vw; |
| max-height: 90vh; |
| overflow: auto; |
| position: relative; |
| } |
| |
| .modal-close { |
| position: absolute; |
| top: 15px; |
| right: 15px; |
| background: #dc2626; |
| color: white; |
| border: none; |
| width: 30px; |
| height: 30px; |
| border-radius: 50%; |
| cursor: pointer; |
| font-size: 1.2rem; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .modal img { |
| max-width: 100%; |
| height: auto; |
| border-radius: 10px; |
| } |
| |
| @media (max-width: 768px) { |
| .container { |
| padding: 10px; |
| } |
| |
| .header h1 { |
| font-size: 2rem; |
| } |
| |
| .stats-grid { |
| grid-template-columns: repeat(2, 1fr); |
| } |
| |
| .analysis-header { |
| flex-direction: column; |
| align-items: flex-start; |
| } |
| |
| .controls { |
| flex-direction: column; |
| align-items: stretch; |
| } |
| |
| .search-box { |
| min-width: auto; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <a href="/" class="back-button">← Retour à l'accueil</a> |
|
|
| <div class="container" x-data="gestionApp()"> |
| <div class="header"> |
| <h1>Gestion des Analyses</h1> |
| <p>Visualisez et gérez toutes les analyses effectuées par les utilisateurs</p> |
| </div> |
|
|
| |
| <div class="stats-grid"> |
| <div class="stat-card"> |
| <div class="stat-number" x-text="stats.total"></div> |
| <div class="stat-label">Total Analyses</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-number" x-text="stats.deepthink"></div> |
| <div class="stat-label">DeepThink</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-number" x-text="stats.standard"></div> |
| <div class="stat-label">Standard</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-number" x-text="stats.avgTime + 's'"></div> |
| <div class="stat-label">Temps Moyen</div> |
| </div> |
| </div> |
|
|
| |
| <div class="controls"> |
| <div class="search-box"> |
| <input |
| type="text" |
| placeholder="Rechercher par nom d'image, consignes..." |
| x-model="searchTerm" |
| @input="filterAnalyses()" |
| > |
| </div> |
| <div class="filter-buttons"> |
| <button |
| class="filter-btn" |
| :class="{ 'active': filter === 'all' }" |
| @click="setFilter('all')" |
| > |
| Toutes |
| </button> |
| <button |
| class="filter-btn" |
| :class="{ 'active': filter === 'deepthink' }" |
| @click="setFilter('deepthink')" |
| > |
| DeepThink |
| </button> |
| <button |
| class="filter-btn" |
| :class="{ 'active': filter === 'standard' }" |
| @click="setFilter('standard')" |
| > |
| Standard |
| </button> |
| </div> |
| <button class="refresh-btn" @click="loadAnalyses()"> |
| 🔄 Actualiser |
| </button> |
| </div> |
|
|
| |
| <div class="error" x-show="error" x-text="error"></div> |
|
|
| |
| <div class="loading" x-show="loading"> |
| <div class="spinner"></div> |
| <p>Chargement des analyses...</p> |
| </div> |
|
|
| |
| <div class="no-data" x-show="!loading && filteredAnalyses.length === 0 && !error"> |
| <p>Aucune analyse trouvée</p> |
| </div> |
|
|
| |
| <div class="analyses-grid"> |
| <template x-for="analysis in filteredAnalyses" :key="analysis.id"> |
| <div class="analysis-card"> |
| <div class="analysis-header"> |
| <div class="analysis-info"> |
| <h3 x-text="analysis.image_name || 'Image sans nom'"></h3> |
| <div class="analysis-meta"> |
| <div class="meta-item"> |
| <span>📅</span> |
| <span x-text="formatDate(analysis.timestamp)"></span> |
| </div> |
| <div class="meta-item"> |
| <span>🌐</span> |
| <span x-text="analysis.user_ip"></span> |
| </div> |
| <div class="meta-item"> |
| <span>⏱️</span> |
| <span x-text="analysis.processing_time + 's'"></span> |
| </div> |
| </div> |
| </div> |
| <div class="analysis-badges"> |
| <span |
| class="badge" |
| :class="analysis.use_deepthink ? 'deepthink' : 'standard'" |
| x-text="analysis.use_deepthink ? 'DeepThink' : 'Standard'" |
| ></span> |
| </div> |
| </div> |
|
|
| |
| <div class="image-section"> |
| <img |
| :src="`/api/analysis/${analysis.id}/image`" |
| :alt="analysis.image_name" |
| class="analysis-image" |
| @click="showImageModal(analysis.id, analysis.image_name)" |
| @error="$event.target.style.display='none'" |
| > |
| </div> |
|
|
| |
| <div class="consignes-section" x-show="analysis.consignes"> |
| <h4>Consignes utilisateur :</h4> |
| <p x-text="analysis.consignes"></p> |
| </div> |
|
|
| |
| <div class="analysis-content"> |
| |
| <div class="content-section"> |
| <div class="content-header"> |
| <span>📊 Tableau d'Analyse</span> |
| <button |
| class="toggle-btn" |
| @click="toggleSection(analysis.id, 'tableau')" |
| x-text="expandedSections[analysis.id + '_tableau'] ? 'Réduire' : 'Voir'" |
| ></button> |
| </div> |
| <div |
| class="content-body" |
| x-show="expandedSections[analysis.id + '_tableau']" |
| x-transition |
| > |
| <pre x-text="analysis.tableau_output || 'Aucun contenu'"></pre> |
| </div> |
| </div> |
|
|
| |
| <div class="content-section"> |
| <div class="content-header"> |
| <span>✍️ Dissertation</span> |
| <button |
| class="toggle-btn" |
| @click="toggleSection(analysis.id, 'dissertation')" |
| x-text="expandedSections[analysis.id + '_dissertation'] ? 'Réduire' : 'Voir'" |
| ></button> |
| </div> |
| <div |
| class="content-body" |
| x-show="expandedSections[analysis.id + '_dissertation']" |
| x-transition |
| > |
| <pre x-text="analysis.dissertation_output || 'Aucun contenu'"></pre> |
| </div> |
| </div> |
| </div> |
| </div> |
| </template> |
| </div> |
|
|
| |
| <div class="modal" x-show="showModal" x-transition @click="closeModal()"> |
| <div class="modal-content" @click.stop> |
| <button class="modal-close" @click="closeModal()">×</button> |
| <img :src="modalImage" :alt="modalTitle"> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| function gestionApp() { |
| return { |
| analyses: [], |
| filteredAnalyses: [], |
| loading: true, |
| error: '', |
| searchTerm: '', |
| filter: 'all', |
| expandedSections: {}, |
| showModal: false, |
| modalImage: '', |
| modalTitle: '', |
| stats: { |
| total: 0, |
| deepthink: 0, |
| standard: 0, |
| avgTime: 0 |
| }, |
| |
| init() { |
| this.loadAnalyses(); |
| }, |
| |
| async loadAnalyses() { |
| this.loading = true; |
| this.error = ''; |
| |
| try { |
| const response = await fetch('/api/analyses'); |
| if (!response.ok) { |
| throw new Error('Erreur lors du chargement des données'); |
| } |
| |
| this.analyses = await response.json(); |
| this.calculateStats(); |
| this.filterAnalyses(); |
| } catch (err) { |
| this.error = err.message; |
| console.error('Error loading analyses:', err); |
| } finally { |
| this.loading = false; |
| } |
| }, |
| |
| calculateStats() { |
| this.stats.total = this.analyses.length; |
| this.stats.deepthink = this.analyses.filter(a => a.use_deepthink).length; |
| this.stats.standard = this.stats.total - this.stats.deepthink; |
| |
| if (this.stats.total > 0) { |
| const totalTime = this.analyses.reduce((sum, a) => sum + (a.processing_time || 0), 0); |
| this.stats.avgTime = Math.round(totalTime / this.stats.total * 100) / 100; |
| } else { |
| this.stats.avgTime = 0; |
| } |
| }, |
| |
| filterAnalyses() { |
| let filtered = this.analyses; |
| |
| |
| if (this.filter === 'deepthink') { |
| filtered = filtered.filter(a => a.use_deepthink); |
| } else if (this.filter === 'standard') { |
| filtered = filtered.filter(a => !a.use_deepthink); |
| } |
| |
| |
| if (this.searchTerm.trim()) { |
| const term = this.searchTerm.toLowerCase(); |
| filtered = filtered.filter(a => |
| (a.image_name && a.image_name.toLowerCase().includes(term)) || |
| (a.consignes && a.consignes.toLowerCase().includes(term)) || |
| (a.user_ip && a.user_ip.includes(term)) |
| ); |
| } |
| |
| this.filteredAnalyses = filtered; |
| }, |
| |
| setFilter(newFilter) { |
| this.filter = newFilter; |
| this.filterAnalyses(); |
| }, |
| |
| toggleSection(analysisId, sectionType) { |
| const key = analysisId + '_' + sectionType; |
| this.expandedSections[key] = !this.expandedSections[key]; |
| }, |
| |
| showImageModal(analysisId, imageName) { |
| this.modalImage = `/api/analysis/${analysisId}/image`; |
| this.modalTitle = imageName; |
| this.showModal = true; |
| }, |
| |
| closeModal() { |
| this.showModal = false; |
| this.modalImage = ''; |
| this.modalTitle = ''; |
| }, |
| |
| formatDate(dateString) { |
| return new Date(dateString).toLocaleString('fr-FR', { |
| year: 'numeric', |
| month: 'short', |
| day: 'numeric', |
| hour: '2-digit', |
| minute: '2-digit' |
| }); |
| } |
| } |
| } |
| </script> |
| </body> |
| </html> |