Docfile commited on
Commit
1950996
·
verified ·
1 Parent(s): 380ae1f

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +373 -147
templates/index.html CHANGED
@@ -62,11 +62,9 @@
62
  box-shadow: var(--box-shadow);
63
  padding: 30px;
64
  margin-bottom: 30px;
65
- /* text-align: center; enlevé pour que le contenu dynamique ne soit pas centré par défaut */
66
  }
67
 
68
- /* Ajustement pour le contenu généré */
69
- .content-box > h1, .content-box > p {
70
  text-align: center;
71
  }
72
  .content-box .feature-list h2 {
@@ -75,6 +73,10 @@
75
  .content-box .upload-section {
76
  text-align: center;
77
  }
 
 
 
 
78
  .content-box .upgrade-section {
79
  text-align: center;
80
  margin-top: 30px;
@@ -126,6 +128,7 @@
126
  margin: 10px; /* Ajustement marge */
127
  border: none;
128
  cursor: pointer;
 
129
  }
130
 
131
  .cta-button:hover {
@@ -178,6 +181,7 @@
178
  overflow-wrap: break-word; /* Standard CSS */
179
  }
180
 
 
181
  .code-section {
182
  background-color: transparent !important; /* Le fond est géré par header/content */
183
  padding: 0 !important;
@@ -188,15 +192,35 @@
188
  background-color: var(--output-bg) !important;
189
  border-left: 4px solid var(--secondary-color); /* Distinction visuelle */
190
  padding-left: calc(20px - 4px);
 
 
 
 
191
  }
192
 
193
  .step-section {
194
  background-color: #ffffff; /* Fond blanc pour les étapes normales */
195
  border-left: 4px solid var(--primary-color);
196
  padding-left: calc(20px - 4px); /* Compenser la bordure */
197
- font-size: 16px;
198
  line-height: 1.8;
199
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  /* --- Amélioration du rendu KaTeX --- */
202
  .step-section .katex {
@@ -204,18 +228,22 @@
204
  line-height: normal; /* Laisser KaTeX gérer */
205
  text-indent: 0;
206
  text-rendering: auto;
207
- /* display: inline-block; */ /* Peut causer des problèmes de wrapping, à tester si besoin */
 
208
  }
209
 
210
  .step-section .katex-display {
211
  display: block; /* Comportement block pour $$...$$ */
212
  text-align: center; /* Centrer les équations display */
213
- margin: 1em 0; /* Espacement vertical */
214
  overflow-x: auto; /* Permet le scroll horizontal si équation trop large */
215
  overflow-y: hidden;
216
- padding: 0.5em 0; /* Un peu d'espace interne */
 
 
 
217
  }
218
- /* Ceci est le conteneur interne créé par KaTeX */
219
  .step-section .katex-display > .katex {
220
  display: inline-block; /* Permet le centrage et évite la pleine largeur */
221
  text-align: initial; /* Texte de l'équation aligné à gauche par défaut */
@@ -223,11 +251,12 @@
223
  }
224
  /* --- Fin Amélioration KaTeX --- */
225
 
 
226
  .code-header {
227
  background-color: #343a40;
228
  color: white;
229
  padding: 10px 15px;
230
- font-size: 14px;
231
  font-family: 'Courier New', monospace;
232
  display: flex;
233
  justify-content: space-between;
@@ -241,12 +270,27 @@
241
  color: #e6e6e6;
242
  overflow-x: auto;
243
  font-family: 'Courier New', monospace;
244
- font-size: 14px;
245
  line-height: 1.5;
246
  border-bottom: 1px solid #444; /* Bordure sous le code */
247
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- /* --- Règles pour les coins arrondis --- */
250
  #solution > *:first-child,
251
  #solution > .code-section:first-child .code-header {
252
  border-top-left-radius: 8px;
@@ -263,18 +307,32 @@
263
  #solution > *:only-child {
264
  border-radius: 8px;
265
  }
 
 
 
 
 
 
 
 
 
 
 
266
 
267
  /* --- Amélioration du rendu Markdown --- */
 
 
 
 
 
268
  .step-section ul, .step-section ol {
269
  margin-block-start: 1em;
270
  margin-block-end: 1em;
271
  padding-inline-start: 30px; /* Indentation standard */
272
  }
273
-
274
  .step-section ul li, .step-section ol li {
275
  margin-bottom: 0.5em;
276
  }
277
- /* Meilleur espacement pour les listes imbriquées */
278
  .step-section ul ul,
279
  .step-section ol ol,
280
  .step-section ul ol,
@@ -284,11 +342,14 @@
284
  }
285
 
286
  /* Amélioration des tables en Markdown */
 
 
 
 
287
  .step-section table {
288
  border-collapse: collapse;
289
- width: auto; /* Ne prend que la largeur nécessaire */
290
- max-width: 100%; /* Empêche le débordement */
291
- margin: 1em auto; /* Centrer les tableaux */
292
  border: 1px solid #ccc; /* Bordure extérieure */
293
  }
294
  .step-section th,
@@ -305,31 +366,36 @@
305
  background-color: #f9f9f9;
306
  }
307
 
308
- /* Meilleur rendu des blocs de code en Markdown (pas Python) */
309
  .step-section pre {
310
- background-color: #f0f0f0; /* Légèrement différent du fond Python */
311
  border-radius: 4px;
312
  padding: 12px;
313
  overflow-x: auto;
314
  margin: 1em 0;
315
  border: 1px solid #ddd;
 
316
  }
317
  .step-section pre code { /* Style pour le code dans pre */
318
- background-color: transparent; /* Le fond est sur pre */
319
  padding: 0;
320
  border-radius: 0;
321
  font-size: 0.95em; /* Un peu plus petit */
 
322
  }
323
  /* Style pour `code` inline */
324
  .step-section code:not(pre *) {
325
- font-family: 'Courier New', Courier, monospace;
326
- background-color: #e8e8e8;
327
- padding: 2px 5px;
328
  border-radius: 3px;
329
  font-size: 0.9em;
330
- color: #c7254e; /* Couleur pour code inline */
 
 
331
  }
332
 
 
333
  .thinking-indicator, .executing-indicator, .answering-indicator {
334
  display: flex;
335
  align-items: center;
@@ -351,6 +417,7 @@
351
  animation: pulse 1.5s infinite ease-in-out;
352
  font-size: 1.1rem;
353
  }
 
354
 
355
  @keyframes pulse {
356
  0% { opacity: 0.6; transform: scale(1); }
@@ -358,11 +425,11 @@
358
  100% { opacity: 0.6; transform: scale(1); }
359
  }
