Docfile commited on
Commit
42a564e
·
verified ·
1 Parent(s): a6bd134

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +459 -0
templates/index.html ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <!-- Tailwind CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
9
+ <!-- Marked -->
10
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
+ <!-- SweetAlert2 -->
12
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
13
+ <!-- Moment.js -->
14
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/moment.min.js"></script>
15
+ <style>
16
+ /* Loader et animations */
17
+ .loader {
18
+ width: 48px;
19
+ height: 48px;
20
+ border: 5px solid #FFF;
21
+ border-bottom-color: #6366F1;
22
+ border-radius: 50%;
23
+ animation: rotation 1s linear infinite;
24
+ }
25
+ @keyframes rotation {
26
+ from { transform: rotate(0deg); }
27
+ to { transform: rotate(360deg); }
28
+ }
29
+ .fade-in {
30
+ animation: fadeIn 0.5s ease-in;
31
+ }
32
+ @keyframes fadeIn {
33
+ from { opacity: 0; }
34
+ to { opacity: 1; }
35
+ }
36
+
37
+ /* Zone de drop d'image */
38
+ .upload-zone {
39
+ border: 2px dashed #6366F1;
40
+ transition: background-color 0.3s ease, border-color 0.3s ease;
41
+ }
42
+ .upload-zone:hover {
43
+ border-color: #4F46E5;
44
+ background-color: #EEF2FF;
45
+ }
46
+
47
+ /* Contenu Markdown */
48
+ .markdown-content {
49
+ overflow-x: auto;
50
+ padding-bottom: 1rem;
51
+ }
52
+ .markdown-content table {
53
+ border-collapse: collapse;
54
+ width: 100%;
55
+ margin: 1rem 0;
56
+ }
57
+ .markdown-content th,
58
+ .markdown-content td {
59
+ border: 1px solid #e5e7eb;
60
+ padding: 0.75rem;
61
+ text-align: left;
62
+ white-space: nowrap;
63
+ min-width: 150px;
64
+ }
65
+ .markdown-content th {
66
+ background-color: #f9fafb;
67
+ position: sticky;
68
+ top: 0;
69
+ z-index: 10;
70
+ }
71
+ /* Pour conserver les espaces et retours à la ligne dans le Markdown */
72
+ .markdown-content p,
73
+ .markdown-content pre,
74
+ .markdown-content code {
75
+ white-space: pre-wrap;
76
+ word-wrap: break-word;
77
+ }
78
+
79
+ /* Scroll horizontal */
80
+ .scroll-indicator {
81
+ position: absolute;
82
+ bottom: 20px;
83
+ right: 20px;
84
+ background: rgba(99, 102, 241, 0.9);
85
+ color: white;
86
+ padding: 8px 12px;
87
+ border-radius: 4px;
88
+ font-size: 0.875rem;
89
+ opacity: 0;
90
+ transition: opacity 0.3s ease;
91
+ pointer-events: none;
92
+ }
93
+ .scroll-indicator.visible { opacity: 1; }
94
+
95
+ /* Prévisualisation de l'image */
96
+ .image-preview-container {
97
+ position: relative;
98
+ max-width: 100%;
99
+ margin: 1rem auto;
100
+ border-radius: 0.5rem;
101
+ overflow: hidden;
102
+ box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
103
+ }
104
+ .image-preview-container img {
105
+ display: block;
106
+ width: 100%;
107
+ height: auto;
108
+ max-height: 400px;
109
+ object-fit: contain;
110
+ }
111
+ .remove-image {
112
+ position: absolute;
113
+ top: 0.5rem;
114
+ right: 0.5rem;
115
+ background-color: rgba(255,255,255,0.9);
116
+ border-radius: 50%;
117
+ padding: 0.5rem;
118
+ cursor: pointer;
119
+ transition: background-color 0.2s ease;
120
+ }
121
+ .remove-image:hover {
122
+ background-color: #EF4444;
123
+ color: white;
124
+ }
125
+
126
+ /* Boutons d'onglets */
127
+ .tab-buttons button {
128
+ margin: 0 0.5rem;
129
+ padding: 0.5rem 1rem;
130
+ border: 1px solid #6366F1;
131
+ background-color: #EEF2FF;
132
+ color: #6366F1;
133
+ border-radius: 0.25rem;
134
+ transition: background-color 0.3s ease;
135
+ }
136
+ .tab-buttons button.active {
137
+ background-color: #6366F1;
138
+ color: white;
139
+ }
140
+
141
+ /* Liste des sauvegardes */
142
+ #backupList li {
143
+ margin-bottom: 0.5rem;
144
+ padding: 0.75rem;
145
+ border: 1px solid #e5e7eb;
146
+ border-radius: 0.25rem;
147
+ background-color: #fff;
148
+ cursor: pointer;
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: center;
152
+ }
153
+ #backupList li:hover { background-color: #f9fafb; }
154
+ #backupList .backup-date { font-size: 0.8rem; color: #9ca3af; }
155
+ #backupList button {
156
+ padding: 0.25rem 0.5rem;
157
+ border-radius: 0.25rem;
158
+ }
159
+
160
+ /* Ajustement SweetAlert2 */
161
+ .swal2-popup {
162
+ width: 90% !important;
163
+ max-width: 1200px !important;
164
+ }
165
+ .swal2-html-container {
166
+ max-height: 80vh !important;
167
+ overflow-y: auto !important;
168
+ }
169
+ </style>
170
+ </head>
171
+ <body class="bg-gray-50">
172
+ <div class="min-h-screen flex flex-col">
173
+ <!-- Header -->
174
+ <header class="bg-white shadow">
175
+ <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
176
+ <h1 class="text-3xl font-bold text-indigo-600">Mariam AI</h1>
177
+ <p class="mt-1 text-sm text-gray-500">Assistant pour commentaire composé.</p>
178
+ </div>
179
+ </header>
180
+
181
+ <!-- Contenu principal -->
182
+ <main class="flex-grow max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
183
+ <!-- Formulaire d'upload -->
184
+ <div class="bg-white rounded-lg shadow p-6 mb-8">
185
+ <form id="uploadForm" class="space-y-6">
186
+ <div class="upload-zone rounded-lg p-8 text-center cursor-pointer">
187
+ <input type="file" id="imageInput" accept="image/*" required class="hidden" onchange="handleImageSelect()">
188
+ <label for="imageInput" class="block">
189
+ <div class="space-y-2">
190
+ <svg class="mx-auto h-12 w-12 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
191
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
192
+ 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" />
193
+ </svg>
194
+ <div id="fileName" class="text-sm text-gray-500">Cliquez ou glissez une image ici</div>
195
+ </div>
196
+ </label>
197
+ </div>
198
+
199
+ <!-- Prévisualisation de l'image -->
200
+ <div id="imagePreview" class="hidden image-preview-container">
201
+ <img id="preview" src="#" alt="Prévisualisation">
202
+ <button type="button" class="remove-image" onclick="removeImage()">
203
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
204
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
205
+ d="M6 18L18 6M6 6l12 12" />
206
+ </svg>
207
+ </button>
208
+ </div>
209
+
210
+ <div class="flex justify-center">
211
+ <button type="submit"
212
+ class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors">
213
+ Analyser l'image
214
+ </button>
215
+ </div>
216
+ </form>
217
+ </div>
218
+
219
+ <!-- Loader -->
220
+ <div id="loading" class="hidden">
221
+ <div class="flex flex-col items-center justify-center space-y-4">
222
+ <span class="loader"></span>
223
+ <p class="text-gray-500">Analyse en cours, veuillez patienter...</p>
224
+ </div>
225
+ </div>
226
+
227
+ <!-- Résultats -->
228
+ <div id="results" class="space-y-8 hidden">
229
+ <div class="tab-buttons flex flex-wrap justify-center mb-4">
230
+ <button id="showDissertation" class="active">Dissertation</button>
231
+ <button id="showTableau">Tableau d'analyse</button>
232
+ <button id="showBackups">Sauvegardes</button>
233
+ </div>
234
+
235
+ <!-- Onglet Dissertation -->
236
+ <div id="dissertationTab" class="tab-content active bg-white rounded-lg shadow p-6">
237
+ <h2 class="text-2xl font-bold text-gray-900 mb-4">Dissertation</h2>
238
+ <div id="dissertationResult" class="prose max-w-none markdown-content"></div>
239
+ <button id="saveDissertation" class="mt-4 px-6 py-3 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors">
240
+ Sauvegarder la dissertation
241
+ </button>
242
+ </div>
243
+
244
+ <!-- Onglet Tableau d'analyse -->
245
+ <div id="tableauTab" class="tab-content bg-white rounded-lg shadow p-6 hidden">
246
+ <h2 class="text-2xl font-bold text-gray-900 mb-4">Tableau d'analyse</h2>
247
+ <div id="tableauResult" class="markdown-content"></div>
248
+ <button id="saveTableau" class="mt-4 px-6 py-3 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors">
249
+ Sauvegarder le tableau
250
+ </button>
251
+ </div>
252
+
253
+ <!-- Onglet Sauvegardes -->
254
+ <div id="backupsTab" class="tab-content bg-white rounded-lg shadow p-6 hidden">
255
+ <h2 class="text-2xl font-bold text-gray-900 mb-4">Sauvegardes locales</h2>
256
+ <ul id="backupList" class="space-y-2"></ul>
257
+ </div>
258
+ </div>
259
+ </main>
260
+ </div>
261
+
262
+ <!-- JavaScript -->
263
+ <script>
264
+ let tableauContent = '';
265
+ let dissertationContent = '';
266
+ let backups = loadBackups();
267
+
268
+ function handleImageSelect() {
269
+ const input = document.getElementById('imageInput');
270
+ const preview = document.getElementById('preview');
271
+ const previewContainer = document.getElementById('imagePreview');
272
+ const fileName = document.getElementById('fileName');
273
+
274
+ if (input.files && input.files[0]) {
275
+ const reader = new FileReader();
276
+ reader.onload = e => {
277
+ preview.src = e.target.result;
278
+ previewContainer.classList.remove('hidden');
279
+ previewContainer.classList.add('fade-in');
280
+ };
281
+ reader.readAsDataURL(input.files[0]);
282
+ fileName.textContent = input.files[0].name;
283
+ } else {
284
+ removeImage();
285
+ }
286
+ }
287
+
288
+ function removeImage() {
289
+ const input = document.getElementById('imageInput');
290
+ const preview = document.getElementById('preview');
291
+ const previewContainer = document.getElementById('imagePreview');
292
+ const fileName = document.getElementById('fileName');
293
+
294
+ input.value = '';
295
+ preview.src = '#';
296
+ previewContainer.classList.add('hidden');
297
+ fileName.textContent = 'Cliquez ou glissez une image ici';
298
+ }
299
+
300
+ function showScrollIndicator(container) {
301
+ if (container.scrollWidth > container.clientWidth) {
302
+ const indicator = document.createElement('div');
303
+ indicator.className = 'scroll-indicator visible';
304
+ indicator.textContent = '← Faites défiler →';
305
+ container.parentElement.appendChild(indicator);
306
+
307
+ setTimeout(() => indicator.classList.remove('visible'), 3000);
308
+ container.addEventListener('scroll', () => {
309
+ clearTimeout(container.scrollTimeout);
310
+ indicator.classList.toggle('visible', container.scrollLeft > 0);
311
+ container.scrollTimeout = setTimeout(() => indicator.classList.remove('visible'), 1000);
312
+ });
313
+ }
314
+ }
315
+
316
+ // Gestion des onglets
317
+ document.getElementById('showTableau').addEventListener('click', () => {
318
+ showTab('tableau');
319
+ renderTableauContent();
320
+ });
321
+ document.getElementById('showDissertation').addEventListener('click', () => showTab('dissertation'));
322
+ document.getElementById('showBackups').addEventListener('click', () => {
323
+ showTab('backups');
324
+ updateBackupList();
325
+ });
326
+ function showTab(tabName) {
327
+ document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
328
+ document.querySelectorAll('.tab-buttons button').forEach(btn => btn.classList.remove('active'));
329
+ document.getElementById(`${tabName}Tab`).classList.add('active');
330
+ document.getElementById(`show${tabName.charAt(0).toUpperCase() + tabName.slice(1)}`).classList.add('active');
331
+ }
332
+
333
+ function loadBackups() {
334
+ try {
335
+ const stored = localStorage.getItem('mariamAIBackups');
336
+ return stored ? JSON.parse(stored) : [];
337
+ } catch (error) {
338
+ console.error("Erreur lors du chargement des sauvegardes :", error);
339
+ return [];
340
+ }
341
+ }
342
+
343
+ function saveBackup(type, content) {
344
+ const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
345
+ backups.push({ timestamp, type, content });
346
+ localStorage.setItem('mariamAIBackups', JSON.stringify(backups));
347
+ updateBackupList();
348
+ Swal.fire({ icon: 'success', title: 'Sauvegarde réussie!' });
349
+ }
350
+
351
+ function updateBackupList() {
352
+ const backupList = document.getElementById('backupList');
353
+ backupList.innerHTML = '';
354
+ backups.forEach((backup, index) => {
355
+ const li = document.createElement('li');
356
+ li.innerHTML = `
357
+ <span>${backup.type === 'dissertation' ? 'Dissertation' : 'Tableau'} - ${backup.timestamp}</span>
358
+ <div>
359
+ <button data-index="${index}" class="view-backup bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded text-xs">Voir</button>
360
+ <button data-index="${index}" class="delete-backup bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded text-xs">Supprimer</button>
361
+ </div>
362
+ `;
363
+ backupList.appendChild(li);
364
+ li.querySelector('.view-backup').addEventListener('click', viewBackup);
365
+ li.querySelector('.delete-backup').addEventListener('click', deleteBackup);
366
+ });
367
+ }
368
+
369
+ function viewBackup(e) {
370
+ const index = e.target.dataset.index;
371
+ const backup = backups[index];
372
+ Swal.fire({
373
+ title: backup.type === 'dissertation' ? 'Dissertation sauvegardée' : 'Tableau sauvegardé',
374
+ html: marked.parse(backup.content),
375
+ width: '90%',
376
+ customClass: { htmlContainer: 'markdown-content' },
377
+ didRender: () => {
378
+ const container = document.querySelector('.markdown-content');
379
+ showScrollIndicator(container);
380
+ }
381
+ });
382
+ }
383
+
384
+ function deleteBackup(e) {
385
+ const index = e.target.dataset.index;
386
+ Swal.fire({
387
+ title: 'Êtes-vous sûr ?',
388
+ text: "Vous ne pourrez pas revenir en arrière !",
389
+ icon: 'warning',
390
+ showCancelButton: true,
391
+ confirmButtonColor: '#3085d6',
392
+ cancelButtonColor: '#d33',
393
+ confirmButtonText: 'Oui, supprimer!'
394
+ }).then((result) => {
395
+ if (result.isConfirmed) {
396
+ backups.splice(index, 1);
397
+ localStorage.setItem('mariamAIBackups', JSON.stringify(backups));
398
+ updateBackupList();
399
+ Swal.fire('Supprimé!', 'Votre fichier a été supprimé.', 'success');
400
+ }
401
+ });
402
+ }
403
+
404
+ document.getElementById('saveDissertation').addEventListener('click', () => saveBackup('dissertation', dissertationContent));
405
+ document.getElementById('saveTableau').addEventListener('click', () => saveBackup('tableau', tableauContent));
406
+
407
+ document.getElementById('uploadForm').addEventListener('submit', async (e) => {
408
+ e.preventDefault();
409
+ const imageInput = document.getElementById('imageInput');
410
+ if (!imageInput.files || !imageInput.files[0]) {
411
+ return Swal.fire({
412
+ icon: 'error',
413
+ title: 'Aucune image sélectionnée',
414
+ text: 'Veuillez sélectionner une image à analyser.'
415
+ });
416
+ }
417
+ const loading = document.getElementById('loading');
418
+ const results = document.getElementById('results');
419
+ const dissertationResult = document.getElementById('dissertationResult');
420
+ const formData = new FormData();
421
+ formData.append('image', imageInput.files[0]);
422
+
423
+ loading.classList.remove('hidden');
424
+ results.classList.add('hidden');
425
+ try {
426
+ const response = await fetch('/analyze', { method: 'POST', body: formData });
427
+ if (!response.ok) {
428
+ const errorData = await response.json();
429
+ throw new Error(errorData.error || `Erreur serveur ${response.status}`);
430
+ }
431
+ const data = await response.json();
432
+ dissertationContent = data.dissertation;
433
+ tableauContent = data.tableau;
434
+ dissertationResult.innerHTML = marked.parse(data.dissertation);
435
+ results.classList.remove('hidden');
436
+ results.classList.add('fade-in');
437
+ showTab('dissertation');
438
+ } catch (error) {
439
+ console.error("Erreur lors de l'analyse :", error);
440
+ Swal.fire({
441
+ icon: 'error',
442
+ title: 'Erreur',
443
+ text: error.message || 'Une erreur est survenue lors de la communication avec le serveur.'
444
+ });
445
+ } finally {
446
+ loading.classList.add('hidden');
447
+ }
448
+ });
449
+
450
+ function renderTableauContent() {
451
+ const tableauResult = document.getElementById('tableauResult');
452
+ if (tableauContent) {
453
+ tableauResult.innerHTML = marked.parse(tableauContent);
454
+ showScrollIndicator(tableauResult);
455
+ }
456
+ }
457
+ </script>
458
+ </body>
459
+ </html>