Docfile commited on
Commit
fb4ab9a
·
verified ·
1 Parent(s): 1e4539a

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +172 -68
templates/index.html CHANGED
@@ -8,17 +8,29 @@
8
  <!-- Inclusion de Tailwind CSS via CDN -->
9
  <script src="https://cdn.tailwindcss.com"></script>
10
 
 
 
 
11
  <!-- Inclusion de MathJax pour le rendu LaTeX -->
12
- <!-- Il est préférable de charger MathJax v3 -->
13
  <script>
14
  MathJax = {
15
  tex: {
16
- inlineMath: [['$', '$'], ['\\(', '\\)']], // Délimiteurs pour les maths en ligne
17
- displayMath: [['$$', '$$'], ['\\[', '\\]']], // Délimiteurs pour les maths en affichage
18
- processEscapes: true // Traiter les échappements comme \$
19
  },
20
  svg: {
21
- fontCache: 'global' // Améliore la performance du rendu SVG
 
 
 
 
 
 
 
 
 
 
22
  }
23
  };
24
  </script>
@@ -27,23 +39,73 @@
27
  </script>
28
 
29
  <!-- Inclusion de highlight.js pour la coloration syntaxique -->
30
- <!-- Choisir un thème (ex: github-dark) -->
31
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
32
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
33
- <!-- Optionnel: Charger des langages spécifiques si nécessaire -->
34
- <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script> -->
35
 
36
- <!-- Styles personnalisés additionnels si besoin (peuvent être intégrés à Tailwind config pour des projets plus gros) -->
37
  <style>
38
- /* Style pour s'assurer que MathJax n'affecte pas trop la hauteur de ligne */
39
  mjx-container {
40
- line-height: normal !important; /* Ajuster si nécessaire */
41
- display: inline-block !important; /* Pour un meilleur alignement inline */
 
 
42
  }