360
 
361
- /* Améliorations supplémentaires pour Markdown */
362
  .step-section blockquote {
363
  border-left: 4px solid #ccc; /* Gris standard */
364
- margin: 1em 0;
365
- padding: 0.5em 15px;
366
  color: #555;
367
  background-color: #f9f9f9;
368
  }
@@ -372,7 +439,7 @@
372
 
373
  .step-section hr {
374
  border: none;
375
- border-top: 1px solid #eee;
376
  margin: 2em 0;
377
  }
378
 
@@ -382,19 +449,27 @@
382
  .step-section h4,
383
  .step-section h5,
384
  .step-section h6 {
385
- margin-top: 1.5em;
386
  margin-bottom: 0.8em;
387
  color: var(--primary-color);
388
  font-weight: 600; /* Un peu plus gras */
389
- padding-bottom: 0.2em;
390
  border-bottom: 1px solid #eee; /* Souligner légèrement les titres */
 
391
  }
 
 
 
 
 
 
 
392
  .step-section h1 { font-size: 1.8em; border-bottom-width: 2px; }
393
  .step-section h2 { font-size: 1.6em; }
394
  .step-section h3 { font-size: 1.4em; }
395
  .step-section h4 { font-size: 1.2em; }
396
- .step-section h5 { font-size: 1.1em; }
397
- .step-section h6 { font-size: 1.0em; color: #666; }
398
 
399
  </style>
400
  </head>
@@ -412,9 +487,9 @@
412
  <div class="feature-list">
413
  <h2>Fonctionnalités disponibles :</h2>
414
  <ul class="feature-list">
415
- <li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques basiques</li>
416
  <li><i class="fas fa-check-circle"></i> 3 résolutions gratuites par jour</li>
417
- <li><i class="fas fa-check-circle"></i> Explication des étapes de résolution</li>
418
  <li><i class="fas fa-times-circle"></i> <span style="color: #999;">Mode d'exécution de code avancé (version Pro)</span></li>
419
  <li><i class="fas fa-times-circle"></i> <span style="color: #999;">Résolutions illimitées (version Pro)</span></li>
420
  <li><i class="fas fa-times-circle"></i> <span style="color: #999;">Support prioritaire (version Pro)</span></li>
@@ -428,7 +503,7 @@
428
  <button type="button" id="uploadButton" class="cta-button" onclick="document.getElementById('imageInput').click()">
429
  <i class="fas fa-upload"></i> Télécharger une image
430
  </button>
431
- <p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555;"></p>
432
  </form>
433
 
434
  <div id="imagePreview" style="display: none; margin: 20px auto; max-width: 400px; max-height: 300px; overflow: hidden; border: 1px solid #ddd; border-radius: 8px;">
@@ -444,8 +519,7 @@
444
  <div id="solutionOutput"> <!-- Initialement caché via CSS -->
445
  <h3>Solution :</h3>
446
  <div id="loadingIndicator" class="thinking-indicator" style="display: none;">
447
- <i class="fas fa-brain indicator-icon"></i>
448
- <span>Je réfléchis au problème...</span>
449
  </div>
450
  <!-- Container pour les blocs de contenu dynamiques -->
451
  <div id="solution">
@@ -465,30 +539,43 @@
465
  </footer>
466
  </div>
467
 
 
468
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
469
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
470
-
471
- <!-- KaTeX Scripts -->
472
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js"></script>
473
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min.js"></script>
 
474
 
475
  <script>
476
- // Configuration de marked pour le rendu Markdown
477
  marked.setOptions({
478
  gfm: true,
479
- breaks: true, // Convertit les simples retours à la ligne en <br> (peut être désactivé si non souhaité)
 
480
  smartLists: true,
481
- smartypants: true,
482
  xhtml: true,
483
- highlight: function(code, lang) {
484
- // Utilise highlight.js pour la coloration syntaxique DANS les blocs de code Markdown
485
- // (différent des blocs de code Python dédiés)
486
  const language = hljs.getLanguage(lang) ? lang : 'plaintext';
487
- return hljs.highlight(code, { language }).value;
488
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  });
490
 
491
- // Configuration de KaTeX (options globales)
492
  const katexOptions = {
493
  delimiters: [
494
  {left: '$$', right: '$$', display: true},
@@ -496,201 +583,340 @@
496
  {left: '\\(', right: '\\)', display: false},
497
  {left: '\\[', right: '\\]', display: true}
498
  ],
499
- throwOnError: false, // Ne pas bloquer toute la page en cas d'erreur LaTeX
500
- // trust: true, // Activer seulement si besoin de commandes comme \htmlClass
501
  strict: (errorCode) => {
502
- // Permet d'ignorer certaines erreurs "mineures" si nécessaire
503
  if (errorCode === 'unicodeTextInMathMode') {
504
  return 'ignore'; // Ou 'warn'
505
  }
506
- return 'warn'; // Affiche les erreurs dans la console sans planter
507
- }
 
 
 
 
 
 
508
  };
509
 
 
510
  document.getElementById('imageInput').addEventListener('change', function(event) {
511
  const file = event.target.files[0];
 
 
 
 
 
 
 
512
  if (file) {
513
  const reader = new FileReader();
514
  reader.onload = function(e) {
515
- document.getElementById('preview').src = e.target.result;
516
- document.getElementById('imagePreview').style.display = 'block';
517
- document.getElementById('solveButton').style.display = 'inline-block';
518
- document.getElementById('uploadStatus').textContent = `Image sélectionnée : ${file.name}`;
519
- // Cacher l'ancienne solution si une nouvelle image est chargée
520
- document.getElementById('solutionOutput').style.display = 'none';
521
- document.getElementById('solution').innerHTML = '';
 
522
  }
523
  reader.readAsDataURL(file);
 
 
 
 
 
524
  }
525
  });
526
 
 
527
  document.getElementById('solveButton').addEventListener('click', function() {
528
  const formData = new FormData(document.getElementById('imageForm'));
529
  const solutionOutputDiv = document.getElementById('solutionOutput');
530
  const loadingIndicator = document.getElementById('loadingIndicator');
531
  const solutionContainer = document.getElementById('solution');
532
 
 
 
 
 
 
 
533
  solutionOutputDiv.style.display = 'block'; // Afficher la section solution
 
 
534
  loadingIndicator.style.display = 'flex'; // Afficher l'indicateur de chargement
535
  solutionContainer.innerHTML = ''; // Effacer la solution précédente
536
- // Scroller pour rendre l'indicateur visible
 
537
  loadingIndicator.scrollIntoView({ behavior: 'smooth', block: 'center' });
538
 
539
- fetch('/solved', { // Endpoint Flask
540
  method: 'POST',
541
  body: formData
542
  })
543
  .then(response => {
544
- if (!response.ok) {
545
- return response.text().then(text => {
546
- throw new Error(`Erreur serveur: ${response.status} ${response.statusText}\n${text || '(Aucun détail)'}`);
547
- });
548
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  const reader = response.body.getReader();
550
  const decoder = new TextDecoder();
551
  let buffer = '';
 
 
 
 
552
 
553
  function processStream({ done, value }) {
554
  if (done) {
555
- loadingIndicator.style.display = 'none';
556
- // Passe finale de rendu KaTeX pour être sûr
 
 
 
 
 
557
  try {
558
- renderMathInElement(solutionContainer, katexOptions);
 
 
559
  } catch (e) {
560
  console.error('Erreur de rendu KaTeX final:', e);
561
  }
562
  return;
563
  }
564
 
 
565
  buffer += decoder.decode(value, { stream: true });
566
- const messages = buffer.split(/\r?\n\r?\n/); // Sépare les messages SSE
567
- buffer = messages.pop(); // Garde le dernier morceau potentiellement incomplet
568
 
569
- messages.forEach(message => {
 
 
 
 
 
570
  if (message.startsWith('data: ')) {
571
  try {
572
- const data = JSON.parse(message.substring(6)); // Extrait le JSON
573
 
574
  // --- Gérer les mises à jour de mode ---
575
- if (data.mode) {
576
- const modes = {
577
- thinking: { icon: 'fa-brain', text: 'Je réfléchis...', class: 'thinking-indicator' },
578
- answering: { icon: 'fa-pencil-alt', text: 'Je rédige la réponse...', class: 'answering-indicator' },
579
- executing_code: { icon: 'fa-play', text: 'Exécution du code...', class: 'executing-indicator' }, // Icône différente
580
- code_result: { icon: 'fa-terminal', text: 'Traitement des résultats...', class: 'executing-indicator' }
581
- };
582
- const modeInfo = modes[data.mode] || { icon: 'fa-sync-alt fa-spin', text: 'Traitement...', class: 'thinking-indicator' }; // Fallback
583
-
584
- loadingIndicator.className = `${modeInfo.class}`; // Assigner la classe CSS
585
- loadingIndicator.innerHTML = `<i class="fas ${modeInfo.icon} indicator-icon"></i><span>${modeInfo.text}</span>`;
586
- loadingIndicator.style.display = 'flex';
587
  }
588
 
589
  // --- Gérer les blocs de contenu ---
590
  if (data.content) {
591
- loadingIndicator.style.display = 'none'; // Cacher l'indicateur quand le contenu arrive
592
- const content = data.content;
593
- const tempDiv = document.createElement('div');
594
- tempDiv.innerHTML = content; // Analyser le fragment HTML reçu
595
 
 
 
 
 
596
  const codeSection = tempDiv.querySelector('.code-section');
597
  const outputSection = tempDiv.querySelector('.output-section');
598
- const stepSection = tempDiv.querySelector('.step-section'); // Peut être directement le contenu
 
599
 
600
  if (codeSection) {
601
- // C'est une section de code Python dédiée
602
- solutionContainer.appendChild(codeSection);
603
- codeSection.querySelectorAll('pre code').forEach((block) => {
604
- hljs.highlightElement(block); // Appliquer highlight.js
 
 
 
 
 
 
605
  });
 
606
  } else if (outputSection) {
607
- // C'est une section de sortie de code
608
- solutionContainer.appendChild(outputSection);
 
 
609
  } else {
610
- // C'est une section d'étape (texte, Markdown, LaTeX)
611
- // Créer un conteneur propre pour cette étape
612
  const stepDiv = document.createElement('div');
613
- stepDiv.className = 'step-section'; // Appliquer la classe pour le style
 
614
 
615
  try {
616
- // 1. Convertir le Markdown en HTML en utilisant marked.js
617
- const htmlContent = marked.parse(content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  stepDiv.innerHTML = htmlContent;
619
 
620
- // 2. Ajouter le div au conteneur principal
621
- solutionContainer.appendChild(stepDiv);
622
-
623
- // 3. Rendre le LaTeX DANS ce div spécifique avec KaTeX
624
- renderMathInElement(stepDiv, katexOptions);
625
 
626
  } catch (e) {
627
- console.error('Erreur pendant le traitement Markdown ou KaTeX:', e);
628
- // En cas d'erreur, afficher le contenu brut pour le débogage
629
- stepDiv.innerHTML = `<p>Erreur de rendu :</p><pre>${content.replace(/</g, "<")}</pre>`;
630
- solutionContainer.appendChild(stepDiv);
631
  }
632
  }
633
- }
634
 
635
- // --- Gérer les erreurs spécifiques ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  if (data.error) {
637
- const errorDiv = document.createElement('div');
638
- // Utiliser une classe existante ou créer une classe 'error-section'
639
- errorDiv.className = 'step-section error-message'; // Réutiliser step-section avec une classe d'erreur
640
- errorDiv.style.color = 'red';
641
- errorDiv.style.backgroundColor = '#fff0f0';
642
- errorDiv.style.borderColor = 'red';
643
- errorDiv.innerHTML = `<strong>Erreur :</strong> ${marked.parse(data.error || 'Une erreur inconnue est survenue.')}`; // Permet le markdown dans l'erreur
644
- solutionContainer.appendChild(errorDiv);
645
- loadingIndicator.style.display = 'none';
646
  }
647
 
648
  } catch (e) {
649
  console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
650
- const errorDiv = document.createElement('div');
651
- errorDiv.className = 'step-section error-message';
652
- errorDiv.style.color = 'orange';
653
- errorDiv.style.backgroundColor = '#fff8e1';
654
- errorDiv.style.borderColor = 'orange';
655
- errorDiv.textContent = `Erreur de traitement du message reçu: ${message.substring(0, 150)}...`;
656
- solutionContainer.appendChild(errorDiv);
657
  loadingIndicator.style.display = 'none';
658
  }
 
 
 
659
  }
660
- });
661
-
662
- // Défiler vers le bas pour voir le nouveau contenu au fur et à mesure
663
- solutionContainer.lastChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
664
 
 
 
 
665
 
666
  // Continuer à lire le flux
667
- return reader.read().then(processStream);
668
- }
669
 
670
  // Démarrer le traitement du flux
671
  reader.read().then(processStream);
 
672
  })
673
  .catch(error => {
 
674
  console.error('Erreur Fetch ou connexion:', error);
675
- const errorDiv = document.createElement('div');
676
- errorDiv.className = 'step-section error-message';
677
- errorDiv.style.color = 'red';
678
- errorDiv.style.backgroundColor = '#fff0f0';
679
- errorDiv.style.borderColor = 'red';
680
- errorDiv.innerHTML = `<strong>Erreur de connexion ou serveur :</strong> ${error.message || error}`;
681
- solutionContainer.appendChild(errorDiv);
682
- loadingIndicator.style.display = 'none';
683
- errorDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
684
  });
685
  });
686
 
687
- // Premier rendu KaTeX au chargement de la page (pour tout contenu statique éventuel)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
  document.addEventListener('DOMContentLoaded', function() {
 
689
  try {
690
- // Utilise document.body pour scanner toute la page initiale
691
- renderMathInElement(document.body, katexOptions);
 
692
  } catch (e) {
693
- console.error('Erreur KaTeX initiale:', e)
694
  }
695
  });
696
  </script>
 
62
  box-shadow: var(--box-shadow);
63
  padding: 30px;
64
  margin-bottom: 30px;
 
65
  }
66
 
67
+ .content-box > h1, .content-box > p:first-of-type { /* Cibler le premier paragraphe après h1 */
 
68
  text-align: center;
69
  }
70
  .content-box .feature-list h2 {
 
73
  .content-box .upload-section {
74
  text-align: center;
75
  }
76
+ .content-box .upload-section h2 {
77
+ color: var(--primary-color);
78
+ margin-bottom: 20px;
79
+ }
80
  .content-box .upgrade-section {
81
  text-align: center;
82
  margin-top: 30px;
 
128
  margin: 10px; /* Ajustement marge */
129
  border: none;
130
  cursor: pointer;
131
+ font-size: 1rem; /* Taille de police cohérente */
132
  }
133
 
134
  .cta-button:hover {
 
181
  overflow-wrap: break-word; /* Standard CSS */
182
  }
183
 
184
+ /* --- Sections spécifiques --- */
185
  .code-section {
186
  background-color: transparent !important; /* Le fond est géré par header/content */
187
  padding: 0 !important;
 
192
  background-color: var(--output-bg) !important;
193
  border-left: 4px solid var(--secondary-color); /* Distinction visuelle */
194
  padding-left: calc(20px - 4px);
195
+ font-family: 'Courier New', Courier, monospace;
196
+ font-size: 0.95em;
197
+ white-space: pre-wrap; /* Respecter les retours à la ligne et espaces de la sortie */
198
+ word-break: break-all; /* Casser les longues lignes sans espaces */
199
  }
200
 
201
  .step-section {
202
  background-color: #ffffff; /* Fond blanc pour les étapes normales */
203
  border-left: 4px solid var(--primary-color);
204
  padding-left: calc(20px - 4px); /* Compenser la bordure */
205
+ font-size: 1rem; /* Taille de base */
206
  line-height: 1.8;
207
  }
208
+ .error-message { /* Style pour les messages d'erreur injectés */
209
+ color: #a94442; /* Rouge foncé */
210
+ background-color: #f2dede; /* Rose pâle */
211
+ border-color: #ebccd1 !important; /* Bordure rouge plus pâle */
212
+ border-left-width: 4px !important;
213
+ border-style: solid;
214
+ }
215
+ .error-message strong {
216
+ color: #a94442;
217
+ }
218
+ .error-message pre { /* Si l'erreur contient du code brut */
219
+ background-color: #eacccc;
220
+ color: #843534;
221
+ border-color: #d9a9a9;
222
+ }
223
+
224
 
225
  /* --- Amélioration du rendu KaTeX --- */
226
  .step-section .katex {
 
228
  line-height: normal; /* Laisser KaTeX gérer */
229
  text-indent: 0;
230
  text-rendering: auto;
231
+ /* Eviter le saut de ligne au milieu d'une formule inline si possible */
232
+ white-space: nowrap;
233
  }
234
 
235
  .step-section .katex-display {
236
  display: block; /* Comportement block pour $$...$$ */
237
  text-align: center; /* Centrer les équations display */
238
+ margin: 1.2em 0; /* Espacement vertical un peu plus grand */
239
  overflow-x: auto; /* Permet le scroll horizontal si équation trop large */
240
  overflow-y: hidden;
241
+ padding: 0.5em 0.2em; /* Un peu d'espace interne, moins sur les côtés */
242
+ /* background-color: #fdfdfd; */ /* Optionnel : léger fond différent */
243
+ /* border: 1px solid #eee; */ /* Optionnel : bordure légère */
244
+ /* border-radius: 4px; */ /* Optionnel : coins arrondis */
245
  }
246
+ /* Conteneur interne créé par KaTeX */
247
  .step-section .katex-display > .katex {
248
  display: inline-block; /* Permet le centrage et évite la pleine largeur */
249
  text-align: initial; /* Texte de l'équation aligné à gauche par défaut */
 
251
  }
252
  /* --- Fin Amélioration KaTeX --- */
253
 
254
+ /* --- Sections de Code Dédiées (Python) --- */
255
  .code-header {
256
  background-color: #343a40;
257
  color: white;
258
  padding: 10px 15px;
259
+ font-size: 0.9em; /* Plus petit */
260
  font-family: 'Courier New', monospace;
261
  display: flex;
262
  justify-content: space-between;
 
270
  color: #e6e6e6;
271
  overflow-x: auto;
272
  font-family: 'Courier New', monospace;
273
+ font-size: 0.9em; /* Plus petit */
274
  line-height: 1.5;
275
  border-bottom: 1px solid #444; /* Bordure sous le code */
276
  }
277
+ .code-content pre { /* S'assurer que pre dans le bloc code est stylé correctement */
278
+ margin: 0;
279
+ padding: 0;
280
+ background-color: transparent;
281
+ border: none;
282
+ border-radius: 0;
283
+ }
284
+ .code-content pre code {
285
+ font-size: 1em; /* Hérite de .code-content */
286
+ line-height: inherit;
287
+ background-color: transparent;
288
+ color: inherit;
289
+ padding: 0;
290
+ }
291
+ /* --- Fin Sections de Code Dédiées --- */
292
 
293
+ /* --- Règles pour les coins arrondis et bordures --- */
294
  #solution > *:first-child,
295
  #solution > .code-section:first-child .code-header {
296
  border-top-left-radius: 8px;
 
307
  #solution > *:only-child {
308
  border-radius: 8px;
309
  }
310
+ /* Supprimer la bordure du bas si le suivant est une section de code */
311
+ #solution > *:not(:last-child):not(.code-section) {
312
+ border-bottom: 1px solid #eee;
313
+ }
314
+ #solution > .code-section + * { /* Si un élément suit une section code */
315
+ border-top: 1px solid #eee; /* Ajouter une ligne de séparation au dessus */
316
+ }
317
+ #solution > .code-section .code-content {
318
+ border-bottom: none; /* Pas de double bordure */
319
+ }
320
+
321
 
