Docfile commited on
Commit
1e5f437
·
verified ·
1 Parent(s): 6ade419

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +155 -165
templates/index.html CHANGED
@@ -1,33 +1,29 @@
1
 
 
2
  <!DOCTYPE html>
3
  <html lang="fr">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Mariam M-0 | Solution Mathématique</title>
8
- <!-- Tailwind CSS (version 2.2.19 utilisée ici, à mettre à jour si besoin) -->
9
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
10
 
11
- <!-- Configuration optimisée de MathJax avec support étendu LaTeX -->
12
  <script>
13
  window.MathJax = {
14
  tex: {
15
  inlineMath: [['$', '$']],
16
  displayMath: [['$$', '$$']],
17
  processEscapes: true,
18
- // Chargement automatique de commandes supplémentaires (ex. environnements de tableaux)
19
- packages: {'[+]': ['autoload','ams']}
20
  },
21
-
22
  options: {
23
  enableMenu: false,
24
  messageStyle: 'none'
25
  },
26
  startup: {
27
- pageReady: () => {
28
- console.log('MathJax est complètement chargé.');
29
- window.mathJaxReady = true;
30
- }
31
  }
32
  };
33
  </script>
@@ -36,88 +32,56 @@
36
 
37
  <style>
38
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
39
-
40
- body {
41
- font-family: 'Space Grotesk', sans-serif;
42
- }
43
-
44
  .uploadArea {
45
  background: #f3f4f6;
46
  border: 2px dashed #d1d5db;
47
  transition: border-color 0.2s ease;
48
  }
49
- .uploadArea:hover {
50
- border-color: #3b82f6;
51
- }
52
-
53
- .blue-button {
54
- background: #3b82f6;
55
- transition: background-color 0.2s ease;
56
- }
57
- .blue-button:hover {
58
- background: #2563eb;
59
- }
60
-
61
  .loader {
62
- width: 48px;
63
- height: 48px;
64
  border: 3px solid #3b82f6;
65
  border-bottom-color: transparent;
66
  border-radius: 50%;
67
  display: inline-block;
68
  animation: rotation 1s linear infinite;
69
  }
70
- @keyframes rotation {
71
- 0% { transform: rotate(0deg); }
72
- 100% { transform: rotate(360deg); }
73
- }
74
-
75
  .thought-box {
76
  transition: max-height 0.3s ease-out;
77
  max-height: 0;
78
  overflow: hidden;
79
  }
80
- .thought-box.open {
81
- max-height: 500px;
82
- }
83
-
84
  #thoughtsContent, #answerContent {
85
  max-height: 500px;
86
  overflow-y: auto;
87
  scroll-behavior: smooth;
88
  white-space: pre-wrap;
89
  }
90
-
91
- .preview-image {
92
- max-width: 300px;
93
- max-height: 300px;
94
- object-fit: contain;
95
- }
96
-
97
- .timestamp {
98
- color: #3b82f6;
99
- font-size: 0.9em;
100
- margin-left: 8px;
 
 
101
  }
102
-
103
- /* Styles supplémentaires pour une meilleure présentation des tableaux */
104
- table {
105
- border-collapse: collapse;
106
  width: 100%;
107
- margin-bottom: 1rem;
108
- }
109
- th, td {
110
- border: 1px solid #d1d5db;
111
- padding: 0.5rem;
112
- text-align: left;
113
- }
114
- th {
115
- background-color: #f3f4f6;
116
- font-weight: 600;
117
- }
118
- /* Pour un rendu responsive, ajouter éventuellement : */
119
- .table-responsive {
120
- overflow-x: auto;
121
  }
122
  </style>
123
  </head>
@@ -126,11 +90,14 @@
126
  <header class="p-6 text-center mb-8">
127
  <h1 class="text-4xl font-bold text-blue-600">Mariam M-0</h1>
128
  <p class="text-gray-600">Solution Mathématique/Physique/Chimie Intelligente</p>
 
 
 
129
  </header>
130
 
131
- <main>
132
  <form id="problemForm" class="space-y-6" novalidate>
133
- <!-- Zone de dépôt et sélection d'image -->
134
  <div class="uploadArea p-8 text-center relative" aria-label="Zone de dépôt d'image">
135
  <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
136
  <div class="space-y-3">
@@ -144,25 +111,23 @@
144
  <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
145
  </div>
146
  </div>
