Docfile commited on
Commit
1e647e7
·
verified ·
1 Parent(s): fc28c41

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +244 -91
templates/index.html CHANGED
@@ -3,39 +3,96 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Résolveur d'Images</title>
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"></script>
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js"></script>
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css">
10
  <style>
11
  body {
12
- font-family: Arial, sans-serif;
13
  max-width: 800px;
14
  margin: 0 auto;
15
  padding: 20px;
16
  line-height: 1.6;
 
 
17
  }
18
  h1 {
19
  text-align: center;
20
- color: #333;
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  .container {
23
  display: flex;
24
  flex-direction: column;
25
- gap: 20px;
 
 
 
 
26
  }
27
  .upload-section {
28
  display: flex;
29
  flex-direction: column;
30
  align-items: center;
31
- padding: 20px;
32
- border: 2px dashed #ccc;
33
- border-radius: 8px;
 
34
  cursor: pointer;
35
- transition: all 0.3s;
 
 
36
  }
37
  .upload-section:hover {
38
- border-color: #888;
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
  #file-input {
41
  display: none;
@@ -43,87 +100,133 @@
43
  .preview-container {
44
  width: 100%;
45
  text-align: center;
46
- margin-top: 10px;
47
  }
48
  #image-preview {
49
  max-width: 100%;
50
  max-height: 300px;
51
  display: none;
 
 
 
52
  }
53
  #solving-container {
54
  display: none;
55
- background-color: #f5f5f5;
56
- padding: 20px;
57
- border-radius: 8px;
 
58
  }
59
  .response-container {
60
  margin-top: 20px;
61
- padding: 20px;
62
- border: 1px solid #ddd;
63
- border-radius: 8px;
64
  background-color: #fff;
65
  display: none;
 
 
 
 
 
 
 
 
 
 
66
  }
67
- .thinking {
68
- color: #777;
69
  font-style: italic;
 
70
  }
71
  .button {
72
- background-color: #4CAF50;
73
  color: white;
74
  border: none;
75
- padding: 10px 20px;
76
  text-align: center;
77
  text-decoration: none;
78
  display: inline-block;
79
  font-size: 16px;
80
- margin: 10px 2px;
 
 
81
  cursor: pointer;
82
- border-radius: 4px;
83
- transition: background-color 0.3s;
 
84
  }
85
  .button:hover {
86
- background-color: #45a049;
 
 
 
 
87
  }
88
  .button:disabled {
89
- background-color: #cccccc;
90
  cursor: not-allowed;
 
 
91
  }
92
  .copy-button {
93
- background-color: #2196F3;
 
94
  }
95
  .copy-button:hover {
96
- background-color: #0b7dda;
97
  }
98
  .telegram-notice {
99
- background-color: #e3f2fd;
100
- border-left: 4px solid #2196F3;
101
- padding: 10px;
102
  margin: 15px 0;
103
- font-size: 14px;
 
104
  }
105
  .loading {
106
  text-align: center;
107
  font-style: italic;
108
- margin: 10px 0;
 
 
 
 
109
  }
110
  .status {
111
  text-align: center;
112
- margin-bottom: 10px;
113
  font-weight: bold;
 
 
114
  }
115
  .status small {
116
  font-weight: normal;
117
- color: #666;
118
- font-size: 0.85em;
 
 
119
  }
 
 
 
 
120
  </style>
121
  </head>
122
  <body>
123
- <h1>Résolveur d'Images</h1>
 
 
 
 
 
 
 
124
 
125
  <div class="container">
126
  <div id="upload-section" class="upload-section">
 
127
  <p>Cliquez ou glissez-déposez une image ici</p>
128
  <input type="file" id="file-input" accept="image/*">
129
  <div class="preview-container">
@@ -131,17 +234,18 @@
131
  </div>
132
  </div>
133
 
134
- <button id="solve-button" class="button" disabled>Résoudre</button>
135
 
136
  <div id="solving-container">
137
  <div class="status" id="status">En attente de résolution...</div>
138
  <div class="telegram-notice">
139
- La réponse complète sera également envoyée sous forme de fichier texte sur Telegram.
140
  </div>
141
  <div class="loading" id="loading-text">Traitement en cours...</div>
142
  <div class="response-container" id="response-container">
 
