Docfile commited on
Commit
e17df7c
·
verified ·
1 Parent(s): 77a17cf

Create math.html

Browse files
Files changed (1) hide show
  1. templates/math.html +643 -0
templates/math.html ADDED
@@ -0,0 +1,643 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <meta name="description" content="Assistant intelligent pour la résolution détaillée de problèmes mathématiques">
7
+ <title>Mariam - Résolution de Problèmes Mathématiques</title>
8
+
9
+ <!-- Preload des ressources critiques -->
10
+ <link rel="preload" href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" as="style">
11
+ <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" as="style">
12
+
13
+ <!-- CSS -->
14
+ <script src="https://cdn.tailwindcss.com"></script>
15
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
16
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
17
+
18
+ <style>
19
+ /* Base styles */
20
+ :root {
21
+ --primary-color: #3b82f6;
22
+ --primary-dark: #2563eb;
23
+ --error-color: #ef4444;
24
+ --success-color: #22c55e;
25
+ --border-color: #d1d5db;
26
+ }
27
+
28
+ body {
29
+ font-family: 'Poppins', system-ui, sans-serif;
30
+ background-color: #f9fafb;
31
+ }
32
+
33
+ /* Optimized dropzone */
34
+ .dropzone {
35
+ border: 3px dashed var(--primary-color);
36
+ transition: all 0.2s ease;
37
+ border-radius: 1rem;
38
+ padding: 2rem;
39
+ background-color: rgba(59, 130, 246, 0.05);
40
+ min-height: 200px;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ }
45
+
46
+ .dropzone:hover, .dropzone.drag-active {
47
+ border-color: var(--primary-dark);
48
+ background-color: rgba(59, 130, 246, 0.1);
49
+ transform: scale(1.01);
50
+ }
51
+
52
+ /* Enhanced button styles */
53
+ .btn {
54
+ padding: 0.75rem 1.5rem;
55
+ border-radius: 0.5rem;
56
+ font-weight: 600;
57
+ transition: all 0.2s ease;
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: 0.5rem;
61
+ cursor: pointer;
62
+ }
63
+
64
+ .btn:disabled {
65
+ opacity: 0.6;
66
+ cursor: not-allowed;
67
+ }
68
+
69
+ .btn-primary {
70
+ background-color: var(--primary-color);
71
+ color: white;
72
+ }
73
+
74
+ .btn-primary:hover:not(:disabled) {
75
+ background-color: var(--primary-dark);
76
+ transform: translateY(-1px);
77
+ }
78
+
79
+ .btn-danger {
80
+ background-color: var(--error-color);
81
+ color: white;
82
+ }
83
+
84
+ .btn-danger:hover {
85
+ background-color: #dc2626;
86
+ transform: translateY(-1px);
87
+ }
88
+
89
+ /* Optimized math content display */
90
+ .math-content {
91
+ font-size: 1.1em;
92
+ line-height: 1.6;
93
+ overflow-x: auto;
94
+ opacity: 0;
95
+ transition: opacity 0.3s ease;
96
+ }
97
+
98
+ .math-content.visible {
99
+ opacity: 1;
100
+ }
101
+
102
+ .math-content p {
103
+ margin-bottom: 1rem;
104
+ white-space: pre-wrap;
105
+ }
106
+
107
+ /* Enhanced form controls */
108
+ .form-input {
109
+ width: 100%;
110
+ padding: 0.75rem 1rem;
111
+ border: 2px solid var(--border-color);
112
+ border-radius: 0.5rem;
113
+ transition: all 0.2s ease;
114
+ }
115
+
116
+ .form-input:focus {
117
+ border-color: var(--primary-color);
118
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
119
+ outline: none;
120
+ }
121
+
122
+ /* Improved loading animation */
123
+ @keyframes pulse {
124
+ 0%, 100% { transform: scale(1); }
125
+ 50% { transform: scale(1.1); }
126
+ }
127
+
128
+ .loading-spinner {
129
+ width: 3rem;
130
+ height: 3rem;
131
+ border: 4px solid var(--primary-color);
132
+ border-radius: 50%;
133
+ border-top-color: transparent;
134
+ animation: spin 1s linear infinite;
135
+ }
136
+
137
+ @keyframes spin {
138
+ to { transform: rotate(360deg); }
139
+ }
140
+
141
+ /* Responsive adjustments */
142
+ @media (max-width: 640px) {
143
+ .container {
144
+ padding: 1rem;
145
+ }
146
+
147
+ .btn {
148
+ width: 100%;
149
+ justify-content: center;
150
+ }
151
+
152
+ .dropzone {
153
+ padding: 1.5rem;
154
+ }
155
+ }
156
+
157
+ /* Enhanced accessibility */
158
+ .sr-only {
159
+ position: absolute;
160
+ width: 1px;
161
+ height: 1px;
162
+ padding: 0;
163
+ margin: -1px;
164
+ overflow: hidden;
165
+ clip: rect(0, 0, 0, 0);
166
+ white-space: nowrap;
167
+ border: 0;
168
+ }
169
+
170
+ /* Toast notifications */
171
+ .toast {
172
+ position: fixed;
173
+ bottom: 1rem;
174
+ right: 1rem;
175
+ padding: 1rem;
176
+ border-radius: 0.5rem;
177
+ background: white;
178
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
179
+ z-index: 50;
180
+ max-width: 24rem;
181
+ transform: translateY(100%);
182
+ opacity: 0;
183
+ transition: all 0.3s ease;
184
+ }
185
+
186
+ .toast.show {
187
+ transform: translateY(0);
188
+ opacity: 1;
189
+ }
190
+ </style>
191
+ </head>
192
+
193
+ <body>
194
+ <div class="container mx-auto px-4 py-8 max-w-4xl">
195
+ <!-- Header -->
196
+ <header class="text-center mb-12">
197
+ <h1 class="text-4xl font-bold mb-4">
198
+ <span class="bg-gradient-to-r from-blue-500 to-blue-700 text-transparent bg-clip-text">Mariam</span>
199
+ - Résolution de Problèmes Mathématiques
200
+ </h1>
201
+ <p class="text-gray-600 text-lg">Assistant intelligent pour des solutions mathématiques détaillées</p>
202
+ </header>
203
+
204
+ <!-- Main Form -->
205
+ <form id="uploadForm" class="space-y-6">
206
+ <div id="dropzone" class="dropzone">
207
+ <input type="file" id="fileInput" class="sr-only" accept="image/*">
208
+ <div class="text-center">
209
+ <i class="fas fa-cloud-upload-alt text-5xl text-blue-500 mb-4"></i>
210
+ <p class="text-lg text-gray-700">
211
+ Glissez votre image ici ou <button type="button" class="text-blue-500 font-semibold hover:text-blue-700">parcourez vos fichiers</button>
212
+ </p>
213
+ <p class="text-sm text-gray-500 mt-2">Formats acceptés: PNG, JPG, JPEG</p>
214
+ </div>
215
+ </div>
216
+
217
+ <div class="space-y-4">
218
+ <div>
219
+ <label for="customInstruction" class="block text-gray-700 font-medium mb-2">
220
+ Instruction personnalisée (optionnel)
221
+ </label>
222
+ <input type="text" id="customInstruction" class="form-input"
223
+ placeholder="Exemple : Résoudre en utilisant le théorème de Pythagore">
224
+ </div>
225
+
226
+ <div class="flex flex-col sm:flex-row gap-4">
227
+ <select id="modelChoice" class="form-input">
228
+ <option value="mariam's">Mariam's (Ultra performant)</option>
229
+ <option value="qwen2">Qwen2 (lent et performant)</option>
230
+ </select>
231
+
232
+ <button type="submit" class="btn btn-primary" disabled>
233
+ <i class="fas fa-paper-plane"></i>
234
+ <span>Analyser l'image</span>
235
+ </button>
236
+ </div>
237
+ </div>
238
+ </form>
239
+
240
+ <!-- Loading State -->
241
+ <div id="loading" class="hidden mt-8 text-center">
242
+ <div class="loading-spinner mx-auto mb-4"></div>
243
+ <p class="text-gray-600 font-medium">Analyse en cours...</p>
244
+ </div>
245
+
246
+ <!-- Response Display -->
247
+ <div id="response" class="hidden mt-8">
248
+ <div class="bg-white rounded-xl shadow-lg p-6">
249
+ <h2 class="text-2xl font-semibold text-blue-600 mb-4">
250
+ Solution (Modèle: <span id="modelName"></span>)
251
+ </h2>
252
+ <div id="latexContent" class="math-content"></div>
253
+ </div>
254
+ </div>
255
+
256
+ <!-- Saved Responses -->
257
+ <section id="savedResponsesSection" class="mt-8">
258
+ <div class="flex justify-between items-center mb-4">
259
+ <h2 class="text-2xl font-semibold text-blue-600">Réponses Sauvegardées</h2>
260
+ <button id="clearSavedResponses" class="btn btn-danger">
261
+ <i class="fas fa-trash-alt"></i>
262
+ <span>Effacer Tout</span>
263
+ </button>
264
+ </div>
265
+ <div id="savedResponses" class="space-y-4"></div>
266
+ </section>
267
+
268
+ <!-- Toast Container -->
269
+ <div id="toastContainer"></div>
270
+ </div>
271
+
272
+ <!-- Scripts -->
273
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js" defer></script>
274
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11" defer></script>
275
+ <script src="https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js" defer></script>
276
+ <script>
277
+ document.addEventListener('DOMContentLoaded', () => {
278
+ // Initialize MathJax
279
+ window.MathJax = {
280
+ tex: {
281
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
282
+ displayMath: [['$$', '$$'], ['\\[', '\\]']],
283
+ processEscapes: true,
284
+ macros: {
285
+ R: "{\\mathbb{R}}",
286
+ N: "{\\mathbb{N}}",
287
+ Z: "{\\mathbb{Z}}",
288
+ vecv: ["\\begin{pmatrix}#1\\\\#2\\\\#3\\end{pmatrix}", 3]
289
+ }
290
+ },
291
+ svg: {
292
+ fontCache: 'global'
293
+ }
294
+ };
295
+
296
+ // Load MathJax dynamically
297
+ const loadMathJax = () => {
298
+ const script = document.createElement('script');
299
+ script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
300
+ script.async = true;
301
+ document.head.appendChild(script);
302
+ return new Promise(resolve => script.onload = resolve);
303
+ };
304
+
305
+ // Toast notification system
306
+ const showToast = (message, type = 'info') => {
307
+ const toast = document.createElement('div');
308
+ toast.className = `toast ${type}`;
309
+ toast.innerHTML = `
310
+ <div class="flex items-center">
311
+ <i class="fas fa-${type === 'error' ? 'exclamation-circle' : 'info-circle'} mr-2"></i>
312
+ <p>${message}</p>
313
+ </div>
314
+ `;
315
+ document.getElementById('toastContainer').appendChild(toast);
316
+ requestAnimationFrame(() => toast.classList.add('show'));
317
+ setTimeout(() => {
318
+ toast.classList.remove('show');
319
+ setTimeout(() => toast.remove(), 300);
320
+ }, 3000);
321
+ };
322
+
323
+ // Enhanced file handling
324
+ const handleFile = file => {
325
+ if (!file.type.startsWith('image/')) {
326
+ showToast('Veuillez sélectionner une image valide', 'error');
327
+ return false;
328
+ }
329
+
330
+ const maxSize = 10 * 1024 * 1024; // 10MB
331
+ if (file.size > maxSize) {
332
+ showToast('L\'image est trop volumineuse. Maximum 10MB.', 'error');
333
+ return false;
334
+ }
335
+
336
+ return true;
337
+ };
338
+
339
+ // Initialize the application
340
+ const init = async () => {
341
+ const dropzone = document.getElementById('dropzone');
342
+ const fileInput = document.getElementById('fileInput');
343
+ const form = document.getElementById('uploadForm');
344
+ const submitBtn = form.querySelector('button[type="submit"]');
345
+
346
+ // Enhanced drag and drop
347
+ dropzone.addEventListener('dragenter', e => {
348
+ e.preventDefault();
349
+ dropzone.classList.add('drag-active');
350
+ });
351
+
352
+ dropzone.addEventListener('dragleave', e => {
353
+ e.preventDefault();
354
+ dropzone.classList.remove('drag-active');
355
+ });
356
+
357
+ dropzone.addEventListener('dragover', e => {
358
+ e.preventDefault();
359
+ });
360
+
361
+ dropzone.addEventListener('drop', e => {
362
+ e.preventDefault();
363
+ dropzone.classList.remove('drag-active');
364
+ const file = e.dataTransfer.files[0];
365
+ if (handleFile(file)) {
366
+ fileInput.files = e.dataTransfer.files;
367
+ handleFileSelect(file);
368
+ }
369
+ });
370
+
371
+ // File selection
372
+ const handleFileSelect = file => {
373
+ const reader = new FileReader();
374
+ reader.onload = e => {
375
+ const preview = dropzone.querySelector('img') || document.createElement('img');
376
+ preview.src = e.target.result;
377
+ preview.className = 'max-h-48 mx-auto mt-4 rounded-lg';
378
+ if (!dropzone.querySelector('img')) {
379
+ dropzone.appendChild(preview);
380
+ }
381
+ submitBtn.disabled = false;
382
+ };
383
+ reader.readAsDataURL(file);
384
+ };
385
+
386
+ fileInput.addEventListener('change', e => {
387
+ const file = e.target.files[0];
388
+ if (handleFile(file)) {
389
+ handleFileSelect(file);
390
+ }
391
+ });
392
+
393
+ // Form submission
394
+ form.addEventListener('submit', async e => {
395
+ e.preventDefault();
396
+ if (!fileInput.files.length) {
397
+ showToast('Veuillez sélectionner une image', 'error');
398
+ return;
399
+ }
400
+
401
+ const formData = new FormData(form);
402
+ submitBtn.disabled = true;
403
+
404
+ try {
405
+ document.getElementById('loading').classList.remove('hidden');
406
+ document.getElementById('response').classList.add('hidden');
407
+
408
+ const response = await fetch('/upload', {
409
+ method: 'POST',
410
+ body: formData
411
+ });
412
+
413
+ const data = await response.json();
414
+ if (!response.ok) throw new Error(data.error || 'Erreur serveur');
415
+
416
+ await renderMathContent(data.result);
417
+ document.getElementById('modelName').textContent = data.model;
418
+ await saveResponse(data.result, data.model);
419
+
420
+ // Gestion des graphiques générés
421
+ if (data.image_paths?.length) {
422
+ const imageContainer = document.createElement('div');
423
+ imageContainer.className = 'mt-4 grid gap-4 grid-cols-1 sm:grid-cols-2';
424
+
425
+ for (const path of data.image_paths) {
426
+ const figure = document.createElement('figure');
427
+ figure.className = 'relative group';
428
+
429
+ const img = document.createElement('img');
430
+ img.src = `/temp/${path.split('/').pop()}`;
431
+ img.className = 'w-full h-auto rounded-lg shadow-md transition-transform hover:scale-105';
432
+ img.loading = 'lazy';
433
+
434
+ figure.appendChild(img);
435
+ imageContainer.appendChild(figure);
436
+ }
437
+
438
+ document.getElementById('latexContent').appendChild(imageContainer);
439
+ }
440
+
441
+ showToast('Analyse terminée avec succès', 'success');
442
+ } catch (error) {
443
+ console.error('Erreur:', error);
444
+ showToast(error.message || 'Une erreur est survenue', 'error');
445
+ } finally {
446
+ document.getElementById('loading').classList.add('hidden');
447
+ submitBtn.disabled = false;
448
+ }
449
+ });
450
+
451
+ // Optimized MathJax rendering
452
+ const renderMathContent = async (text) => {
453
+ const latexContent = document.getElementById('latexContent');
454
+ try {
455
+ latexContent.innerHTML = '';
456
+ latexContent.classList.remove('visible');
457
+
458
+ // Utilisation de marked avec des options de sécurité
459
+ const htmlContent = marked.parse(text, {
460
+ breaks: true,
461
+ gfm: true,
462
+ sanitize: true
463
+ });
464
+
465
+ latexContent.innerHTML = htmlContent;
466
+ await MathJax.typesetPromise([latexContent]);
467
+
468
+ document.getElementById('response').classList.remove('hidden');
469
+ requestAnimationFrame(() => latexContent.classList.add('visible'));
470
+ } catch (error) {
471
+ console.error('Erreur de rendu:', error);
472
+ showToast('Erreur lors du rendu mathématique', 'error');
473
+ latexContent.innerHTML = `
474
+ <div class="text-red-600 mb-4">Une erreur s'est produite lors du rendu. Voici le texte brut :</div>
475
+ <pre class="bg-gray-100 p-4 rounded-lg overflow-x-auto">${text}</pre>
476
+ `;
477
+ }
478
+ };
479
+
480
+ // Enhanced local storage management
481
+ const saveResponse = async (response, model) => {
482
+ try {
483
+ const timestamp = Date.now();
484
+ const key = `response-${timestamp}-${model}`;
485
+ await localforage.setItem(key, {
486
+ content: response,
487
+ model,
488
+ timestamp,
489
+ id: key
490
+ });
491
+ await loadSavedResponses();
492
+ } catch (error) {
493
+ console.error('Erreur de sauvegarde:', error);
494
+ showToast('Erreur lors de la sauvegarde', 'error');
495
+ }
496
+ };
497
+
498
+ const loadSavedResponses = async () => {
499
+ const container = document.getElementById('savedResponses');
500
+ container.innerHTML = '';
501
+
502
+ try {
503
+ const keys = await localforage.keys();
504
+ const responses = await Promise.all(
505
+ keys.map(async key => {
506
+ const data = await localforage.getItem(key);
507
+ return { ...data, key };
508
+ })
509
+ );
510
+
511
+ // Tri par date décroissante
512
+ responses.sort((a, b) => b.timestamp - a.timestamp);
513
+
514
+ for (const response of responses) {
515
+ const element = createResponseElement(response);
516
+ container.appendChild(element);
517
+ }
518
+
519
+ if (responses.length === 0) {
520
+ container.innerHTML = `
521
+ <div class="text-center text-gray-500 py-8">
522
+ <i class="fas fa-inbox text-4xl mb-4"></i>
523
+ <p>Aucune réponse sauvegardée</p>
524
+ </div>
525
+ `;
526
+ }
527
+ } catch (error) {
528
+ console.error('Erreur de chargement:', error);
529
+ showToast('Erreur lors du chargement des réponses', 'error');
530
+ }
531
+ };
532
+
533
+ const createResponseElement = (response) => {
534
+ const element = document.createElement('div');
535
+ element.className = 'bg-white rounded-lg shadow-md overflow-hidden';
536
+
537
+ const header = document.createElement('div');
538
+ header.className = 'flex items-center justify-between p-4 bg-gray-50';
539
+
540
+ const date = new Date(response.timestamp).toLocaleString('fr-FR', {
541
+ dateStyle: 'medium',
542
+ timeStyle: 'short'
543
+ });
544
+
545
+ header.innerHTML = `
546
+ <div class="flex items-center space-x-2">
547
+ <span class="font-medium text-gray-700">${date}</span>
548
+ <span class="text-sm text-gray-500">(${response.model})</span>
549
+ </div>
550
+ <div class="flex space-x-2">
551
+ <button class="btn-toggle p-2 rounded-full hover:bg-gray-200 transition-colors">
552
+ <i class="fas fa-chevron-down"></i>
553
+ </button>
554
+ <button class="btn-delete p-2 rounded-full hover:bg-red-100 text-red-500 transition-colors">
555
+ <i class="fas fa-trash-alt"></i>
556
+ </button>
557
+ </div>
558
+ `;
559
+
560
+ const content = document.createElement('div');
561
+ content.className = 'p-4 math-content hidden';
562
+ content.innerHTML = marked.parse(response.content);
563
+
564
+ element.appendChild(header);
565
+ element.appendChild(content);
566
+
567
+ // Event listeners
568
+ header.querySelector('.btn-toggle').addEventListener('click', async (e) => {
569
+ const icon = e.currentTarget.querySelector('i');
570
+ icon.classList.toggle('fa-chevron-down');
571
+ icon.classList.toggle('fa-chevron-up');
572
+ content.classList.toggle('hidden');
573
+
574
+ if (!content.classList.contains('hidden')) {
575
+ await MathJax.typesetPromise([content]);
576
+ }
577
+ });
578
+
579
+ header.querySelector('.btn-delete').addEventListener('click', async () => {
580
+ if (await confirmDelete()) {
581
+ try {
582
+ await localforage.removeItem(response.key);
583
+ element.remove();
584
+ showToast('Réponse supprimée', 'success');
585
+ } catch (error) {
586
+ console.error('Erreur de suppression:', error);
587
+ showToast('Erreur lors de la suppression', 'error');
588
+ }
589
+ }
590
+ });
591
+
592
+ return element;
593
+ };
594
+
595
+ const confirmDelete = () => {
596
+ return Swal.fire({
597
+ title: 'Confirmer la suppression',
598
+ text: 'Cette action est irréversible',
599
+ icon: 'warning',
600
+ showCancelButton: true,
601
+ confirmButtonColor: '#ef4444',
602
+ cancelButtonColor: '#6b7280',
603
+ confirmButtonText: 'Supprimer',
604
+ cancelButtonText: 'Annuler'
605
+ }).then(result => result.isConfirmed);
606
+ };
607
+
608
+ // Initialize
609
+ await loadMathJax();
610
+ await loadSavedResponses();
611
+
612
+ // Clear all responses
613
+ document.getElementById('clearSavedResponses').addEventListener('click', async () => {
614
+ const result = await Swal.fire({
615
+ title: 'Tout effacer ?',
616
+ text: 'Cette action supprimera toutes vos réponses sauvegardées',
617
+ icon: 'warning',
618
+ showCancelButton: true,
619
+ confirmButtonColor: '#ef4444',
620
+ cancelButtonColor: '#6b7280',
621
+ confirmButtonText: 'Tout effacer',
622
+ cancelButtonText: 'Annuler'
623
+ });
624
+
625
+ if (result.isConfirmed) {
626
+ try {
627
+ await localforage.clear();
628
+ await loadSavedResponses();
629
+ showToast('Toutes les réponses ont été supprimées', 'success');
630
+ } catch (error) {
631
+ console.error('Erreur de suppression:', error);
632
+ showToast('Erreur lors de la suppression', 'error');
633
+ }
634
+ }
635
+ });
636
+ };
637
+
638
+ // Start the application
639
+ init().catch(console.error);
640
+ });
641
+ </script>
642
+ </body>
643
+ </html>