Docfile commited on
Commit
525bd03
·
verified ·
1 Parent(s): 89c8e2e

Create admin.html

Browse files
Files changed (1) hide show
  1. templates/admin.html +444 -0
templates/admin.html ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Admin - Conversations</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ darkMode: 'class',
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ dark: {
15
+ 50: '#f8fafc',
16
+ 100: '#f1f5f9',
17
+ 200: '#e2e8f0',
18
+ 300: '#cbd5e1',
19
+ 400: '#94a3b8',
20
+ 500: '#64748b',
21
+ 600: '#475569',
22
+ 700: '#334155',
23
+ 800: '#1e293b',
24
+ 900: '#0f172a',
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ <style>
32
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
33
+ body { font-family: 'Inter', sans-serif; }
34
+ .conversation-card {
35
+ transition: all 0.3s ease;
36
+ }
37
+ .conversation-card:hover {
38
+ transform: translateY(-2px);
39
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
40
+ }
41
+ .message-bubble {
42
+ animation: fadeInUp 0.3s ease-out;
43
+ }
44
+ @keyframes fadeInUp {
45
+ from {
46
+ opacity: 0;
47
+ transform: translateY(10px);
48
+ }
49
+ to {
50
+ opacity: 1;
51
+ transform: translateY(0);
52
+ }
53
+ }
54
+ .loading-dots {
55
+ display: inline-block;
56
+ }
57
+ .loading-dots::after {
58
+ content: '';
59
+ animation: dots 2s infinite;
60
+ }
61
+ @keyframes dots {
62
+ 0%, 20% { content: '.'; }
63
+ 40% { content: '..'; }
64
+ 60%, 100% { content: '...'; }
65
+ }
66
+ </style>
67
+ </head>
68
+ <body class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 min-h-screen text-white">
69
+ <!-- Header -->
70
+ <header class="bg-slate-800/50 backdrop-blur-sm border-b border-slate-700/50 sticky top-0 z-50">
71
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
72
+ <div class="flex items-center justify-between h-16">
73
+ <div class="flex items-center space-x-3">
74
+ <div class="w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
75
+ <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.031 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
77
+ </svg>
78
+ </div>
79
+ <h1 class="text-xl font-bold text-white">Admin Dashboard</h1>
80
+ </div>
81
+
82
+ <div class="flex items-center space-x-4">
83
+ <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">
84
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
85
+ <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"/>
86
+ </svg>
87
+ <span>Actualiser</span>
88
+ </button>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </header>
93
+
94
+ <!-- Stats Bar -->
95
+ <div class="bg-slate-800/30 border-b border-slate-700/30">
96
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
97
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
98
+ <div class="bg-gradient-to-r from-blue-500/20 to-blue-600/20 rounded-lg p-4 border border-blue-500/30">
99
+ <div class="flex items-center">
100
+ <div class="p-2 bg-blue-500/30 rounded-lg">
101
+ <svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
102
+ <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"/>
103
+ </svg>
104
+ </div>
105
+ <div class="ml-4">
106
+ <p class="text-sm text-slate-400">Conversations</p>
107
+ <p class="text-2xl font-bold text-white" id="totalConversations">0</p>
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ <div class="bg-gradient-to-r from-green-500/20 to-green-600/20 rounded-lg p-4 border border-green-500/30">
113
+ <div class="flex items-center">
114
+ <div class="p-2 bg-green-500/30 rounded-lg">
115
+ <svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
116
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
117
+ </svg>
118
+ </div>
119
+ <div class="ml-4">
120
+ <p class="text-sm text-slate-400">Messages</p>
121
+ <p class="text-2xl font-bold text-white" id="totalMessages">0</p>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="bg-gradient-to-r from-purple-500/20 to-purple-600/20 rounded-lg p-4 border border-purple-500/30">
127
+ <div class="flex items-center">
128
+ <div class="p-2 bg-purple-500/30 rounded-lg">
129
+ <svg class="w-6 h-6 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
131
+ </svg>
132
+ </div>
133
+ <div class="ml-4">
134
+ <p class="text-sm text-slate-400">Actives</p>
135
+ <p class="text-2xl font-bold text-white" id="activeConversations">0</p>
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <div class="bg-gradient-to-r from-orange-500/20 to-orange-600/20 rounded-lg p-4 border border-orange-500/30">
141
+ <div class="flex items-center">
142
+ <div class="p-2 bg-orange-500/30 rounded-lg">
143
+ <svg class="w-6 h-6 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
144
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2h-2a2 2 0 00-2-2z"/>
145
+ </svg>
146
+ </div>
147
+ <div class="ml-4">
148
+ <p class="text-sm text-slate-400">Avec fichiers</p>
149
+ <p class="text-2xl font-bold text-white" id="fileConversations">0</p>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <!-- Main Content -->
158
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
159
+ <!-- Search & Filter -->
160
+ <div class="mb-6">
161
+ <div class="flex flex-col sm:flex-row gap-4 items-center justify-between">
162
+ <div class="relative flex-1 max-w-md">
163
+ <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">
164
+ <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"/>
165
+ </svg>
166
+ <input
167
+ type="text"
168
+ id="searchInput"
169
+ placeholder="Rechercher dans les conversations..."
170
+ 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"
171
+ >
172
+ </div>
173
+
174
+ <div class="flex gap-2">
175
+ <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">
176
+ <option value="newest">Plus récentes</option>
177
+ <option value="oldest">Plus anciennes</option>
178
+ <option value="messages">Plus de messages</option>
179
+ </select>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Loading State -->
185
+ <div id="loadingState" class="text-center py-12">
186
+ <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
187
+ <p class="mt-4 text-slate-400">Chargement des conversations<span class="loading-dots"></span></p>
188
+ </div>
189
+
190
+ <!-- Empty State -->
191
+ <div id="emptyState" class="text-center py-12 hidden">
192
+ <div class="w-16 h-16 bg-slate-700 rounded-full flex items-center justify-center mx-auto mb-4">
193
+ <svg class="w-8 h-8 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <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"/>
195
+ </svg>
196
+ </div>
197
+ <h3 class="text-lg font-medium text-white mb-2">Aucune conversation</h3>
198
+ <p class="text-slate-400">Aucune conversation n'a été trouvée.</p>
199
+ </div>
200
+
201
+ <!-- Conversations Grid -->
202
+ <div id="conversationsGrid" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6 hidden">
203
+ <!-- Les conversations seront ajoutées ici dynamiquement -->
204
+ </div>
205
+
206
+ <!-- Modal pour voir une conversation -->
207
+ <div id="conversationModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
208
+ <div class="flex items-center justify-center min-h-screen p-4">
209
+ <div class="bg-slate-800 rounded-2xl max-w-4xl w-full max-h-[80vh] overflow-hidden">
210
+ <div class="flex items-center justify-between p-6 border-b border-slate-700">
211
+ <h2 class="text-xl font-bold text-white" id="modalTitle">Conversation</h2>
212
+ <button id="closeModal" class="text-slate-400 hover:text-white transition-colors">
213
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
214
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
215
+ </svg>
216
+ </button>
217
+ </div>
218
+ <div class="p-6 overflow-y-auto max-h-96" id="modalContent">
219
+ <!-- Contenu de la conversation -->
220
+ </div>
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </main>
225
+
226
+ <script>
227
+ let conversations = [];
228
+ let filteredConversations = [];
229
+
230
+ // Initialisation
231
+ document.addEventListener('DOMContentLoaded', function() {
232
+ loadConversations();
233
+ setupEventListeners();
234
+ });
235
+
236
+ function setupEventListeners() {
237
+ document.getElementById('refreshBtn').addEventListener('click', loadConversations);
238
+ document.getElementById('searchInput').addEventListener('input', filterConversations);
239
+ document.getElementById('sortSelect').addEventListener('change', sortConversations);
240
+ document.getElementById('closeModal').addEventListener('click', closeModal);
241
+
242
+ // Fermer modal avec Escape
243
+ document.addEventListener('keydown', function(e) {
244
+ if (e.key === 'Escape') closeModal();
245
+ });
246
+ }
247
+
248
+ async function loadConversations() {
249
+ showLoading();
250
+
251
+ try {
252
+ const response = await fetch('/admin/conversations');
253
+ const data = await response.json();
254
+
255
+ conversations = data.conversations || [];
256
+ filteredConversations = [...conversations];
257
+
258
+ updateStats(data.stats || {});
259
+ displayConversations();
260
+
261
+ } catch (error) {
262
+ console.error('Erreur lors du chargement:', error);
263
+ showError('Erreur lors du chargement des conversations');
264
+ }
265
+ }
266
+
267
+ function updateStats(stats) {
268
+ document.getElementById('totalConversations').textContent = stats.total || 0;
269
+ document.getElementById('totalMessages').textContent = stats.totalMessages || 0;
270
+ document.getElementById('activeConversations').textContent = stats.active || 0;
271
+ document.getElementById('fileConversations').textContent = stats.withFiles || 0;
272
+ }
273
+
274
+ function displayConversations() {
275
+ const grid = document.getElementById('conversationsGrid');
276
+ const loading = document.getElementById('loadingState');
277
+ const empty = document.getElementById('emptyState');
278
+
279
+ loading.classList.add('hidden');
280
+
281
+ if (filteredConversations.length === 0) {
282
+ grid.classList.add('hidden');
283
+ empty.classList.remove('hidden');
284
+ return;
285
+ }
286
+
287
+ empty.classList.add('hidden');
288
+ grid.classList.remove('hidden');
289
+
290
+ grid.innerHTML = filteredConversations.map(conv => createConversationCard(conv)).join('');
291
+ }
292
+
293
+ function createConversationCard(conv) {
294
+ const lastMessage = conv.messages[conv.messages.length - 1] || {};
295
+ const messageCount = conv.messages.length;
296
+ const hasFiles = conv.messages.some(m => m.hasFile);
297
+
298
+ const lastActivity = conv.lastActivity ?
299
+ new Date(conv.lastActivity).toLocaleString('fr-FR', {
300
+ day: '2-digit',
301
+ month: '2-digit',
302
+ year: 'numeric',
303
+ hour: '2-digit',
304
+ minute: '2-digit'
305
+ }) : 'Inconnue';
306
+
307
+ return `
308
+ <div class="conversation-card bg-slate-800/50 rounded-xl border border-slate-700/50 p-6 cursor-pointer hover:border-slate-600"
309
+ onclick="openConversation('${conv.id}')">
310
+ <div class="flex items-start justify-between mb-4">
311
+ <div class="flex items-center space-x-3">
312
+ <div class="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
313
+ <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
314
+ <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"/>
315
+ </svg>
316
+ </div>
317
+ <div>
318
+ <h3 class="font-semibold text-white">${conv.id}</h3>
319
+ <p class="text-sm text-slate-400">${messageCount} message${messageCount > 1 ? 's' : ''}</p>
320
+ </div>
321
+ </div>
322
+
323
+ <div class="flex flex-col items-end space-y-2">
324
+ ${hasFiles ? `
325
+ <span class="px-2 py-1 bg-orange-500/20 text-orange-400 text-xs rounded-full border border-orange-500/30">
326
+ 📎 Fichiers
327
+ </span>
328
+ ` : ''}
329
+ <span class="px-2 py-1 bg-blue-500/20 text-blue-400 text-xs rounded-full border border-blue-500/30">
330
+ ${conv.status || 'Active'}
331
+ </span>
332
+ </div>
333
+ </div>
334
+
335
+ <div class="mb-4">
336
+ <p class="text-sm text-slate-300 line-clamp-2">
337
+ ${lastMessage.content ?
338
+ (lastMessage.content.length > 100 ?
339
+ lastMessage.content.substring(0, 100) + '...' :
340
+ lastMessage.content
341
+ ) : 'Conversation vide'
342
+ }
343
+ </p>
344
+ </div>
345
+
346
+ <div class="flex items-center justify-between text-xs text-slate-500">
347
+ <span>Dernière activité</span>
348
+ <span>${lastActivity}</span>
349
+ </div>
350
+ </div>
351
+ `;
352
+ }
353
+
354
+ function openConversation(conversationId) {
355
+ const conv = conversations.find(c => c.id === conversationId);
356
+ if (!conv) return;
357
+
358
+ document.getElementById('modalTitle').textContent = `Conversation: ${conversationId}`;
359
+
360
+ const modalContent = document.getElementById('modalContent');
361
+ modalContent.innerHTML = conv.messages.map(msg => `
362
+ <div class="message-bubble mb-4 ${msg.role === 'user' ? 'ml-8' : 'mr-8'}">
363
+ <div class="flex items-start space-x-3">
364
+ <div class="flex-shrink-0">
365
+ <div class="w-8 h-8 rounded-full flex items-center justify-center ${
366
+ msg.role === 'user'
367
+ ? 'bg-blue-500/20 text-blue-400'
368
+ : 'bg-green-500/20 text-green-400'
369
+ }">
370
+ ${msg.role === 'user' ? '👤' : '🤖'}
371
+ </div>
372
+ </div>
373
+ <div class="flex-1">
374
+ <div class="flex items-center space-x-2 mb-1">
375
+ <span class="text-sm font-medium ${msg.role === 'user' ? 'text-blue-400' : 'text-green-400'}">
376
+ ${msg.role === 'user' ? 'Utilisateur' : 'Assistant'}
377
+ </span>
378
+ ${msg.timestamp ? `
379
+ <span class="text-xs text-slate-500">
380
+ ${new Date(msg.timestamp).toLocaleString('fr-FR')}
381
+ </span>
382
+ ` : ''}
383
+ ${msg.hasFile ? '<span class="text-xs bg-orange-500/20 text-orange-400 px-2 py-1 rounded">📎 Fichier</span>' : ''}
384
+ </div>
385
+ <div class="bg-slate-700/50 rounded-lg p-3 text-sm text-slate-200">
386
+ ${msg.content || 'Message vide'}
387
+ </div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ `).join('');
392
+
393
+ document.getElementById('conversationModal').classList.remove('hidden');
394
+ }
395
+
396
+ function closeModal() {
397
+ document.getElementById('conversationModal').classList.add('hidden');
398
+ }
399
+
400
+ function filterConversations() {
401
+ const searchTerm = document.getElementById('searchInput').value.toLowerCase();
402
+
403
+ filteredConversations = conversations.filter(conv => {
404
+ return conv.id.toLowerCase().includes(searchTerm) ||
405
+ conv.messages.some(msg =>
406
+ msg.content && msg.content.toLowerCase().includes(searchTerm)
407
+ );
408
+ });
409
+
410
+ displayConversations();
411
+ }
412
+
413
+ function sortConversations() {
414
+ const sortBy = document.getElementById('sortSelect').value;
415
+
416
+ filteredConversations.sort((a, b) => {
417
+ switch(sortBy) {
418
+ case 'newest':
419
+ return new Date(b.lastActivity || 0) - new Date(a.lastActivity || 0);
420
+ case 'oldest':
421
+ return new Date(a.lastActivity || 0) - new Date(b.lastActivity || 0);
422
+ case 'messages':
423
+ return b.messages.length - a.messages.length;
424
+ default:
425
+ return 0;
426
+ }
427
+ });
428
+
429
+ displayConversations();
430
+ }
431
+
432
+ function showLoading() {
433
+ document.getElementById('loadingState').classList.remove('hidden');
434
+ document.getElementById('conversationsGrid').classList.add('hidden');
435
+ document.getElementById('emptyState').classList.add('hidden');
436
+ }
437
+
438
+ function showError(message) {
439
+ // Vous pouvez implémenter une notification d'erreur ici
440
+ console.error(message);
441
+ }
442
+ </script>
443
+ </body>
444
+ </html>