Docfile commited on
Commit
d5ce766
·
verified ·
1 Parent(s): 57ee656

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +481 -0
templates/index.html ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Mariam AI - Analyse d'Image</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/moment.min.js"></script>
11
+ <style>
12
+ @layer utilities {
13
+ .markdown-content {
14
+ @apply prose prose-sm md:prose-base lg:prose-lg mx-auto;
15
+ }
16
+
17
+ .markdown-content table {
18
+ @apply w-full table-auto border-collapse my-4;
19
+ }
20
+
21
+ .markdown-content th,
22
+ .markdown-content td {
23
+ @apply border border-gray-300 p-2 text-left align-top break-words;
24
+ min-width: auto;
25
+ white-space: normal;
26
+ }
27
+
28
+ .markdown-content pre {
29
+ @apply overflow-x-auto p-4 rounded-lg bg-gray-100;
30
+ }
31
+ }
32
+
33
+ .loader {
34
+ width: 48px;
35
+ height: 48px;
36
+ border: 5px solid #FFF;
37
+ border-bottom-color: #6366F1;
38
+ border-radius: 50%;
39
+ display: inline-block;
40
+ box-sizing: border-box;
41
+ animation: rotation 1s linear infinite;
42
+ }
43
+
44
+ @keyframes rotation {
45
+ 0% { transform: rotate(0deg); }
46
+ 100% { transform: rotate(360deg); }
47
+ }
48
+
49
+ .fade-in {
50
+ animation: fadeIn 0.3s ease-in;
51
+ }
52
+
53
+ @keyframes fadeIn {
54
+ 0% { opacity: 0; }
55
+ 100% { opacity: 1; }
56
+ }
57
+
58
+ .upload-zone {
59
+ @apply relative border-2 border-dashed border-indigo-500 rounded-lg p-8 transition-all duration-300 ease-in-out;
60
+ }
61
+
62
+ .upload-zone.drag-over {
63
+ @apply border-indigo-600 bg-indigo-50;
64
+ }
65
+
66
+ .tab-button {
67
+ @apply px-4 py-2 rounded-lg transition-all duration-200 text-sm md:text-base;
68
+ }
69
+
70
+ .tab-button:not(.active) {
71
+ @apply bg-white text-indigo-600 hover:bg-indigo-50;
72
+ }
73
+
74
+ .tab-button.active {
75
+ @apply bg-indigo-600 text-white;
76
+ }
77
+
78
+ .backup-item {
79
+ @apply flex flex-col md:flex-row justify-between items-start md:items-center gap-2 p-4 rounded-lg border border-gray-200 hover:bg-gray-50 transition-colors duration-200;
80
+ }
81
+
82
+ .backup-actions {
83
+ @apply flex gap-2 w-full md:w-auto justify-end;
84
+ }
85
+ </style>
86
+ </head>
87
+
88
+ <body class="min-h-screen bg-gray-50">
89
+ <header class="bg-white shadow-sm sticky top-0 z-50">
90
+ <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
91
+ <h1 class="text-2xl md:text-3xl font-bold text-indigo-600">Mariam AI</h1>
92
+ <p class="mt-1 text-sm text-gray-500">Assistant pour commentaire composé</p>
93
+ </div>
94
+ </header>
95
+
96
+ <main class="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8 space-y-8">
97
+ <form id="uploadForm" class="bg-white rounded-xl shadow-sm p-6">
98
+ <div class="upload-zone" id="dropZone">
99
+ <input type="file" id="imageInput" accept="image/*" class="hidden" />
100
+ <label for="imageInput" class="block text-center cursor-pointer">
101
+ <svg class="mx-auto h-12 w-12 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
102
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
103
+ </svg>
104
+ <p id="fileName" class="mt-2 text-sm text-gray-500">
105
+ Cliquez ou glissez une image ici
106
+ </p>
107
+ </label>
108
+ </div>
109
+
110
+ <div id="imagePreview" class="hidden mt-6">
111
+ <div class="relative max-w-md mx-auto">
112
+ <img id="preview" src="#" alt="Prévisualisation" class="rounded-lg shadow-md max-h-[400px] w-full object-contain" />
113
+ <button type="button" onclick="removeImage()" class="absolute top-2 right-2 p-2 rounded-full bg-white/90 hover:bg-red-500 hover:text-white transition-colors duration-200">
114
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
115
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
116
+ </svg>
117
+ </button>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="mt-6 flex justify-center">
122
+ <button type="submit" class="inline-flex items-center px-6 py-3 rounded-lg bg-indigo-600 text-white font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
123
+ Analyser l'image
124
+ </button>
125
+ </div>
126
+ </form>
127
+
128
+ <div id="loading" class="hidden">
129
+ <div class="flex flex-col items-center justify-center gap-4">
130
+ <span class="loader"></span>
131
+ <p class="text-gray-500">Analyse en cours, veuillez patienter...</p>
132
+ </div>
133
+ </div>
134
+
135
+ <div id="results" class="hidden space-y-6">
136
+ <div class="flex justify-center gap-2">
137
+ <button id="showDissertation" class="tab-button active">Dissertation</button>
138
+ <button id="showTableau" class="tab-button">Tableau</button>
139
+ <button id="showBackups" class="tab-button">Sauvegardes</button>
140
+ </div>
141
+
142
+ <div id="dissertationTab" class="tab-content active">
143
+ <div class="bg-white rounded-xl shadow-sm p-6">
144
+ <h2 class="text-xl md:text-2xl font-bold text-gray-900 mb-4">Dissertation</h2>
145
+ <div id="dissertationResult" class="markdown-content"></div>
146
+ <button id="saveDissertation" class="mt-6 px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 transition-colors duration-200">
147
+ Sauvegarder la dissertation
148
+ </button>
149
+ </div>
150
+ </div>
151
+
152
+ <div id="tableauTab" class="tab-content hidden">
153
+ <div class="bg-white rounded-xl shadow-sm p-6">
154
+ <h2 class="text-xl md:text-2xl font-bold text-gray-900 mb-4">Tableau d'analyse</h2>
155
+ <div id="tableauResult" class="markdown-content"></div>
156
+ <button id="saveTableau" class="mt-6 px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 transition-colors duration-200">
157
+ Sauvegarder le tableau
158
+ </button>
159
+ </div>
160
+ </div>
161
+
162
+ <div id="backupsTab" class="tab-content hidden">
163
+ <div class="bg-white rounded-xl shadow-sm p-6">
164
+ <h2 class="text-xl md:text-2xl font-bold text-gray-900 mb-4">Sauvegardes locales</h2>
165
+ <div id="backupList" class="space-y-3"></div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </main>
170
+
171
+ <script>
172
+ let tableauContent = '';
173
+ let dissertationContent = '';
174
+ let backups = loadBackups();
175
+
176
+ // Gestion du drag & drop
177
+ const dropZone = document.getElementById('dropZone');
178
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
179
+ dropZone.addEventListener(eventName, preventDefaults, false);
180
+ });
181
+
182
+ function preventDefaults(e) {
183
+ e.preventDefault();
184
+ e.stopPropagation();
185
+ }
186
+
187
+ ['dragenter', 'dragover'].forEach(eventName => {
188
+ dropZone.addEventListener(eventName, highlight, false);
189
+ });
190
+
191
+ ['dragleave', 'drop'].forEach(eventName => {
192
+ dropZone.addEventListener(eventName, unhighlight, false);
193
+ });
194
+
195
+ function highlight(e) {
196
+ dropZone.classList.add('drag-over');
197
+ }
198
+
199
+ function unhighlight(e) {
200
+ dropZone.classList.remove('drag-over');
201
+ }
202
+
203
+ dropZone.addEventListener('drop', handleDrop, false);
204
+
205
+ function handleDrop(e) {
206
+ const dt = e.dataTransfer;
207
+ const files = dt.files;
208
+
209
+ if (files && files[0]) {
210
+ handleFiles(files);
211
+ }
212
+ }
213
+
214
+ function handleFiles(files) {
215
+ const imageInput = document.getElementById('imageInput');
216
+ imageInput.files = files;
217
+ handleImageSelect();
218
+ }
219
+
220
+ function handleImageSelect() {
221
+ const input = document.getElementById('imageInput');
222
+ const preview = document.getElementById('preview');
223
+ const previewContainer = document.getElementById('imagePreview');
224
+ const fileName = document.getElementById('fileName');
225
+
226
+ if (input.files && input.files[0]) {
227
+ const reader = new FileReader();
228
+
229
+ reader.onload = function(e) {
230
+ preview.src = e.target.result;
231
+ previewContainer.classList.remove('hidden');
232
+ previewContainer.classList.add('fade-in');
233
+ fileName.textContent = input.files[0].name;
234
+ };
235
+
236
+ reader.readAsDataURL(input.files[0]);
237
+ }
238
+ }
239
+
240
+ function removeImage() {
241
+ const input = document.getElementById('imageInput');
242
+ const previewContainer = document.getElementById('imagePreview');
243
+ const fileName = document.getElementById('fileName');
244
+
245
+ input.value = '';
246
+ previewContainer.classList.add('hidden');
247
+ fileName.textContent = 'Cliquez ou glissez une image ici';
248
+ }
249
+
250
+ // Gestion des onglets
251
+ document.querySelectorAll('.tab-button').forEach(button => {
252
+ button.addEventListener('click', () => {
253
+ const tabName = button.id.replace('show', '').toLowerCase();
254
+ showTab(tabName);
255
+ });
256
+ });
257
+
258
+ function showTab(tabName) {
259
+ document.querySelectorAll('.tab-content').forEach(tab => tab.classList.add('hidden'));
260
+ document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
261
+
262
+ const targetTab = document.getElementById(`${tabName}Tab`);
263
+ const targetButton = document.getElementById(`show${tabName.charAt(0).toUpperCase() + tabName.slice(1)}`);
264
+
265
+ targetTab.classList.remove('hidden');
266
+ targetButton.classList.add('active');
267
+
268
+ if (tabName === 'tableau') {
269
+ renderTableauContent();
270
+ } else if (tabName === 'backups') {
271
+ updateBackupList();
272
+ }
273
+ }
274
+
275
+ // Gestion des sauvegardes
276
+ function loadBackups() {
277
+ try {
278
+ return JSON.parse(localStorage.getItem('mariamAIBackups')) || [];
279
+ } catch {
280
+ return [];
281
+ }
282
+ }
283
+
284
+ function saveBackup(type, content) {
285
+ const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
286
+ backups.unshift({ timestamp, type, content });
287
+ localStorage.setItem('mariamAIBackups', JSON.stringify(backups));
288
+ updateBackupList();
289
+ Swal.fire({
290
+ icon: 'success',
291
+ title: 'Sauvegarde réussie!',
292
+ toast: true,
293
+ position: 'top-end',
294
+ showConfirmButton: false,
295
+ timer: 3000
296
+ });
297
+ }
298
+
299
+ function updateBackupList() {
300
+ const backupList = document.getElementById('backupList');
301
+ backupList.innerHTML = backups.length ? '' : '<p class="text-gray-500 text-center py-4">Aucune sauvegarde disponible</p>';
302
+
303
+ backups.forEach((backup, index) => {
304
+ const div = document.createElement('div');
305
+ div.className = 'backup-item';
306
+ div.innerHTML = `
307
+ <div class="flex-1">
308
+ <h3 class="font-medium">${backup.type === 'dissertation' ? 'Dissertation' : 'Tableau'}</h3>
309
+ <p class="text-sm text-gray-500">${backup.timestamp}</p>
310
+ </div>
311
+ <div class="backup-actions">
312
+ <button data-index="${index}" class="view-backup px-3 py-1 rounded-lg bg-blue-500 text-white text-sm hover:bg-blue-600">
313
+ Voir
314
+ </button>
315
+ <button data-index="${index}" class="delete-backup px-3 py-1 rounded-lg bg-red-500 text-white text-sm hover:bg-red-600">
316
+ Supprimer
317
+ </button>
318
+ </div>
319
+ `;
320
+ backupList.appendChild(div);
321
+ });
322
+
323
+ document.querySelectorAll('.view-backup').forEach(btn =>
324
+ btn.addEventListener('click', viewBackup));
325
+ document.querySelectorAll('.delete-backup').forEach(btn =>
326
+ btn.addEventListener('click', deleteBackup));
327
+ }
328
+
329
+ function viewBackup(event) {
330
+ const backup = backups[event.target.dataset.index];
331
+ Swal.fire({
332
+ title: backup.type === 'dissertation' ? 'Dissertation sauvegardée' : 'Tableau sauvegardé',
333
+ html: marked.parse(backup.content),
334
+ width: '90%',
335
+ customClass: {
336
+ popup: 'swal2-popup-large',
337
+ htmlContainer: 'markdown-content px-4'
338
+ },
339
+ showCloseButton: true,
340
+ showConfirmButton: false
341
+ });
342
+ }
343
+
344
+ function deleteBackup(event) {
345
+ const index = event.target.dataset.index;
346
+ const backup = backups[index];
347
+
348
+ Swal.fire({
349
+ title: 'Confirmer la suppression',
350
+ text: `Voulez-vous vraiment supprimer cette sauvegarde du ${backup.timestamp} ?`,
351
+ icon: 'warning',
352
+ showCancelButton: true,
353
+ confirmButtonColor: '#EF4444',
354
+ cancelButtonColor: '#6B7280',
355
+ confirmButtonText: 'Supprimer',
356
+ cancelButtonText: 'Annuler'
357
+ }).then((result) => {
358
+ if (result.isConfirmed) {
359
+ backups.splice(index, 1);
360
+ localStorage.setItem('mariamAIBackups', JSON.stringify(backups));
361
+ updateBackupList();
362
+
363
+ Swal.fire({
364
+ title: 'Supprimé!',
365
+ text: 'La sauvegarde a été supprimée.',
366
+ icon: 'success',
367
+ toast: true,
368
+ position: 'top-end',
369
+ showConfirmButton: false,
370
+ timer: 3000
371
+ });
372
+ }
373
+ });
374
+ }
375
+
376
+ // Gestion des boutons de sauvegarde
377
+ document.getElementById('saveDissertation').addEventListener('click', () => {
378
+ if (!dissertationContent.trim()) {
379
+ Swal.fire({
380
+ icon: 'error',
381
+ title: 'Erreur',
382
+ text: 'Aucun contenu à sauvegarder'
383
+ });
384
+ return;
385
+ }
386
+ saveBackup('dissertation', dissertationContent);
387
+ });
388
+
389
+ document.getElementById('saveTableau').addEventListener('click', () => {
390
+ if (!tableauContent.trim()) {
391
+ Swal.fire({
392
+ icon: 'error',
393
+ title: 'Erreur',
394
+ text: 'Aucun contenu à sauvegarder'
395
+ });
396
+ return;
397
+ }
398
+ saveBackup('tableau', tableauContent);
399
+ });
400
+
401
+ // Gestion du formulaire d'upload
402
+ document.getElementById('uploadForm').addEventListener('submit', async (e) => {
403
+ e.preventDefault();
404
+
405
+ const imageInput = document.getElementById('imageInput');
406
+ if (!imageInput.files || !imageInput.files[0]) {
407
+ Swal.fire({
408
+ icon: 'error',
409
+ title: 'Aucune image sélectionnée',
410
+ text: 'Veuillez sélectionner une image à analyser.'
411
+ });
412
+ return;
413
+ }
414
+
415
+ const loading = document.getElementById('loading');
416
+ const results = document.getElementById('results');
417
+ const dissertationResult = document.getElementById('dissertationResult');
418
+
419
+ const formData = new FormData();
420
+ formData.append('image', imageInput.files[0]);
421
+
422
+ loading.classList.remove('hidden');
423
+ results.classList.add('hidden');
424
+
425
+ try {
426
+ const response = await fetch('/analyze', {
427
+ method: 'POST',
428
+ body: formData
429
+ });
430
+
431
+ if (!response.ok) {
432
+ throw new Error(`Erreur serveur: ${response.status}`);
433
+ }
434
+
435
+ const data = await response.json();
436
+
437
+ dissertationContent = data.dissertation;
438
+ tableauContent = data.tableau;
439
+
440
+ // Configuration de marked pour le rendu Markdown
441
+ marked.setOptions({
442
+ breaks: true,
443
+ gfm: true,
444
+ tables: true,
445
+ smartLists: true,
446
+ smartypants: true
447
+ });
448
+
449
+ dissertationResult.innerHTML = marked.parse(data.dissertation);
450
+
451
+ results.classList.remove('hidden');
452
+ results.classList.add('fade-in');
453
+
454
+ // Scroll vers les résultats
455
+ results.scrollIntoView({ behavior: 'smooth', block: 'start' });
456
+
457
+ } catch (error) {
458
+ console.error("Erreur lors de l'analyse:", error);
459
+ Swal.fire({
460
+ icon: 'error',
461
+ title: 'Erreur',
462
+ text: "Une erreur est survenue lors de l'analyse de l'image. Veuillez réessayer."
463
+ });
464
+ } finally {
465
+ loading.classList.add('hidden');
466
+ }
467
+ });
468
+
469
+ function renderTableauContent() {
470
+ const tableauResult = document.getElementById('tableauResult');
471
+ if (tableauContent) {
472
+ tableauResult.innerHTML = marked.parse(tableauContent);
473
+ }
474
+ }
475
+
476
+ // Initialisation
477
+ document.getElementById('imageInput').addEventListener('change', handleImageSelect);
478
+ updateBackupList();
479
+ </script>
480
+ </body>
481
+ </html>