143
  <div id="response"></div>
144
- <button id="copy-button" class="button copy-button">Copier la réponse</button>
145
  </div>
146
  </div>
147
  </div>
@@ -154,28 +258,30 @@
154
  const solveButton = document.getElementById('solve-button');
155
  const solvingContainer = document.getElementById('solving-container');
156
  const responseContainer = document.getElementById('response-container');
157
- const response = document.getElementById('response');
158
  const copyButton = document.getElementById('copy-button');
159
  const statusElement = document.getElementById('status');
160
  const loadingText = document.getElementById('loading-text');
161
 
162
  let selectedFile = null;
163
 
164
- // Événements pour l'upload d'image
165
  uploadSection.addEventListener('click', () => fileInput.click());
166
 
167
  uploadSection.addEventListener('dragover', (e) => {
168
  e.preventDefault();
169
- uploadSection.style.borderColor = '#2196F3';
 
170
  });
171
 
172
  uploadSection.addEventListener('dragleave', () => {
173
- uploadSection.style.borderColor = '#ccc';
 
174
  });
175
 
176
  uploadSection.addEventListener('drop', (e) => {
177
  e.preventDefault();
178
- uploadSection.style.borderColor = '#ccc';
 
179
 
180
  if (e.dataTransfer.files.length) {
181
  handleFileSelection(e.dataTransfer.files[0]);
@@ -190,12 +296,13 @@
190
 
191
  function handleFileSelection(file) {
192
  if (!file.type.startsWith('image/')) {
193
- alert('Veuillez sélectionner une image valide');
194
  return;
195
  }
196
 
197
  selectedFile = file;
198
  solveButton.disabled = false;
 
199
 
200
  const reader = new FileReader();
201
  reader.onload = (e) => {
@@ -203,28 +310,37 @@
203
  imagePreview.style.display = 'block';
204
  };
205
  reader.readAsDataURL(file);
 
 
 
 
206
  }
207
 
208
- // Événement pour résoudre l'image
209
  solveButton.addEventListener('click', () => {
210
  if (!selectedFile) return;
211
 
212
  solveButton.disabled = true;
 
213
  solvingContainer.style.display = 'block';
214
  responseContainer.style.display = 'none';
215
- statusElement.textContent = 'En attente de résolution...';
 
216
  loadingText.style.display = 'block';
217
- response.innerHTML = '';
218
 
219
  const formData = new FormData();
220
  formData.append('image', selectedFile);
221
 
222
- // Soumettre l'image pour traitement en arrière-plan
223
  fetch('/solve', {
224
  method: 'POST',
225
  body: formData
226
  })
227
- .then(response => response.json())
 
 
 
 
 
228
  .then(data => {
229
  if (data.error) {
230
  throw new Error(data.error);
@@ -233,112 +349,149 @@
233
  const taskId = data.task_id;
234
  statusElement.textContent = 'Traitement en arrière-plan (ID: ' + taskId + ')';
235
 
236
- // Création d'une connexion SSE pour suivre le progrès
237
  const eventSource = new EventSource('/stream/' + taskId);
238
- let fullResponse = '';
239
 
240
  eventSource.onmessage = function(event) {
241
  const data = JSON.parse(event.data);
242
 
243
  if (data.error) {
244
- statusElement.textContent = 'Erreur:';
245
- response.innerHTML = data.error;
 
246
  responseContainer.style.display = 'block';
247
  loadingText.style.display = 'none';
248
  eventSource.close();
249
  solveButton.disabled = false;
 
250
  return;
251
  }
252
 
253
  if (data.status === 'pending') {
254
- statusElement.textContent = 'En attente de traitement...';
255
  } else if (data.status === 'processing') {
256
- statusElement.textContent = 'Gemini traite votre image...';
257
- statusElement.innerHTML += '<br><small>La réponse sera également envoyée sur Telegram</small>';
258
  } else if (data.status === 'completed') {
259
- statusElement.textContent = 'Traitement terminé!';
 
260
  responseContainer.style.display = 'block';
261
  loadingText.style.display = 'none';
262
 
263
- fullResponse = data.response;
264
- response.innerHTML = fullResponse;
265
- renderMathInElement(response);
 
 
 
 
 
 
266
 
267
  eventSource.close();
268
  solveButton.disabled = false;
 
269
  } else if (data.status === 'error') {
270
- statusElement.textContent = 'Erreur:';
271
- response.innerHTML = data.error || 'Une erreur inattendue est survenue';
 
272
  responseContainer.style.display = 'block';
273
  loadingText.style.display = 'none';
274
 
275
  eventSource.close();
276
  solveButton.disabled = false;
 
277
  }
278
  };
279
 
280
  eventSource.onerror = function() {
281
  eventSource.close();
282
- // Essayer de récupérer le statut via une requête GET normale
283
  fetch('/task/' + taskId)
284
  .then(response => response.json())
285
  .then(taskData => {
286
  if (taskData.status === 'completed') {
287
- statusElement.textContent = 'Traitement terminé!';
 
288
  responseContainer.style.display = 'block';
289
  loadingText.style.display = 'none';
290
 
291
- response.innerHTML = taskData.response;
292
- renderMathInElement(response);
293
- } else if (taskData.status === 'error') {
294
- throw new Error(taskData.error || 'Une erreur inattendue est survenue');
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  }
296
  })
297
  .catch(error => {
 
298
  statusElement.textContent = 'Erreur de connexion:';
299
- response.innerHTML = 'La connexion a été perdue, mais le traitement continue en arrière-plan. La réponse sera envoyée sur Telegram.';
300
- responseContainer.style.display = 'block';
301
- loadingText.style.display = 'none';
302
  })
303
  .finally(() => {
 
 
304
  solveButton.disabled = false;
 
305
  });
306
  };
307
  })
308
  .catch(error => {
309
- statusElement.textContent = 'Erreur:';
310
- response.innerHTML = error.message || 'Une erreur est survenue lors de la communication avec le serveur.';
 
311
  responseContainer.style.display = 'block';
312
  loadingText.style.display = 'none';
313
  solveButton.disabled = false;
 
314
  });
315
  });
316
 
317
- // Événement pour copier la réponse
318
  copyButton.addEventListener('click', () => {
319
- const range = document.createRange();
320
- range.selectNode(response);
321
- window.getSelection().removeAllRanges();
322
- window.getSelection().addRange(range);
323
- document.execCommand('copy');
324
- window.getSelection().removeAllRanges();
325
-
326
- copyButton.textContent = 'Copié!';
327
- setTimeout(() => {
328
- copyButton.textContent = 'Copier la réponse';
329
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  });
331
- });
332
-
333
- // Rendu des formules LaTeX
334
- document.addEventListener('DOMContentLoaded', function() {
335
  renderMathInElement(document.body, {
336
  delimiters: [
337
  {left: '$$', right: '$$', display: true},
338
  {left: '$', right: '$', display: false},
339
  {left: '\\(', right: '\\)', display: false},
340
  {left: '\\[', right: '\\]', display: true}
341
- ]
 
342
  });
343
  });
344
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Résolveur d'Images - Mariam</title>
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"></script>
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js"></script>
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css">
10
  <style>
11
  body {
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
  max-width: 800px;
14
  margin: 0 auto;
15
  padding: 20px;
16
  line-height: 1.6;
17
+ background-color: #f4f7f6; /* Fond légèrement gris */
18
+ color: #333;
19
  }
20
  h1 {
21
  text-align: center;
22
+ color: #2c3e50; /* Bleu foncé */
23
+ margin-bottom: 10px;
24
+ font-size: 2.5em;
25
+ }
26
+ .subtitle {
27
+ text-align: center;
28
+ color: #555;
29
+ margin-bottom: 20px;
30
+ font-size: 1.1em;
31
+ }
32
+
33
+ .telegram-join-button-container {
34
+ text-align: center;
35
+ margin-bottom: 30px;
36
  }
37
+
38
+ .telegram-button {
39
+ background-color: #0088cc; /* Bleu Telegram */
40
+ color: white;
41
+ border: none;
42
+ padding: 12px 25px;
43
+ text-align: center;
44
+ text-decoration: none;
45
+ display: inline-block;
46
+ font-size: 16px;
47
+ cursor: pointer;
48
+ border-radius: 8px;
49
+ transition: background-color 0.3s, transform 0.2s;
50
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
51
+ }
52
+ .telegram-button:hover {
53
+ background-color: #0077b3;
54
+ transform: translateY(-2px);
55
+ }
56
+ .telegram-button:active {
57
+ transform: translateY(0px);
58
+ }
59
+
60
+
61
  .container {
62
  display: flex;
63
  flex-direction: column;
64
+ gap: 25px;
65
+ background-color: #ffffff;
66
+ padding: 30px;
67
+ border-radius: 12px;
68
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
69
  }
70
  .upload-section {
71
  display: flex;
72
  flex-direction: column;
73
  align-items: center;
74
+ justify-content: center;
75
+ padding: 30px;
76
+ border: 3px dashed #bdc3c7; /* Gris clair */
77
+ border-radius: 10px;
78
  cursor: pointer;
79
+ transition: all 0.3s ease;
80
+ background-color: #ecf0f1; /* Gris très clair */
81
+ min-height: 150px;
82
  }
83
  .upload-section:hover {
84
+ border-color: #3498db; /* Bleu lors du survol */
85
+ background-color: #e8f4fb;
86
+ }
87
+ .upload-section p {
88
+ margin: 0;
89
+ font-size: 1.1em;
90
+ color: #555;
91
+ }
92
+ .upload-icon {
93
+ font-size: 2.5em;
94
+ color: #3498db;
95
+ margin-bottom: 10px;
96
  }
97
  #file-input {
98
  display: none;
 
100
  .preview-container {
101
  width: 100%;
102
  text-align: center;
103
+ margin-top: 15px;
104
  }
105
  #image-preview {
106
  max-width: 100%;
107
  max-height: 300px;
108
  display: none;
109
+ border-radius: 8px;
110
+ border: 1px solid #ddd;
111
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
112
  }
113
  #solving-container {
114
  display: none;
115
+ background-color: #f9f9f9;
116
+ padding: 25px;
117
+ border-radius: 10px;
118
+ border: 1px solid #e0e0e0;
119
  }
120
  .response-container {
121
  margin-top: 20px;
122
+ padding: 25px;
123
+ border: 1px solid #d1d8dd;
124
+ border-radius: 10px;
125
  background-color: #fff;
126
  display: none;
127
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
128
+ }
129
+ #response {
130
+ background-color: #fdfdfd;
131
+ padding: 15px;
132
+ border-radius: 6px;
133
+ border: 1px solid #eee;
134
+ min-height: 50px;
135
+ white-space: pre-wrap; /* Pour conserver les sauts de ligne */
136
+ word-wrap: break-word; /* Pour couper les mots longs */
137
  }
138
+ .thinking { /* Style pour le texte "Mariam réfléchit..." */
139
+ color: #3498db; /* Bleu */
140
  font-style: italic;
141
+ font-weight: bold;
142
  }
143
  .button {
144
+ background-color: #3498db; /* Bleu primaire */
145
  color: white;
146
  border: none;
147
+ padding: 12px 25px;
148
  text-align: center;
149
  text-decoration: none;
150
  display: inline-block;
151
  font-size: 16px;
152
+ margin: 10px 0; /* Centré et prend toute la largeur */
153
+ width: 100%;
154
+ box-sizing: border-box;
155
  cursor: pointer;
156
+ border-radius: 8px;
157
+ transition: background-color 0.3s, transform 0.2s;
158
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
159
  }
160
  .button:hover {
161
+ background-color: #2980b9; /* Bleu plus foncé au survol */
162
+ transform: translateY(-2px);
163
+ }
164
+ .button:active {
165
+ transform: translateY(0px);
166
  }
167
  .button:disabled {
168
+ background-color: #bdc3c7; /* Gris pour désactivé */
169
  cursor: not-allowed;
170
+ transform: translateY(0px);
171
+ box-shadow: none;
172
  }
173
  .copy-button {
174
+ background-color: #2ecc71; /* Vert pour copier */
175
+ margin-top: 15px;
176
  }
177
  .copy-button:hover {
178
+ background-color: #27ae60; /* Vert plus foncé */
179
  }
180
  .telegram-notice {
181
+ background-color: #eaf5ff; /* Bleu très clair */
182
+ border-left: 5px solid #3498db; /* Bordure bleue */
183
+ padding: 12px 15px;
184
  margin: 15px 0;
185
+ font-size: 0.95em;
186
+ border-radius: 0 5px 5px 0;
187
  }
188
  .loading {
189
  text-align: center;
190
  font-style: italic;
191
+ margin: 15px 0;
192
+ color: #555;
193
+ }
194
+ .loading::before {
195
+ content: "⏳ ";
196
  }
197
  .status {
198
  text-align: center;
199
+ margin-bottom: 15px;
200
  font-weight: bold;
201
+ font-size: 1.1em;
202
+ color: #2c3e50;
203
  }
204
  .status small {
205
  font-weight: normal;
206
+ color: #7f8c8d; /* Gris */
207
+ font-size: 0.9em;
208
+ display: block;
209
+ margin-top: 5px;
210
  }
211
+ /* Styles pour les messages d'erreur/succès dans le status */
212
+ .status.error { color: #e74c3c; } /* Rouge */
213
+ .status.completed { color: #2ecc71; } /* Vert */
214
+
215
  </style>
216
  </head>
217
  <body>
218
+ <h1>🖼️ Science ( Math, physique, chimie)🧠</h1>
219
+ <p class="subtitle">Avec Mariam, votre assistante IA</p>
220
+
221
+ <div class="telegram-join-button-container">
222
+ <a href="https://t.me/+ic4zemy1E1k0MzQ0" target="_blank" class="telegram-button">
223
+ 🚀 Rejoindre le Groupe Telegram
224
+ </a>
225
+ </div>
226
 
227
  <div class="container">
228
  <div id="upload-section" class="upload-section">
229
+ <div class="upload-icon">📤</div>
230
  <p>Cliquez ou glissez-déposez une image ici</p>
231
  <input type="file" id="file-input" accept="image/*">
232
  <div class="preview-container">
 
234
  </div>
235
  </div>
236
 
237
+ <button id="solve-button" class="button" disabled>🔍 Résoudre</button>
238
 
239
  <div id="solving-container">
240
  <div class="status" id="status">En attente de résolution...</div>
241
  <div class="telegram-notice">
242
+ La réponse complète sera également envoyée sous forme de fichier texte sur notre groupe Telegram.
243
  </div>
244
  <div class="loading" id="loading-text">Traitement en cours...</div>
245
  <div class="response-container" id="response-container">
246
+ <h3>Réponse de Mariam :</h3>
247
  <div id="response"></div>
248
+ <button id="copy-button" class="button copy-button">📋 Copier la réponse</button>
249
  </div>
250
  </div>
251
  </div>
 
258
  const solveButton = document.getElementById('solve-button');
259
  const solvingContainer = document.getElementById('solving-container');
260
  const responseContainer = document.getElementById('response-container');
261
+ const responseDiv = document.getElementById('response'); // Renommé pour clarté
262
  const copyButton = document.getElementById('copy-button');
263
  const statusElement = document.getElementById('status');
264
  const loadingText = document.getElementById('loading-text');
265
 
266
  let selectedFile = null;
267
 
 
268
  uploadSection.addEventListener('click', () => fileInput.click());
269
 
270
  uploadSection.addEventListener('dragover', (e) => {
271
  e.preventDefault();
272
+ uploadSection.style.borderColor = '#3498db';
273
+ uploadSection.style.backgroundColor = '#e8f4fb';
274
  });
275
 
276
  uploadSection.addEventListener('dragleave', () => {
277
+ uploadSection.style.borderColor = '#bdc3c7';
278
+ uploadSection.style.backgroundColor = '#ecf0f1';
279
  });
280
 
281
  uploadSection.addEventListener('drop', (e) => {
282
  e.preventDefault();
283
+ uploadSection.style.borderColor = '#bdc3c7';
284
+ uploadSection.style.backgroundColor = '#ecf0f1';
285
 
286
  if (e.dataTransfer.files.length) {
287
  handleFileSelection(e.dataTransfer.files[0]);
 
296
 
297
  function handleFileSelection(file) {
298
  if (!file.type.startsWith('image/')) {
299
+ alert('Veuillez sélectionner une image valide (format PNG, JPG, GIF, etc.)');
300
  return;
301
  }
302
 
303
  selectedFile = file;
304
  solveButton.disabled = false;
305
+ solveButton.textContent = '🔍 Résoudre'; // Réinitialiser le texte du bouton
306
 
307
  const reader = new FileReader();
308
  reader.onload = (e) => {
 
310
  imagePreview.style.display = 'block';
311
  };
312
  reader.readAsDataURL(file);
313
+
314
+ // Cacher la zone de résolution si une nouvelle image est sélectionnée
315
+ solvingContainer.style.display = 'none';
316
+ responseContainer.style.display = 'none';
317
  }
318
 
 
319
  solveButton.addEventListener('click', () => {
320
  if (!selectedFile) return;
321
 
322
  solveButton.disabled = true;
323
+ solveButton.textContent = '⏳ Traitement...';
324
  solvingContainer.style.display = 'block';
325
  responseContainer.style.display = 'none';
326
+ statusElement.className = 'status'; // Reset class
327
+ statusElement.textContent = 'Préparation de la requête...';
328
  loadingText.style.display = 'block';
329
+ responseDiv.innerHTML = '';
330
 
331
  const formData = new FormData();
332
  formData.append('image', selectedFile);
333
 
 
334
  fetch('/solve', {
335
  method: 'POST',
336
  body: formData
337
  })
338
+ .then(response => {
339
+ if (!response.ok) {
340
+ return response.json().then(err => { throw new Error(err.error || `Erreur Serveur: ${response.status}`) });
341
+ }
342
+ return response.json();
343
+ })
344
  .then(data => {
345
  if (data.error) {
346
  throw new Error(data.error);
 
349
  const taskId = data.task_id;
350
  statusElement.textContent = 'Traitement en arrière-plan (ID: ' + taskId + ')';
351
 
 
352
  const eventSource = new EventSource('/stream/' + taskId);
 
353
 
354
  eventSource.onmessage = function(event) {
355
  const data = JSON.parse(event.data);
356
 
357
  if (data.error) {
358
+ statusElement.className = 'status error';
359
+ statusElement.textContent = 'Erreur de traitement:';
360
+ responseDiv.innerHTML = `<p style="color:red;">${data.error}</p>`;
361
  responseContainer.style.display = 'block';
362
  loadingText.style.display = 'none';
363
  eventSource.close();
364
  solveButton.disabled = false;
365
+ solveButton.textContent = '🔍 Résoudre';
366
  return;
367
  }
368
 
369
  if (data.status === 'pending') {
370
+ statusElement.textContent = 'En file d\'attente...';
371
  } else if (data.status === 'processing') {
372
+ statusElement.innerHTML = '<span class="thinking">Mariam</span> traite votre image... <br><small>La réponse sera également envoyée sur Telegram.</small>';
 
373
  } else if (data.status === 'completed') {
374
+ statusElement.className = 'status completed';
375
+ statusElement.textContent = 'Traitement terminé avec succès ! 🎉';
376
  responseContainer.style.display = 'block';
377
  loadingText.style.display = 'none';
378
 
379
+ responseDiv.innerHTML = data.response; // Utiliser innerHTML si la réponse contient du HTML
380
+ renderMathInElement(responseDiv, {
381
+ delimiters: [
382
+ {left: '$$', right: '$$', display: true},
383
+ {left: '$', right: '$', display: false},
384
+ {left: '\\(', right: '\\)', display: false},
385
+ {left: '\\[', right: '\\]', display: true}
386
+ ]
387
+ });
388
 
389
  eventSource.close();
390
  solveButton.disabled = false;
391
+ solveButton.textContent = '🔍 Résoudre';
392
  } else if (data.status === 'error') {
393
+ statusElement.className = 'status error';
394
+ statusElement.textContent = 'Erreur de traitement:';
395
+ responseDiv.innerHTML = `<p style="color:red;">${data.error || 'Une erreur inattendue est survenue durant le traitement.'}</p>`;
396
  responseContainer.style.display = 'block';
397
  loadingText.style.display = 'none';
398
 
399
  eventSource.close();
400
  solveButton.disabled = false;
401
+ solveButton.textContent = '🔍 Résoudre';
402
  }
403
  };
404
 
405
  eventSource.onerror = function() {
406
  eventSource.close();
 
407
  fetch('/task/' + taskId)
408
  .then(response => response.json())
409
  .then(taskData => {
410
  if (taskData.status === 'completed') {
411
+ statusElement.className = 'status completed';
412
+ statusElement.textContent = 'Traitement terminé (récupéré après déconnexion) !';
413
  responseContainer.style.display = 'block';
414
  loadingText.style.display = 'none';
415
 
416
+ responseDiv.innerHTML = taskData.response;
417
+ renderMathInElement(responseDiv, {
418
+ delimiters: [
419
+ {left: '$$', right: '$$', display: true},
420
+ {left: '$', right: '$', display: false},
421
+ {left: '\\(', right: '\\)', display: false},
422
+ {left: '\\[', right: '\\]', display: true}
423
+ ]
424
+ });
425
+ } else if (taskData.status === 'error' || (taskData.error && taskData.error !== "Task not found or not completed yet")) {
426
+ statusElement.className = 'status error';
427
+ statusElement.textContent = 'Erreur (récupéré après déconnexion):';
428
+ responseDiv.innerHTML = `<p style="color:red;">${taskData.error || 'Une erreur inattendue est survenue.'}</p>`;
429
+ } else {
430
+ statusElement.className = 'status error';
431
+ statusElement.textContent = 'Erreur de connexion:';
432
+ responseDiv.innerHTML = 'La connexion au flux a été perdue, mais le traitement continue en arrière-plan. La réponse sera envoyée sur Telegram si configuré. Vous pouvez essayer de rafraîchir pour voir si la tâche est terminée.';
433
  }
434
  })
435
  .catch(error => {
436
+ statusElement.className = 'status error';
437
  statusElement.textContent = 'Erreur de connexion:';
438
+ responseDiv.innerHTML = 'La connexion au flux a été perdue et la récupération a échoué. Le traitement peut continuer en arrière-plan. La réponse sera envoyée sur Telegram si configuré.';
 
 
439
  })
440
  .finally(() => {
441
+ responseContainer.style.display = 'block';
442
+ loadingText.style.display = 'none';
443
  solveButton.disabled = false;
444
+ solveButton.textContent = '🔍 Résoudre';
445
  });
446
  };
447
  })
448
  .catch(error => {
449
+ statusElement.className = 'status error';
450
+ statusElement.textContent = 'Erreur Initiale:';
451
+ responseDiv.innerHTML = `<p style="color:red;">${error.message || 'Une erreur est survenue lors de la communication avec le serveur.'}</p>`;
452
  responseContainer.style.display = 'block';
453
  loadingText.style.display = 'none';
454
  solveButton.disabled = false;
455
+ solveButton.textContent = '🔍 Résoudre';
456
  });
457
  });
458
 
 
459
  copyButton.addEventListener('click', () => {
460
+ const textToCopy = responseDiv.innerText || responseDiv.textContent; // Pour mieux copier le texte brut
461
+ navigator.clipboard.writeText(textToCopy).then(() => {
462
+ copyButton.textContent = '✅ Copié!';
463
+ setTimeout(() => {
464
+ copyButton.textContent = '📋 Copier la réponse';
465
+ }, 2000);
466
+ }).catch(err => {
467
+ console.error('Erreur de copie: ', err);
468
+ // Fallback pour les anciens navigateurs (moins fiable)
469
+ const range = document.createRange();
470
+ range.selectNode(responseDiv);
471
+ window.getSelection().removeAllRanges();
472
+ window.getSelection().addRange(range);
473
+ try {
474
+ document.execCommand('copy');
475
+ copyButton.textContent = '✅ Copié! (fallback)';
476
+ } catch (e) {
477
+ copyButton.textContent = 'Erreur copie';
478
+ }
479
+ window.getSelection().removeAllRanges();
480
+ setTimeout(() => {
481
+ copyButton.textContent = '📋 Copier la réponse';
482
+ }, 2000);
483
+ });
484
  });
485
+
486
+ // Rendu KaTeX initial pour toute la page (si des formules sont en dehors des réponses)
 
 
487
  renderMathInElement(document.body, {
488
  delimiters: [
489
  {left: '$$', right: '$$', display: true},
490
  {left: '$', right: '$', display: false},
491
  {left: '\\(', right: '\\)', display: false},
492
  {left: '\\[', right: '\\]', display: true}
493
+ ],
494
+ throwOnError: false // Important pour ne pas bloquer le script si KaTeX rencontre une erreur
495
  });
496
  });
497
  </script>