322
  /* --- Amélioration du rendu Markdown --- */
323
+ .step-section *:first-child { margin-top: 0; } /* Éviter marge sup en début de bloc */
324
+ .step-section *:last-child { margin-bottom: 0; } /* Éviter marge inf en fin de bloc */
325
+
326
+ .step-section p { margin-bottom: 1em; } /* Espacement standard paragraphe */
327
+
328
  .step-section ul, .step-section ol {
329
  margin-block-start: 1em;
330
  margin-block-end: 1em;
331
  padding-inline-start: 30px; /* Indentation standard */
332
  }
 
333
  .step-section ul li, .step-section ol li {
334
  margin-bottom: 0.5em;
335
  }
 
336
  .step-section ul ul,
337
  .step-section ol ol,
338
  .step-section ul ol,
 
342
  }
343
 
344
  /* Amélioration des tables en Markdown */
345
+ .step-section .table-wrapper { /* Conteneur pour le défilement horizontal */
346
+ overflow-x: auto;
347
+ margin: 1em 0;
348
+ }
349
  .step-section table {
350
  border-collapse: collapse;
351
+ width: 100%; /* Prend la largeur du wrapper */
352
+ min-width: 400px; /* Largeur minimale pour éviter écrasement */
 
353
  border: 1px solid #ccc; /* Bordure extérieure */
354
  }
