Docfile commited on
Commit
9a2ee9d
·
verified ·
1 Parent(s): 5309523

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +609 -140
templates/index.html CHANGED
@@ -3,12 +3,15 @@
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
  <!-- SweetAlert2 -->
10
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
- <!-- MathJax Configuration -->
 
 
 
12
  <script>
13
  window.MathJax = {
14
  tex: {
@@ -28,51 +31,196 @@
28
  </script>
29
  <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script" async></script>
30
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"></script>
 
31
  <style>
32
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
33
  body { font-family: 'Space Grotesk', sans-serif; }
34
- .uploadArea { background: #f3f4f6; border: 2px dashed #d1d5db; transition: border-color 0.2s ease; }
 
 
 
 
 
35
  .uploadArea:hover { border-color: #3b82f6; }
 
36
  .blue-button { background: #3b82f6; transition: background-color 0.2s ease; }
37
- .blue-button:hover { background: #2563eb; }
38
- .blue-button:disabled { background: #9ca3af; cursor: not-allowed; }
 
 
 
 
 
 
 
 
 
 
 
 
39
  .loader {
40
  width: 48px; height: 48px; border: 3px solid #3b82f6; border-bottom-color: transparent;
41
  border-radius: 50%; display: inline-block; animation: rotation 1s linear infinite;
42
  }
43
  @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
44
- .thought-box { transition: max-height 0.3s ease-out; max-height: 0; overflow: hidden; }
45
- .thought-box.open { max-height: 500px; /* Ajustez au besoin */ }
 
 
 
 
46
  #thoughtsContent, #answerContent {
47
- max-height: 500px; /* Ajustez au besoin */
48
- overflow-y: auto; scroll-behavior: smooth; white-space: pre-wrap;
49
  }
 
50
  .preview-image { max-width: 300px; max-height: 300px; object-fit: contain; }
51
  .timestamp { color: #3b82f6; font-size: 0.9em; margin-left: 8px; }
52
- table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; }
53
- th, td { border: 1px solid #d1d5db; padding: 0.5rem; text-align: left; }
 
 
 
 
 
54
  th { background-color: #f3f4f6; font-weight: 600; }
55
  .table-responsive { overflow-x: auto; }
 
56
  #saveButton {
57
  background: #3b82f6; color: white; padding: 0.5rem 1rem;
58
  border-radius: 0.375rem; transition: background-color 0.2s ease;
59
  }
60
  #saveButton:hover { background: #2563eb; }
61
-
62
- /* Modal plein écran pour les sauvegardes (style original) */
63
  #savedModal {
64
- display: none;
65
- position: fixed;
66
- inset: 0;
67
- background: rgba(0,0,0,0.5);
68
- z-index: 50;
69
  }
70
  #savedModal.active { display: block; }
71
  #savedModalContent {
72
- background: #fff;
73
- width: 100%;
74
- height: 100%;
75
- overflow-y: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
  </style>
78
  </head>
@@ -81,7 +229,7 @@
81
  <header class="p-6 text-center mb-8">
82
  <h1 class="text-4xl font-bold text-blue-600">Mariam M-1</h1>
83
  <p class="text-gray-600">Solution Mathématique/Physique/Chimie Intelligente</p>
84
-
85
  <div class="mt-4 flex justify-end">
86
  <button id="openSaved" class="blue-button px-4 py-2 text-white rounded">Sauvegardes</button>
87
  </div>
@@ -89,8 +237,9 @@
89
 
90
  <main id="mainContent">
91
  <form id="problemForm" class="space-y-6" novalidate>
 
92
  <div class="uploadArea p-8 text-center relative" aria-label="Zone de dépôt d'image">
93
- <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
94
  <div class="space-y-3">
95
  <div class="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
96
  <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
@@ -102,20 +251,110 @@
102
  <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
103
  </div>
104
  </div>
 
 
105
  <div id="imagePreview" class="hidden text-center">
106
  <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation de l'image">
107
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  <button type="submit" id="submitButton" class="blue-button w-full py-3 text-white font-medium rounded-lg">
109
  Résoudre le problème
110
  </button>
111
  </form>
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  <div id="loader" class="hidden mt-8 text-center">
114
  <span class="loader"></span>
115
  <p class="mt-4 text-gray-600">Analyse en cours...</p>
116
  </div>
117
 
118
- <section id="solutionSection" class="hidden mt-8 space-y-6 relative">
 
119
  <div class="border-t pt-4">
120
  <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-2 hover:bg-gray-100 rounded">
121
  <span class="font-medium text-gray-700">Processus de Réflexion</span>
@@ -126,9 +365,9 @@
126
  </div>
127
  </div>
128
  <div class="border-t pt-6">
129
- <div class="flex justify-between items-center mb-4">
130
- <h3 class="text-xl font-bold text-gray-800">Solution</h3>
131
- <button id="saveButton" class="blue-button">Sauvegarder</button>
132
  </div>
133
  <div id="answerContent" class="text-gray-700 table-responsive"></div>
134
  </div>
@@ -136,18 +375,20 @@
136
  </main>
137
  </div>
138
 
139
- <!-- Modal plein écran pour les sauvegardes (structure originale) -->
140
  <div id="savedModal">
141
- <div id="savedModalContent" class="p-6"> <!-- p-6 est important ici pour le contenu du modal -->
142
  <header class="flex justify-between items-center border-b pb-4">
143
  <h2 class="text-2xl font-bold">Sauvegardes</h2>
144
  <button id="closeSaved" class="text-3xl text-gray-600 hover:text-gray-800">×</button>
145
  </header>
146
  <div id="savedListContainer" class="mt-4">
147
- <ul id="savedList" class="space-y-4"></ul>
 
 
148
  </div>
149
  <div class="mt-6">
150
- <button id="newExerciseButton" class="blue-button w-full py-3 text-white font-medium rounded-lg">
151
  Résoudre un nouvel exercice
152
  </button>
153
  </div>
@@ -156,18 +397,13 @@
156
 
157
  <script>
158
  document.addEventListener('DOMContentLoaded', () => {
159
- // Configuration
160
- const COOLDOWN_DURATION_MS = 3 * 60 * 1000; // 3 minutes
161
- const LAST_SUBMISSION_TIME_KEY = 'mariamM0_lastSubmissionTime';
162
- const SUBMIT_BUTTON_ORIGINAL_TEXT = 'Résoudre le problème';
163
-
164
- // DOM Elements
165
  const elements = {
166
  form: document.getElementById('problemForm'),
167
  imageInput: document.getElementById('imageInput'),
168
  submitButton: document.getElementById('submitButton'),
169
  loader: document.getElementById('loader'),
170
- solutionSection: document.getElementById('solutionSection'),
171
  thoughtsContent: document.getElementById('thoughtsContent'),
172
  answerContent: document.getElementById('answerContent'),
173
  thoughtsToggle: document.getElementById('thoughtsToggle'),
@@ -180,23 +416,65 @@
180
  closeSaved: document.getElementById('closeSaved'),
181
  savedModal: document.getElementById('savedModal'),
182
  savedList: document.getElementById('savedList'),
183
- newExerciseButton: document.getElementById('newExerciseButton'), // Changé l'ID pour correspondre au HTML
 
 
 
 
 
 
 
 
184
  dropZone: document.querySelector('.uploadArea')
185
  };
186
 
187
- // Application State
188
  const state = {
189
  startTime: null,
190
  timerInterval: null,
191
  cooldownTimerInterval: null,
192
  thoughtsBuffer: '',
193
  answerBuffer: '',
194
- currentMode: null, // 'thinking' or 'answering'
195
  updateTimeout: null,
196
- selectedFile: null
 
 
197
  };
198
 
199
- marked.setOptions({ gfm: true, breaks: true });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  // --- Helper Functions ---
202
  const formatTime = (totalSeconds) => {
@@ -223,30 +501,51 @@
223
  clearInterval(state.timerInterval);
224
  state.timerInterval = null;
225
  };
226
-
227
  const resetSolutionTimer = () => {
228
  stopSolutionTimer();
229
  state.startTime = null;
230
  elements.timestamp.textContent = '';
231
  };
232
 
233
- const resetUIForNewProblem = () => {
234
- elements.form.reset();
235
- elements.imageInput.value = '';
236
- state.selectedFile = null;
237
- elements.imagePreview.classList.add('hidden');
238
- elements.previewImage.src = '#';
239
- elements.solutionSection.classList.add('hidden');
240
- elements.loader.classList.add('hidden');
241
- elements.thoughtsContent.innerHTML = '';
242
- elements.answerContent.innerHTML = '';
243
- state.thoughtsBuffer = '';
244
- state.answerBuffer = '';
245
- state.currentMode = null;
246
- elements.thoughtsBox.classList.remove('open');
247
- elements.form.classList.remove('hidden');
248
- resetSolutionTimer();
249
- updateSubmitButtonState();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  };
251
 
252
  // --- Cooldown Logic ---
@@ -283,14 +582,37 @@
283
  }
284
  };
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  // --- File Handling ---
287
  const handleFileSelect = (file) => {
288
  if (!file || !file.type.startsWith('image/')) {
289
- Swal.fire('Fichier Invalide', 'Veuillez sélectionner un fichier image.', 'error');
290
- elements.imageInput.value = '';
291
- state.selectedFile = null;
292
- elements.imagePreview.classList.add('hidden');
293
- return;
294
  }
295
  state.selectedFile = file;
296
  const reader = new FileReader();
@@ -304,18 +626,24 @@
304
  // --- MathJax & Display Update ---
305
  const typesetMathJaxContent = async (contentElement) => {
306
  if (window.mathJaxReady && contentElement) {
307
- MathJax.startup.document.elements = [contentElement];
308
- try {
309
- await MathJax.typesetPromise();
310
- contentElement.scrollTop = contentElement.scrollHeight;
311
- } catch(err) {
312
- console.error("MathJax typesetting error:", err);
313
- }
314
  } else if (contentElement) {
315
- setTimeout(() => typesetMathJaxContent(contentElement), 200);
316
  }
317
  };
318
 
 
 
 
 
 
 
319
  const scheduleDisplayUpdate = () => {
320
  if (state.updateTimeout) clearTimeout(state.updateTimeout);
321
  state.updateTimeout = setTimeout(async () => {
@@ -325,16 +653,54 @@
325
  if (state.thoughtsBuffer) await typesetMathJaxContent(elements.thoughtsContent);
326
  if (state.answerBuffer) await typesetMathJaxContent(elements.answerContent);
327
 
 
 
328
  state.updateTimeout = null;
329
  }, 150);
330
  };
331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  // --- Event Listeners ---
333
  elements.thoughtsToggle.addEventListener('click', () => elements.thoughtsBox.classList.toggle('open'));
334
  elements.imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
335
 
336
- elements.dropZone.addEventListener('dragover', e => { e.preventDefault(); elements.dropZone.classList.add('border-blue-400'); });
337
- elements.dropZone.addEventListener('dragleave', e => { e.preventDefault(); elements.dropZone.classList.remove('border-blue-400'); });
 
 
 
 
 
 
 
338
  elements.dropZone.addEventListener('drop', e => {
339
  e.preventDefault();
340
  elements.dropZone.classList.remove('border-blue-400');
@@ -344,8 +710,10 @@
344
  }
345
  });
346
 
 
347
  elements.form.addEventListener('submit', async e => {
348
  e.preventDefault();
 
349
  if (!state.selectedFile) {
350
  Swal.fire('Aucune Image', 'Veuillez sélectionner une image.', 'warning');
351
  return;
@@ -362,6 +730,11 @@
362
  setLastSubmissionTime();
363
  updateSubmitButtonState();
364
 
 
 
 
 
 
365
  startSolutionTimer();
366
  elements.loader.classList.remove('hidden');
367
  elements.solutionSection.classList.add('hidden');
@@ -372,11 +745,21 @@
372
  state.currentMode = null;
373
  elements.thoughtsBox.classList.add('open');
374
 
 
 
 
 
 
 
 
 
375
  const formData = new FormData();
376
  formData.append('image', state.selectedFile);
 
 
377
 
378
  try {
379
- const response = await fetch('/solved', { method: 'POST', body: formData });
380
  if (!response.ok) {
381
  throw new Error(`HTTP error! status: ${response.status}`);
382
  }
@@ -384,32 +767,42 @@
384
  const decoder = new TextDecoder();
385
  let streamBuffer = '';
386
 
387
- // eslint-disable-next-line no-constant-condition
388
  while (true) {
389
  const { done, value } = await reader.read();
390
  if (done) {
391
  if (streamBuffer.startsWith('data:')) {
392
- try {
393
- const data = JSON.parse(streamBuffer.slice(5));
394
- if (data.content) {
395
- if (state.currentMode === 'thinking') state.thoughtsBuffer += data.content;
396
- else if (state.currentMode === 'answering') state.answerBuffer += data.content;
397
- }
398
- } catch (parseError) { console.warn("Error parsing final chunk:", parseError, "Buffer:", streamBuffer); }
 
 
399
  }
400
  scheduleDisplayUpdate();
 
 
 
 
 
 
 
 
401
  break;
402
  }
403
 
404
  streamBuffer += decoder.decode(value, { stream: true });
405
  const parts = streamBuffer.split('\n\n');
406
- streamBuffer = parts.pop();
407
 
408
  for (const part of parts) {
409
  if (!part.startsWith('data:')) continue;
410
  try {
411
  const jsonData = part.slice(5);
412
  const data = JSON.parse(jsonData);
 
413
  if (data.mode) {
414
  state.currentMode = data.mode;
415
  if (!elements.loader.classList.contains('hidden')) {
@@ -417,12 +810,61 @@
417
  elements.solutionSection.classList.remove('hidden');
418
  }
419
  }
 
420
  if (data.content) {
421
- if (state.currentMode === 'thinking') state.thoughtsBuffer += data.content;
422
- else if (state.currentMode === 'answering') state.answerBuffer += data.content;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  }
424
- } catch (parseError) {
425
- console.warn("Error parsing stream part:", parseError, "Part:", part);
426
  }
427
  }
428
  scheduleDisplayUpdate();
@@ -431,32 +873,35 @@
431
  console.error('Erreur de soumission:', error);
432
  Swal.fire('Erreur', `Une erreur est survenue lors de la résolution: ${error.message}`, 'error');
433
  elements.loader.classList.add('hidden');
 
434
  } finally {
435
- stopSolutionTimer();
436
  }
437
  });
438
 
439
  // --- Saved Solutions Logic ---
440
  elements.saveButton.addEventListener('click', async () => {
441
  const { value: saveName } = await Swal.fire({
442
- title: 'Nom de la sauvegarde',
443
- input: 'text',
444
- inputPlaceholder: 'Ex: Exercice Maths Ch.3',
445
- showCancelButton: true,
446
- confirmButtonText: 'Sauvegarder',
447
- cancelButtonText: 'Annuler',
448
- inputValidator: (value) => {
449
- if (!value) return 'Vous devez entrer un nom !';
450
- const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
451
- if (savedExercises[value]) return 'Ce nom existe déjà. Choisissez-en un autre.';
452
- }
453
  });
454
 
455
  if (saveName) {
456
  const saveData = {
457
  answer: elements.answerContent.innerHTML,
458
  thinking: elements.thoughtsContent.innerHTML,
459
- date: new Date().toLocaleString('fr-FR')
 
 
460
  };
461
  let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
462
  savedExercises[saveName] = saveData;
@@ -469,15 +914,26 @@
469
  elements.savedList.innerHTML = '';
470
  const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
471
  if (Object.keys(savedExercises).length === 0) {
472
- elements.savedList.innerHTML = '<li class="text-gray-500">Aucune sauvegarde pour le moment.</li>';
473
- return;
474
  }
 
475
  for (const [name, data] of Object.entries(savedExercises).sort((a,b) => new Date(b[1].date) - new Date(a[1].date))) {
476
  const li = document.createElement('li');
477
- li.className = 'flex justify-between items-center p-2 hover:bg-gray-100 rounded'; // Un peu de style pour la liste est ok
 
 
 
 
 
 
 
 
 
478
  li.innerHTML = `
479
  <button class="text-left text-blue-600 hover:underline focus:outline-none" data-save-name="${name}">
480
- ${name} <span class="text-gray-500 text-xs">(${data.date})</span>
 
481
  </button>
482
  <button class="text-red-500 hover:text-red-700 text-xs p-1 focus:outline-none" data-delete-name="${name}" aria-label="Supprimer ${name}">
483
  Supprimer
@@ -506,53 +962,66 @@
506
  elements.answerContent.innerHTML = data.answer;
507
  state.thoughtsBuffer = '';
508
  state.answerBuffer = '';
509
- typesetMathJaxContent(elements.thoughtsContent).then(() => typesetMathJaxContent(elements.answerContent));
 
 
 
 
510
  elements.thoughtsBox.classList.add('open');
511
  elements.savedModal.classList.remove('active');
512
  resetSolutionTimer();
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  }
514
  } else if (deleteName) {
515
- Swal.fire({
516
- title: `Supprimer "${deleteName}" ?`,
517
- text: "Cette action est irréversible.",
518
- icon: 'warning',
519
- showCancelButton: true,
520
- confirmButtonColor: '#d33',
521
- cancelButtonColor: '#3085d6',
522
- confirmButtonText: 'Oui, supprimer !',
523
- cancelButtonText: 'Annuler'
524
- }).then((result) => {
525
- if (result.isConfirmed) {
526
- let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
527
- delete savedExercises[deleteName];
528
- localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
529
- loadSavedList();
530
- Swal.fire('Supprimé!', `"${deleteName}" a été supprimé.`, 'success');
531
- }
532
- });
533
  }
534
  });
535
 
536
- elements.openSaved.addEventListener('click', () => { loadSavedList(); elements.savedModal.classList.add('active'); });
537
- elements.closeSaved.addEventListener('click', () => elements.savedModal.classList.remove('active'));
 
 
 
 
 
 
538
 
539
- // Référence au bouton "Nouvel exercice" dans le modal
540
- elements.newExerciseButton.addEventListener('click', () => {
541
  resetUIForNewProblem();
542
  elements.savedModal.classList.remove('active');
543
  });
544
 
545
  // --- Initialization ---
546
  resetUIForNewProblem();
547
-
548
- // Optionnel: Fermer le modal en cliquant à l'extérieur (si le modal n'est pas plein écran)
549
- // Pour un modal plein écran, cette logique n'est pas pertinente.
550
- // elements.savedModal.addEventListener('click', (e) => {
551
- // if (e.target === elements.savedModal) { // Ne fonctionne bien que si savedModalContent est plus petit que savedModal
552
- // elements.savedModal.classList.remove('active');
553
- // }
554
- // });
555
-
556
  });
557
  </script>
558
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mariam M-1 | Solution Mathématique Intelligente</title>
7
  <!-- Tailwind CSS -->
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
9
  <!-- SweetAlert2 -->
10
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
+ <!-- Highlight.js pour la coloration syntaxique -->
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
14
+ <!-- Configuration de MathJax -->
15
  <script>
16
  window.MathJax = {
17
  tex: {
 
31
  </script>
32
  <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script" async></script>
33
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"></script>
34
+
35
  <style>
36
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
37
  body { font-family: 'Space Grotesk', sans-serif; }
38
+
39
+ .uploadArea {
40
+ background: #f3f4f6;
41
+ border: 2px dashed #d1d5db;
42
+ transition: border-color 0.2s ease;
43
+ }
44
  .uploadArea:hover { border-color: #3b82f6; }
45
+
46
  .blue-button { background: #3b82f6; transition: background-color 0.2s ease; }
47
+ .blue-button:hover:not(:disabled) { background: #2563eb; }
48
+ .blue-button:disabled {
49
+ background: #9ca3af;
50
+ cursor: not-allowed;
51
+ }
52
+
53
+ .green-button { background: #10b981; transition: background-color 0.2s ease; }
54
+ .green-button:hover:not(:disabled) { background: #059669; }
55
+ .green-button.active { background: #059669; }
56
+
57
+ .purple-button { background: #8b5cf6; transition: background-color 0.2s ease; }
58
+ .purple-button:hover:not(:disabled) { background: #7c3aed; }
59
+ .purple-button.active { background: #7c3aed; }
60
+
61
  .loader {
62
  width: 48px; height: 48px; border: 3px solid #3b82f6; border-bottom-color: transparent;
63
  border-radius: 50%; display: inline-block; animation: rotation 1s linear infinite;
64
  }
65
  @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
66
+
67
+ .thought-box {
68
+ transition: max-height 0.3s ease-out; max-height: 0; overflow: hidden;
69
+ }
70
+ .thought-box.open { max-height: 800px; }
71
+
72
  #thoughtsContent, #answerContent {
73
+ max-height: 500px; overflow-y: auto; scroll-behavior: smooth; white-space: pre-wrap;
 
74
  }
75
+
76
  .preview-image { max-width: 300px; max-height: 300px; object-fit: contain; }
77
  .timestamp { color: #3b82f6; font-size: 0.9em; margin-left: 8px; }
78
+
79
+ table {
80
+ border-collapse: collapse; width: 100%; margin-bottom: 1rem;
81
+ }
82
+ th, td {
83
+ border: 1px solid #d1d5db; padding: 0.5rem; text-align: left;
84
+ }
85
  th { background-color: #f3f4f6; font-weight: 600; }
86
  .table-responsive { overflow-x: auto; }
87
+
88
  #saveButton {
89
  background: #3b82f6; color: white; padding: 0.5rem 1rem;
90
  border-radius: 0.375rem; transition: background-color 0.2s ease;
91
  }
92
  #saveButton:hover { background: #2563eb; }
93
+
94
+ /* Modal plein écran pour les sauvegardes */
95
  #savedModal {
96
+ display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 50;
 
 
 
 
97
  }
98
  #savedModal.active { display: block; }
99
  #savedModalContent {
100
+ background: #fff; width: 100%; height: 100%; overflow-y: auto;
101
+ }
102
+
103
+ /* Styles pour le code et son exécution */
104
+ pre {
105
+ background-color: #f8f8f8; border: 1px solid #e2e8f0; border-radius: 0.375rem;
106
+ padding: 1rem; margin: 1rem 0; overflow-x: auto;
107
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
108
+ }
109
+
110
+ code {
111
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
112
+ }
113
+
114
+ .code-execution-result {
115
+ background-color: #f0fff4; border-left: 4px solid #48bb78;
116
+ padding: 1rem; margin: 1rem 0;
117
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
118
+ white-space: pre-wrap;
119
+ }
120
+
121
+ .content-text {}
122
+ .content-code { padding: 0; }
123
+ .content-result {
124
+ background-color: #f0fff4; border-left: 4px solid #48bb78;
125
+ padding: 1rem; margin: 0.5rem 0;
126
+ }
127
+ .content-image { margin: 1rem 0; text-align: center; }
128
+ .content-image img { max-width: 100%; }
129
+
130
+ /* Styles pour les options */
131
+ .option-card {
132
+ background: #f8fafc; border: 2px solid #e2e8f0; border-radius: 0.5rem;
133
+ padding: 1rem; margin: 1rem 0; transition: all 0.2s ease;
134
+ }
135
+ .option-card.enabled {
136
+ border-color: #10b981;
137
+ }
138
+ .option-card.enabled.calculator {
139
+ background: #ecfdf5; border-color: #10b981;
140
+ }
141
+ .option-card.enabled.reasoning {
142
+ background: #f3e8ff; border-color: #8b5cf6;
143
+ }
144
+
145
+ .toggle-switch {
146
+ position: relative; display: inline-block; width: 60px; height: 34px;
147
+ }
148
+ .toggle-switch input {
149
+ opacity: 0; width: 0; height: 0;
150
+ }
151
+ .slider {
152
+ position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
153
+ background-color: #ccc; transition: .4s; border-radius: 34px;
154
+ }
155
+ .slider:before {
156
+ position: absolute; content: ""; height: 26px; width: 26px;
157
+ left: 4px; bottom: 4px; background-color: white;
158
+ transition: .4s; border-radius: 50%;
159
+ }
160
+ input:checked + .slider {
161
+ background-color: #10b981;
162
+ }
163
+ input:checked + .slider:before {
164
+ transform: translateX(26px);
165
+ }
166
+
167
+ .slider.purple:checked {
168
+ background-color: #8b5cf6;
169
+ }
170
+
171
+ /* Styles pour les étapes du raisonnement étendu */
172
+ .reasoning-step {
173
+ border-left: 4px solid #8b5cf6;
174
+ background: #faf5ff;
175
+ padding: 1rem;
176
+ margin: 0.5rem 0;
177
+ border-radius: 0 0.375rem 0.375rem 0;
178
+ }
179
+
180
+ .reasoning-step h3 {
181
+ color: #7c3aed;
182
+ font-weight: 600;
183
+ margin-bottom: 0.5rem;
184
+ }
185
+
186
+ .progress-indicator {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 0.5rem;
190
+ margin-bottom: 1rem;
191
+ }
192
+
193
+ .step-indicator {
194
+ width: 2rem;
195
+ height: 2rem;
196
+ border-radius: 50%;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ font-size: 0.875rem;
201
+ font-weight: 600;
202
+ transition: all 0.3s ease;
203
+ }
204
+
205
+ .step-indicator.pending {
206
+ background: #e5e7eb;
207
+ color: #6b7280;
208
+ }
209
+
210
+ .step-indicator.active {
211
+ background: #8b5cf6;
212
+ color: white;
213
+ animation: pulse 2s infinite;
214
+ }
215
+
216
+ .step-indicator.completed {
217
+ background: #10b981;
218
+ color: white;
219
+ }
220
+
221
+ @keyframes pulse {
222
+ 0%, 100% { opacity: 1; }
223
+ 50% { opacity: 0.7; }
224
  }
225
  </style>
226
  </head>
 
229
  <header class="p-6 text-center mb-8">
230
  <h1 class="text-4xl font-bold text-blue-600">Mariam M-1</h1>
231
  <p class="text-gray-600">Solution Mathématique/Physique/Chimie Intelligente</p>
232
+
233
  <div class="mt-4 flex justify-end">
234
  <button id="openSaved" class="blue-button px-4 py-2 text-white rounded">Sauvegardes</button>
235
  </div>
 
237
 
238
  <main id="mainContent">
239
  <form id="problemForm" class="space-y-6" novalidate>
240
+ <!-- Zone de dépôt / sélection d'image -->
241
  <div class="uploadArea p-8 text-center relative" aria-label="Zone de dépôt d'image">
242
+ <input type="file" id="imageInput" name="image" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
243
  <div class="space-y-3">
244
  <div class="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
245
  <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
 
251
  <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
252
  </div>
253
  </div>
254
+
255
+ <!-- Aperçu de l'image -->
256
  <div id="imagePreview" class="hidden text-center">
257
  <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation de l'image">
258
  </div>
259
+
260
+ <!-- Option Calculatrice -->
261
+ <div id="calculatorOption" class="option-card calculator">
262
+ <div class="flex items-center justify-between">
263
+ <div class="flex items-center space-x-3">
264
+ <div class="text-2xl">🧮</div>
265
+ <div>
266
+ <h3 class="font-semibold text-gray-800">Mode Calculatrice</h3>
267
+ <p class="text-sm text-gray-600">Active l'exécution de code Python pour les calculs numériques et graphiques</p>
268
+ </div>
269
+ </div>
270
+ <label class="toggle-switch">
271
+ <input type="checkbox" id="calculatorToggle">
272
+ <span class="slider"></span>
273
+ </label>
274
+ </div>
275
+
276
+ <div id="calculatorFeatures" class="mt-3 text-xs text-gray-500 hidden">
277
+ <div class="grid grid-cols-2 gap-2">
278
+ <div class="flex items-center space-x-1">
279
+ <span>✓</span><span>Calculs numériques précis</span>
280
+ </div>
281
+ <div class="flex items-center space-x-1">
282
+ <span>✓</span><span>Graphiques matplotlib</span>
283
+ </div>
284
+ <div class="flex items-center space-x-1">
285
+ <span>✓</span><span>Vérification des résultats</span>
286
+ </div>
287
+ <div class="flex items-center space-x-1">
288
+ <span>✓</span><span>Visualisations 2D/3D</span>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </div>
293
+
294
+ <!-- Option Raisonnement Étendu -->
295
+ <div id="reasoningOption" class="option-card reasoning">
296
+ <div class="flex items-center justify-between">
297
+ <div class="flex items-center space-x-3">
298
+ <div class="text-2xl">🧠</div>
299
+ <div>
300
+ <h3 class="font-semibold text-gray-800">Raisonnement Étendu</h3>
301
+ <p class="text-sm text-gray-600">Système multi-agents avec auto-amélioration et vérification rigoureuse</p>
302
+ </div>
303
+ </div>
304
+ <label class="toggle-switch">
305
+ <input type="checkbox" id="reasoningToggle">
306
+ <span class="slider purple"></span>
307
+ </label>
308
+ </div>
309
+
310
+ <div id="reasoningFeatures" class="mt-3 text-xs text-gray-500 hidden">
311
+ <div class="grid grid-cols-1 gap-2">
312
+ <div class="flex items-center space-x-1">
313
+ <span>✓</span><span>Génération de solution initiale</span>
314
+ </div>
315
+ <div class="flex items-center space-x-1">
316
+ <span>✓</span><span>Auto-amélioration de la solution</span>
317
+ </div>
318
+ <div class="flex items-center space-x-1">
319
+ <span>✓</span><span>Vérification rigoureuse par agent expert</span>
320
+ </div>
321
+ <div class="flex items-center space-x-1">
322
+ <span>✓</span><span>Correction automatique des erreurs</span>
323
+ </div>
324
+ </div>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- Le bouton de soumission -->
329
  <button type="submit" id="submitButton" class="blue-button w-full py-3 text-white font-medium rounded-lg">
330
  Résoudre le problème
331
  </button>
332
  </form>
333
 
334
+ <!-- Indicateur de progression pour le raisonnement étendu -->
335
+ <div id="progressIndicator" class="hidden mt-8">
336
+ <div class="progress-indicator">
337
+ <div class="step-indicator pending" data-step="0">📝</div>
338
+ <div class="flex-1 h-1 bg-gray-300 rounded"></div>
339
+ <div class="step-indicator pending" data-step="1">🔧</div>
340
+ <div class="flex-1 h-1 bg-gray-300 rounded"></div>
341
+ <div class="step-indicator pending" data-step="2">✅</div>
342
+ <div class="flex-1 h-1 bg-gray-300 rounded"></div>
343
+ <div class="step-indicator pending" data-step="3">🛠️</div>
344
+ </div>
345
+ <div class="text-sm text-gray-600 mt-2">
346
+ <span id="currentStepText">Préparation...</span>
347
+ </div>
348
+ </div>
349
+
350
+ <!-- Loader -->
351
  <div id="loader" class="hidden mt-8 text-center">
352
  <span class="loader"></span>
353
  <p class="mt-4 text-gray-600">Analyse en cours...</p>
354
  </div>
355
 
356
+ <!-- Zone d'affichage de la solution -->
357
+ <section id="solution" class="hidden mt-8 space-y-6 relative">
358
  <div class="border-t pt-4">
359
  <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-2 hover:bg-gray-100 rounded">
360
  <span class="font-medium text-gray-700">Processus de Réflexion</span>
 
365
  </div>
366
  </div>
367
  <div class="border-t pt-6">
368
+ <div class="flex justify-between items-center">
369
+ <h3 class="text-xl font-bold text-gray-800 mb-4">Solution</h3>
370
+ <button id="saveButton">Sauvegarder</button>
371
  </div>
372
  <div id="answerContent" class="text-gray-700 table-responsive"></div>
373
  </div>
 
375
  </main>
376
  </div>
377
 
378
+ <!-- Modal plein écran pour les sauvegardes -->
379
  <div id="savedModal">
380
+ <div id="savedModalContent" class="p-6">
381
  <header class="flex justify-between items-center border-b pb-4">
382
  <h2 class="text-2xl font-bold">Sauvegardes</h2>
383
  <button id="closeSaved" class="text-3xl text-gray-600 hover:text-gray-800">×</button>
384
  </header>
385
  <div id="savedListContainer" class="mt-4">
386
+ <ul id="savedList" class="space-y-4">
387
+ <!-- Liste des sauvegardes insérée dynamiquement -->
388
+ </ul>
389
  </div>
390
  <div class="mt-6">
391
+ <button id="newExercise" class="blue-button w-full py-3 text-white font-medium rounded-lg">
392
  Résoudre un nouvel exercice
393
  </button>
394
  </div>
 
397
 
398
  <script>
399
  document.addEventListener('DOMContentLoaded', () => {
400
+ // Récupération des éléments
 
 
 
 
 
401
  const elements = {
402
  form: document.getElementById('problemForm'),
403
  imageInput: document.getElementById('imageInput'),
404
  submitButton: document.getElementById('submitButton'),
405
  loader: document.getElementById('loader'),
406
+ solutionSection: document.getElementById('solution'),
407
  thoughtsContent: document.getElementById('thoughtsContent'),
408
  answerContent: document.getElementById('answerContent'),
409
  thoughtsToggle: document.getElementById('thoughtsToggle'),
 
416
  closeSaved: document.getElementById('closeSaved'),
417
  savedModal: document.getElementById('savedModal'),
418
  savedList: document.getElementById('savedList'),
419
+ newExercise: document.getElementById('newExercise'),
420
+ calculatorToggle: document.getElementById('calculatorToggle'),
421
+ calculatorOption: document.getElementById('calculatorOption'),
422
+ calculatorFeatures: document.getElementById('calculatorFeatures'),
423
+ reasoningToggle: document.getElementById('reasoningToggle'),
424
+ reasoningOption: document.getElementById('reasoningOption'),
425
+ reasoningFeatures: document.getElementById('reasoningFeatures'),
426
+ progressIndicator: document.getElementById('progressIndicator'),
427
+ currentStepText: document.getElementById('currentStepText'),
428
  dropZone: document.querySelector('.uploadArea')
429
  };
430
 
431
+ // État de l'application
432
  const state = {
433
  startTime: null,
434
  timerInterval: null,
435
  cooldownTimerInterval: null,
436
  thoughtsBuffer: '',
437
  answerBuffer: '',
438
+ currentMode: null,
439
  updateTimeout: null,
440
+ selectedFile: null,
441
+ currentStep: -1,
442
+ isExtendedReasoning: false
443
  };
444
 
445
+ // Configuration
446
+ const COOLDOWN_DURATION_MS = 3 * 60 * 1000; // 3 minutes
447
+ const LAST_SUBMISSION_TIME_KEY = 'mariamM1_lastSubmissionTime';
448
+ const SUBMIT_BUTTON_ORIGINAL_TEXT = 'Résoudre le problème';
449
+
450
+ const stepTexts = {
451
+ 0: "Extraction du problème de l'image...",
452
+ 1: "Génération de la solution initiale...",
453
+ 2: "Auto-amélioration de la solution...",
454
+ 3: "Vérification rigoureuse...",
455
+ 4: "Correction des erreurs identifiées..."
456
+ };
457
+
458
+ marked.setOptions({
459
+ gfm: true,
460
+ breaks: true,
461
+ highlight: function(code, lang) {
462
+ if (lang && hljs.getLanguage(lang)) {
463
+ try {
464
+ return hljs.highlight(code, { language: lang }).value;
465
+ } catch (error) {
466
+ console.error("Highlighting error:", error);
467
+ return code;
468
+ }
469
+ }
470
+ try {
471
+ return hljs.highlightAuto(code).value;
472
+ } catch (error) {
473
+ console.error("Auto highlighting error:", error);
474
+ return code;
475
+ }
476
+ }
477
+ });
478
 
479
  // --- Helper Functions ---
480
  const formatTime = (totalSeconds) => {
 
501
  clearInterval(state.timerInterval);
502
  state.timerInterval = null;
503
  };
504
+
505
  const resetSolutionTimer = () => {
506
  stopSolutionTimer();
507
  state.startTime = null;
508
  elements.timestamp.textContent = '';
509
  };
510
 
511
+ const updateProgressStep = (step) => {
512
+ // Marquer les étapes précédentes comme complétées
513
+ for (let i = 0; i < step; i++) {
514
+ const indicator = document.querySelector(`[data-step="${i}"]`);
515
+ if (indicator) {
516
+ indicator.className = 'step-indicator completed';
517
+ }
518
+ }
519
+
520
+ // Marquer l'étape courante comme active
521
+ const currentIndicator = document.querySelector(`[data-step="${step}"]`);
522
+ if (currentIndicator) {
523
+ currentIndicator.className = 'step-indicator active';
524
+ }
525
+
526
+ // Marquer les étapes suivantes comme en attente
527
+ for (let i = step + 1; i < 4; i++) {
528
+ const indicator = document.querySelector(`[data-step="${i}"]`);
529
+ if (indicator) {
530
+ indicator.className = 'step-indicator pending';
531
+ }
532
+ }
533
+
534
+ // Mettre à jour le texte
535
+ if (stepTexts[step]) {
536
+ elements.currentStepText.textContent = stepTexts[step];
537
+ }
538
+
539
+ state.currentStep = step;
540
+ };
541
+
542
+ const resetProgressIndicator = () => {
543
+ const indicators = document.querySelectorAll('.step-indicator');
544
+ indicators.forEach(indicator => {
545
+ indicator.className = 'step-indicator pending';
546
+ });
547
+ elements.currentStepText.textContent = 'Préparation...';
548
+ state.currentStep = -1;
549
  };
550
 
551
  // --- Cooldown Logic ---
 
582
  }
583
  };
584
 
585
+ // --- Toggle Handlers ---
586
+ elements.calculatorToggle.addEventListener('change', () => {
587
+ const isEnabled = elements.calculatorToggle.checked;
588
+ if (isEnabled) {
589
+ elements.calculatorOption.classList.add('enabled');
590
+ elements.calculatorFeatures.classList.remove('hidden');
591
+ } else {
592
+ elements.calculatorOption.classList.remove('enabled');
593
+ elements.calculatorFeatures.classList.add('hidden');
594
+ }
595
+ });
596
+
597
+ elements.reasoningToggle.addEventListener('change', () => {
598
+ const isEnabled = elements.reasoningToggle.checked;
599
+ if (isEnabled) {
600
+ elements.reasoningOption.classList.add('enabled');
601
+ elements.reasoningFeatures.classList.remove('hidden');
602
+ } else {
603
+ elements.reasoningOption.classList.remove('enabled');
604
+ elements.reasoningFeatures.classList.add('hidden');
605
+ }
606
+ });
607
+
608
  // --- File Handling ---
609
  const handleFileSelect = (file) => {
610
  if (!file || !file.type.startsWith('image/')) {
611
+ Swal.fire('Fichier Invalide', 'Veuillez sélectionner un fichier image.', 'error');
612
+ elements.imageInput.value = '';
613
+ state.selectedFile = null;
614
+ elements.imagePreview.classList.add('hidden');
615
+ return;
616
  }
617
  state.selectedFile = file;
618
  const reader = new FileReader();
 
626
  // --- MathJax & Display Update ---
627
  const typesetMathJaxContent = async (contentElement) => {
628
  if (window.mathJaxReady && contentElement) {
629
+ MathJax.startup.document.elements = [contentElement];
630
+ try {
631
+ await MathJax.typesetPromise();
632
+ contentElement.scrollTop = contentElement.scrollHeight;
633
+ } catch(err) {
634
+ console.error("MathJax typesetting error:", err);
635
+ }
636
  } else if (contentElement) {
637
+ setTimeout(() => typesetMathJaxContent(contentElement), 200);
638
  }
639
  };
640
 
641
+ const applyHighlighting = () => {
642
+ document.querySelectorAll('pre code').forEach((block) => {
643
+ hljs.highlightBlock(block);
644
+ });
645
+ };
646
+
647
  const scheduleDisplayUpdate = () => {
648
  if (state.updateTimeout) clearTimeout(state.updateTimeout);
649
  state.updateTimeout = setTimeout(async () => {
 
653
  if (state.thoughtsBuffer) await typesetMathJaxContent(elements.thoughtsContent);
654
  if (state.answerBuffer) await typesetMathJaxContent(elements.answerContent);
655
 
656
+ applyHighlighting();
657
+
658
  state.updateTimeout = null;
659
  }, 150);
660
  };
661
 
662
+ const resetUIForNewProblem = () => {
663
+ elements.form.reset();
664
+ elements.imageInput.value = '';
665
+ state.selectedFile = null;
666
+ elements.imagePreview.classList.add('hidden');
667
+ elements.previewImage.src = '#';
668
+ elements.solutionSection.classList.add('hidden');
669
+ elements.loader.classList.add('hidden');
670
+ elements.progressIndicator.classList.add('hidden');
671
+ elements.thoughtsContent.innerHTML = '';
672
+ elements.answerContent.innerHTML = '';
673
+ state.thoughtsBuffer = '';
674
+ state.answerBuffer = '';
675
+ state.currentMode = null;
676
+ state.isExtendedReasoning = false;
677
+ elements.thoughtsBox.classList.remove('open');
678
+ elements.form.classList.remove('hidden');
679
+ resetSolutionTimer();
680
+ resetProgressIndicator();
681
+ updateSubmitButtonState();
682
+ // Reset toggles
683
+ elements.calculatorToggle.checked = false;
684
+ elements.calculatorOption.classList.remove('enabled');
685
+ elements.calculatorFeatures.classList.add('hidden');
686
+ elements.reasoningToggle.checked = false;
687
+ elements.reasoningOption.classList.remove('enabled');
688
+ elements.reasoningFeatures.classList.add('hidden');
689
+ };
690
+
691
  // --- Event Listeners ---
692
  elements.thoughtsToggle.addEventListener('click', () => elements.thoughtsBox.classList.toggle('open'));
693
  elements.imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
694
 
695
+ // Drag & Drop
696
+ elements.dropZone.addEventListener('dragover', e => {
697
+ e.preventDefault();
698
+ elements.dropZone.classList.add('border-blue-400');
699
+ });
700
+ elements.dropZone.addEventListener('dragleave', e => {
701
+ e.preventDefault();
702
+ elements.dropZone.classList.remove('border-blue-400');
703
+ });
704
  elements.dropZone.addEventListener('drop', e => {
705
  e.preventDefault();
706
  elements.dropZone.classList.remove('border-blue-400');
 
710
  }
711
  });
712
 
713
+ // Form submission
714
  elements.form.addEventListener('submit', async e => {
715
  e.preventDefault();
716
+
717
  if (!state.selectedFile) {
718
  Swal.fire('Aucune Image', 'Veuillez sélectionner une image.', 'warning');
719
  return;
 
730
  setLastSubmissionTime();
731
  updateSubmitButtonState();
732
 
733
+ const useCalculator = elements.calculatorToggle.checked;
734
+ const useExtendedReasoning = elements.reasoningToggle.checked;
735
+
736
+ state.isExtendedReasoning = useExtendedReasoning;
737
+
738
  startSolutionTimer();
739
  elements.loader.classList.remove('hidden');
740
  elements.solutionSection.classList.add('hidden');
 
745
  state.currentMode = null;
746
  elements.thoughtsBox.classList.add('open');
747
 
748
+ // Afficher l'indicateur de progression pour le raisonnement étendu
749
+ if (useExtendedReasoning) {
750
+ elements.progressIndicator.classList.remove('hidden');
751
+ resetProgressIndicator();
752
+ } else {
753
+ elements.progressIndicator.classList.add('hidden');
754
+ }
755
+
756
  const formData = new FormData();
757
  formData.append('image', state.selectedFile);
758
+ formData.append('use_calculator', useCalculator.toString());
759
+ formData.append('use_extended_reasoning', useExtendedReasoning.toString());
760
 
761
  try {
762
+ const response = await fetch('/solve', { method: 'POST', body: formData });
763
  if (!response.ok) {
764
  throw new Error(`HTTP error! status: ${response.status}`);
765
  }
 
767
  const decoder = new TextDecoder();
768
  let streamBuffer = '';
769
 
 
770
  while (true) {
771
  const { done, value } = await reader.read();
772
  if (done) {
773
  if (streamBuffer.startsWith('data:')) {
774
+ try {
775
+ const data = JSON.parse(streamBuffer.slice(5));
776
+ if (data.content) {
777
+ if (state.currentMode === 'thinking') state.thoughtsBuffer += data.content;
778
+ else if (state.currentMode === 'answering') state.answerBuffer += data.content;
779
+ }
780
+ } catch (parseError) {
781
+ console.warn("Error parsing final chunk:", parseError, "Buffer:", streamBuffer);
782
+ }
783
  }
784
  scheduleDisplayUpdate();
785
+
786
+ // Marquer la dernière étape comme complétée si raisonnement étendu
787
+ if (state.isExtendedReasoning && state.currentStep >= 0) {
788
+ const finalIndicator = document.querySelector(`[data-step="${state.currentStep}"]`);
789
+ if (finalIndicator) {
790
+ finalIndicator.className = 'step-indicator completed';
791
+ }
792
+ }
793
  break;
794
  }
795
 
796
  streamBuffer += decoder.decode(value, { stream: true });
797
  const parts = streamBuffer.split('\n\n');
798
+ streamBuffer = parts.pop();
799
 
800
  for (const part of parts) {
801
  if (!part.startsWith('data:')) continue;
802
  try {
803
  const jsonData = part.slice(5);
804
  const data = JSON.parse(jsonData);
805
+
806
  if (data.mode) {
807
  state.currentMode = data.mode;
808
  if (!elements.loader.classList.contains('hidden')) {
 
810
  elements.solutionSection.classList.remove('hidden');
811
  }
812
  }
813
+
814
  if (data.content) {
815
+ // Détecter les étapes du raisonnement étendu
816
+ if (state.isExtendedReasoning && state.currentMode === 'thinking') {
817
+ const content = data.content;
818
+ if (content.includes('EXTRACTION DU PROBLÈME')) {
819
+ updateProgressStep(0);
820
+ } else if (content.includes('ÉTAPE 1') || content.includes('GÉNÉRATION DE LA SOLUTION INITIALE')) {
821
+ updateProgressStep(1);
822
+ } else if (content.includes('ÉTAPE 2') || content.includes('AUTO-AMÉLIORATION')) {
823
+ updateProgressStep(2);
824
+ } else if (content.includes('ÉTAPE 3') || content.includes('VÉRIFICATION RIGOUREUSE')) {
825
+ updateProgressStep(3);
826
+ } else if (content.includes('ÉTAPE 5') || content.includes('CORRECTION DES ERREURS')) {
827
+ updateProgressStep(4);
828
+ }
829
+ }
830
+
831
+ if (state.currentMode === 'thinking') {
832
+ state.thoughtsBuffer += data.content;
833
+ } else if (state.currentMode === 'answering') {
834
+ switch(data.type) {
835
+ case 'code':
836
+ state.answerBuffer += "\n```python\n" + data.content + "\n```\n";
837
+ break;
838
+ case 'result':
839
+ const formattedResult = data.content.split('\n').map(line => `> ${line}`).join('\n');
840
+ state.answerBuffer += "\n" + formattedResult + "\n";
841
+ break;
842
+ case 'image':
843
+ state.answerBuffer += `\n![Résultat](data:image/png;base64,${data.content})\n`;
844
+ break;
845
+ case 'text':
846
+ default:
847
+ state.answerBuffer += data.content;
848
+ break;
849
+ }
850
+ }
851
+ }
852
+
853
+ if (data.error) {
854
+ if (state.currentMode === 'thinking') {
855
+ state.thoughtsBuffer += `\n**Erreur pendant la réflexion:** ${data.error}\n`;
856
+ } else {
857
+ state.answerBuffer += `\n**Erreur:** ${data.error}\n`;
858
+ }
859
+ }
860
+
861
+ } catch (e) {
862
+ console.error('Error parsing JSON data:', part.slice(5), e);
863
+ if (state.currentMode === 'thinking') {
864
+ state.thoughtsBuffer += `\n[Erreur de traitement du flux]`;
865
+ } else {
866
+ state.answerBuffer += `\n[Erreur de traitement du flux]`;
867
  }
 
 
868
  }
869
  }
870
  scheduleDisplayUpdate();
 
873
  console.error('Erreur de soumission:', error);
874
  Swal.fire('Erreur', `Une erreur est survenue lors de la résolution: ${error.message}`, 'error');
875
  elements.loader.classList.add('hidden');
876
+ elements.progressIndicator.classList.add('hidden');
877
  } finally {
878
+ stopSolutionTimer();
879
  }
880
  });
881
 
882
  // --- Saved Solutions Logic ---
883
  elements.saveButton.addEventListener('click', async () => {
884
  const { value: saveName } = await Swal.fire({
885
+ title: 'Nom de la sauvegarde',
886
+ input: 'text',
887
+ inputPlaceholder: 'Ex: Exercice Maths Ch.3',
888
+ showCancelButton: true,
889
+ confirmButtonText: 'Sauvegarder',
890
+ cancelButtonText: 'Annuler',
891
+ inputValidator: (value) => {
892
+ if (!value) return 'Vous devez entrer un nom !';
893
+ const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
894
+ if (savedExercises[value]) return 'Ce nom existe déjà. Choisissez-en un autre.';
895
+ }
896
  });
897
 
898
  if (saveName) {
899
  const saveData = {
900
  answer: elements.answerContent.innerHTML,
901
  thinking: elements.thoughtsContent.innerHTML,
902
+ date: new Date().toLocaleString('fr-FR'),
903
+ calculatorUsed: elements.calculatorToggle.checked,
904
+ extendedReasoningUsed: elements.reasoningToggle.checked
905
  };
906
  let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
907
  savedExercises[saveName] = saveData;
 
914
  elements.savedList.innerHTML = '';
915
  const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
916
  if (Object.keys(savedExercises).length === 0) {
917
+ elements.savedList.innerHTML = '<li class="text-gray-500">Aucune sauvegarde pour le moment.</li>';
918
+ return;
919
  }
920
+
921
  for (const [name, data] of Object.entries(savedExercises).sort((a,b) => new Date(b[1].date) - new Date(a[1].date))) {
922
  const li = document.createElement('li');
923
+ li.className = 'flex justify-between items-center p-2 hover:bg-gray-100 rounded';
924
+
925
+ const badges = [];
926
+ if (data.calculatorUsed) {
927
+ badges.push('<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 ml-2">🧮 Calculatrice</span>');
928
+ }
929
+ if (data.extendedReasoningUsed) {
930
+ badges.push('<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-800 ml-2">🧠 Raisonnement</span>');
931
+ }
932
+
933
  li.innerHTML = `
934
  <button class="text-left text-blue-600 hover:underline focus:outline-none" data-save-name="${name}">
935
+ ${name} ${badges.join('')}<br>
936
+ <span class="text-gray-500 text-xs">(${data.date})</span>
937
  </button>
938
  <button class="text-red-500 hover:text-red-700 text-xs p-1 focus:outline-none" data-delete-name="${name}" aria-label="Supprimer ${name}">
939
  Supprimer
 
962
  elements.answerContent.innerHTML = data.answer;
963
  state.thoughtsBuffer = '';
964
  state.answerBuffer = '';
965
+ typesetMathJaxContent(elements.thoughtsContent).then(() => {
966
+ typesetMathJaxContent(elements.answerContent).then(() => {
967
+ applyHighlighting();
968
+ });
969
+ });
970
  elements.thoughtsBox.classList.add('open');
971
  elements.savedModal.classList.remove('active');
972
  resetSolutionTimer();
973
+ elements.timestamp.textContent = data.date;
974
+
975
+ // Restore toggle states
976
+ if (data.calculatorUsed) {
977
+ elements.calculatorToggle.checked = true;
978
+ elements.calculatorOption.classList.add('enabled');
979
+ elements.calculatorFeatures.classList.remove('hidden');
980
+ }
981
+ if (data.extendedReasoningUsed) {
982
+ elements.reasoningToggle.checked = true;
983
+ elements.reasoningOption.classList.add('enabled');
984
+ elements.reasoningFeatures.classList.remove('hidden');
985
+ }
986
  }
987
  } else if (deleteName) {
988
+ Swal.fire({
989
+ title: `Supprimer "${deleteName}" ?`,
990
+ text: "Cette action est irréversible.",
991
+ icon: 'warning',
992
+ showCancelButton: true,
993
+ confirmButtonColor: '#d33',
994
+ cancelButtonColor: '#3085d6',
995
+ confirmButtonText: 'Oui, supprimer !',
996
+ cancelButtonText: 'Annuler'
997
+ }).then((result) => {
998
+ if (result.isConfirmed) {
999
+ let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
1000
+ delete savedExercises[deleteName];
1001
+ localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
1002
+ loadSavedList();
1003
+ Swal.fire('Supprimé!', `"${deleteName}" a été supprimé.`, 'success');
1004
+ }
1005
+ });
1006
  }
1007
  });
1008
 
1009
+ elements.openSaved.addEventListener('click', () => {
1010
+ loadSavedList();
1011
+ elements.savedModal.classList.add('active');
1012
+ });
1013
+
1014
+ elements.closeSaved.addEventListener('click', () => {
1015
+ elements.savedModal.classList.remove('active');
1016
+ });
1017
 
1018
+ elements.newExercise.addEventListener('click', () => {
 
1019
  resetUIForNewProblem();
1020
  elements.savedModal.classList.remove('active');
1021
  });
1022
 
1023
  // --- Initialization ---
1024
  resetUIForNewProblem();
 
 
 
 
 
 
 
 
 
1025
  });
1026
  </script>
1027
  </body>