147
-
148
  <!-- Aperçu de l'image -->
149
  <div id="imagePreview" class="hidden text-center">
150
- <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation de l'image sélectionnée">
151
  </div>
152
-
153
  <button type="submit" class="blue-button w-full py-3 text-white font-medium rounded-lg">
154
  Résoudre le problème
155
  </button>
156
  </form>
157
 
158
- <!-- Loader d'analyse -->
159
  <div id="loader" class="hidden mt-8 text-center">
160
  <span class="loader"></span>
161
  <p class="mt-4 text-gray-600">Analyse en cours...</p>
162
  </div>
163
 
164
- <!-- Affichage de la solution -->
165
- <section id="solution" class="hidden mt-8 space-y-6">
166
  <div class="border-t pt-4">
167
  <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-2">
168
  <span class="font-medium text-gray-700">Processus de Réflexion</span>
@@ -173,17 +138,40 @@
173
  </div>
174
  </div>
175
  <div class="border-t pt-6">
176
- <h3 class="text-xl font-bold text-gray-800 mb-4">Solution</h3>
177
- <!-- Enveloppement d'éventuels tableaux dans une div responsive -->
 
 
 
178
  <div id="answerContent" class="text-gray-700 table-responsive"></div>
179
  </div>
180
  </section>
181
  </main>
