Docfile commited on
Commit
059bcb4
·
verified ·
1 Parent(s): 2a86310

Create index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +466 -0
templates/index.html ADDED
@@ -0,0 +1,466 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 M-0 | Solution Mathématique</title>
7
+ <!-- Tailwind CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
9
+
10
+ <!-- SweetAlert2 -->
11
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
12
+
13
+ <!-- Configuration de MathJax -->
14
+ <script>
15
+ window.MathJax = {
16
+ tex: {
17
+ inlineMath: [['$', '$']],
18
+ displayMath: [['$$', '$$']],
19
+ processEscapes: true,
20
+ packages: {'[+]': ['autoload', 'ams']}
21
+ },
22
+ options: {
23
+ enableMenu: false,
24
+ messageStyle: 'none'
25
+ },
26
+ startup: {
27
+ pageReady: () => { window.mathJaxReady = true; }
28
+ }
29
+ };
30
+ </script>
31
+ <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script" async></script>
32
+ <script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.min.js"></script>
33
+
34
+ <style>
35
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
36
+ body { font-family: 'Space Grotesk', sans-serif; }
37
+
38
+ .uploadArea {
39
+ background: #f3f4f6;
40
+ border: 2px dashed #d1d5db;
41
+ transition: border-color 0.2s ease;
42
+ }
43
+ .uploadArea:hover { border-color: #3b82f6; }
44
+
45
+ .blue-button { background: #3b82f6; transition: background-color 0.2s ease; }
46
+ .blue-button:hover { background: #2563eb; }
47
+
48
+ .loader {
49
+ width: 48px;
50
+ height: 48px;
51
+ border: 3px solid #3b82f6;
52
+ border-bottom-color: transparent;
53
+ border-radius: 50%;
54
+ display: inline-block;
55
+ animation: rotation 1s linear infinite;
56
+ }
57
+ @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
58
+
59
+ .thought-box {
60
+ transition: max-height 0.3s ease-out;
61
+ max-height: 0;
62
+ overflow: hidden;
63
+ }
64
+ .thought-box.open { max-height: 500px; }
65
+
66
+ #thoughtsContent, #answerContent {
67
+ max-height: 500px;
68
+ overflow-y: auto;
69
+ scroll-behavior: smooth;
70
+ white-space: pre-wrap;
71
+ }
72
+
73
+ .preview-image { max-width: 300px; max-height: 300px; object-fit: contain; }
74
+
75
+ .timestamp { color: #3b82f6; font-size: 0.9em; margin-left: 8px; }
76
+
77
+ table {
78
+ border-collapse: collapse;
79
+ width: 100%;
80
+ margin-bottom: 1rem;
81
+ }
82
+ th, td {
83
+ border: 1px solid #d1d5db;
84
+ padding: 0.5rem;
85
+ text-align: left;
86
+ }
87
+ th { background-color: #f3f4f6; font-weight: 600; }
88
+ .table-responsive { overflow-x: auto; }
89
+
90
+ /* Style pour le bouton Sauvegarder */
91
+ #saveButton {
92
+ background: #3b82f6;
93
+ color: white;
94
+ padding: 0.5rem 1rem;
95
+ border-radius: 0.375rem;
96
+ transition: background-color 0.2s ease;
97
+ }
98
+ #saveButton:hover { background: #2563eb; }
99
+
100
+ /* Modal plein écran pour les sauvegardes */
101
+ #savedModal {
102
+ display: none;
103
+ position: fixed;
104
+ inset: 0;
105
+ background: rgba(0,0,0,0.5);
106
+ z-index: 50;
107
+ }
108
+ #savedModal.active { display: block; }
109
+ #savedModalContent {
110
+ background: #fff;
111
+ width: 100%;
112
+ height: 100%;
113
+ overflow-y: auto;
114
+ }
115
+ </style>
116
+ </head>
117
+ <body class="p-4">
118
+ <div class="max-w-4xl mx-auto">
119
+ <header class="p-6 text-center mb-8">
120
+ <h1 class="text-4xl font-bold text-blue-600">Mariam M-0</h1>
121
+ <p class="text-gray-600">Solution Mathématique/Physique/Chimie Intelligente</p>
122
+ <div class="mt-4 flex justify-end">
123
+ <button id="openSaved" class="blue-button px-4 py-2 text-white rounded">Sauvegardes</button>
124
+ </div>
125
+ </header>
126
+
127
+ <main id="mainContent">
128
+ <form id="problemForm" class="space-y-6" novalidate>
129
+ <!-- Zone de dépôt / sélection d'image -->
130
+ <div class="uploadArea p-8 text-center relative" aria-label="Zone de dépôt d'image">
131
+ <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
132
+ <div class="space-y-3">
133
+ <div class="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
134
+ <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
135
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
136
+ 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" />
137
+ </svg>
138
+ </div>
139
+ <p class="text-gray-700 font-medium">Déposez votre image ici</p>
140
+ <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
141
+ </div>
142
+ </div>
143
+ <!-- Aperçu de l'image -->
144
+ <div id="imagePreview" class="hidden text-center">
145
+ <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation de l'image">
146
+ </div>
147
+ <button type="submit" class="blue-button w-full py-3 text-white font-medium rounded-lg">
148
+ Résoudre le problème
149
+ </button>
150
+ </form>
151
+
152
+ <!-- Loader -->
153
+ <div id="loader" class="hidden mt-8 text-center">
154
+ <span class="loader"></span>
155
+ <p class="mt-4 text-gray-600">Analyse en cours...</p>
156
+ </div>
157
+
158
+ <!-- Zone d'affichage de la solution -->
159
+ <section id="solution" class="hidden mt-8 space-y-6 relative">
160
+ <div class="border-t pt-4">
161
+ <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-2">
162
+ <span class="font-medium text-gray-700">Processus de Réflexion</span>
163
+ <span id="timestamp" class="timestamp"></span>
164
+ </button>
165
+ <div id="thoughtsBox" class="thought-box">
166
+ <div id="thoughtsContent" class="p-4 text-gray-600"></div>
167
+ </div>
168
+ </div>
169
+ <div class="border-t pt-6">
170
+ <div class="flex justify-between items-center">
171
+ <h3 class="text-xl font-bold text-gray-800 mb-4">Solution</h3>
172
+ <!-- Bouton Sauvegarder -->
173
+ <button id="saveButton">Sauvegarder</button>
174
+ </div>
175
+ <div id="answerContent" class="text-gray-700 table-responsive"></div>
176
+ </div>
177
+ </section>
178
+ </main>
179
+ </div>
180
+
181
+ <!-- Modal plein écran pour les sauvegardes -->
182
+ <div id="savedModal">
183
+ <div id="savedModalContent" class="p-6">
184
+ <header class="flex justify-between items-center border-b pb-4">
185
+ <h2 class="text-2xl font-bold">Sauvegardes</h2>
186
+ <button id="closeSaved" class="text-3xl text-gray-600">&times;</button>
187
+ </header>
188
+ <div id="savedListContainer" class="mt-4">
189
+ <ul id="savedList" class="space-y-4">
190
+ <!-- Liste des sauvegardes insérée dynamiquement -->
191
+ </ul>
192
+ </div>
193
+ <div class="mt-6">
194
+ <button id="newExercise" class="blue-button w-full py-3 text-white font-medium rounded-lg">
195
+ Résoudre un nouvel exercice
196
+ </button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <script>
202
+ document.addEventListener('DOMContentLoaded', () => {
203
+ // Récupération des éléments
204
+ const form = document.getElementById('problemForm');
205
+ const imageInput = document.getElementById('imageInput');
206
+ const loader = document.getElementById('loader');
207
+ const solutionSection = document.getElementById('solution');
208
+ const thoughtsContent = document.getElementById('thoughtsContent');
209
+ const answerContent = document.getElementById('answerContent');
210
+ const thoughtsToggle = document.getElementById('thoughtsToggle');
211
+ const thoughtsBox = document.getElementById('thoughtsBox');
212
+ const imagePreview = document.getElementById('imagePreview');
213
+ const previewImage = document.getElementById('previewImage');
214
+ const timestamp = document.getElementById('timestamp');
215
+ const saveButton = document.getElementById('saveButton');
216
+ const openSaved = document.getElementById('openSaved');
217
+ const closeSaved = document.getElementById('closeSaved');
218
+ const savedModal = document.getElementById('savedModal');
219
+ const savedList = document.getElementById('savedList');
220
+ const newExercise = document.getElementById('newExercise');
221
+
222
+ let startTime = null;
223
+ let timerInterval = null;
224
+ let thoughtsBuffer = '';
225
+ let answerBuffer = '';
226
+ let currentMode = null;
227
+ let updateTimeout = null;
228
+
229
+ // Mise à jour du temps écoulé
230
+ const updateTimestamp = () => {
231
+ if (startTime) {
232
+ const seconds = Math.floor((Date.now() - startTime) / 1000);
233
+ timestamp.textContent = `${seconds}s`;
234
+ }
235
+ };
236
+ const startTimer = () => { startTime = Date.now(); timerInterval = setInterval(updateTimestamp, 1000); updateTimestamp(); };
237
+ const stopTimer = () => { clearInterval(timerInterval); startTime = null; timestamp.textContent = ''; };
238
+
239
+ // Affichage de l'image sélectionnée
240
+ const handleFileSelect = file => {
241
+ if (!file) return;
242
+ const reader = new FileReader();
243
+ reader.onload = e => {
244
+ previewImage.src = e.target.result;
245
+ imagePreview.classList.remove('hidden');
246
+ };
247
+ reader.readAsDataURL(file);
248
+ };
249
+
250
+ thoughtsToggle.addEventListener('click', () => { thoughtsBox.classList.toggle('open'); });
251
+ imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
252
+
253
+ // Gestion du glisser-déposer
254
+ const dropZone = document.querySelector('.uploadArea');
255
+ dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('border-blue-400'); });
256
+ dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400'); });
257
+ dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400'); handleFileSelect(e.dataTransfer.files[0]); });
258
+
259
+ /*
260
+ Fonction améliorée de pré-traitement pour envelopper automatiquement
261
+ les commandes LaTeX dans des délimiteurs $...$.
262
+ Cette fonction parcourt le texte caractère par caractère, en tenant compte
263
+ du mode mathématique (délimité par $) pour éviter de retraiter du contenu déjà formaté.
264
+ */
265
+ function autoWrapMath(text) {
266
+ let result = "";
267
+ let i = 0;
268
+ let inMath = false;
269
+ while(i < text.length) {
270
+ if (text[i] === '$') {
271
+ result += '$';
272
+ inMath = !inMath;
273
+ i++;
274
+ } else if (!inMath && text[i] === '\\') {
275
+ // On est hors du mode math, on traite la commande LaTeX
276
+ let start = i;
277
+ i++; // On saute le backslash
278
+ // Lecture du nom de la commande (lettres uniquement)
279
+ while(i < text.length && /[a-zA-Z]/.test(text[i])) { i++; }
280
+ // Gestion d'un argument optionnel éventuel : [ ... ]
281
+ if(i < text.length && text[i] === '[') {
282
+ let bracketCount = 0;
283
+ while(i < text.length) {
284
+ if(text[i] === '[') { bracketCount++; }
285
+ else if(text[i] === ']') { bracketCount--; }
286
+ i++;
287
+ if(bracketCount === 0) break;
288
+ }
289
+ }
290
+ // Gestion d'un ou plusieurs arguments obligatoires : { ... }
291
+ while(i < text.length && text[i] === '{') {
292
+ let braceCount = 0;
293
+ while(i < text.length) {
294
+ if(text[i] === '{') { braceCount++; }
295
+ else if(text[i] === '}') { braceCount--; }
296
+ i++;
297
+ if(braceCount === 0) break;
298
+ }
299
+ }
300
+ let commandStr = text.substring(start, i);
301
+ // On enveloppe la commande complète dans des délimiteurs $...$
302
+ result += '$' + commandStr + '$';
303
+ } else {
304
+ result += text[i];
305
+ i++;
306
+ }
307
+ }
308
+ return result;
309
+ }
310
+
311
+ // Rendu MathJax et mise à jour de l'affichage
312
+ const typesetAnswerIfReady = async () => {
313
+ if (window.mathJaxReady) {
314
+ MathJax.startup.document.elements = [document.getElementById('answerContent')];
315
+ await MathJax.typesetPromise();
316
+ answerContent.scrollTop = answerContent.scrollHeight;
317
+ } else {
318
+ setTimeout(typesetAnswerIfReady, 200);
319
+ }
320
+ };
321
+ const updateDisplay = async () => {
322
+ // Appliquer le pré-traitement pour ajouter les délimiteurs mathématiques
323
+ const processedThoughts = autoWrapMath(thoughtsBuffer);
324
+ const processedAnswer = autoWrapMath(answerBuffer);
325
+ thoughtsContent.innerHTML = marked.parse(processedThoughts);
326
+ answerContent.innerHTML = marked.parse(processedAnswer);
327
+ await typesetAnswerIfReady();
328
+ updateTimeout = null;
329
+ };
330
+ const scheduleUpdate = () => { if (!updateTimeout) updateTimeout = setTimeout(updateDisplay, 200); };
331
+
332
+ marked.setOptions({ gfm: true, breaks: true });
333
+
334
+ // Envoi de l'image pour résolution
335
+ form.addEventListener('submit', async e => {
336
+ e.preventDefault();
337
+ const file = imageInput.files[0];
338
+ if (!file) { alert('Veuillez sélectionner une image.'); return; }
339
+ startTimer();
340
+ loader.classList.remove('hidden');
341
+ solutionSection.classList.add('hidden');
342
+ thoughtsContent.innerHTML = '';
343
+ answerContent.innerHTML = '';
344
+ thoughtsBuffer = '';
345
+ answerBuffer = '';
346
+ currentMode = null;
347
+ thoughtsBox.classList.add('open');
348
+
349
+ const formData = new FormData();
350
+ formData.append('image', file);
351
+ try {
352
+ const response = await fetch('/solve', { method: 'POST', body: formData });
353
+ const reader = response.body.getReader();
354
+ const decoder = new TextDecoder();
355
+ let buffer = '';
356
+ const processChunk = async chunk => {
357
+ buffer += decoder.decode(chunk, { stream: true });
358
+ const lines = buffer.split('\n\n');
359
+ buffer = lines.pop();
360
+ for (const line of lines) {
361
+ if (!line.startsWith('data:')) continue;
362
+ const data = JSON.parse(line.slice(5));
363
+ if (data.mode) {
364
+ currentMode = data.mode;
365
+ loader.classList.add('hidden');
366
+ solutionSection.classList.remove('hidden');
367
+ }
368
+ if (data.content) {
369
+ if (currentMode === 'thinking') { thoughtsBuffer += data.content; }
370
+ else if (currentMode === 'answering') { answerBuffer += data.content; }
371
+ }
372
+ }
373
+ scheduleUpdate();
374
+ };
375
+ while (true) {
376
+ const { done, value } = await reader.read();
377
+ if (done) {
378
+ if (buffer) {
379
+ const data = JSON.parse(buffer.slice(5));
380
+ if (data.content) {
381
+ if (currentMode === 'thinking') { thoughtsBuffer += data.content; }
382
+ else if (currentMode === 'answering') { answerBuffer += data.content; }
383
+ }
384
+ }
385
+ scheduleUpdate();
386
+ break;
387
+ }
388
+ await processChunk(value);
389
+ }
390
+ stopTimer();
391
+ } catch (error) {
392
+ console.error('Erreur:', error);
393
+ alert('Une erreur est survenue.');
394
+ loader.classList.add('hidden');
395
+ stopTimer();
396
+ }
397
+ });
398
+
399
+ // Sauvegarde de la solution avec SweetAlert2
400
+ saveButton.addEventListener('click', () => {
401
+ const saveName = prompt("Entrez un nom pour la sauvegarde :");
402
+ if (!saveName) return;
403
+ const saveData = {
404
+ answer: answerContent.innerHTML,
405
+ thinking: thoughtsContent.innerHTML,
406
+ date: new Date().toLocaleString()
407
+ };
408
+ let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
409
+ savedExercises[saveName] = saveData;
410
+ localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
411
+ Swal.fire({
412
+ icon: 'success',
413
+ title: 'Sauvegarde réussie',
414
+ text: 'Votre solution a bien été sauvegardée !',
415
+ timer: 2000,
416
+ showConfirmButton: false
417
+ });
418
+ });
419
+
420
+ // Chargement des sauvegardes dans le modal
421
+ const loadSavedList = () => {
422
+ savedList.innerHTML = '';
423
+ const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
424
+ for (const [name, data] of Object.entries(savedExercises)) {
425
+ const li = document.createElement('li');
426
+ li.innerHTML = `<button class="w-full text-left text-blue-600 hover:underline" data-save="${name}">${name} <span class="text-gray-500 text-xs">(${data.date})</span></button>`;
427
+ savedList.appendChild(li);
428
+ }
429
+ };
430
+
431
+ savedList.addEventListener('click', (e) => {
432
+ if (e.target && e.target.dataset.save) {
433
+ const saveName = e.target.dataset.save;
434
+ const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
435
+ const data = savedExercises[saveName];
436
+ if (data) {
437
+ form.classList.add('hidden');
438
+ loader.classList.add('hidden');
439
+ solutionSection.classList.remove('hidden');
440
+ thoughtsContent.innerHTML = data.thinking;
441
+ answerContent.innerHTML = data.answer;
442
+ savedModal.classList.remove('active');
443
+ }
444
+ }
445
+ });
446
+
447
+ // Ouverture / fermeture du modal de sauvegardes
448
+ openSaved.addEventListener('click', () => { loadSavedList(); savedModal.classList.add('active'); });
449
+ closeSaved.addEventListener('click', () => { savedModal.classList.remove('active'); });
450
+
451
+ // Bouton dans le modal pour lancer un nouvel exercice
452
+ newExercise.addEventListener('click', () => {
453
+ form.reset();
454
+ form.classList.remove('hidden');
455
+ solutionSection.classList.add('hidden');
456
+ imagePreview.classList.add('hidden');
457
+ thoughtsContent.innerHTML = '';
458
+ answerContent.innerHTML = '';
459
+ thoughtsBuffer = '';
460
+ answerBuffer = '';
461
+ savedModal.classList.remove('active');
462
+ });
463
+ });
464
+ </script>
465
+ </body>
466
+ </html>