355
  .step-section th,
 
366
  background-color: #f9f9f9;
367
  }
368
 
369
+ /* Rendu des blocs de code en Markdown (pas Python dédié) */
370
  .step-section pre {
371
+ background-color: #f5f5f5;
372
  border-radius: 4px;
373
  padding: 12px;
374
  overflow-x: auto;
375
  margin: 1em 0;
376
  border: 1px solid #ddd;
377
+ line-height: 1.45; /* Consistant avec hljs */
378
  }
379
  .step-section pre code { /* Style pour le code dans pre */
380
+ background-color: transparent;
381
  padding: 0;
382
  border-radius: 0;
383
  font-size: 0.95em; /* Un peu plus petit */
384
+ color: #333; /* Couleur de base du code */
385
  }
386
  /* Style pour `code` inline */
387
  .step-section code:not(pre *) {
388
+ font-family: Consolas, 'Courier New', Courier, monospace; /* Police plus adaptée */
389
+ background-color: #f0f0f0; /* Gris clair */
390
+ padding: 3px 6px; /* Un peu plus d'espace */
391
  border-radius: 3px;
392
  font-size: 0.9em;
393
+ color: #c7254e; /* Couleur style "rouge" pour code inline */
394
+ border: 1px solid #e8e8e8;
395
+ word-break: break-word; /* Casser si trop long */
396
  }