182
  </div>
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  <script>
185
  document.addEventListener('DOMContentLoaded', () => {
186
- // Récupération des éléments DOM
187
  const form = document.getElementById('problemForm');
188
  const imageInput = document.getElementById('imageInput');
189
  const loader = document.getElementById('loader');
@@ -195,35 +183,32 @@
195
  const imagePreview = document.getElementById('imagePreview');
196
  const previewImage = document.getElementById('previewImage');
197
  const timestamp = document.getElementById('timestamp');
 
 
 
 
 
 
 
198
 
199
  let startTime = null;
200
  let timerInterval = null;
201
  let thoughtsBuffer = '';
202
  let answerBuffer = '';
203
  let currentMode = null;
204
- let updateTimeout = null; // Variable pour le debounce des mises à jour
205
 
206
- // Mise à jour de l'affichage du temps écoulé
207
  const updateTimestamp = () => {
208
  if (startTime) {
209
  const seconds = Math.floor((Date.now() - startTime) / 1000);
210
  timestamp.textContent = `${seconds}s`;
211
  }
212
  };
 
 
213
 
214
- const startTimer = () => {
215
- startTime = Date.now();
216
- timerInterval = setInterval(updateTimestamp, 1000);
217
- updateTimestamp();
218
- };
219
-
220
- const stopTimer = () => {
221
- clearInterval(timerInterval);
222
- startTime = null;
223
- timestamp.textContent = '';
224
- };
225
-
226
- // Gestion de la sélection ou du dépôt de l'image
227
  const handleFileSelect = file => {
228
  if (!file) return;
229
  const reader = new FileReader();
@@ -234,73 +219,38 @@
234
  reader.readAsDataURL(file);
235
  };
236
 
237
- // Toggle pour afficher/cacher le processus de réflexion
238
- thoughtsToggle.addEventListener('click', () => {
239
- thoughtsBox.classList.toggle('open');
240
- });
241
-
242
  imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
243
 
244
- // Gestion des événements de glisser-déposer
245
  const dropZone = document.querySelector('.uploadArea');
246
- dropZone.addEventListener('dragover', e => {
247
- e.preventDefault();
248
- dropZone.classList.add('border-blue-400');
249
- });
250
- dropZone.addEventListener('dragleave', e => {
251
- e.preventDefault();
252
- dropZone.classList.remove('border-blue-400');
253
- });
254
- dropZone.addEventListener('drop', e => {
255
- e.preventDefault();
256
- dropZone.classList.remove('border-blue-400');
257
- handleFileSelect(e.dataTransfer.files[0]);
258
- });
259
 
260
- // Fonction pour relancer le rendu MathJax dès que le contenu de la réponse est mis à jour
261
  const typesetAnswerIfReady = async () => {
262
  if (window.mathJaxReady) {
263
  MathJax.startup.document.elements = [document.getElementById('answerContent')];
264
  await MathJax.typesetPromise();
265
  answerContent.scrollTop = answerContent.scrollHeight;
266
- } else {
267
- setTimeout(typesetAnswerIfReady, 200);
268
- }
269
  };
270
-
271
- // Fonction pour déclencher la mise à jour de l'affichage
272
  const updateDisplay = async () => {
273
- // Mise à jour du contenu via marked
274
  thoughtsContent.innerHTML = marked.parse(thoughtsBuffer);
275
  answerContent.innerHTML = marked.parse(answerBuffer);
276
-
277
- // Lancer le rendu MathJax
278
  await typesetAnswerIfReady();
279
  updateTimeout = null;
280
  };
 
281
 
282
- // Fonction pour planifier la mise à jour (dé-bounce)
283
- const scheduleUpdate = () => {
284
- if (updateTimeout) return;
285
- updateTimeout = setTimeout(updateDisplay, 200);
286
- };
287
-
288
- // Configuration de marked avec support du mode GFM (pour les tableaux) et interprétation des sauts de ligne
289
- marked.setOptions({
290
- gfm: true,
291
- breaks: true
292
- });
293
 
294
- // Soumission du formulaire
295
  form.addEventListener('submit', async e => {
296
  e.preventDefault();
297
  const file = imageInput.files[0];
298
- if (!file) {
299
- alert('Veuillez sélectionner une image.');
300
- return;
301
- }
302
-
303
- // Initialisation de l'affichage et des variables
304
  startTimer();
305
  loader.classList.remove('hidden');
306
  solutionSection.classList.add('hidden');
@@ -313,58 +263,38 @@
313
 
314
  const formData = new FormData();
315
  formData.append('image', file);
316
-
317
  try {
318
- // Envoi de la requête au serveur
319
- const response = await fetch('/solve', {
320
- method: 'POST',
321
- body: formData
322
- });
323
-
324
  const reader = response.body.getReader();
325
  const decoder = new TextDecoder();
326
  let buffer = '';
327
-
328
- // Traitement d'un chunk de données
329
  const processChunk = async chunk => {
330
  buffer += decoder.decode(chunk, { stream: true });
331
  const lines = buffer.split('\n\n');
332
- buffer = lines.pop(); // Conserver la dernière ligne incomplète
333
-
334
  for (const line of lines) {
335
  if (!line.startsWith('data:')) continue;
336
  const data = JSON.parse(line.slice(5));
337
-
338
  if (data.mode) {
339
  currentMode = data.mode;
340
  loader.classList.add('hidden');
341
  solutionSection.classList.remove('hidden');
342
  }
343
  if (data.content) {
344
- if (currentMode === 'thinking') {
345
- thoughtsBuffer += data.content;
346
- } else if (currentMode === 'answering') {
347
- answerBuffer += data.content;
348
- }
349
  }
350
  }
351
- // Planifier la mise à jour dé-bouncée
352
  scheduleUpdate();
353
  };
354
-
355
- // Lecture du flux de réponse
356
  while (true) {
357
  const { done, value } = await reader.read();
358
  if (done) {
359
- // Traitement du contenu restant dans le buffer
360
  if (buffer) {
361
  const data = JSON.parse(buffer.slice(5));
362
  if (data.content) {
363
- if (currentMode === 'thinking') {
364
- thoughtsBuffer += data.content;
365
- } else if (currentMode === 'answering') {
366
- answerBuffer += data.content;
367
- }
368
  }
369
  }
370
  scheduleUpdate();
@@ -380,7 +310,67 @@
380
  stopTimer();
381
  }
382
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  });
384
  </script>
385
  </body>
386
- </html>
 
 
1
 
2
+
3
  <!DOCTYPE html>
4
  <html lang="fr">
5
  <head>
6
  <meta charset="UTF-8">
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <title>Mariam M-0 | Solution Mathématique</title>
9
+ <!-- Tailwind CSS -->
10
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
11
 
12
+ <!-- Configuration de MathJax -->
13
  <script>
14
  window.MathJax = {
15
  tex: {
16
  inlineMath: [['$', '$']],
17
  displayMath: [['$$', '$$']],
18
  processEscapes: true,
19
+ packages: {'[+]': ['autoload', 'ams']}
 
20
  },
 
21
  options: {
22
  enableMenu: false,
23
  messageStyle: 'none'
24
  },
25
  startup: {
26
+ pageReady: () => { window.mathJaxReady = true; }
 
 
 
27
  }
28
  };
29
  </script>
 
32
 
33
  <style>
34
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
35
+ body { font-family: 'Space Grotesk', sans-serif; }
 
 
 
 
36
  .uploadArea {
37
  background: #f3f4f6;
38
  border: 2px dashed #d1d5db;
39
  transition: border-color 0.2s ease;
40
  }
41
+ .uploadArea:hover { border-color: #3b82f6; }
42
+ .blue-button { background: #3b82f6; transition: background-color 0.2s ease; }
43
+ .blue-button:hover { background: #2563eb; }
 
 
 
 
 
 
 
 
 
44
  .loader {
45
+ width: 48px; height: 48px;
 
46
  border: 3px solid #3b82f6;
47
  border-bottom-color: transparent;
48
  border-radius: 50%;
49
  display: inline-block;
50
  animation: rotation 1s linear infinite;
51
  }
52
+ @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
 
 
 
 
53
  .thought-box {
54
  transition: max-height 0.3s ease-out;
55
  max-height: 0;
56
  overflow: hidden;
57
  }
58
+ .thought-box.open { max-height: 500px; }
 
 
 
59
  #thoughtsContent, #answerContent {
60
  max-height: 500px;
61
  overflow-y: auto;
62
  scroll-behavior: smooth;
63
  white-space: pre-wrap;
64
  }
65
+ .preview-image { max-width: 300px; max-height: 300px; object-fit: contain; }
66
+ .timestamp { color: #3b82f6; font-size: 0.9em; margin-left: 8px; }
67
+ table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; }
68
+ th, td { border: 1px solid #d1d5db; padding: 0.5rem; text-align: left; }
69
+ th { background-color: #f3f4f6; font-weight: 600; }
70
+ .table-responsive { overflow-x: auto; }
71
+ /* Modal plein écran pour les sauvegardes */
72
+ #savedModal {
73
+ display: none;
74
+ position: fixed;
75
+ inset: 0;
76
+ background: rgba(0,0,0,0.5);
77
+ z-index: 50;
78
  }
79
+ #savedModal.active { display: block; }
80
+ #savedModalContent {
81
+ background: #fff;
 
82
  width: 100%;
83
+ height: 100%;
84
+ overflow-y: auto;
 
 
 
 
 
 
 
 
 
 
 
 
85
  }
86
  </style>
87
  </head>
 
90
  <header class="p-6 text-center mb-8">
91
  <h1 class="text-4xl font-bold text-blue-600">Mariam M-0</h1>
92
  <p class="text-gray-600">Solution Mathématique/Physique/Chimie Intelligente</p>
93
+ <div class="mt-4 flex justify-end">
94
+ <button id="openSaved" class="blue-button px-4 py-2 text-white rounded">Sauvegardes</button>
95
+ </div>
96
  </header>
97
 
98
+ <main id="mainContent">
99
  <form id="problemForm" class="space-y-6" novalidate>
100
+ <!-- Zone de dépôt / sélection d'image -->
101
  <div class="uploadArea p-8 text-center relative" aria-label="Zone de dépôt d'image">
102
  <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
103
  <div class="space-y-3">
 
111
  <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
112
  </div>
113
  </div>
 
114
  <!-- Aperçu de l'image -->
115
  <div id="imagePreview" class="hidden text-center">
116
+ <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation de l'image">
117
  </div>
 
118
  <button type="submit" class="blue-button w-full py-3 text-white font-medium rounded-lg">
119
  Résoudre le problème
120
  </button>
121
  </form>
122
 
123
+ <!-- Loader -->
124
  <div id="loader" class="hidden mt-8 text-center">
125
  <span class="loader"></span>
126
  <p class="mt-4 text-gray-600">Analyse en cours...</p>
127
  </div>
128
 
129
+ <!-- Zone d'affichage de la solution -->
130
+ <section id="solution" class="hidden mt-8 space-y-6 relative">
131
  <div class="border-t pt-4">
132
  <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-2">
133
  <span class="font-medium text-gray-700">Processus de Réflexion</span>
 
138
  </div>
139
  </div>
140
  <div class="border-t pt-6">
141
+ <div class="flex justify-between items-center">
142
+ <h3 class="text-xl font-bold text-gray-800 mb-4">Solution</h3>
143
+ <!-- Bouton Sauvegarder repositionné et discret -->
144
+ <button id="saveButton" class="text-sm text-blue-600 hover:underline">Sauvegarder</button>
145
+ </div>
146
  <div id="answerContent" class="text-gray-700 table-responsive"></div>
147
  </div>
148
  </section>
149
  </main>
150
  </div>
151
 
152
+ <!-- Modal plein écran pour les sauvegardes -->
153
+ <div id="savedModal">
154
+ <div id="savedModalContent" class="p-6">
155
+ <header class="flex justify-between items-center border-b pb-4">
156
+ <h2 class="text-2xl font-bold">Sauvegardes</h2>
157
+ <button id="closeSaved" class="text-3xl text-gray-600">&times;</button>
158
+ </header>
159
+ <div id="savedListContainer" class="mt-4">
160
+ <ul id="savedList" class="space-y-4">
161
+ <!-- Liste des sauvegardes insérée dynamiquement -->
162
+ </ul>
163
+ </div>
164
+ <div class="mt-6">
165
+ <button id="newExercise" class="blue-button w-full py-3 text-white font-medium rounded-lg">
166
+ Résoudre un nouvel exercice
167
+ </button>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
  <script>
173
  document.addEventListener('DOMContentLoaded', () => {
174
+ // Récupération des éléments
175
  const form = document.getElementById('problemForm');
176
  const imageInput = document.getElementById('imageInput');
177
  const loader = document.getElementById('loader');
 
183
  const imagePreview = document.getElementById('imagePreview');
184
  const previewImage = document.getElementById('previewImage');
185
  const timestamp = document.getElementById('timestamp');
186
+ const saveButton = document.getElementById('saveButton');
187
+ const openSaved = document.getElementById('openSaved');
188
+ const closeSaved = document.getElementById('closeSaved');
189
+ const savedModal = document.getElementById('savedModal');
190
+ const savedList = document.getElementById('savedList');
191
+ const newExercise = document.getElementById('newExercise');
192
+ const mainContent = document.getElementById('mainContent');
193
 
194
  let startTime = null;
195
  let timerInterval = null;
196
  let thoughtsBuffer = '';
197
  let answerBuffer = '';
198
  let currentMode = null;
199
+ let updateTimeout = null;
200
 
201
+ // Mise à jour du temps écoulé
202
  const updateTimestamp = () => {
203
  if (startTime) {
204
  const seconds = Math.floor((Date.now() - startTime) / 1000);
205
  timestamp.textContent = `${seconds}s`;
206
  }
207
  };
208
+ const startTimer = () => { startTime = Date.now(); timerInterval = setInterval(updateTimestamp, 1000); updateTimestamp(); };
209
+ const stopTimer = () => { clearInterval(timerInterval); startTime = null; timestamp.textContent = ''; };
210
 
211
+ // Affichage de l'image sélectionnée
 
 
 
 
 
 
 
 
 
 
 
 
212
  const handleFileSelect = file => {
213
  if (!file) return;
214
  const reader = new FileReader();
 
219
  reader.readAsDataURL(file);
220
  };
221
 
222
+ thoughtsToggle.addEventListener('click', () => { thoughtsBox.classList.toggle('open'); });
 
 
 
 
223
  imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
224
 
225
+ // Gestion du glisser-déposer
226
  const dropZone = document.querySelector('.uploadArea');
227
+ dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('border-blue-400'); });
228
+ dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400'); });
229
+ dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400'); handleFileSelect(e.dataTransfer.files[0]); });
 
 
 
 
 
 
 
 
 
 
230
 
231
+ // Rendu MathJax et mise à jour de l'affichage
232
  const typesetAnswerIfReady = async () => {
233
  if (window.mathJaxReady) {
234
  MathJax.startup.document.elements = [document.getElementById('answerContent')];
235
  await MathJax.typesetPromise();
236
  answerContent.scrollTop = answerContent.scrollHeight;
237
+ } else { setTimeout(typesetAnswerIfReady, 200); }
 
 
238
  };
 
 
239
  const updateDisplay = async () => {
 
240
  thoughtsContent.innerHTML = marked.parse(thoughtsBuffer);
241
  answerContent.innerHTML = marked.parse(answerBuffer);
 
 
242
  await typesetAnswerIfReady();
243
  updateTimeout = null;
244
  };
245
+ const scheduleUpdate = () => { if (!updateTimeout) updateTimeout = setTimeout(updateDisplay, 200); };
246
 
247
+ marked.setOptions({ gfm: true, breaks: true });
 
 
 
 
 
 
 
 
 
 
248
 
249
+ // Envoi de l'image pour résolution
250
  form.addEventListener('submit', async e => {
251
  e.preventDefault();
252
  const file = imageInput.files[0];
253
+ if (!file) { alert('Veuillez sélectionner une image.'); return; }
 
 
 
 
 
254
  startTimer();
255
  loader.classList.remove('hidden');
256
  solutionSection.classList.add('hidden');
 
263
 
264
  const formData = new FormData();
265
  formData.append('image', file);
 
266
  try {
267
+ const response = await fetch('/solve', { method: 'POST', body: formData });
 
 
 
 
 
268
  const reader = response.body.getReader();
269
  const decoder = new TextDecoder();
270
  let buffer = '';
 
 
271
  const processChunk = async chunk => {
272
  buffer += decoder.decode(chunk, { stream: true });
273
  const lines = buffer.split('\n\n');
274
+ buffer = lines.pop();
 
275
  for (const line of lines) {
276
  if (!line.startsWith('data:')) continue;
277
  const data = JSON.parse(line.slice(5));
 
278
  if (data.mode) {
279
  currentMode = data.mode;
280
  loader.classList.add('hidden');
281
  solutionSection.classList.remove('hidden');
282
  }
283
  if (data.content) {
284
+ if (currentMode === 'thinking') { thoughtsBuffer += data.content; }
285
+ else if (currentMode === 'answering') { answerBuffer += data.content; }
 
 
 
286
  }
287
  }
 
288
  scheduleUpdate();
289
  };
 
 
290
  while (true) {
291
  const { done, value } = await reader.read();
292
  if (done) {
 
293
  if (buffer) {
294
  const data = JSON.parse(buffer.slice(5));
295
  if (data.content) {
296
+ if (currentMode === 'thinking') { thoughtsBuffer += data.content; }
297
+ else if (currentMode === 'answering') { answerBuffer += data.content; }
 
 
 
298
  }
299
  }
300
  scheduleUpdate();
 
310
  stopTimer();
311
  }
312
  });
313
+
314
+ // Sauvegarde de la solution
315
+ saveButton.addEventListener('click', () => {
316
+ const saveName = prompt("Entrez un nom pour la sauvegarde :");
317
+ if (!saveName) return;
318
+ const saveData = {
319
+ answer: answerContent.innerHTML,
320
+ thinking: thoughtsContent.innerHTML,
321
+ date: new Date().toLocaleString()
322
+ };
323
+ let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
324
+ savedExercises[saveName] = saveData;
325
+ localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
326
+ alert("Solution sauvegardée !");
327
+ });
328
+
329
+ // Chargement des sauvegardes dans le modal
330
+ const loadSavedList = () => {
331
+ savedList.innerHTML = '';
332
+ const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
333
+ for (const [name, data] of Object.entries(savedExercises)) {
334
+ const li = document.createElement('li');
335
+ 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>`;
336
+ savedList.appendChild(li);
337
+ }
338
+ };
339
+
340
+ savedList.addEventListener('click', (e) => {
341
+ if (e.target && e.target.dataset.save) {
342
+ const saveName = e.target.dataset.save;
343
+ const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
344
+ const data = savedExercises[saveName];
345
+ if (data) {
346
+ form.classList.add('hidden');
347
+ loader.classList.add('hidden');
348
+ solutionSection.classList.remove('hidden');
349
+ thoughtsContent.innerHTML = data.thinking;
350
+ answerContent.innerHTML = data.answer;
351
+ savedModal.classList.remove('active');
352
+ }
353
+ }
354
+ });
355
+
356
+ // Ouverture / fermeture du modal de sauvegardes
357
+ openSaved.addEventListener('click', () => { loadSavedList(); savedModal.classList.add('active'); });
358
+ closeSaved.addEventListener('click', () => { savedModal.classList.remove('active'); });
359
+
360
+ // Bouton présent uniquement dans le modal pour lancer un nouvel exercice
361
+ newExercise.addEventListener('click', () => {
362
+ form.reset();
363
+ form.classList.remove('hidden');
364
+ solutionSection.classList.add('hidden');
365
+ imagePreview.classList.add('hidden');
366
+ thoughtsContent.innerHTML = '';
367
+ answerContent.innerHTML = '';
368
+ thoughtsBuffer = '';
369
+ answerBuffer = '';
370
+ savedModal.classList.remove('active');
371
+ });
372
  });
373
  </script>
374
  </body>
375
+ </html>
376
+