Docfile commited on
Commit
7affd69
·
verified ·
1 Parent(s): 601e910

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +302 -323
templates/index.html CHANGED
@@ -4,351 +4,330 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Résolution d'exercices avec Gemini</title>
7
-
 
 
 
8
  <!-- Inclusion de MathJax pour le rendu LaTeX -->
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
10
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  <!-- Inclusion de highlight.js pour la coloration syntaxique -->
12
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
13
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
14
-
 
 
 
 
15
  <style>
16
- body {
17
- font-family: Arial, sans-serif;
18
- line-height: 1.6;
19
- max-width: 800px;
20
- margin: 0 auto;
21
- padding: 20px;
22
- }
23
-
24
- h1, h2 {
25
- color: #333;
26
- }
27
-
28
- .container {
29
- margin-bottom: 30px;
30
- }
31
-
32
- .form-group {
33
- margin-bottom: 15px;
34
- }
35
-
36
- label {
37
- display: block;
38
- margin-bottom: 5px;
39
- font-weight: bold;
40
- }
41
-
42
- input[type="file"] {
43
- margin-bottom: 10px;
44
- }
45
-
46
- button {
47
- background-color: #4CAF50;
48
- color: white;
49
- padding: 10px 15px;
50
- border: none;
51
- border-radius: 4px;
52
- cursor: pointer;
53
- font-size: 16px;
54
- }
55
-
56
- button:hover {
57
- background-color: #45a049;
58
- }
59
-
60
- #result-container {
61
- margin-top: 30px;
62
- border: 1px solid #ddd;
63
- padding: 20px;
64
- border-radius: 5px;
65
- background-color: #f9f9f9;
66
- }
67
-
68
- .thinking-indicator {
69
- color: #666;
70
- font-style: italic;
71
- margin-bottom: 10px;
72
- }
73
-
74
- .loading-indicator {
75
- color: #666;
76
- font-style: italic;
77
- margin-bottom: 10px;
78
- }
79
-
80
- .error-message {
81
- color: red;
82
- font-weight: bold;
83
- margin-top: 10px;
84
- }
85
-
86
- pre {
87
- background-color: #f8f8f8;
88
- border: 1px solid #ddd;
89
- border-radius: 3px;
90
- padding: 10px;
91
- overflow-x: auto;
92
- }
93
-
94
- code {
95
- font-family: 'Courier New', Courier, monospace;
96
  }
97
-
98
- .code-result {
99
- background-color: #eaffea;
100
- padding: 10px;
101
- border-left: 3px solid #4CAF50;
102
- margin-bottom: 20px;
103
- }
104
-
105
- .markdown-content {
106
- margin-bottom: 15px;
107
- }
108
-
109
- img {
110
- max-width: 100%;
111
- height: auto;
112
- margin: 10px 0;
113
- border: 1px solid #ddd;
114
- border-radius: 4px;
115
- padding: 5px;
116
- }
117
-
118
- .buttons-container {
119
- display: flex;
120
- gap: 10px;
121
- margin-top: 20px;
122
- }
123
-
124
- .tab-content {
125
- display: none;
126
- }
127
-
128
- .tab-content.active {
129
- display: block;
130
  }
131
  </style>
132
  </head>
133
- <body>
134
- <h1>Résolution d'exercices avec Gemini</h1>
135
-
136
- <div class="container">
137
- <h2>Mode Normal (Gemini Pro)</h2>
138
- <form id="solve-form" enctype="multipart/form-data">
139
- <div class="form-group">
140
- <label for="image">Téléchargez une image de votre exercice:</label>
141
- <input type="file" id="image" name="image" accept="image/*" required>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  </div>
143
- <button type="submit">Résoudre</button>
144
- </form>
145
- </div>
146
-
147
- <div class="container">
148
- <h2>Mode Rapide (Gemini Flash)</h2>
149
- <form id="solved-form" enctype="multipart/form-data">
150
- <div class="form-group">
151
- <label for="image2">Téléchargez une image de votre exercice:</label>
152
- <input type="file" id="image2" name="image" accept="image/*" required>
 
 
 
 
 
 
 
 
 
 
 
 
153
  </div>
154
- <button type="submit">Résoudre rapidement</button>
155
- </form>
156
- </div>
157
-
158
- <div id="result-container">
159
- <p>Les résultats apparaîtront ici après avoir soumis une image.</p>
 
 
 
160
  </div>
161
-
162
- <!-- Inclusion du script JS personnalisé -->
163
  <script>
164
- // Configuration de MathJax pour le rendu LaTeX
165
- MathJax.Hub.Config({
166
- tex2jax: {
167
- inlineMath: [['$', '$'], ['\\(', '\\)']],
168
- displayMath: [['$$', '$$'], ['\\[', '\\]']],
169
- processEscapes: true
170
- }
171
- });
172
-
173
- // Initialisation de highlight.js une fois que le DOM est chargé
174
  document.addEventListener('DOMContentLoaded', function() {
175
- document.querySelectorAll('pre code').forEach((block) => {
176
- hljs.highlightBlock(block);
177
- });
178
- });
179
- </script>
180
 