397
 
398
+ /* --- Indicateurs de statut --- */
399
  .thinking-indicator, .executing-indicator, .answering-indicator {
400
  display: flex;
401
  align-items: center;
 
417
  animation: pulse 1.5s infinite ease-in-out;
418
  font-size: 1.1rem;
419
  }
420
+ /* --- Fin Indicateurs --- */
421
 
422
  @keyframes pulse {
423
  0% { opacity: 0.6; transform: scale(1); }
 
425
  100% { opacity: 0.6; transform: scale(1); }
426
  }
427
 
428
+ /* --- Améliorations supplémentaires pour Markdown --- */
429
  .step-section blockquote {
430
  border-left: 4px solid #ccc; /* Gris standard */
431
+ margin: 1.5em 0; /* Plus de marge verticale */
432
+ padding: 0.8em 15px; /* Plus de padding */
433
  color: #555;
434
  background-color: #f9f9f9;
435
  }
 
439
 
440
  .step-section hr {
441
  border: none;
442
+ border-top: 2px solid #eee; /* Ligne plus visible */
443
  margin: 2em 0;
444
  }
445
 
 
449
  .step-section h4,
450
  .step-section h5,
451
  .step-section h6 {
452
+ margin-top: 1.8em; /* Plus d'espace avant les titres */
453
  margin-bottom: 0.8em;
454
  color: var(--primary-color);
455
  font-weight: 600; /* Un peu plus gras */
456
+ padding-bottom: 0.3em;
457
  border-bottom: 1px solid #eee; /* Souligner légèrement les titres */
458
+ line-height: 1.3; /* Ajuster interligne titre */
459
  }
460
+ .step-section h1:first-child, /* Pas de marge sup pour le tout premier titre */
461
+ .step-section h2:first-child,
462
+ .step-section h3:first-child,
463
+ .step-section h4:first-child,
464
+ .step-section h5:first-child,
465
+ .step-section h6:first-child { margin-top: 0; }
466
+
467
  .step-section h1 { font-size: 1.8em; border-bottom-width: 2px; }
468
  .step-section h2 { font-size: 1.6em; }
469
  .step-section h3 { font-size: 1.4em; }
470
  .step-section h4 { font-size: 1.2em; }
