Docfile commited on
Commit
78d4ff0
·
verified ·
1 Parent(s): b550f54

Create sv.html

Browse files
Files changed (1) hide show
  1. templates/sv.html +816 -0
templates/sv.html ADDED
@@ -0,0 +1,816 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 - SVT</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
10
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
13
+
14
+ :root {
15
+ --color-primary: #2563eb;
16
+ --color-primary-dark: #1d4ed8;
17
+ --color-secondary: #4b5563;
18
+ --color-accent: #8b5cf6;
19
+ --color-background: #f6f8fc;
20
+ --color-surface: #ffffff;
21
+ --color-error: #ef4444;
22
+ --color-success: #10b981;
23
+ }
24
+
25
+ body {
26
+ font-family: 'Poppins', sans-serif;
27
+ background: linear-gradient(135deg, var(--color-background) 0%, #e9f0f7 100%);
28
+ }
29
+
30
+ .animate-fade-in {
31
+ animation: fadeIn 0.5s ease-in;
32
+ }
33
+
34
+ .animate-slide-up {
35
+ animation: slideUp 0.5s ease-out;
36
+ }
37
+
38
+ .animate-slide-in {
39
+ animation: slideIn 0.5s ease-out;
40
+ }
41
+
42
+ @keyframes fadeIn {
43
+ from { opacity: 0; }
44
+ to { opacity: 1; }
45
+ }
46
+
47
+ @keyframes slideUp {
48
+ from { transform: translateY(20px); opacity: 0; }
49
+ to { transform: translateY(0); opacity: 1; }
50
+ }
51
+
52
+ @keyframes slideIn {
53
+ from { transform: translateX(20px); opacity: 0; }
54
+ to { transform: translateX(0); opacity: 1; }
55
+ }
56
+
57
+ .glass-effect {
58
+ background: rgba(255, 255, 255, 0.95);
59
+ backdrop-filter: blur(10px);
60
+ border: 1px solid rgba(255, 255, 255, 0.2);
61
+ }
62
+
63
+ .hover-scale {
64
+ transition: transform 0.2s;
65
+ }
66
+
67
+ .hover-scale:hover {
68
+ transform: scale(1.02);
69
+ }
70
+
71
+ .image-preview {
72
+ transition: all 0.3s ease;
73
+ }
74
+
75
+ .image-preview:hover .image-overlay {
76
+ opacity: 1;
77
+ }
78
+
79
+ .image-overlay {
80
+ opacity: 0;
81
+ transition: opacity 0.3s ease;
82
+ background: rgba(0, 0, 0, 0.5);
83
+ }
84
+
85
+ .preview-container {
86
+ display: grid;
87
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
88
+ gap: 1rem;
89
+ }
90
+
91
+ /* Tabs */
92
+ .tab-active {
93
+ border-bottom: 3px solid var(--color-primary);
94
+ color: var(--color-primary);
95
+ }
96
+
97
+ .tab-content {
98
+ display: none;
99
+ }
100
+
101
+ .tab-content.active {
102
+ display: block;
103
+ }
104
+
105
+ #response {
106
+ white-space: pre-wrap;
107
+ word-wrap: break-word;
108
+ overflow-wrap: break-word;
109
+ }
110
+
111
+ /* Styles pour le contenu markdown */
112
+ #response p {
113
+ margin-bottom: 1em;
114
+ line-height: 1.6;
115
+ }
116
+
117
+ #response ul, #response ol {
118
+ padding-left: 1.5em;
119
+ margin-bottom: 1em;
120
+ }
121
+
122
+ #response li {
123
+ margin-bottom: 0.5em;
124
+ }
125
+
126
+ /* Notification */
127
+ .notification {
128
+ position: fixed;
129
+ top: 20px;
130
+ right: 20px;
131
+ padding: 15px 20px;
132
+ border-radius: 8px;
133
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
134
+ z-index: 1000;
135
+ transform: translateX(120%);
136
+ transition: transform 0.3s ease-out;
137
+ }
138
+
139
+ .notification.show {
140
+ transform: translateX(0);
141
+ }
142
+
143
+ /* Ajustements responsives pour mobile */
144
+ @media (max-width: 640px) {
145
+ .preview-container {
146
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
147
+ }
148
+ .image-preview img {
149
+ height: 32px;
150
+ object-fit: cover;
151
+ }
152
+ #historyContainer .grid {
153
+ grid-template-columns: repeat(1, minmax(0, 1fr));
154
+ }
155
+ #response {
156
+ font-size: 0.95rem;
157
+ padding: 1rem;
158
+ }
159
+
160
+ #response p, #response li {
161
+ line-height: 1.7;
162
+ }
163
+
164
+ #response ul, #response ol {
165
+ padding-left: 1.2em;
166
+ }
167
+ }
168
+ </style>
169
+ </head>
170
+ <body class="min-h-screen flex items-center justify-center p-4 md:p-8">
171
+ <div id="notificationContainer"></div>
172
+
173
+ <div class="container mx-auto glass-effect rounded-2xl shadow-xl max-w-4xl animate-fade-in p-6 md:p-8">
174
+ <header class="flex flex-col md:flex-row justify-between items-center mb-8">
175
+ <div class="text-center md:text-left mb-4 md:mb-0">
176
+ <h1 class="text-4xl font-bold text-blue-900">Mariam AI</h1>
177
+ <p class="text-gray-600 mt-2">Assistant SVT Intelligent</p>
178
+ </div>
179
+ <div class="flex space-x-4">
180
+ <button onclick="showInfo()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl hover-scale transition-all duration-300 flex items-center gap-2" aria-label="Guide d'utilisation">
181
+ <i class="fas fa-info-circle"></i>
182
+ <span>Guide d'utilisation</span>
183
+ </button>
184
+ </div>
185
+ </header>
186
+
187
+ <!-- Tabs navigation -->
188
+ <div class="flex border-b border-gray-200 mb-6">
189
+ <button id="tabMain" class="tab-active px-4 py-2 font-medium text-lg focus:outline-none" onclick="switchTab('main')" aria-label="Onglet principal">
190
+ Analyse
191
+ </button>
192
+ <button id="tabHistory" class="px-4 py-2 font-medium text-lg text-gray-600 focus:outline-none" onclick="switchTab('history')" aria-label="Onglet historique">
193
+ Historique
194
+ </button>
195
+ </div>
196
+
197
+ <!-- Main tab content -->
198
+ <div id="mainContent" class="tab-content active animate-slide-up">
199
+ <div class="space-y-8">
200
+ <div class="relative">
201
+ <label for="svtOption" class="block mb-3 text-lg font-medium text-gray-700">Type d'exercice :</label>
202
+ <select id="svtOption" class="w-full p-4 border border-gray-200 rounded-xl bg-white shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-300" aria-label="Sélectionner le type d'exercice">
203
+ <option value="Restitution organisée des connaissances">Restitution Organisée des Connaissances</option>
204
+ <option value="Exploitation du document">Exploitation du Document</option>
205
+ <option value="Synthèse">Synthèse</option>
206
+ </select>
207
+ </div>
208
+
209
+ <div class="relative">
210
+ <label class="block mb-3 text-lg font-medium text-gray-700">Images du sujet :</label>
211
+ <div class="border-2 border-dashed border-gray-300 rounded-xl p-8 text-center hover:border-blue-500 transition-all duration-300"
212
+ id="dropZone"
213
+ aria-label="Zone de dépôt d'images">
214
+ <input type="file" id="imageUpload" class="hidden" multiple accept="image/*" onchange="handleImageUpload(event)">
215
+ <label for="imageUpload" class="cursor-pointer">
216
+ <i class="fas fa-cloud-upload-alt text-4xl text-blue-500 mb-4"></i>
217
+ <p class="text-gray-600">Glissez vos images ici ou cliquez pour sélectionner</p>
218
+ <p class="text-sm text-gray-500 mt-2">Format acceptés : JPG, PNG, GIF</p>
219
+ </label>
220
+ </div>
221
+
222
+ <!-- Conteneur des prévisualisations -->
223
+ <div id="previewContainer" class="preview-container mt-4" aria-live="polite">
224
+ <!-- Les prévisualisations seront ajoutées ici -->
225
+ </div>
226
+ </div>
227
+
228
+ <button onclick="submitQuestion()" class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white py-4 rounded-xl hover-scale transition-all duration-300 font-semibold text-lg flex items-center justify-center gap-2" aria-label="Analyser les images">
229
+ <i class="fas fa-paper-plane"></i>
230
+ Analyser
231
+ </button>
232
+
233
+ <div id="loader" class="hidden" aria-hidden="true">
234
+ <div class="flex flex-col items-center space-y-4 p-8">
235
+ <div class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
236
+ <p class="text-gray-600 text-lg">Analyse en cours...</p>
237
+ </div>
238
+ </div>
239
+
240
+ <div id="response" class="mt-6 p-6 bg-white rounded-xl shadow-sm prose max-w-none" aria-live="polite"></div>
241
+
242
+ <div id="copyResponseContainer" class="hidden mb-8">
243
+ <button onclick="copyResponse()" class="w-full bg-gray-800 hover:bg-gray-900 text-white px-6 py-3 rounded-xl hover-scale transition-all duration-300 flex items-center justify-center gap-2" aria-label="Copier la réponse">
244
+ <i class="fas fa-copy"></i>
245
+ Copier la réponse
246
+ </button>
247
+ </div>
248
+ </div>
249
+ </div>
250
+
251
+ <!-- History tab content -->
252
+ <div id="historyContent" class="tab-content animate-slide-in">
253
+ <div class="space-y-8">
254
+ <div class="flex justify-between items-center">
255
+ <h2 class="text-2xl font-bold text-blue-900">Historique des Réponses</h2>
256
+ <button onclick="clearLocalStorage()" class="bg-red-500 hover:bg-red-700 text-white px-4 py-2 rounded-lg hover-scale transition-all duration-300" aria-label="Effacer l'historique">
257
+ <i class="fas fa-trash-alt mr-2"></i>
258
+ Effacer l'historique
259
+ </button>
260
+ </div>
261
+ <div id="historyContainer" aria-live="polite">
262
+ <!-- L'historique des réponses sera affiché ici -->
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </div>
267
+
268
+ <script>
269
+ let uploadedFiles = [];
270
+
271
+ // Gestion des onglets
272
+ function switchTab(tabName) {
273
+ // Désactiver tous les onglets
274
+ document.querySelectorAll('.tab-content').forEach(tab => {
275
+ tab.classList.remove('active');
276
+ });
277
+
278
+ // Réinitialiser tous les boutons d'onglet
279
+ document.getElementById('tabMain').classList.remove('tab-active');
280
+ document.getElementById('tabHistory').classList.remove('tab-active');
281
+
282
+ // Activer l'onglet demandé
283
+ if (tabName === 'main') {
284
+ document.getElementById('mainContent').classList.add('active');
285
+ document.getElementById('tabMain').classList.add('tab-active');
286
+ } else if (tabName === 'history') {
287
+ document.getElementById('historyContent').classList.add('active');
288
+ document.getElementById('tabHistory').classList.add('tab-active');
289
+ // Rafraîchir l'historique à chaque fois qu'on ouvre l'onglet
290
+ displayHistory();
291
+ }
292
+ }
293
+
294
+ // Système de notification
295
+ function showNotification(message, type = 'info') {
296
+ const container = document.getElementById('notificationContainer');
297
+ const notification = document.createElement('div');
298
+
299
+ let bgColor, icon;
300
+ switch(type) {
301
+ case 'success':
302
+ bgColor = 'bg-green-500';
303
+ icon = 'fa-check-circle';
304
+ break;
305
+ case 'error':
306
+ bgColor = 'bg-red-500';
307
+ icon = 'fa-exclamation-circle';
308
+ break;
309
+ case 'warning':
310
+ bgColor = 'bg-yellow-500';
311
+ icon = 'fa-exclamation-triangle';
312
+ break;
313
+ default:
314
+ bgColor = 'bg-blue-500';
315
+ icon = 'fa-info-circle';
316
+ }
317
+
318
+ notification.className = `notification ${bgColor} text-white flex items-center gap-2`;
319
+ notification.innerHTML = `
320
+ <i class="fas ${icon}"></i>
321
+ <span>${message}</span>
322
+ `;
323
+
324
+ container.appendChild(notification);
325
+
326
+ // Animer l'apparition
327
+ setTimeout(() => {
328
+ notification.classList.add('show');
329
+ }, 10);
330
+
331
+ // Supprimer après 3 secondes
332
+ setTimeout(() => {
333
+ notification.classList.remove('show');
334
+ setTimeout(() => {
335
+ container.removeChild(notification);
336
+ }, 300);
337
+ }, 3000);
338
+ }
339
+
340
+ // Gestion du drag and drop
341
+ const dropZone = document.getElementById('dropZone');
342
+
343
+ dropZone.addEventListener('dragover', (e) => {
344
+ e.preventDefault();
345
+ dropZone.classList.add('border-blue-500');
346
+ });
347
+
348
+ dropZone.addEventListener('dragleave', () => {
349
+ dropZone.classList.remove('border-blue-500');
350
+ });
351
+
352
+ dropZone.addEventListener('drop', (e) => {
353
+ e.preventDefault();
354
+ dropZone.classList.remove('border-blue-500');
355
+
356
+ if (e.dataTransfer.files.length > 0) {
357
+ handleImageUpload({ target: { files: e.dataTransfer.files } });
358
+ }
359
+ }
360
+ });
361
+
362
+ function handleImageUpload(event) {
363
+ const files = event.target.files;
364
+ const previewContainer = document.getElementById('previewContainer');
365
+
366
+ for (let i = 0; i < files.length; i++) {
367
+ const file = files[i];
368
+ if (!file.type.startsWith('image/')) {
369
+ showNotification('Seuls les fichiers image sont acceptés', 'error');
370
+ continue;
371
+ }
372
+
373
+ uploadedFiles.push(file);
374
+
375
+ const reader = new FileReader();
376
+ reader.onload = function(e) {
377
+ const imageId = `img-${Date.now()}-${i}`;
378
+ const previewDiv = document.createElement('div');
379
+ previewDiv.className = 'image-preview relative rounded-lg overflow-hidden animate-fade-in';
380
+ previewDiv.id = imageId;
381
+ previewDiv.innerHTML = `
382
+ <img src="${e.target.result}" alt="${file.name}" class="w-full h-40 object-cover">
383
+ <div class="image-overlay absolute inset-0 flex items-center justify-center">
384
+ <button onclick="removeImage('${imageId}')" class="bg-red-500 hover:bg-red-600 text-white p-2 rounded-full transition-all duration-300" aria-label="Supprimer l'image">
385
+ <i class="fas fa-trash"></i>
386
+ </button>
387
+ <button onclick="previewImage('${e.target.result}')" class="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded-full ml-2 transition-all duration-300" aria-label="Prévisualiser l'image">
388
+ <i class="fas fa-eye"></i>
389
+ </button>
390
+ <button onclick="editImage('${imageId}', '${e.target.result}')" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded-full ml-2 transition-all duration-300" aria-label="Modifier l'image">
391
+ <i class="fas fa-crop-alt"></i>
392
+ </button>
393
+ </div>
394
+ <div class="bg-black bg-opacity-50 text-white text-xs p-1 absolute bottom-0 left-0 right-0">
395
+ ${file.name.substring(0, 15)}${file.name.length > 15 ? '...' : ''}
396
+ </div>
397
+ `;
398
+ previewContainer.appendChild(previewDiv);
399
+ };
400
+ reader.readAsDataURL(file);
401
+ }
402
+
403
+ if (files.length > 0) {
404
+ showNotification(`${files.length} image(s) ajoutée(s)`, 'success');
405
+ }
406
+ }
407
+
408
+ function removeImage(imageId) {
409
+ const imageIndex = uploadedFiles.findIndex((file, index) => {
410
+ const element = document.getElementById(imageId);
411
+ return element && element.querySelector('img').src.includes(file.name);
412
+ });
413
+
414
+ if (imageIndex !== -1) {
415
+ uploadedFiles.splice(imageIndex, 1);
416
+ }
417
+
418
+ const element = document.getElementById(imageId);
419
+ if (element) {
420
+ element.classList.add('animate-fade-out');
421
+ setTimeout(() => {
422
+ element.remove();
423
+ showNotification('Image supprimée', 'info');
424
+ }, 300);
425
+ }
426
+ }
427
+
428
+ function previewImage(src) {
429
+ Swal.fire({
430
+ imageUrl: src,
431
+ imageAlt: 'Prévisualisation',
432
+ width: '80%',
433
+ showConfirmButton: false,
434
+ showCloseButton: true,
435
+ customClass: {
436
+ image: 'max-h-[80vh] object-contain'
437
+ }
438
+ });
439
+ }
440
+
441
+ // Fonction d'édition d'image (recadrage et rotation)
442
+ function editImage(imageId, src) {
443
+ Swal.fire({
444
+ title: 'Modification de l\'image',
445
+ html: `
446
+ <div class="image-editor">
447
+ <div class="flex justify-center mb-4">
448
+ <img id="editableImage" src="${src}" class="max-h-[50vh] object-contain" />
449
+ </div>
450
+ <div class="flex justify-center gap-4">
451
+ <button id="rotateLeft" class="bg-gray-200 hover:bg-gray-300 p-2 rounded-full">
452
+ <i class="fas fa-undo"></i>
453
+ </button>
454
+ <button id="rotateRight" class="bg-gray-200 hover:bg-gray-300 p-2 rounded-full">
455
+ <i class="fas fa-redo"></i>
456
+ </button>
457
+ <button id="cropMode" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg">
458
+ Mode recadrage
459
+ </button>
460
+ </div>
461
+ </div>
462
+ `,
463
+ showCancelButton: true,
464
+ showConfirmButton: true,
465
+ confirmButtonText: 'Appliquer',
466
+ cancelButtonText: 'Annuler',
467
+ confirmButtonColor: '#3085d6',
468
+ didOpen: () => {
469
+ // Ici vous pourriez initialiser une bibliothèque de recadrage comme Cropper.js
470
+ // Pour cette démo, nous ajoutons juste des gestionnaires d'événements simples
471
+ let rotation = 0;
472
+ const img = document.getElementById('editableImage');
473
+
474
+ document.getElementById('rotateLeft').addEventListener('click', () => {
475
+ rotation -= 90;
476
+ img.style.transform = `rotate(${rotation}deg)`;
477
+ });
478
+
479
+ document.getElementById('rotateRight').addEventListener('click', () => {
480
+ rotation += 90;
481
+ img.style.transform = `rotate(${rotation}deg)`;
482
+ });
483
+
484
+ document.getElementById('cropMode').addEventListener('click', () => {
485
+ // Pour une implémentation complète, vous intégreriez une bibliothèque de recadrage ici
486
+ Swal.fire({
487
+ icon: 'info',
488
+ text: 'Fonctionnalité de recadrage en développement',
489
+ toast: true,
490
+ position: 'top-end',
491
+ showConfirmButton: false,
492
+ timer: 2000
493
+ });
494
+ });
495
+ }
496
+ }).then((result) => {
497
+ if (result.isConfirmed) {
498
+ // Ici vous appliqueriez les modifications à l'image
499
+ // Pour cette démo, nous affichons juste une notification
500
+ showNotification('Modifications appliquées', 'success');
501
+
502
+ // En production, vous remplaceriez l'image originale par la version modifiée
503
+ // const element = document.getElementById(imageId);
504
+ // if (element) {
505
+ // element.querySelector('img').src = modifiedImageSrc;
506
+ // }
507
+ }
508
+ });
509
+ }
510
+
511
+ function showInfo() {
512
+ Swal.fire({
513
+ title: 'Guide d\'utilisation',
514
+ html: `
515
+ <div class="text-left space-y-4">
516
+ <div class="flex items-start gap-3">
517
+ <i class="fas fa-check-circle text-green-500 mt-1"></i>
518
+ <p>Sélectionnez le type d'exercice correspondant à votre sujet.</p>
519
+ </div>
520
+ <div class="flex items-start gap-3">
521
+ <i class="fas fa-image text-blue-500 mt-1"></i>
522
+ <p>Assurez-vous que vos images sont nettes et bien cadrées.</p>
523
+ </div>
524
+ <div class="flex items-start gap-3">
525
+ <i class="fas fa-crop-alt text-purple-500 mt-1"></i>
526
+ <p>Utilisez notre éditeur pour recadrer et faire pivoter vos images si nécessaire.</p>
527
+ </div>
528
+ <div class="flex items-start gap-3">
529
+ <i class="fas fa-history text-yellow-500 mt-1"></i>
530
+ <p>Consultez l'onglet "Historique" pour retrouver vos analyses précédentes.</p>
531
+ </div>
532
+ </div>
533
+ `,
534
+ icon: 'info',
535
+ confirmButtonText: 'Compris',
536
+ confirmButtonColor: '#2563eb',
537
+ customClass: {
538
+ container: 'font-sans'
539
+ }
540
+ });
541
+ }
542
+
543
+ function copyResponse() {
544
+ const responseDiv = document.getElementById('response');
545
+ const range = document.createRange();
546
+ range.selectNode(responseDiv);
547
+ window.getSelection().removeAllRanges();
548
+ window.getSelection().addRange(range);
549
+ document.execCommand('copy');
550
+ window.getSelection().removeAllRanges();
551
+
552
+ showNotification('Réponse copiée dans le presse-papiers', 'success');
553
+ }
554
+
555
+ async function submitQuestion() {
556
+ if (uploadedFiles.length === 0) {
557
+ Swal.fire({
558
+ icon: 'error',
559
+ title: 'Images manquantes',
560
+ text: 'Veuillez sélectionner au moins une image du sujet.',
561
+ confirmButtonColor: '#2563eb'
562
+ });
563
+ return;
564
+ }
565
+
566
+ const option = document.getElementById('svtOption').value;
567
+ const loader = document.getElementById('loader');
568
+ const responseDiv = document.getElementById('response');
569
+ const copyResponseContainer = document.getElementById('copyResponseContainer');
570
+
571
+ loader.classList.remove('hidden');
572
+ responseDiv.innerHTML = '';
573
+ copyResponseContainer.classList.add('hidden');
574
+
575
+ const formData = new FormData();
576
+ formData.append('option', option);
577
+
578
+ for (let i = 0; i < uploadedFiles.length; i++) {
579
+ formData.append('images', uploadedFiles[i]);
580
+ }
581
+
582
+ try {
583
+ const response = await fetch('/svt_submit', {
584
+ method: 'POST',
585
+ body: formData
586
+ });
587
+
588
+ const data = await response.json();
589
+ loader.classList.add('hidden');
590
+
591
+ if (data.error) {
592
+ responseDiv.innerHTML = `
593
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
594
+ <p class="text-red-700">Erreur : ${data.error}</p>
595
+ </div>
596
+ `;
597
+ showNotification('Une erreur est survenue', 'error');
598
+ } else {
599
+ const htmlContent = marked.parse(data.response);
600
+ responseDiv.innerHTML = htmlContent;
601
+ responseDiv.classList.add('animate-fade-in');
602
+ copyResponseContainer.classList.remove('hidden');
603
+ showNotification('Analyse complétée avec succès', 'success');
604
+
605
+ // Convertir les images en base64 avant de les sauvegarder
606
+ const imagesData = await Promise.all(uploadedFiles.map(file => {
607
+ return new Promise((resolve) => {
608
+ const reader = new FileReader();
609
+ reader.onload = (e) => resolve(e.target.result);
610
+ reader.readAsDataURL(file);
611
+ });
612
+ }));
613
+
614
+ saveResponseToLocalStorage(option, imagesData, data.response);
615
+ }
616
+ } catch (error) {
617
+ loader.classList.add('hidden');
618
+ responseDiv.innerHTML = `
619
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
620
+ <p class="text-red-700">Erreur de connexion au serveur. Veuillez réessayer plus tard.</p>
621
+ </div>
622
+ `;
623
+ showNotification('Erreur de connexion', 'error');
624
+ console.error('Error:', error);
625
+ }
626
+ }
627
+
628
+ // Fonctions pour la gestion du localStorage
629
+ function saveResponseToLocalStorage(option, images, response) {
630
+ const timestamp = new Date().toISOString();
631
+ const data = { option, images, response, timestamp };
632
+ localStorage.setItem('svt_response_' + timestamp, JSON.stringify(data));
633
+ }
634
+
635
+ function loadResponsesFromLocalStorage() {
636
+ const responses = [];
637
+ for (let i = 0; i < localStorage.length; i++) {
638
+ const key = localStorage.key(i);
639
+ if (key.startsWith('svt_response_')) {
640
+ try {
641
+ const data = JSON.parse(localStorage.getItem(key));
642
+ responses.push(data);
643
+ } catch (e) {
644
+ console.error('Error parsing stored response:', e);
645
+ }
646
+ }
647
+ }
648
+ return responses.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
649
+ }
650
+
651
+ function clearLocalStorage() {
652
+ Swal.fire({
653
+ title: 'Êtes-vous sûr ?',
654
+ text: "Tout l'historique de vos analyses sera définitivement supprimé.",
655
+ icon: 'warning',
656
+ showCancelButton: true,
657
+ confirmButtonColor: '#ef4444',
658
+ cancelButtonColor: '#6b7280',
659
+ confirmButtonText: 'Oui, supprimer',
660
+ cancelButtonText: 'Annuler'
661
+ }).then((result) => {
662
+ if (result.isConfirmed) {
663
+ const keysToRemove = [];
664
+ for (let i = 0; i < localStorage.length; i++) {
665
+ const key = localStorage.key(i);
666
+ if (key.startsWith('svt_response_')) {
667
+ keysToRemove.push(key);
668
+ }
669
+ }
670
+ for (const key of keysToRemove) {
671
+ localStorage.removeItem(key);
672
+ }
673
+ displayHistory();
674
+ showNotification('Historique supprimé', 'success');
675
+ }
676
+ });
677
+ }
678
+
679
+ function displayHistory() {
680
+ const responses = loadResponsesFromLocalStorage();
681
+ const historyContainer = document.getElementById('historyContainer');
682
+ historyContainer.innerHTML = '';
683
+
684
+ if (responses.length === 0) {
685
+ historyContainer.innerHTML = '<p class="text-gray-500 text-center py-10">Aucun historique disponible.</p>';
686
+ return;
687
+ }
688
+
689
+ const responseList = document.createElement('div');
690
+ responseList.className = 'grid gap-4 md:grid-cols-2';
691
+
692
+ responses.forEach(response => {
693
+ const card = document.createElement('div');
694
+ card.className = 'bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition duration-300 animate-fade-in';
695
+
696
+ const header = document.createElement('div');
697
+ header.className = 'flex justify-between items-center mb-3';
698
+
699
+ const title = document.createElement('h4');
700
+ title.className = 'text-lg font-semibold text-blue-800';
701
+ title.textContent = response.option;
702
+ header.appendChild(title);
703
+
704
+ const date = document.createElement('span');
705
+ date.className = 'text-sm text-gray-500';
706
+ date.textContent = new Date(response.timestamp).toLocaleString();
707
+ header.appendChild(date);
708
+
709
+ card.appendChild(header);
710
+
711
+ const previewContainer = document.createElement('div');
712
+ previewContainer.className = 'flex gap-2 mb-3 overflow-x-auto pb-2';
713
+
714
+ if (response.images && response.images.length > 0) {
715
+ response.images.forEach(imageData => {
716
+ const imgContainer = document.createElement('div');
717
+ imgContainer.className = 'flex-shrink-0 relative';
718
+
719
+ const img = document.createElement('img');
720
+ img.src = imageData;
721
+ img.className = 'h-16 w-16 object-cover rounded-md cursor-pointer transition-transform hover:scale-105';
722
+ img.alt = "Image du sujet";
723
+ img.setAttribute('aria-label', 'Cliquez pour agrandir');
724
+ img.onclick = () => previewImage(imageData);
725
+
726
+ imgContainer.appendChild(img);
727
+ previewContainer.appendChild(imgContainer);
728
+ });
729
+ } else {
730
+ const noImages = document.createElement('p');
731
+ noImages.className = 'text-sm text-gray-400 italic';
732
+ noImages.textContent = 'Pas d\'images disponibles';
733
+ previewContainer.appendChild(noImages);
734
+ }
735
+
736
+ card.appendChild(previewContainer);
737
+
738
+ const contentContainer = document.createElement('div');
739
+ contentContainer.className = 'border-t border-gray-100 pt-3';
740
+
741
+ const responsePreview = document.createElement('p');
742
+ responsePreview.className = 'text-gray-600 text-sm mb-3';
743
+ responsePreview.textContent = response.response.substring(0, 150) + (response.response.length > 150 ? '...' : '');
744
+ contentContainer.appendChild(responsePreview);
745
+
746
+ const actionButtons = document.createElement('div');
747
+ actionButtons.className = 'flex justify-between';
748
+
749
+ const viewButton = document.createElement('button');
750
+ viewButton.className = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm transition-colors';
751
+ viewButton.innerHTML = '<i class="fas fa-eye mr-2"></i>Voir';
752
+ viewButton.onclick = () => {
753
+ document.getElementById('response').innerHTML = marked.parse(response.response);
754
+ document.getElementById('copyResponseContainer').classList.remove('hidden');
755
+ switchTab('main');
756
+ setTimeout(() => {
757
+ document.getElementById('response').scrollIntoView({ behavior: 'smooth' });
758
+ }, 300);
759
+ };
760
+ viewButton.setAttribute('aria-label', 'Voir la réponse complète');
761
+
762
+ const deleteButton = document.createElement('button');
763
+ deleteButton.className = 'bg-gray-200 hover:bg-red-100 text-gray-700 hover:text-red-500 px-4 py-2 rounded-lg text-sm transition-colors';
764
+ deleteButton.innerHTML = '<i class="fas fa-trash-alt mr-2"></i>Supprimer';
765
+ deleteButton.onclick = (e) => {
766
+ e.stopPropagation();
767
+ Swal.fire({
768
+ title: 'Confirmer la suppression',
769
+ text: "Voulez-vous supprimer cette analyse ?",
770
+ icon: 'warning',
771
+ showCancelButton: true,
772
+ confirmButtonColor: '#ef4444',
773
+ cancelButtonColor: '#6b7280',
774
+ confirmButtonText: 'Supprimer',
775
+ cancelButtonText: 'Annuler'
776
+ }).then((result) => {
777
+ if (result.isConfirmed) {
778
+ localStorage.removeItem('svt_response_' + response.timestamp);
779
+ displayHistory();
780
+ showNotification('Analyse supprimée', 'success');
781
+ }
782
+ });
783
+ };
784
+ deleteButton.setAttribute('aria-label', 'Supprimer cette analyse');
785
+
786
+ actionButtons.appendChild(viewButton);
787
+ actionButtons.appendChild(deleteButton);
788
+ contentContainer.appendChild(actionButtons);
789
+
790
+ card.appendChild(contentContainer);
791
+ responseList.appendChild(card);
792
+ });
793
+
794
+ historyContainer.appendChild(responseList);
795
+ }
796
+
797
+ // Initialisation de l'application
798
+ function init() {
799
+ // Afficher l'historique au chargement de la page
800
+ displayHistory();
801
+
802
+ // Initialiser la manipulation du drag and drop
803
+ document.addEventListener('dragover', (e) => {
804
+ e.preventDefault();
805
+ });
806
+
807
+ document.addEventListener('drop', (e) => {
808
+ e.preventDefault();
809
+ });
810
+ }
811
+
812
+ // Exécuter l'initialisation au chargement de la page
813
+ window.addEventListener('DOMContentLoaded', init);
814
+ </script>
815
+ </body>
816
+ </html>