43
- /* Amélioration visuelle pour les blocs de code */
44
- pre code.hljs {
45
  border-radius: 0.375rem; /* rounded-md */
46
  padding: 1rem; /* p-4 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
  </style>
49
  </head>
@@ -103,10 +165,8 @@
103
  </div>
104
 
105
  <!-- Conteneur de Résultats -->
 
106
  <div id="result-container" class="bg-white p-6 rounded-xl shadow-lg border border-gray-200 w-full min-h-[150px] prose max-w-none prose-indigo">
107
- <!-- "prose" applique des styles par défaut au contenu -->
108
- <!-- "max-w-none" empêche prose de limiter la largeur -->
109
- <!-- "prose-indigo" adapte les couleurs de prose au thème indigo -->
110
  <p class="text-gray-500 italic text-center">Les résultats apparaîtront ici...</p>
111
  </div>
112
  </div>
@@ -116,41 +176,55 @@
116
  document.addEventListener('DOMContentLoaded', function() {
117
  const resultContainer = document.getElementById('result-container');
118
 
 
 
 
 
 
 
 
 
 
 
 
119
  // Fonction pour créer un élément de code formaté et le colorer
120
  function createCodeElement(code, language = null) {
121
  const pre = document.createElement('pre');
122
- // Note: La classe 'not-prose' peut être utile si les styles de 'prose' interfèrent
123
- pre.className = 'my-4 p-0 bg-transparent not-prose'; // Réinitialise le padding/bg pour que hljs prenne le dessus
124
 
125
  const codeEl = document.createElement('code');
126
  // Ajouter la classe de langage si spécifié pour highlight.js
127
  if (language) {
128
  codeEl.className = `language-${language}`;
 
 
129
  }
130
- codeEl.textContent = code;
131
 
132
  pre.appendChild(codeEl);
133
  // Appliquer highlight.js à cet élément spécifique
134
- hljs.highlightElement(codeEl);
 
135
  return pre;
136
  }
137
 
138
- // Fonction pour créer un élément de texte/Markdown (sera stylisé par "prose")
139
  function createMarkdownElement(text) {
140
  const div = document.createElement('div');
141
- div.className = 'markdown-content mb-4'; // prose gère le style général
142
- // Simple conversion des sauts de ligne en <br> pour un rendu basique
143
- // Une vraie lib Markdown (marked.js, markdown-it) serait mieux pour du vrai Markdown
144
- div.innerHTML = text.replace(/\n/g, '<br>');
145
  return div;
146
  }
147
 
148
  // Fonction pour créer un élément spécialisé pour le résultat de code
149
  function createResultElement(text) {
150
  const div = document.createElement('div');
151
- // Styles Tailwind pour un résultat distinct
152
- div.className = 'code-result my-4 p-3 bg-green-50 border-l-4 border-green-500 text-sm text-green-800 rounded-r-lg';
153
- div.innerHTML = text.replace(/\n/g, '<br>');
 
154
  return div;
155
  }
156
 
@@ -159,17 +233,17 @@
159
  function createImageElement(base64Data, format = 'png') {
160
  const img = document.createElement('img');
161
  img.src = `data:image/${format};base64,${base64Data}`;
162
- // Styles Tailwind pour les images
163
- img.className = 'max-w-full h-auto my-4 border border-gray-300 rounded p-1 shadow-sm mx-auto block'; // Centrer l'image
164
  return img;
165
  }
166
 
167
  // Fonction pour créer un indicateur (chargement, réflexion)
168
  function createIndicator(text, type = 'loading') {
169
  const div = document.createElement('div');
170
- div.dataset.indicatorType = type; // Pour pouvoir le retrouver/supprimer
171
- // Styles Tailwind pour les indicateurs
172
- div.className = 'indicator my-3 text-center italic text-gray-500';
173
  div.textContent = text;
174
  return div;
175
  }
@@ -177,38 +251,41 @@
177
  // Fonction pour créer un message d'erreur
178
  function createErrorElement(text) {
179
  const div = document.createElement('div');
180
- // Styles Tailwind pour les erreurs
181
- div.className = 'error-message my-4 p-4 bg-red-100 border border-red-300 text-red-700 rounded-lg';
182
  div.textContent = 'Erreur: ' + text;
183
  return div;
184
  }
185
 
186
  // Fonction pour traiter les données SSE et mettre à jour l'UI
187
  function processSseData(jsonData) {
 
188
  if (jsonData.mode === 'thinking') {
189
- // Supprimer l'indicateur de chargement s'il existe
190
  const loadingIndicator = resultContainer.querySelector('[data-indicator-type="loading"]');
191
  if (loadingIndicator) loadingIndicator.remove();
192
- // Afficher l'indicateur de réflexion (s'il n'existe pas déjà)
193
  if (!resultContainer.querySelector('[data-indicator-type="thinking"]')) {
194
  resultContainer.appendChild(createIndicator('Gemini réfléchit...', 'thinking'));
195
  }
196
  } else if (jsonData.mode === 'answering') {
197
- // Supprimer l'indicateur de réflexion s'il existe
198
  const thinkingIndicator = resultContainer.querySelector('[data-indicator-type="thinking"]');
199
  if (thinkingIndicator) thinkingIndicator.remove();
200
  }
201
 
 
202
  if (jsonData.content) {
203
  let element;
 
 
204
  switch(jsonData.type) {
205
  case 'text':
 
206
  element = createMarkdownElement(jsonData.content);
 
 
207
  break;
208
  case 'code':
209
- // Essayez de détecter le langage si possible (sinon hljs tente de deviner)
210
- // Vous pourriez passer le langage depuis le backend si connu
211
- element = createCodeElement(jsonData.content);
212
  break;
213
  case 'result':
214
  element = createResultElement(jsonData.content);
@@ -217,28 +294,43 @@
217
  element = createImageElement(jsonData.content);
218
  break;
219
  default: // Traiter comme du texte par défaut
220
- element = createMarkdownElement(jsonData.content);
 
 
221
  }
222
- resultContainer.appendChild(element);
223
 
224
- // Demander à MathJax de re-scanner le conteneur pour le nouveau contenu LaTeX
225
- // Utilisation de la nouvelle API MathJax 3
226
- if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {
227
- MathJax.typesetPromise([element]).catch((err) => console.error('MathJax processing error:', err));
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  }
229
  }
230
 
 
231
  if (jsonData.error) {
232
  resultContainer.appendChild(createErrorElement(jsonData.error));
233
- // Supprimer les indicateurs en cas d'erreur
234
  resultContainer.querySelectorAll('.indicator').forEach(el => el.remove());
235
  }
236
  }
237
 
238
 
239
- // Fonction pour gérer les événements SSE via Fetch API
240
  async function setupFetchStream(url, formData) {
241
- // Vider le conteneur de résultats et afficher chargement
 
242
  resultContainer.innerHTML = '';
243
  resultContainer.appendChild(createIndicator('Chargement en cours...', 'loading'));
244
 
@@ -246,19 +338,26 @@
246
  const response = await fetch(url, {
247
  method: 'POST',
248
  body: formData
249
- // Pas besoin de 'Content-Type': 'multipart/form-data', le navigateur le met avec FormData
250
  });
251
 
252
  if (!response.ok) {
253
- throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
 
 
 
 
 
 
 
 
254
  }
255
 
256
  // Vider à nouveau au cas où la requête prend du temps avant que le stream commence
257
- resultContainer.innerHTML = '';
258
 
259
  const reader = response.body.getReader();
260
  const decoder = new TextDecoder();
261
- let buffer = ''; // Pour gérer les messages SSE coupés entre les chunks
262
 
263
  while (true) {
264
  const { done, value } = await reader.read();
@@ -266,48 +365,54 @@
266
 
267
  buffer += decoder.decode(value, { stream: true });
268
 
269
- // Traiter les messages complets dans le buffer
270
  let boundary = buffer.indexOf('\n\n');
271
  while (boundary !== -1) {
272
  const message = buffer.substring(0, boundary);
273
- buffer = buffer.substring(boundary + 2); // +2 pour \n\n
274
 
275
  if (message.startsWith('data: ')) {
276
  try {
277
  const jsonData = JSON.parse(message.substring(6));
278
- processSseData(jsonData);
279
  } catch (e) {
280
  console.error('Erreur parsing JSON du SSE:', e, 'Data:', message.substring(6));
 
 
281
  }
282
  }
283
- // Rechercher la prochaine limite
284
  boundary = buffer.indexOf('\n\n');
285
  }
286
- // Faire défiler vers le bas
287
- window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
 
288
  }
289
- // Traiter ce qui reste dans le buffer (au cas où le stream se termine sans \n\n final)
290
  if (buffer.startsWith('data: ')) {
291
  try {
292
  const jsonData = JSON.parse(buffer.substring(6));
293
  processSseData(jsonData);
294
  } catch (e) {
295
  console.error('Erreur parsing JSON du dernier chunk SSE:', e, 'Data:', buffer.substring(6));
 
296
  }
297
  }
298
 
299
  } catch (error) {
300
  console.error('Erreur Fetch Stream:', error);
301
- resultContainer.innerHTML = ''; // Nettoyer les indicateurs
 
 
302
  resultContainer.appendChild(createErrorElement(error.message));
 
303
  } finally {
304
- // Optionnel: Supprimer l'indicateur de chargement final s'il est toujours là
305
- const loadingIndicator = resultContainer.querySelector('[data-indicator-type="loading"]');
306
- if (loadingIndicator) loadingIndicator.remove();
 
307
  }
308
  }
309
 
310
- // Gestion du formulaire pour /solve
311
  const solveForm = document.getElementById('solve-form');
312
  if (solveForm) {
313
  solveForm.addEventListener('submit', function(e) {
@@ -317,7 +422,6 @@
317
  });
318
  }
319
 
320
- // Gestion du formulaire pour /solved
321
  const solvedForm = document.getElementById('solved-form');
322
  if (solvedForm) {
323
  solvedForm.addEventListener('submit', function(e) {
 
8
  <!-- Inclusion de Tailwind CSS via CDN -->
9
  <script src="https://cdn.tailwindcss.com"></script>
10
 
11
+ <!-- *** NOUVEAU: Inclusion de marked.js *** -->
12
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
13
+
14
  <!-- Inclusion de MathJax pour le rendu LaTeX -->
 
15
  <script>
16
  MathJax = {
17
  tex: {
18
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
19
+ displayMath: [['$$', '$$'], ['\\[', '\\]']],
20
+ processEscapes: true
21
  },
22
  svg: {
23
+ fontCache: 'global'
24
+ },
25
+ // *** IMPORTANT: Désactiver le rendu initial automatique ***
26
+ // Nous le déclencherons manuellement après avoir ajouté du contenu.
27
+ startup: {
28
+ ready: () => {
29
+ console.log('MathJax is ready');
30
+ MathJax.startup.defaultReady();
31
+ // Ne pas faire de typeset initial ici
32
+ // MathJax.startup.promise.then(() => { console.log('Initial typeset done'); });
33
+ }
34
  }
35
  };
36
  </script>
 
39
  </script>
40
 
41
  <!-- Inclusion de highlight.js pour la coloration syntaxique -->
 
42
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
43
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
 
 
44
 
45
+ <!-- Styles personnalisés -->
46
  <style>
 
47
  mjx-container {
48
+ line-height: normal !important;
49
+ display: inline-block !important; /* Important pour l'alignement */
50
+ margin: 0 0.15em; /* Ajouter un petit espace autour */
51
+ vertical-align: middle; /* Essayer d'aligner verticalement */
52
  }