471
+ .step-section h5 { font-size: 1.1em; color: var(--secondary-color); } /* Couleur différente pour H5/H6 */
472
+ .step-section h6 { font-size: 1.0em; color: #666; font-style: italic;}
473
 
474
  </style>
475
  </head>
 
487
  <div class="feature-list">
488
  <h2>Fonctionnalités disponibles :</h2>
489
  <ul class="feature-list">
490
+ <li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques</li>
491
  <li><i class="fas fa-check-circle"></i> 3 résolutions gratuites par jour</li>
492
+ <li><i class="fas fa-check-circle"></i> Explication détaillée des étapes</li>
493
  <li><i class="fas fa-times-circle"></i> <span style="color: #999;">Mode d'exécution de code avancé (version Pro)</span></li>
494
  <li><i class="fas fa-times-circle"></i> <span style="color: #999;">Résolutions illimitées (version Pro)</span></li>
495
  <li><i class="fas fa-times-circle"></i> <span style="color: #999;">Support prioritaire (version Pro)</span></li>
 
503
  <button type="button" id="uploadButton" class="cta-button" onclick="document.getElementById('imageInput').click()">
504
  <i class="fas fa-upload"></i> Télécharger une image
505
  </button>
506
+ <p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555; min-height: 1.2em;"></p>
507
  </form>
508
 
509
  <div id="imagePreview" style="display: none; margin: 20px auto; max-width: 400px; max-height: 300px; overflow: hidden; border: 1px solid #ddd; border-radius: 8px;">
 
519
  <div id="solutionOutput"> <!-- Initialement caché via CSS -->
520
  <h3>Solution :</h3>
521
  <div id="loadingIndicator" class="thinking-indicator" style="display: none;">
522
+ <!-- Contenu mis à jour par JS -->
 
523
  </div>
524
  <!-- Container pour les blocs de contenu dynamiques -->
525
  <div id="solution">
 
539
  </footer>
540
  </div>
541
 
542
+ <!-- JS Libraries -->
543
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
544
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
 
 
545
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js"></script>
546
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min.js"></script>
547
+ <!-- Marked.js est déjà chargé dans le <head> -->
548
 
549
  <script>
550
+ // --- Configuration Marked.js ---
551
  marked.setOptions({
552
  gfm: true,
553
+ breaks: false, // Standard GFM: ne pas convertir les simples retours à la ligne en <br>
554
+ pedantic: false,
555
  smartLists: true,
556
+ smartypants: false, // Désactiver pour éviter interférence avec $
557
  xhtml: true,
558
+ highlight: function(code, lang) {
559
+ // Utilise highlight.js pour la coloration dans les blocs ```lang ... ```
 
560
  const language = hljs.getLanguage(lang) ? lang : 'plaintext';
561
+ return hljs.highlight(code, { language, ignoreIllegals: true }).value;
562
+ },
563
+ // Ajouter un wrapper pour les tables pour permettre le scroll horizontal
564
+ renderer: (function() {
565
+ const renderer = new marked.Renderer();
566
+ // Conserver le rendu par défaut pour la plupart des éléments
567
+ const defaultTableRenderer = renderer.table;
568
+ renderer.table = function(header, body) {
569
+ // Appeler le rendu par défaut pour obtenir le HTML de la table
570
+ const tableHtml = defaultTableRenderer.call(this, header, body);
571
+ // Envelopper la table dans un div pour le style et le défilement
572
+ return `<div class="table-wrapper">${tableHtml}</div>`;
573
+ };
574
+ return renderer;
575
+ })()
576
  });
577
 
578
+ // --- Configuration KaTeX ---
579
  const katexOptions = {
580
  delimiters: [
581
  {left: '$$', right: '$$', display: true},
 
583
  {left: '\\(', right: '\\)', display: false},
584
  {left: '\\[', right: '\\]', display: true}
585
  ],
586
+ throwOnError: false, // Ne pas bloquer en cas d'erreur LaTeX mineure
 
587
  strict: (errorCode) => {
588
+ // Gérer ou ignorer certains avertissements KaTeX si nécessaire
589
  if (errorCode === 'unicodeTextInMathMode') {
590
  return 'ignore'; // Ou 'warn'
591
  }
592
+ // 'warn' affiche l'erreur dans la console sans arrêter le rendu
593
+ // 'error' arrêterait le rendu de cette formule
594
+ return 'warn';
595
+ },
596
+ macros: { // Optionnel: Définir des macros LaTeX personnalisées
597
+ // "\\RR": "\\mathbb{R}"
598
+ },
599
+ // trust: (context) => context.command === '\\htmlClass' // Activer si besoin de commandes spécifiques
600
  };
601
 
602
+ // --- Gestion Upload Image ---
603
  document.getElementById('imageInput').addEventListener('change', function(event) {
604
  const file = event.target.files[0];
605
+ const uploadStatus = document.getElementById('uploadStatus');
606
+ const imagePreviewDiv = document.getElementById('imagePreview');
607
+ const previewImg = document.getElementById('preview');
608
+ const solveButton = document.getElementById('solveButton');
609
+ const solutionOutputDiv = document.getElementById('solutionOutput');
610
+ const solutionContainer = document.getElementById('solution');
611
+
612
  if (file) {
613
  const reader = new FileReader();
614
  reader.onload = function(e) {
615
+ previewImg.src = e.target.result;
616
+ imagePreviewDiv.style.display = 'block';
617
+ solveButton.style.display = 'inline-block';
618
+ uploadStatus.textContent = `Image sélectionnée : ${file.name}`;
619
+ // Cacher l'ancienne solution et l'indicateur
620
+ solutionOutputDiv.style.display = 'none';
621
+ solutionContainer.innerHTML = '';
622
+ document.getElementById('loadingIndicator').style.display = 'none';
623
  }
624
  reader.readAsDataURL(file);
625
+ uploadStatus.textContent = 'Chargement de l\'aperçu...';
626
+ } else {
627
+ imagePreviewDiv.style.display = 'none';
628
+ solveButton.style.display = 'none';
629
+ uploadStatus.textContent = '';
630
  }
631
  });
632
 
633
+ // --- Gestion Clic Bouton Résoudre ---
634
  document.getElementById('solveButton').addEventListener('click', function() {
635
  const formData = new FormData(document.getElementById('imageForm'));
636
  const solutionOutputDiv = document.getElementById('solutionOutput');
637
  const loadingIndicator = document.getElementById('loadingIndicator');
638
  const solutionContainer = document.getElementById('solution');
639
 
640
+ // Vérifier s'il y a une image sélectionnée
641
+ if (!document.getElementById('imageInput').files[0]) {
642
+ alert("Veuillez d'abord sélectionner une image.");
643
+ return;
644
+ }
645
+
646
  solutionOutputDiv.style.display = 'block'; // Afficher la section solution
647
+ loadingIndicator.innerHTML = `<i class="fas fa-spinner fa-spin indicator-icon"></i><span>Envoi de l'image...</span>`;
648
+ loadingIndicator.className = 'thinking-indicator'; // Classe par défaut
649
  loadingIndicator.style.display = 'flex'; // Afficher l'indicateur de chargement
650
  solutionContainer.innerHTML = ''; // Effacer la solution précédente
651
+
652
+ // Scroller pour rendre l'indicateur visible
653
  loadingIndicator.scrollIntoView({ behavior: 'smooth', block: 'center' });
654
 
655
+ fetch('/solved', { // Endpoint Flask (adapter si nécessaire)
656
  method: 'POST',
657
  body: formData
658
  })
659
  .then(response => {
660
+ if (!response.ok || !response.body) {
661
+ // Tenter de lire le message d'erreur du corps si disponible
662
+ return response.text().then(text => {
663
+ let errorMsg = `Erreur serveur: ${response.status} ${response.statusText}`;
664
+ if (text) {
665
+ // Essayer d'extraire un message d'erreur plus précis si c'est du JSON
666
+ try {
667
+ const errorJson = JSON.parse(text);
668
+ errorMsg += `\n${errorJson.error || errorJson.message || text}`;
669
+ } catch (e) {
670
+ errorMsg += `\n${text}`; // Afficher le texte brut si ce n'est pas du JSON
671
+ }
672
+ } else {
673
+ errorMsg += "\nAucun détail supplémentaire fourni par le serveur.";
674
+ }
675
+ throw new Error(errorMsg);
676
+ });
677
+ }
678
+ // Préparation à la lecture du flux SSE
679
  const reader = response.body.getReader();
680
  const decoder = new TextDecoder();
681
  let buffer = '';
682
+ let currentMode = 'thinking'; // Suivre l'état actuel
683
+
684
+ // Mettre à jour l'indicateur pour le premier état
685
+ updateLoadingIndicator('thinking');
686
 
687
  function processStream({ done, value }) {
688
  if (done) {
689
+ // Le flux est terminé
690
+ if (solutionContainer.innerHTML === '') {
691
+ // Si rien n'a été affiché, montrer un message
692
+ displayError("Aucune réponse reçue du serveur après la fin du flux.", solutionContainer);
693
+ }
694
+ loadingIndicator.style.display = 'none'; // Cacher l'indicateur final
695
+ // Optionnel : Passe finale de rendu KaTeX pour toute la solution
696
  try {
697
+ if (window.renderMathInElement) { // Vérifier si la fonction existe
698
+ renderMathInElement(solutionContainer, katexOptions);
699
+ }
700
  } catch (e) {
701
  console.error('Erreur de rendu KaTeX final:', e);
702
  }
703
  return;
704
  }
705
 
706
+ // Ajouter les nouvelles données au buffer
707
  buffer += decoder.decode(value, { stream: true });
 
 
708
 
709
+ // Traiter les messages complets dans le buffer (séparés par \n\n)
710
+ let boundary = buffer.indexOf('\n\n');
711
+ while (boundary >= 0) {
712
+ const message = buffer.substring(0, boundary).trim(); // Prendre un message complet
713
+ buffer = buffer.substring(boundary + 2); // Enlever le message traité du buffer
714
+
715
  if (message.startsWith('data: ')) {
716
  try {
717
+ const data = JSON.parse(message.substring(6)); // Extraire le JSON
718
 
719
  // --- Gérer les mises à jour de mode ---
720
+ if (data.mode && data.mode !== currentMode) {
721
+ currentMode = data.mode;
722
+ updateLoadingIndicator(currentMode);
723
+ loadingIndicator.style.display = 'flex'; // Assurer qu'il est visible
 
 
 
 
 
 
 
 
724
  }
725
 
726
  // --- Gérer les blocs de contenu ---
727
  if (data.content) {
728
+ loadingIndicator.style.display = 'none'; // Cacher l'indicateur dès qu'on reçoit du contenu
729
+ const rawContent = data.content;
 
 
730
 
731
+ // Détecter si c'est du code/output dédié ou un step normal
732
+ // On se base sur la présence de classes spécifiques envoyées par le serveur
733
+ const tempDiv = document.createElement('div');
734
+ tempDiv.innerHTML = rawContent; // Parser temporairement pour inspection
735
  const codeSection = tempDiv.querySelector('.code-section');
736
  const outputSection = tempDiv.querySelector('.output-section');
737
+
738
+ let elementToAppend; // L'élément final à ajouter au DOM
739
 
740
  if (codeSection) {
741
+ // --- Section Code Python Dédiée ---
742
+ elementToAppend = codeSection;
743
+ // Appliquer highlight.js sur les blocs de code DANS cette section
744
+ elementToAppend.querySelectorAll('pre code').forEach((block) => {
745
+ // Vérifier si hljs est chargé
746
+ if (window.hljs) {
747
+ hljs.highlightElement(block);
748
+ } else {
749
+ console.warn("highlight.js (hljs) non trouvé.");
750
+ }
751
  });
752
+
753
  } else if (outputSection) {
754
+ // --- Section Output de Code ---
755
+ elementToAppend = outputSection;
756
+ // Pas de traitement spécial nécessaire ici (styles CSS gèrent l'affichage)
757
+
758
  } else {
759
+ // --- Section d'Étape (Markdown + LaTeX) ---
 
760
  const stepDiv = document.createElement('div');
761
+ stepDiv.className = 'step-section';
762
+ elementToAppend = stepDiv;
763
 
764
  try {
765
+ let processedMdContent = rawContent;
766
+ const latexPlaceholders = {};
767
+ let placeholderIndex = 0;
768
+ const placeholderPrefix = "___KXTEMP_"; // Préfixe court et unique
769
+
770
+ // 1. Protéger $$...$$ (Display Math)
771
+ // Regex: trouve $$ suivi de n'importe quoi (non-gourmand) jusqu'à $$
772
+ processedMdContent = processedMdContent.replace(/(\$\$[\s\S]*?\$\$)/g, (match) => {
773
+ const placeholder = `${placeholderPrefix}D${placeholderIndex++}___`;
774
+ latexPlaceholders[placeholder] = match;
775
+ return placeholder;
776
+ });
777
+
778
+ // 2. Protéger $...$ (Inline Math)
779
+ // Regex: trouve un $ non précédé par \ ou un autre $, suivi de caractères (non-$), jusqu'au prochain $ non précédé par \.
780
+ // Attention: cette regex est simplifiée et peut avoir des edge cases.
781
+ processedMdContent = processedMdContent.replace(/(?<![\$\\])\$([^$]+?)\$(?![\$])/g, (match) => {
782
+ // (?<![\$\\]) : Ne pas être précédé par $ ou \
783
+ // \$ : Le dollar de début
784
+ // ([^$]+?) : Contenu (au moins un caractère, non gourmand)
785
+ // \$ : Le dollar de fin
786
+ // (?![\$]) : Ne pas être suivi par un $
787
+ const placeholder = `${placeholderPrefix}I${placeholderIndex++}___`;
788
+ latexPlaceholders[placeholder] = match;
789
+ return placeholder;
790
+ });
791
+
792
+
793
+ // 3. Parser le Markdown *avec* les placeholders
794
+ let htmlContent = marked.parse(processedMdContent);
795
+
796
+ // 4. Restaurer le LaTeX original à partir des placeholders
797
+ // Utiliser une fonction pour la robustesse
798
+ htmlContent = htmlContent.replace(new RegExp(placeholderPrefix + "(D|I)(\\d+)___", "g"), (match) => {
799
+ return latexPlaceholders[match] || match; // Remplace ou laisse le placeholder si non trouvé
800
+ });
801
+
802
+ // 5. Injecter le HTML final dans le div
803
  stepDiv.innerHTML = htmlContent;
804
 
805
+ // 6. Rendre le LaTeX DANS ce div spécifique avec KaTeX (après ajout au DOM)
806
+ // Note: KaTeX sera appelé après l'ajout au DOM ci-dessous
 
 
 
807
 
808
  } catch (e) {
809
+ console.error('Erreur pendant le traitement Markdown/Placeholder:', e);
810
+ // En cas d'erreur majeure, afficher le contenu brut
811
+ stepDiv.innerHTML = `<p class="error-message"><strong>Erreur de rendu Markdown :</strong> ${e.message}</p><pre>${rawContent.replace(/</g, "<")}</pre>`;
 
812
  }
813
  }
 
814
 
815
+ // --- Ajouter l'élément préparé au conteneur ---
816
+ if (elementToAppend) {
817
+ solutionContainer.appendChild(elementToAppend);
818
+
819
+ // --- Appeler KaTeX si c'était une step-section ---
820
+ if (elementToAppend.classList.contains('step-section')) {
821
+ try {
822
+ if (window.renderMathInElement) {
823
+ renderMathInElement(elementToAppend, katexOptions);
824
+ } else {
825
+ console.warn("KaTeX auto-render (renderMathInElement) non trouvé.");
826
+ }
827
+ } catch (renderError) {
828
+ console.error('Erreur rendu KaTeX sur le bloc:', renderError, elementToAppend);
829
+ // Afficher un message d'erreur dans le bloc lui-même
830
+ const errorDiv = document.createElement('div');
831
+ errorDiv.className = 'error-message';
832
+ errorDiv.innerHTML = `<strong>Erreur Rendu LaTeX:</strong> ${renderError.message || renderError}`;
833
+ elementToAppend.appendChild(errorDiv);
834
+ }
835
+ }
836
+
837
+ // Faire défiler pour voir le nouveau contenu
838
+ elementToAppend.scrollIntoView({ behavior: 'smooth', block: 'end' });
839
+ }
840
+ } // Fin if(data.content)
841
+
842
+ // --- Gérer les erreurs spécifiques envoyées par le serveur ---
843
  if (data.error) {
844
+ displayError(`Erreur reçue du serveur: ${data.error}`, solutionContainer);
845
+ loadingIndicator.style.display = 'none'; // Cacher l'indicateur en cas d'erreur fatale
 
 
 
 
 
 
 
846
  }
847
 
848
  } catch (e) {
849
  console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
850
+ displayError(`Erreur interne lors du traitement de la réponse: ${message.substring(0, 150)}...`, solutionContainer);
 
 
 
 
 
 
851
  loadingIndicator.style.display = 'none';
852
  }
853
+ } else if (message.trim() !== '') {
854
+ // Ignorer les lignes vides mais logguer les autres lignes non-SSE
855
+ console.warn("Message SSE ignoré (ne commence pas par 'data:'):", message);
856
  }
 
 
 
 
857
 
858
+ // Chercher la prochaine limite de message
859
+ boundary = buffer.indexOf('\n\n');
860
+ } // Fin while boundary
861
 
862
  // Continuer à lire le flux
863
+ reader.read().then(processStream);
864
+ } // Fin processStream
865
 
866
  // Démarrer le traitement du flux
867
  reader.read().then(processStream);
868
+
869
  })
870
  .catch(error => {
871
+ // Gérer les erreurs de fetch initial (connexion, DNS, etc.) ou les erreurs levées plus tôt
872
  console.error('Erreur Fetch ou connexion:', error);
873
+ displayError(`Erreur de communication avec le serveur: ${error.message || error}`, solutionContainer);
874
+ loadingIndicator.style.display = 'none'; // Cacher l'indicateur
 
 
 
 
 
 
 
875
  });
876
  });
877
 
878
+ // --- Fonction pour mettre à jour l'indicateur de chargement ---
879
+ function updateLoadingIndicator(mode) {
880
+ const loadingIndicator = document.getElementById('loadingIndicator');
881
+ const modes = {
882
+ thinking: { icon: 'fa-brain', text: 'Je réfléchis au problème...', class: 'thinking-indicator' },
883
+ answering: { icon: 'fa-pencil-alt', text: 'Je rédige la réponse...', class: 'answering-indicator' },
884
+ executing_code: { icon: 'fa-play', text: 'Exécution du code Python...', class: 'executing-indicator' },
885
+ code_result: { icon: 'fa-terminal', text: 'Analyse des résultats du code...', class: 'executing-indicator' },
886
+ // Ajouter d'autres modes si nécessaire
887
+ default: { icon: 'fa-sync-alt fa-spin', text: 'Traitement en cours...', class: 'thinking-indicator' }
888
+ };
889
+ const modeInfo = modes[mode] || modes.default;
890
+
891
+ loadingIndicator.className = modeInfo.class; // Appliquer la classe CSS pour le style
892
+ loadingIndicator.innerHTML = `<i class="fas ${modeInfo.icon} indicator-icon"></i><span>${modeInfo.text}</span>`;
893
+ }
894
+
895
+ // --- Fonction pour afficher les erreurs de manière cohérente ---
896
+ function displayError(errorMessage, container) {
897
+ const errorDiv = document.createElement('div');
898
+ // Utiliser la classe CSS définie pour les erreurs
899
+ errorDiv.className = 'step-section error-message';
900
+ // Utiliser marked pour permettre un formatage simple dans les erreurs si besoin
901
+ try {
902
+ errorDiv.innerHTML = `<strong>Erreur :</strong> ${marked.parseInline(errorMessage || 'Une erreur inconnue est survenue.')}`;
903
+ } catch (e) { // Fallback si marked échoue
904
+ errorDiv.innerHTML = `<strong>Erreur :</strong> ${errorMessage || 'Une erreur inconnue est survenue.'}`;
905
+ }
906
+ container.appendChild(errorDiv);
907
+ errorDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
908
+ }
909
+
910
+
911
+ // --- Initialisation au chargement de la page ---
912
  document.addEventListener('DOMContentLoaded', function() {
913
+ // Optionnel : Rendre le LaTeX statique si présent dans la page initiale
914
  try {
915
+ if (window.renderMathInElement) {
916
+ renderMathInElement(document.body, katexOptions);
917
+ }
918
  } catch (e) {
919
+ console.error('Erreur KaTeX initiale sur le corps de la page:', e)
920
  }
921
  });
922
  </script>