181
- <script>
182
-
183
- // Ce script doit être inclus dans votre template HTML
184
-
185
- document.addEventListener('DOMContentLoaded', function() {
186
- // Supposons que nous avons un élément pour afficher les résultats
187
- const resultContainer = document.getElementById('result-container');
188
-
189
- // Fonction pour créer un élément de code formaté
190
- function createCodeElement(code) {
191
- const pre = document.createElement('pre');
192
- const codeEl = document.createElement('code');
193
- codeEl.textContent = code;
194
- pre.appendChild(codeEl);
195
- return pre;
196
- }
197
-
198
- // Fonction pour créer un élément Markdown
199
- function createMarkdownElement(text) {
200
- // Vous pourriez utiliser une bibliothèque comme marked.js ici
201
- // Pour simplifier, nous utilisons simplement un paragraphe
202
- const div = document.createElement('div');
203
- div.className = 'markdown-content';
204
-
205
- // Détection simple des éléments LaTeX
206
- // Remplacez ceci par une bibliothèque complète comme MathJax dans votre implémentation réelle
207
- text = text.replace(/\$\$(.*?)\$\$/g, '<span class="latex-block">$$$1$$</span>');
208
- text = text.replace(/\$(.*?)\$/g, '<span class="latex-inline">$$1$</span>');
209
-
210
- div.innerHTML = text;
211
- return div;
212
- }
213
-
214
- // Fonction pour créer un élément image
215
- function createImageElement(base64Data, format = 'png') {
216
- const img = document.createElement('img');
217
- img.src = `data:image/${format};base64,${base64Data}`;
218
- img.style.maxWidth = '100%';
219
- return img;
220
- }
221
-
222
- // Fonction pour gérer les événements SSE
223
- function setupEventSource(url, formData) {
224
- // Vider le conteneur de résultats
225
- resultContainer.innerHTML = '';
226
-
227
- // Afficher un indicateur de chargement
228
- const loadingIndicator = document.createElement('div');
229
- loadingIndicator.className = 'loading-indicator';
230
- loadingIndicator.textContent = 'Chargement en cours...';
231
- resultContainer.appendChild(loadingIndicator);
232
-
233
- // Envoyer la requête avec fetch pour téléverser l'image
234
- fetch(url, {
235
- method: 'POST',
236
- body: formData
237
- }).then(response => {
238
- // Supprimer l'indicateur de chargement
239
- resultContainer.removeChild(loadingIndicator);
240
-
241
- // Créer un nouvel EventSource avec les données de la réponse
242
- const reader = response.body.getReader();
243
-
244
- function processText(text) {
245
- const lines = text.split('\n\n');
246
- lines.forEach(line => {
247
- if (line.startsWith('data: ')) {
248
- try {
249
- const jsonData = JSON.parse(line.substring(6));
250
-
251
- if (jsonData.mode === 'thinking') {
252
- // Afficher un indicateur de réflexion
253
- const thinkingIndicator = document.createElement('div');
254
- thinkingIndicator.className = 'thinking-indicator';
255
- thinkingIndicator.textContent = 'Gemini réfléchit...';
256
- resultContainer.appendChild(thinkingIndicator);
257
- } else if (jsonData.mode === 'answering') {
258
- // Supprimer l'indicateur de réflexion s'il existe
259
- const thinkingIndicator = resultContainer.querySelector('.thinking-indicator');
260
- if (thinkingIndicator) {
261
- resultContainer.removeChild(thinkingIndicator);
262
- }
263
- }
264
-
265
- if (jsonData.content) {
266
- let element;
267
-
268
- switch(jsonData.type) {
269
- case 'text':
270
- element = createMarkdownElement(jsonData.content);
271
- break;
272
- case 'code':
273
- element = createCodeElement(jsonData.content);
274
- break;
275
- case 'result':
276
- element = createMarkdownElement(jsonData.content);
277
- element.className = 'code-result';
278
- break;
279
- case 'image':
280
- element = createImageElement(jsonData.content);
281
- break;
282
- default:
283
- // Si aucun type n'est spécifié, on traite comme du texte
284
- element = createMarkdownElement(jsonData.content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  }
286
-
287
- resultContainer.appendChild(element);
288
-
289
- // Faire défiler vers le bas
290
- window.scrollTo(0, document.body.scrollHeight);
291
- }
292
-
293
- if (jsonData.error) {
294
- const errorElement = document.createElement('div');
295
- errorElement.className = 'error-message';
296
- errorElement.textContent = 'Erreur: ' + jsonData.error;
297
- resultContainer.appendChild(errorElement);
298
  }
299
- } catch (e) {
300
- console.error('Erreur lors du parsing JSON:', e);
301
  }
 
 
302
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  });
304
  }
305
-
306
- // Fonction pour lire les chunks de données
307
- function readChunk() {
308
- return reader.read().then(({ done, value }) => {
309
- if (done) {
310
- return;
311
- }
312
-
313
- // Convertir le chunk en texte
314
- const chunk = new TextDecoder().decode(value);
315
- processText(chunk);
316
-
317
- // Lire le prochain chunk
318
- return readChunk();
319
  });
320
  }
321
-
322
- return readChunk();
323
- }).catch(error => {
324
- console.error('Erreur:', error);
325
- resultContainer.innerHTML = `<div class="error-message">Erreur de connexion: ${error.message}</div>`;
326
- });
327
- }
328
-
329
- // Gestion du formulaire pour /solve
330
- const solveForm = document.getElementById('solve-form');
331
- if (solveForm) {
332
- solveForm.addEventListener('submit', function(e) {
333
- e.preventDefault();
334
- const formData = new FormData(solveForm);
335
- setupEventSource('/solve', formData);
336
  });
337
- }
338
-
339
- // Gestion du formulaire pour /solved
340
- const solvedForm = document.getElementById('solved-form');
341
- if (solvedForm) {
342
- solvedForm.addEventListener('submit', function(e) {
343
- e.preventDefault();
344
- const formData = new FormData(solvedForm);
345
- setupEventSource('/solved', formData);
346
- });
347
- }
348
- });
349
-
350
- </script>
351
-
352
 
353
  </body>
354
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Résolution d'exercices avec Gemini</title>
7
+
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>
25
+ <script type="text/javascript" id="MathJax-script" async
26
+ src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
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>
50
+ <body class="bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-100 text-gray-800 font-sans min-h-screen flex flex-col items-center py-12 px-4">
51
+
52
+ <div class="w-full max-w-3xl">
53
+ <h1 class="text-4xl font-bold text-center mb-10 text-indigo-700">
54
+ Résolution d'exercices avec Gemini
55
+ </h1>
56
+
57
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
58
+ <!-- Section Mode Normal -->
59
+ <div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
60
+ <h2 class="text-2xl font-semibold mb-5 text-indigo-600">Mode Normal (Gemini Pro)</h2>
61
+ <form id="solve-form" enctype="multipart/form-data">
62
+ <div class="mb-4">
63
+ <label for="image" class="block text-sm font-medium text-gray-700 mb-2">
64
+ Téléchargez une image de votre exercice :
65
+ </label>
66
+ <input type="file" id="image" name="image" accept="image/*" required
67
+ class="block w-full text-sm text-gray-500 border border-gray-300 rounded-lg cursor-pointer
68
+ file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0
69
+ file:text-sm file:font-semibold file:bg-indigo-100 file:text-indigo-700
70
+ hover:file:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
71
+ </div>
72
+ <button type="submit"
73
+ class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg
74
+ focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50
75
+ transition duration-150 ease-in-out">
76
+ Résoudre
77
+ </button>
78
+ </form>
79
  </div>
80
+
81
+ <!-- Section Mode Rapide -->
82
+ <div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
83
+ <h2 class="text-2xl font-semibold mb-5 text-purple-600">Mode Rapide (Gemini Flash)</h2>
84
+ <form id="solved-form" enctype="multipart/form-data">
85
+ <div class="mb-4">
86
+ <label for="image2" class="block text-sm font-medium text-gray-700 mb-2">
87
+ Téléchargez une image de votre exercice :
88
+ </label>
89
+ <input type="file" id="image2" name="image" accept="image/*" required
90
+ class="block w-full text-sm text-gray-500 border border-gray-300 rounded-lg cursor-pointer
91
+ file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0
92
+ file:text-sm file:font-semibold file:bg-purple-100 file:text-purple-700
93
+ hover:file:bg-purple-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
94
+ </div>
95
+ <button type="submit"
96
+ class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-lg
97
+ focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50
98
+ transition duration-150 ease-in-out">
99
+ Résoudre rapidement
100
+ </button>
101
+ </form>
102
  </div>
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>
113
+
114
+ <!-- Inclusion du script JS personnalisé (mis à jour) -->
115
  <script>
 
 
 
 
 
 
 
 
 
 
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
+
157
+
158
+ // Fonction pour créer un élément image
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
+ }
176
+
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);
215
+ break;
216
+ case 'image':
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
+
245
+ try {
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();
265
+ if (done) break;
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) {
314
+ e.preventDefault();
315
+ const formData = new FormData(solveForm);
316
+ setupFetchStream('/solve', formData);
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) {
324
+ e.preventDefault();
325
+ const formData = new FormData(solvedForm);
326
+ setupFetchStream('/solved', formData);
 
 
 
 
 
 
327
  });
328
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  });
330
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
  </body>
333
  </html>