Docfile commited on
Commit
53b133e
·
verified ·
1 Parent(s): f025ca4

Create templates/math.html

Browse files
Files changed (1) hide show
  1. templates/math.html +521 -0
templates/math.html ADDED
@@ -0,0 +1,521 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 - Résolution de Problèmes Mathématiques</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script defer src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
10
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
14
+ <style>
15
+ /* Améliorations CSS :
16
+ - Utilisation de variables CSS pour une maintenance plus facile
17
+ - Ajout de transitions pour une meilleure expérience utilisateur
18
+ - Optimisation des styles pour les écrans mobiles
19
+ */
20
+ :root {
21
+ --primary-color: #3b82f6;
22
+ --primary-color-hover: #2563eb;
23
+ --error-color: #ef4444;
24
+ --error-color-hover: #dc2626;
25
+ --gray-light: #f5f5f5;
26
+ --gray-medium: #e5e7eb;
27
+ --gray-dark: #d1d5db;
28
+ }
29
+ body {
30
+ font-family: 'Poppins', sans-serif;
31
+ }
32
+ .dropzone {
33
+ border: 3px dashed var(--primary-color);
34
+ transition: all 0.3s ease;
35
+ border-radius: 10px;
36
+ padding: 3rem;
37
+ background-color: var(--gray-light);
38
+ }
39
+ .dropzone:hover {
40
+ border-color: var(--primary-color-hover);
41
+ background-color: rgba(59, 130, 246, 0.1);
42
+ }
43
+ .dropzone.dragover { /* Nouvelle classe pour le drag over */
44
+ background-color: rgba(59, 130, 246, 0.2);
45
+ }
46
+ .loading {
47
+ display: none;
48
+ }
49
+ .loading.active {
50
+ display: flex;
51
+ }
52
+ .math-content {
53
+ font-size: 1.1em;
54
+ line-height: 1.6;
55
+ overflow-x: auto;
56
+ }
57
+ .math-content p {
58
+ margin-bottom: 1rem;
59
+ white-space: pre-wrap;
60
+ }
61
+ /* Optimisation pour les petits écrans */
62
+ @media (max-width: 768px) {
63
+ .math-content {
64
+ font-size: 1em;
65
+ }
66
+ }
67
+ .saved-response-header {
68
+ cursor: pointer;
69
+ display: flex;
70
+ justify-content: space-between;
71
+ align-items: center;
72
+ padding: 0.75rem 1rem;
73
+ background-color: var(--gray-medium);
74
+ border-bottom: 1px solid var(--gray-dark);
75
+ border-radius: 8px;
76
+ font-weight: 500;
77
+ }
78
+ .saved-response-content {
79
+ padding: 1rem;
80
+ display: none;
81
+ border-radius: 8px;
82
+ }
83
+ .saved-response-item.open .saved-response-content {
84
+ display: block;
85
+ }
86
+ .btn {
87
+ padding: 0.75rem 1.5rem;
88
+ border-radius: 0.5rem;
89
+ font-weight: 600;
90
+ transition: all 0.3s ease;
91
+ }
92
+ .btn-primary {
93
+ background-color: var(--primary-color);
94
+ color: #fff;
95
+ }
96
+ .btn-primary:hover {
97
+ background-color: var(--primary-color-hover);
98
+ }
99
+ .btn-danger {
100
+ background-color: var(--error-color);
101
+ color: #fff;
102
+ }
103
+ .btn-danger:hover {
104
+ background-color: var(--error-color-hover);
105
+ }
106
+ .select {
107
+ border: 1px solid var(--gray-dark);
108
+ border-radius: 0.5rem;
109
+ padding: 0.75rem 1rem;
110
+ appearance: none;
111
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
112
+ background-repeat: no-repeat;
113
+ background-position: right 0.75rem center;
114
+ background-size: 1em;
115
+ padding-right: 2.5rem;
116
+ }
117
+ .select:focus {
118
+ outline: none;
119
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
120
+ }
121
+ </style>
122
+ <script>
123
+ window.MathJax = {
124
+ tex: {
125
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
126
+ displayMath: [['$$', '$$'], ['\\[', '\\]']],
127
+ processEscapes: true,
128
+ macros: {
129
+ R: "{\\mathbb{R}}",
130
+ N: "{\\mathbb{N}}",
131
+ Z: "{\\mathbb{Z}}",
132
+ vecv: ["\\begin{pmatrix}#1\\\\#2\\\\#3\\end{pmatrix}", 3]
133
+ }
134
+ },
135
+ svg: {
136
+ fontCache: 'global'
137
+ },
138
+ startup: {
139
+ pageReady: () => {
140
+ return Promise.resolve();
141
+ }
142
+ },
143
+ options: {
144
+ renderActions: {
145
+ addMenu: [],
146
+ checkLoading: [150, () => {
147
+ document.querySelectorAll('.math-content').forEach(el => {
148
+ el.classList.remove('math-hidden');
149
+ });
150
+ }]
151
+ }
152
+ }
153
+ };
154
+ </script>
155
+ <script>
156
+ // Fonction pour charger MathJax de manière asynchrone
157
+ function loadMathJax() {
158
+ return new Promise((resolve, reject) => {
159
+ const script = document.createElement('script');
160
+ script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
161
+ script.async = true;
162
+ script.id = 'MathJax-script';
163
+ script.onload = resolve;
164
+ script.onerror = reject;
165
+ document.head.appendChild(script);
166
+ });
167
+ }
168
+ loadMathJax().catch(console.error);
169
+ </script>
170
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js"></script>
171
+ </head>
172
+
173
+ <body class="bg-gray-100">
174
+ <div class="container mx-auto px-4 py-8 max-w-4xl">
175
+ <header class="text-center mb-12">
176
+ <h1 class="text-4xl font-bold text-blue-600 mb-4">
177
+ <span class="bg-gradient-to-r from-blue-500 to-blue-700 text-transparent bg-clip-text">Mariam</span>
178
+ - Résolution de Problèmes Mathématiques
179
+ </h1>
180
+ <p class="text-gray-500 text-lg">Votre assistant intelligent pour des solutions mathématiques détaillées</p>
181
+ </header>
182
+ <div class="mb-8">
183
+ <form id="uploadForm" class="space-y-4">
184
+ <div id="dropzone"
185
+ class="dropzone rounded-lg text-center cursor-pointer shadow-md hover:shadow-lg transition-all">
186
+ <input type="file" id="fileInput" class="hidden" accept="image/*">
187
+ <div class="flex flex-col items-center space-y-4">
188
+ <i class="fas fa-cloud-upload-alt text-6xl text-blue-500"></i>
189
+ <div class="text-lg text-gray-600">
190
+ Glissez votre image ici ou <span class="text-blue-500 font-semibold">cliquez pour
191
+ sélectionner</span>
192
+ </div>
193
+ <p class="text-sm text-gray-500">Formats acceptés: PNG, JPG, JPEG</p>
194
+ </div>
195
+ </div>
196
+
197
+ <div class="space-y-4">
198
+ <label for="customInstruction" class="block text-gray-600 font-medium">Instruction
199
+ personnalisée (optionnel)</label>
200
+ <input type="text" id="customInstruction" name="custom_instruction"
201
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
202
+ placeholder="Exemple : Résoudre en utilisant le théorème de Pythagore">
203
+ </div>
204
+
205
+ <div class="flex flex-col md:flex-row justify-center items-center space-y-4 md:space-y-0 md:space-x-4">
206
+ <select id="modelChoice" name="model_choice"
207
+ class="select w-full md:w-auto">
208
+ <option value="mariam's">Mariam's (Ultra performant)</option>
209
+ <option value="qwen2">Qwen2 (lent et performant)</option>
210
+ </select>
211
+ <button type="submit"
212
+ class="btn btn-primary w-full md:w-auto flex items-center space-x-2 disabled:opacity-50 disabled:cursor-not-allowed">
213
+ <i class="fas fa-paper-plane"></i>
214
+ <span>Analyser l'image</span>
215
+ </button>
216
+ </div>
217
+ </form>
218
+ </div>
219
+
220
+ <div id="loading" class="loading flex-col items-center justify-center space-y-4 my-8">
221
+ <div class="animate-spin rounded-full h-16 w-16 border-t-4 border-b-4 border-blue-500"></div>
222
+ <p class="text-gray-600 font-medium text-lg">Analyse en cours...</p>
223
+ </div>
224
+
225
+ <div id="response" class="hidden">
226
+ <div class="bg-white rounded-xl shadow-lg p-6 mb-8">
227
+ <h2 id="modelUsed" class="text-2xl font-semibold text-blue-600 mb-4">Solution (Modèle: <span
228
+ id="modelName"></span>)</h2>
229
+ <div id="latexContent" class="prose max-w-none math-content"></div>
230
+ </div>
231
+ </div>
232
+
233
+ <div id="savedResponsesSection" class="mt-8">
234
+ <div class="flex justify-between items-center mb-4">
235
+ <h2 class="text-2xl font-semibold text-blue-600">Réponses Sauvegardées</h2>
236
+ <button id="clearSavedResponses"
237
+ class="btn btn-danger">
238
+ <i class="fas fa-trash-alt"></i> Effacer Tout
239
+ </button>
240
+ </div>
241
+ <div id="savedResponses" class="space-y-4"></div>
242
+ </div>
243
+
244
+ <div id="errorMessage"
245
+ class="hidden bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg relative my-4" role="alert">
246
+ <strong class="font-bold">Erreur!</strong>
247
+ <span class="block sm:inline" id="errorText"></span>
248
+ </div>
249
+ </div>
250
+
251
+ <script>
252
+ // Améliorations JavaScript :
253
+ // - Utilisation de fonctions fléchées pour une syntaxe plus concise
254
+ // - Utilisation de async/await pour une meilleure lisibilité du code asynchrone
255
+ // - Ajout de gestionnaires d'événements pour le drag and drop
256
+ // - Utilisation de template literals pour une meilleure lisibilité des chaînes de caractères
257
+ document.addEventListener('DOMContentLoaded', () => {
258
+ const dropzone = document.getElementById('dropzone');
259
+ const fileInput = document.getElementById('fileInput');
260
+ const uploadForm = document.getElementById('uploadForm');
261
+ const loading = document.getElementById('loading');
262
+ const response = document.getElementById('response');
263
+ const latexContent = document.getElementById('latexContent');
264
+ const errorMessage = document.getElementById('errorMessage');
265
+ const errorText = document.getElementById('errorText');
266
+ const submitButton = uploadForm.querySelector('button[type="submit"]');
267
+ const savedResponsesContainer = document.getElementById('savedResponses');
268
+ const clearSavedResponsesButton = document.getElementById('clearSavedResponses');
269
+ const modelChoiceSelect = document.getElementById('modelChoice');
270
+ const modelNameSpan = document.getElementById('modelName');
271
+ const customInstructionInput = document.getElementById('customInstruction');
272
+
273
+ let mathJaxReady = false;
274
+ window.MathJax.startup.promise.then(() => {
275
+ mathJaxReady = true;
276
+ });
277
+
278
+ marked.setOptions({
279
+ breaks: true,
280
+ gfm: true,
281
+ pedantic: false,
282
+ smartLists: true
283
+ });
284
+
285
+ const showError = (message) => {
286
+ errorText.textContent = message;
287
+ errorMessage.classList.remove('hidden');
288
+ setTimeout(() => {
289
+ errorMessage.classList.add('hidden');
290
+ }, 5000);
291
+ };
292
+
293
+ const renderMathContent = async (text) => {
294
+ try {
295
+ if (!mathJaxReady) {
296
+ await window.MathJax.startup.promise;
297
+ }
298
+ latexContent.innerHTML = '';
299
+ const htmlContent = marked.parse(text);
300
+ latexContent.innerHTML = htmlContent;
301
+ await MathJax.typesetPromise([latexContent]);
302
+ response.classList.remove('hidden');
303
+ } catch (error) {
304
+ console.error('Erreur lors du rendu:', error);
305
+ showError('Erreur lors du rendu de la formule mathématique');
306
+ latexContent.innerHTML = `
307
+ <div class="text-red-600 mb-4">Une erreur s'est produite lors du rendu. Voici le texte brut :</div>
308
+ <pre class="bg-gray-100 p-4 rounded-lg overflow-x-auto">${text}</pre>
309
+ `;
310
+ }
311
+ };
312
+
313
+ const handleDragOver = (e) => {
314
+ e.preventDefault();
315
+ e.stopPropagation();
316
+ dropzone.classList.add('dragover');
317
+ };
318
+
319
+ const handleDragLeave = (e) => {
320
+ e.preventDefault();
321
+ e.stopPropagation();
322
+ dropzone.classList.remove('dragover');
323
+ };
324
+
325
+ const handleDrop = (e) => {
326
+ e.preventDefault();
327
+ e.stopPropagation();
328
+ dropzone.classList.remove('dragover');
329
+ const files = e.dataTransfer.files;
330
+ if (files.length > 0 && files[0].type.startsWith('image/')) {
331
+ fileInput.files = files;
332
+ handleFileSelect(files[0]);
333
+ } else {
334
+ showError('Veuillez déposer une image valide');
335
+ }
336
+ };
337
+
338
+ const handleFileSelect = (file) => {
339
+ if (file && file.type.startsWith('image/')) {
340
+ const reader = new FileReader();
341
+ reader.onload = (e) => {
342
+ const preview = document.createElement('img');
343
+ preview.src = e.target.result;
344
+ preview.classList.add('max-h-48', 'mx-auto', 'mt-4', 'rounded-lg');
345
+ const oldPreview = dropzone.querySelector('img');
346
+ if (oldPreview) oldPreview.remove();
347
+ dropzone.appendChild(preview);
348
+ submitButton.disabled = false;
349
+ };
350
+ reader.readAsDataURL(file);
351
+ } else {
352
+ showError('Veuillez sélectionner une image valide');
353
+ }
354
+ };
355
+
356
+ dropzone.addEventListener('dragover', handleDragOver);
357
+ dropzone.addEventListener('dragleave', handleDragLeave);
358
+ dropzone.addEventListener('drop', handleDrop);
359
+ dropzone.addEventListener('click', () => fileInput.click());
360
+
361
+ fileInput.addEventListener('change', (e) => {
362
+ if (e.target.files.length > 0) {
363
+ handleFileSelect(e.target.files[0]);
364
+ }
365
+ });
366
+
367
+ const saveResponse = async (response, model) => {
368
+ const timestamp = new Date().getTime();
369
+ const key = `response-${timestamp}-${model}`;
370
+ try {
371
+ await localforage.setItem(key, response);
372
+ loadSavedResponses();
373
+ } catch (error) {
374
+ console.error('Erreur lors de la sauvegarde:', error);
375
+ showError('Erreur lors de la sauvegarde de la réponse en local');
376
+ }
377
+ };
378
+
379
+ const clearSavedResponses = async () => {
380
+ Swal.fire({
381
+ title: 'Êtes-vous sûr?',
382
+ text: "Vous ne pourrez pas revenir en arrière!",
383
+ icon: 'warning',
384
+ showCancelButton: true,
385
+ confirmButtonColor: '#d33',
386
+ cancelButtonColor: '#3085d6',
387
+ confirmButtonText: 'Oui, effacer!',
388
+ cancelButtonText: 'Annuler'
389
+ }).then(async (result) => {
390
+ if (result.isConfirmed) {
391
+ try {
392
+ await localforage.clear();
393
+ console.log('Réponses sauvegardées effacées');
394
+ loadSavedResponses();
395
+ Swal.fire('Effacé!', 'Les réponses ont été supprimées.', 'success');
396
+ } catch (error) {
397
+ console.error("Erreur lors de l'effacement des réponses sauvegardées:", error);
398
+ showError("Erreur lors de l'effacement des réponses sauvegardées");
399
+ Swal.fire('Erreur!', 'Une erreur est survenue lors de la suppression.', 'error');
400
+ }
401
+ }
402
+ });
403
+ };
404
+
405
+ const loadSavedResponses = async () => {
406
+ try {
407
+ savedResponsesContainer.innerHTML = '';
408
+ const keys = await localforage.keys();
409
+ keys.sort((a, b) => parseInt(b.replace('response-', '').split('-')[0]) - parseInt(a.replace('response-', '').split('-')[0]));
410
+
411
+ for (const key of keys) {
412
+ const response = await localforage.getItem(key);
413
+ const [, timestamp, model] = key.split('-');
414
+ const responseItem = document.createElement('div');
415
+ responseItem.className = 'saved-response-item bg-gray-100 rounded-lg shadow-md overflow-hidden';
416
+
417
+ const header = document.createElement('div');
418
+ header.className = 'saved-response-header';
419
+ header.innerHTML = `
420
+ <span class="text-blue-600 font-medium">Réponse du ${new Date(parseInt(timestamp)).toLocaleString()} (Modèle: ${model})</span>
421
+ <div>
422
+ <button class="toggle-content px-2 py-1 rounded-md text-xs bg-blue-500 hover:bg-blue-700 text-white mr-1"><i class="fas fa-chevron-down"></i></button>
423
+ <button class="delete-response px-2 py-1 rounded-md text-xs bg-red-500 hover:bg-red-700 text-white"><i class="fas fa-trash-alt"></i></button>
424
+ </div>
425
+ `;
426
+ responseItem.appendChild(header);
427
+
428
+ const content = document.createElement('div');
429
+ content.className = 'saved-response-content math-content';
430
+ content.innerHTML = marked.parse(response);
431
+ responseItem.appendChild(content);
432
+ savedResponsesContainer.appendChild(responseItem);
433
+
434
+ header.querySelector('.toggle-content').addEventListener('click', () => {
435
+ responseItem.classList.toggle('open');
436
+ header.querySelector('i').classList.toggle('fa-chevron-down');
437
+ header.querySelector('i').classList.toggle('fa-chevron-up');
438
+ MathJax.typesetPromise([content]);
439
+ });
440
+
441
+ header.querySelector('.delete-response').addEventListener('click', async () => {
442
+ try {
443
+ await localforage.removeItem(key);
444
+ responseItem.remove();
445
+ } catch (error) {
446
+ console.error('Erreur lors de la suppression de la réponse:', error);
447
+ showError('Erreur lors de la suppression de la réponse');
448
+ }
449
+ });
450
+
451
+ MathJax.typesetPromise([content]);
452
+ }
453
+ } catch (error) {
454
+ console.error('Erreur lors du chargement des réponses sauvegardées:', error);
455
+ showError('Erreur lors du chargement des réponses sauvegardées');
456
+ }
457
+ };
458
+
459
+ uploadForm.addEventListener('submit', async (e) => {
460
+ e.preventDefault();
461
+
462
+ if (!fileInput.files.length) {
463
+ showError('Veuillez sélectionner une image');
464
+ return;
465
+ }
466
+
467
+ const formData = new FormData();
468
+ formData.append('image', fileInput.files[0]);
469
+ formData.append('model_choice', modelChoiceSelect.value);
470
+ formData.append('custom_instruction', customInstructionInput.value);
471
+
472
+ try {
473
+ submitButton.disabled = true;
474
+ loading.classList.add('active');
475
+ response.classList.add('hidden');
476
+ errorMessage.classList.add('hidden');
477
+
478
+ const res = await fetch('/upload', {
479
+ method: 'POST',
480
+ body: formData
481
+ });
482
+
483
+ const data = await res.json();
484
+
485
+ if (data.error) {
486
+ throw new Error(data.error);
487
+ }
488
+
489
+ await renderMathContent(data.result);
490
+ modelNameSpan.textContent = data.model;
491
+ saveResponse(data.result, data.model);
492
+
493
+ // Afficher les graphiques générés
494
+ if (data.image_paths) {
495
+ const imageContainer = document.createElement('div');
496
+ imageContainer.className = 'mt-4';
497
+ data.image_paths.forEach(imagePath => {
498
+ const img = document.createElement('img');
499
+ img.src = `/temp/${imagePath.split('/').pop()}`;
500
+ img.className = 'max-w-full h-auto mt-2 rounded-lg';
501
+ imageContainer.appendChild(img);
502
+ });
503
+ latexContent.appendChild(imageContainer);
504
+ }
505
+
506
+ } catch (error) {
507
+ console.error('Erreur:', error);
508
+ showError(error.message || 'Une erreur est survenue lors du traitement');
509
+ } finally {
510
+ loading.classList.remove('active');
511
+ submitButton.disabled = false;
512
+ }
513
+ });
514
+
515
+ loadSavedResponses();
516
+ clearSavedResponsesButton.addEventListener('click', clearSavedResponses);
517
+ });
518
+ </script>
519
+ </body>
520
+
521
+ </html>