53
+ /* S'assurer que le code dans prose est stylisé par highlight.js */
54
+ .prose pre code.hljs {
55
  border-radius: 0.375rem; /* rounded-md */
56
  padding: 1rem; /* p-4 */
57
+ /* La couleur de fond est définie par le thème hljs */
58
+ }
59
+ /* Empêcher prose de trop styliser les blocs de code */
60
+ .prose pre {
61
+ background-color: transparent !important;
62
+ padding: 0 !important;
63
+ margin-top: 1em;
64
+ margin-bottom: 1em;
65
+ }
66
+ /* Amélioration pour les listes dans le markdown rendu */
67
+ .prose ul, .prose ol {
68
+ margin-left: 1.5rem;
69
+ }
70
+ /* Styles pour le résultat de code */
71
+ .code-result {
72
+ margin-top: 0.5rem; /* Espacement avant */
73
+ margin-bottom: 1rem; /* Espacement après */
74
+ padding: 0.75rem 1rem; /* p-3 p-4 */
75
+ background-color: #f0fff4; /* bg-green-50 */
76
+ border-left: 4px solid #38a169; /* border-l-4 border-green-500 */
77
+ font-size: 0.875rem; /* text-sm */
78
+ color: #2f855a; /* text-green-800 */
79
+ border-radius: 0 0.375rem 0.375rem 0; /* rounded-r-lg */
80
+ white-space: pre-wrap; /* Conserver les sauts de ligne */
81
+ word-wrap: break-word; /* Retour à la ligne si nécessaire */
82
+ }
83
+ /* Styles pour les indicateurs */
84
+ .indicator {
85
+ margin: 0.75rem 0; /* my-3 */
86
+ text-align: center;
87
+ font-style: italic;
88
+ color: #6b7280; /* text-gray-500 */
89
+ }
90
+ /* Styles pour les messages d'erreur */
91
+ .error-message {
92
+ margin: 1rem 0; /* my-4 */
93
+ padding: 1rem; /* p-4 */
94
+ background-color: #fee2e2; /* bg-red-100 */
95
+ border: 1px solid #fca5a5; /* border-red-300 */
96
+ color: #b91c1c; /* text-red-700 */
97
+ border-radius: 0.5rem; /* rounded-lg */
98
+ }
99
+ /* Styles pour les images générées */
100
+ .generated-image {
101
+ max-width: 100%;
102
+ height: auto;
103
+ margin: 1rem auto; /* my-4 mx-auto */
104
+ border: 1px solid #d1d5db; /* border-gray-300 */
105
+ border-radius: 0.25rem; /* rounded */
106
+ padding: 0.25rem; /* p-1 */
107
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); /* shadow-sm */
108
+ display: block; /* block */
109
  }
