Download templates/admin.html from Docfile/Chatm2: direct link, hf CLI and curl.
- Browser
- Download file 17.5 kB
-
https://huggingface.co/spaces/Docfile/Chatm2/resolve/main/templates/admin.html
- Command line
-
hf download hf://spaces/Docfile/Chatm2/templates/admin.html
-
curl -L -o admin.html https://huggingface.co/spaces/Docfile/Chatm2/resolve/main/templates/admin.html
17.5 kB
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Admin - Conversations</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| colors: { | |
| dark: { | |
| 50: '#f8fafc', | |
| 100: '#f1f5f9', | |
| 200: '#e2e8f0', | |
| 300: '#cbd5e1', | |
| 400: '#94a3b8', | |
| 500: '#64748b', | |
| 600: '#475569', | |
| 700: '#334155', | |
| 800: '#1e293b', | |
| 900: '#0f172a', | |
| } | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { font-family: 'Inter', sans-serif; } | |
| .conversation-card { | |
| transition: all 0.3s ease; | |
| } | |
| .conversation-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .message-bubble { | |
| animation: fadeInUp 0.3s ease-out; | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .loading-dots { | |
| display: inline-block; | |
| } | |
| .loading-dots::after { | |
| content: ''; | |
| animation: dots 2s infinite; | |
| } | |
| @keyframes dots { | |
| 0%, 20% { content: '.'; } | |
| 40% { content: '..'; } | |
| 60%, 100% { content: '...'; } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 min-h-screen text-white"> | |
| <!-- Header --> | |
| <header class="bg-slate-800/50 backdrop-blur-sm border-b border-slate-700/50 sticky top-0 z-50"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex items-center justify-between h-12"> | |
| <h1 class="text-lg font-bold text-white">Admin Dashboard</h1> | |
| <button id="refreshBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors duration-200 flex items-center space-x-2"> | |
| <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/> | |
| </svg> | |
| <span>Actualiser</span> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16"> | |
| <!-- Search & Filter --> | |
| <div class="mb-8 sm:mb-10"> | |
| <div class="flex flex-col sm:flex-row gap-4 items-center justify-between"> | |
| <div class="relative flex-1 max-w-md"> | |
| <svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/> | |
| </svg> | |
| <input | |
| type="text" | |
| id="searchInput" | |
| placeholder="Rechercher dans les conversations..." | |
| class="w-full pl-10 pr-4 py-2 bg-slate-800/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-slate-400" | |
| > | |
| </div> | |
| <div class="flex gap-2"> | |
| <select id="sortSelect" class="px-4 py-2 bg-slate-800/50 border border-slate-600 rounded-lg text-white focus:ring-2 focus:ring-blue-500"> | |
| <option value="newest">Plus récentes</option> | |
| <option value="oldest">Plus anciennes</option> | |
| <option value="messages">Plus de messages</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading State --> | |
| <div id="loadingState" class="text-center py-12"> | |
| <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div> | |
| <p class="mt-4 text-slate-400">Chargement des conversations<span class="loading-dots"></span></p> | |
| </div> | |
| <!-- Empty State --> | |
| <div id="emptyState" class="text-center py-12 hidden"> | |
| <div class="w-16 h-16 bg-slate-700 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <svg class="w-8 h-8 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> | |
| </svg> | |
| </div> | |
| <h3 class="text-lg font-medium text-white mb-2">Aucune conversation</h3> | |
| <p class="text-slate-400">Aucune conversation n'a été trouvée.</p> | |
| </div> | |
| <!-- Conversations Grid --> | |
| <div id="conversationsGrid" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-8 sm:gap-10 hidden"> | |
| <!-- Les conversations seront ajoutées ici dynamiquement --> | |
| </div> | |
| <!-- Modal pour voir une conversation --> | |
| <div id="conversationModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden"> | |
| <div class="flex items-center justify-center min-h-screen p-6 sm:p-4"> | |
| <div class="bg-slate-800 rounded-2xl max-w-4xl w-full max-h-[85vh] overflow-hidden"> | |
| <div class="flex items-center justify-between p-8 border-b border-slate-700"> | |
| <h2 class="text-xl font-bold text-white" id="modalTitle">Conversation</h2> | |
| <button id="closeModal" class="text-slate-400 hover:text-white transition-colors p-2"> | |
| <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="p-8 overflow-y-auto max-h-[60vh]" id="modalContent"> | |
| <!-- Contenu de la conversation --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| let conversations = []; | |
| let filteredConversations = []; | |
| // Initialisation | |
| document.addEventListener('DOMContentLoaded', function() { | |
| loadConversations(); | |
| setupEventListeners(); | |
| }); | |
| function setupEventListeners() { | |
| document.getElementById('refreshBtn').addEventListener('click', loadConversations); | |
| document.getElementById('searchInput').addEventListener('input', filterConversations); | |
| document.getElementById('sortSelect').addEventListener('change', sortConversations); | |
| document.getElementById('closeModal').addEventListener('click', closeModal); | |
| // Fermer modal avec Escape | |
| document.addEventListener('keydown', function(e) { | |
| if (e.key === 'Escape') closeModal(); | |
| }); | |
| } | |
| async function loadConversations() { | |
| showLoading(); | |
| try { | |
| const response = await fetch('/admin/conversations'); | |
| const data = await response.json(); | |
| conversations = data.conversations || []; | |
| filteredConversations = [...conversations]; | |
| // updateStats(data.stats || {}); // Removed stats bar | |
| displayConversations(); | |
| } catch (error) { | |
| console.error('Erreur lors du chargement:', error); | |
| showError('Erreur lors du chargement des conversations'); | |
| } | |
| } | |
| function updateStats(stats) { | |
| document.getElementById('totalConversations').textContent = stats.total || 0; | |
| document.getElementById('totalMessages').textContent = stats.totalMessages || 0; | |
| document.getElementById('activeConversations').textContent = stats.active || 0; | |
| document.getElementById('fileConversations').textContent = stats.withFiles || 0; | |
| } | |
| function displayConversations() { | |
| const grid = document.getElementById('conversationsGrid'); | |
| const loading = document.getElementById('loadingState'); | |
| const empty = document.getElementById('emptyState'); | |
| loading.classList.add('hidden'); | |
| if (filteredConversations.length === 0) { | |
| grid.classList.add('hidden'); | |
| empty.classList.remove('hidden'); | |
| return; | |
| } | |
| empty.classList.add('hidden'); | |
| grid.classList.remove('hidden'); | |
| grid.innerHTML = filteredConversations.map(conv => createConversationCard(conv)).join(''); | |
| } | |
| function createConversationCard(conv) { | |
| const lastMessage = conv.messages[conv.messages.length - 1] || {}; | |
| const messageCount = conv.messages.length; | |
| const hasFiles = conv.messages.some(m => m.hasFile); | |
| const lastActivity = conv.lastActivity ? | |
| new Date(conv.lastActivity).toLocaleString('fr-FR', { | |
| day: '2-digit', | |
| month: '2-digit', | |
| year: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }) : 'Inconnue'; | |
| return ` | |
| <div class="conversation-card bg-slate-800/50 rounded-xl border border-slate-700/50 p-8 cursor-pointer hover:border-slate-600" | |
| onclick="openConversation('${conv.id}')"> | |
| <div class="flex items-start justify-between mb-6"> | |
| <div class="flex items-center space-x-4"> | |
| <div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center"> | |
| <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold text-white">${conv.id}</h3> | |
| <p class="text-sm text-slate-400">${messageCount} message${messageCount > 1 ? 's' : ''}</p> | |
| </div> | |
| </div> | |
| <div class="flex flex-col items-end space-y-2"> | |
| ${hasFiles ? ` | |
| <span class="px-2 py-1 bg-orange-500/20 text-orange-400 text-xs rounded-full border border-orange-500/30"> | |
| 📎 Fichiers | |
| </span> | |
| ` : ''} | |
| <span class="px-2 py-1 bg-blue-500/20 text-blue-400 text-xs rounded-full border border-blue-500/30"> | |
| ${conv.status || 'Active'} | |
| </span> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <p class="text-sm text-slate-300 line-clamp-2"> | |
| ${lastMessage.content ? | |
| (lastMessage.content.length > 100 ? | |
| lastMessage.content.substring(0, 100) + '...' : | |
| lastMessage.content | |
| ) : 'Conversation vide' | |
| } | |
| </p> | |
| </div> | |
| <div class="flex items-center justify-between text-xs text-slate-500"> | |
| <span>Dernière activité</span> | |
| <span>${lastActivity}</span> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| function openConversation(conversationId) { | |
| const conv = conversations.find(c => c.id === conversationId); | |
| if (!conv) return; | |
| document.getElementById('modalTitle').textContent = `Conversation: ${conversationId}`; | |
| const modalContent = document.getElementById('modalContent'); | |
| modalContent.innerHTML = conv.messages.map(msg => ` | |
| <div class="message-bubble mb-4 ${msg.role === 'user' ? 'ml-8' : 'mr-8'}"> | |
| <div class="flex items-start space-x-3"> | |
| <div class="flex-shrink-0"> | |
| <div class="w-8 h-8 rounded-full flex items-center justify-center ${ | |
| msg.role === 'user' | |
| ? 'bg-blue-500/20 text-blue-400' | |
| : 'bg-green-500/20 text-green-400' | |
| }"> | |
| ${msg.role === 'user' ? '👤' : '🤖'} | |
| </div> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="flex items-center space-x-2 mb-1"> | |
| <span class="text-sm font-medium ${msg.role === 'user' ? 'text-blue-400' : 'text-green-400'}"> | |
| ${msg.role === 'user' ? 'Utilisateur' : 'Assistant'} | |
| </span> | |
| ${msg.timestamp ? ` | |
| <span class="text-xs text-slate-500"> | |
| ${new Date(msg.timestamp).toLocaleString('fr-FR')} | |
| </span> | |
| ` : ''} | |
| ${msg.hasFile ? '<span class="text-xs bg-orange-500/20 text-orange-400 px-2 py-1 rounded">📎 Fichier</span>' : ''} | |
| </div> | |
| <div class="bg-slate-700/50 rounded-lg p-3 text-sm text-slate-200"> | |
| ${msg.content || 'Message vide'} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| document.getElementById('conversationModal').classList.remove('hidden'); | |
| } | |
| function closeModal() { | |
| document.getElementById('conversationModal').classList.add('hidden'); | |
| } | |
| function filterConversations() { | |
| const searchTerm = document.getElementById('searchInput').value.toLowerCase(); | |
| filteredConversations = conversations.filter(conv => { | |
| return conv.id.toLowerCase().includes(searchTerm) || | |
| conv.messages.some(msg => | |
| msg.content && msg.content.toLowerCase().includes(searchTerm) | |
| ); | |
| }); | |
| displayConversations(); | |
| } | |
| function sortConversations() { | |
| const sortBy = document.getElementById('sortSelect').value; | |
| filteredConversations.sort((a, b) => { | |
| switch(sortBy) { | |
| case 'newest': | |
| return new Date(b.lastActivity || 0) - new Date(a.lastActivity || 0); | |
| case 'oldest': | |
| return new Date(a.lastActivity || 0) - new Date(b.lastActivity || 0); | |
| case 'messages': | |
| return b.messages.length - a.messages.length; | |
| default: | |
| return 0; | |
| } | |
| }); | |
| displayConversations(); | |
| } | |
| function showLoading() { | |
| document.getElementById('loadingState').classList.remove('hidden'); | |
| document.getElementById('conversationsGrid').classList.add('hidden'); | |
| document.getElementById('emptyState').classList.add('hidden'); | |
| } | |
| function showError(message) { | |
| // Vous pouvez implémenter une notification d'erreur ici | |
| console.error(message); | |
| } | |
| </script> | |
| </body> | |
| </html> |