110
  </style>
111
  </head>
 
165
  </div>
166
 
167
  <!-- Conteneur de Résultats -->
168
+ <!-- Ajout de classes 'prose' pour le style Markdown/texte par défaut -->
169
  <div id="result-container" class="bg-white p-6 rounded-xl shadow-lg border border-gray-200 w-full min-h-[150px] prose max-w-none prose-indigo">
 
 
 
170
  <p class="text-gray-500 italic text-center">Les résultats apparaîtront ici...</p>
171
  </div>
172
  </div>
 
176
  document.addEventListener('DOMContentLoaded', function() {
177
  const resultContainer = document.getElementById('result-container');
178
 
179
+ // Configure marked.js (optionnel, mais utile)
180
+ marked.setOptions({
181
+ highlight: function(code, lang) {
182
+ const language = hljs.getLanguage(lang) ? lang : 'plaintext';
183
+ return hljs.highlight(code, { language }).value;
184
+ },
185
+ langPrefix: 'hljs language-', // pour la compatibilité avec hljs CSS
186
+ gfm: true, // Activer GitHub Flavored Markdown
187
+ breaks: true, // Convertir les sauts de ligne simples en <br>
188
+ });
189
+
190
  // Fonction pour créer un élément de code formaté et le colorer
191
  function createCodeElement(code, language = null) {
192
  const pre = document.createElement('pre');
193
+ // Note: La classe 'not-prose' empêche prose d'interférer avec le style hljs
194
+ pre.className = 'not-prose'; // Empêche le style prose d'affecter ce bloc
195
 
196
  const codeEl = document.createElement('code');
197
  // Ajouter la classe de langage si spécifié pour highlight.js
198
  if (language) {
199
  codeEl.className = `language-${language}`;
200
+ } else {
201
+ codeEl.className = 'language-plaintext'; // Défaut si non spécifié
202
  }
203
+ codeEl.textContent = code; // Utiliser textContent pour éviter l'injection HTML
204
 
205
  pre.appendChild(codeEl);
206
  // Appliquer highlight.js à cet élément spécifique
207
+ // hljs.highlightElement(codeEl); // marqué.js le fait maintenant via l'option highlight
208
+ hljs.highlightBlock(codeEl); // Alternative si on n'utilise pas l'option highlight de marked
209
  return pre;
210
  }
211
 
212
+ // *** MODIFIÉ: Fonction pour créer un élément de texte/Markdown ***
213
  function createMarkdownElement(text) {
214
  const div = document.createElement('div');
215
+ div.className = 'markdown-content'; // La classe 'prose' sur le parent gère le style
216
+ // Utiliser marked.js pour convertir le Markdown en HTML
217
+ div.innerHTML = marked.parse(text);
 
218
  return div;
219
  }
220
 
221
  // Fonction pour créer un élément spécialisé pour le résultat de code
222
  function createResultElement(text) {
223
  const div = document.createElement('div');
224
+ // Utilise la classe CSS définie dans <style>
225
+ div.className = 'code-result';
226
+ // Utiliser textContent pour la sécurité et préserver les espaces/sauts de ligne
227
+ div.textContent = text;
228
  return div;
229
  }
230
 
 
233
  function createImageElement(base64Data, format = 'png') {
234
  const img = document.createElement('img');
235
  img.src = `data:image/${format};base64,${base64Data}`;
236
+ // Utilise la classe CSS définie dans <style>
237
+ img.className = 'generated-image';
238
  return img;
239
  }
240
 
241
  // Fonction pour créer un indicateur (chargement, réflexion)
242
  function createIndicator(text, type = 'loading') {
243
  const div = document.createElement('div');
244
+ div.dataset.indicatorType = type;
245
+ // Utilise la classe CSS définie dans <style>
246
+ div.className = 'indicator';
247
  div.textContent = text;
248
  return div;
249
  }
 
251
  // Fonction pour créer un message d'erreur
252
  function createErrorElement(text) {
253
  const div = document.createElement('div');
254
+ // Utilise la classe CSS définie dans <style>
255
+ div.className = 'error-message';
256
  div.textContent = 'Erreur: ' + text;
257
  return div;
258
  }
259
 
260
  // Fonction pour traiter les données SSE et mettre à jour l'UI
261
  function processSseData(jsonData) {
262
+ // Gestion des modes 'thinking'/'answering'
263
  if (jsonData.mode === 'thinking') {
 
264
  const loadingIndicator = resultContainer.querySelector('[data-indicator-type="loading"]');
265
  if (loadingIndicator) loadingIndicator.remove();
 
266
  if (!resultContainer.querySelector('[data-indicator-type="thinking"]')) {
267
  resultContainer.appendChild(createIndicator('Gemini réfléchit...', 'thinking'));
268
  }
269
  } else if (jsonData.mode === 'answering') {
 
270
  const thinkingIndicator = resultContainer.querySelector('[data-indicator-type="thinking"]');
271
  if (thinkingIndicator) thinkingIndicator.remove();
272
  }
273
 
274
+ // Traitement du contenu
275
  if (jsonData.content) {
276
  let element;
277
+ let requiresMathJax = false;
278
+
279
  switch(jsonData.type) {
280
  case 'text':
281
+ // Utilise la fonction mise à jour avec marked.js
282
  element = createMarkdownElement(jsonData.content);
283
+ // Le texte Markdown peut contenir du LaTeX
284
+ requiresMathJax = true;
285
  break;
286
  case 'code':
287
+ // 'python' pourrait être passé du backend si connu, sinon null
288
+ element = createCodeElement(jsonData.content, 'python');
 
289
  break;
290
  case 'result':
291
  element = createResultElement(jsonData.content);
 
294
  element = createImageElement(jsonData.content);
295
  break;
296
  default: // Traiter comme du texte par défaut
297
+ console.warn("Type de contenu inconnu reçu:", jsonData.type);
298
+ element = createMarkdownElement(jsonData.content); // fallback
299
+ requiresMathJax = true;
300
  }
 
301
 
302
+ if (element) {
303
+ resultContainer.appendChild(element);
304
+
305
+ // *** IMPORTANT: Déclencher MathJax et Highlight.js ***
306
+ // Déclencher MathJax UNIQUEMENT si le contenu ajouté peut contenir du LaTeX
307
+ if (requiresMathJax && typeof MathJax !== 'undefined' && MathJax.typesetPromise) {
308
+ // Typeset le conteneur entier ou juste le nouvel élément.
309
+ // Typesetter le conteneur entier peut être plus simple si les éléments arrivent rapidement
310
+ MathJax.typesetPromise([resultContainer]).catch((err) => console.error('MathJax processing error:', err));
311
+ }
312
+
313
+ // S'assurer que les blocs de code ajoutés (qui ne sont pas dans le markdown) sont colorisés
314
+ // Note: marked.js s'occupe de ceux dans le markdown via l'option `highlight`
315
+ if (jsonData.type === 'code') {
316
+ // hljs.highlightElement(element.querySelector('code')); // Si createCodeElement n'appelle pas highlightBlock
317
+ // Déjà fait dans createCodeElement maintenant.
318
+ }
319
  }
320
  }
321
 
322
+ // Gestion des erreurs
323
  if (jsonData.error) {
324
  resultContainer.appendChild(createErrorElement(jsonData.error));
 
325
  resultContainer.querySelectorAll('.indicator').forEach(el => el.remove());
326
  }
327
  }
328
 
329
 
330
+ // Fonction pour gérer les événements SSE via Fetch API (inchangée)
331
  async function setupFetchStream(url, formData) {
332
+ // ... (le reste de la fonction setupFetchStream reste identique à votre version) ...
333
+ // Vider le conteneur de résultats et afficher chargement
334
  resultContainer.innerHTML = '';
335
  resultContainer.appendChild(createIndicator('Chargement en cours...', 'loading'));
336
 
 
338
  const response = await fetch(url, {
339
  method: 'POST',
340
  body: formData
 
341
  });
342
 
343
  if (!response.ok) {
344
+ // Essayer de lire le corps de l'erreur s'il existe
345
+ let errorBody = await response.text();
346
+ try {
347
+ const errorJson = JSON.parse(errorBody);
348
+ errorBody = errorJson.error || JSON.stringify(errorJson);
349
+ } catch(e) {
350
+ // Le corps n'était pas du JSON, utiliser le texte brut
351
+ }
352
+ throw new Error(`Erreur HTTP ${response.status}: ${errorBody || response.statusText}`);
353
  }
354
 
355
  // Vider à nouveau au cas où la requête prend du temps avant que le stream commence
356
+ // resultContainer.innerHTML = ''; // Peut causer un flash, on le laisse commenté pour l'instant
357
 
358
  const reader = response.body.getReader();
359
  const decoder = new TextDecoder();
360
+ let buffer = '';
361
 
362
  while (true) {
363
  const { done, value } = await reader.read();
 
365
 
366
  buffer += decoder.decode(value, { stream: true });
367
 
 
368
  let boundary = buffer.indexOf('\n\n');
369
  while (boundary !== -1) {
370
  const message = buffer.substring(0, boundary);
371
+ buffer = buffer.substring(boundary + 2);
372
 
373
  if (message.startsWith('data: ')) {
374
  try {
375
  const jsonData = JSON.parse(message.substring(6));
376
+ processSseData(jsonData); // Utilise la fonction de traitement mise à jour
377
  } catch (e) {
378
  console.error('Erreur parsing JSON du SSE:', e, 'Data:', message.substring(6));
379
+ // Afficher une erreur à l'utilisateur pour le JSON invalide
380
+ resultContainer.appendChild(createErrorElement(`Erreur interne lors du traitement de la réponse: ${e.message}`));
381
  }
382
  }
 
383
  boundary = buffer.indexOf('\n\n');
384
  }
385
+ // Faire défiler vers le bas
386
+ // window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
387
+ resultContainer.scrollTop = resultContainer.scrollHeight; // Mieux pour un div scrollable
388
  }
389
+ // Traiter le reste du buffer
390
  if (buffer.startsWith('data: ')) {
391
  try {
392
  const jsonData = JSON.parse(buffer.substring(6));
393
  processSseData(jsonData);
394
  } catch (e) {
395
  console.error('Erreur parsing JSON du dernier chunk SSE:', e, 'Data:', buffer.substring(6));
396
+ resultContainer.appendChild(createErrorElement(`Erreur interne lors du traitement final de la réponse: ${e.message}`));
397
  }
398
  }
399
 
400
  } catch (error) {
401
  console.error('Erreur Fetch Stream:', error);
402
+ // Ne pas vider si des messages d'erreur précédents ont été affichés
403
+ // resultContainer.innerHTML = '';
404
+ // Afficher l'erreur attrapée
405
  resultContainer.appendChild(createErrorElement(error.message));
406
+
407
  } finally {
408
+ // Supprimer tous les indicateurs restants
409
+ resultContainer.querySelectorAll('.indicator').forEach(el => el.remove());
410
+ // Faire défiler vers le bas une dernière fois
411
+ resultContainer.scrollTop = resultContainer.scrollHeight;
412
  }
413
  }
414
 
415
+ // Gestionnaires d'événements pour les formulaires (inchangés)
416
  const solveForm = document.getElementById('solve-form');
417
  if (solveForm) {
418
  solveForm.addEventListener('submit', function(e) {
 
422
  });
423
  }
424
 
 
425
  const solvedForm = document.getElementById('solved-form');
426
  if (solvedForm) {
427
  solvedForm.addEventListener('submit', function(e) {