Docfile commited on
Commit
8da5358
·
verified ·
1 Parent(s): 88f6be7

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +313 -300
templates/index.html CHANGED
@@ -8,292 +8,337 @@
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
  .container {
61
- display: flex;
62
- flex-direction: column;
63
- gap: 25px;
64
- background-color: #ffffff;
65
- padding: 30px;
66
- border-radius: 12px;
67
- box-shadow: 0 4px 15px rgba(0,0,0,0.1);
68
  }
69
-
70
  .style-selection {
71
  background-color: #f9f9f9;
72
- padding: 20px;
73
- border-radius: 10px;
74
- border: 1px solid #e0e0e0;
75
- margin-bottom: 15px;
76
  }
77
-
78
  .style-selection h3 {
79
- margin: 0 0 15px 0;
80
  color: #2c3e50;
81
- font-size: 1.2em;
82
  }
83
-
84
  .radio-group {
85
  display: flex;
86
  flex-direction: column;
87
- gap: 12px;
88
  }
89
-
90
  .radio-option {
91
  display: flex;
92
- align-items: center;
93
- gap: 10px;
94
- padding: 10px;
95
- border-radius: 6px;
96
  transition: background-color 0.2s;
97
  cursor: pointer;
 
98
  }
99
-
100
  .radio-option:hover {
101
  background-color: #f0f4f8;
 
102
  }
103
-
104
  .radio-option input[type="radio"] {
105
- width: 18px;
106
- height: 18px;
107
- accent-color: #3498db;
 
 
108
  }
109
-
110
- .radio-option label {
111
- cursor: pointer;
 
 
 
112
  font-weight: 500;
 
 
113
  }
114
-
115
  .radio-description {
116
- font-size: 0.9em;
117
  color: #666;
118
- margin-left: 28px;
119
- margin-top: -8px;
120
  }
121
-
122
  .upload-section {
123
- display: flex;
124
- flex-direction: column;
125
- align-items: center;
126
- justify-content: center;
127
- padding: 30px;
128
- border: 3px dashed #bdc3c7; /* Gris clair */
129
- border-radius: 10px;
130
  cursor: pointer;
131
  transition: all 0.3s ease;
132
- background-color: #ecf0f1; /* Gris très clair */
133
- min-height: 150px;
134
  }
 
135
  .upload-section:hover {
136
- border-color: #3498db; /* Bleu lors du survol */
137
  background-color: #e8f4fb;
138
  }
139
- .upload-section p {
140
- margin: 0;
141
- font-size: 1.1em;
142
- color: #555;
143
- }
144
  .upload-icon {
145
- font-size: 2.5em;
146
- color: #3498db;
147
- margin-bottom: 10px;
148
  }
 
149
  #file-input {
150
  display: none;
151
  }
 
152
  .preview-container {
153
- width: 100%;
154
- text-align: center;
155
- margin-top: 15px;
156
  }
 
157
  #image-preview {
158
  max-width: 100%;
159
  max-height: 300px;
160
  display: none;
161
- border-radius: 8px;
162
- border: 1px solid #ddd;
163
- box-shadow: 0 2px 5px rgba(0,0,0,0.05);
164
- }
165
- #solving-container {
166
- display: none;
167
- background-color: #f9f9f9;
168
- padding: 25px;
169
- border-radius: 10px;
170
- border: 1px solid #e0e0e0;
171
- }
172
- .response-container {
173
- margin-top: 20px;
174
- padding: 25px;
175
- border: 1px solid #d1d8dd;
176
- border-radius: 10px;
177
- background-color: #fff;
178
- display: none;
179
- box-shadow: 0 2px 8px rgba(0,0,0,0.08);
180
- }
181
- #response {
182
- background-color: #fdfdfd;
183
- padding: 15px;
184
- border-radius: 6px;
185
- border: 1px solid #eee;
186
- min-height: 50px;
187
- white-space: pre-wrap; /* Pour conserver les sauts de ligne */
188
- word-wrap: break-word; /* Pour couper les mots longs */
189
- }
190
- .thinking { /* Style pour le texte "Mariam réfléchit..." */
191
- color: #3498db; /* Bleu */
192
- font-style: italic;
193
- font-weight: bold;
194
  }
 
195
  .button {
196
- background-color: #3498db; /* Bleu primaire */
197
- color: white;
198
- border: none;
199
- padding: 12px 25px;
200
- text-align: center;
201
- text-decoration: none;
202
- display: inline-block;
203
- font-size: 16px;
204
- margin: 10px 0; /* Centré et prend toute la largeur */
205
  width: 100%;
206
- box-sizing: border-box;
 
 
 
207
  cursor: pointer;
208
- border-radius: 8px;
209
- transition: background-color 0.3s, transform 0.2s;
 
 
210
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
211
  }
212
- .button:hover {
213
- background-color: #2980b9; /* Bleu plus foncé au survol */
214
  transform: translateY(-2px);
 
215
  }
216
- .button:active {
217
- transform: translateY(0px);
218
- }
219
  .button:disabled {
220
- background-color: #bdc3c7; /* Gris pour désactivé */
221
  cursor: not-allowed;
222
- transform: translateY(0px);
223
- box-shadow: none;
224
  }
 
225
  .copy-button {
226
- background-color: #2ecc71; /* Vert pour copier */
227
- margin-top: 15px;
228
  }
 
229
  .copy-button:hover {
230
- background-color: #27ae60; /* Vert plus foncé */
 
 
 
 
 
 
 
 
 
231
  }
 
 
 
 
 
 
 
 
 
 
 
232
  .telegram-notice {
233
- background-color: #eaf5ff; /* Bleu très clair */
234
- border-left: 5px solid #3498db; /* Bordure bleue */
235
- padding: 12px 15px;
236
- margin: 15px 0;
237
- font-size: 0.95em;
238
- border-radius: 0 5px 5px 0;
 
 
 
 
 
 
 
 
239
  }
 
 
 
 
 
 
 
 
 
 
 
240
  .loading {
241
  text-align: center;
242
  font-style: italic;
243
- margin: 15px 0;
244
  color: #555;
 
245
  }
 
246
  .loading::before {
247
  content: "⏳ ";
248
  }
249
- .status {
250
- text-align: center;
251
- margin-bottom: 15px;
252
- font-weight: bold;
253
- font-size: 1.1em;
254
- color: #2c3e50;
255
- }
256
- .status small {
257
- font-weight: normal;
258
- color: #7f8c8d; /* Gris */
259
- font-size: 0.9em;
260
- display: block;
261
- margin-top: 5px;
262
- }
263
- /* Styles pour les messages d'erreur/succès dans le status */
264
- .status.error { color: #e74c3c; } /* Rouge */
265
- .status.completed { color: #2ecc71; } /* Vert */
266
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  </style>
268
  </head>
269
  <body>
270
- <h1>🖼️ Science ( Math, physique, chimie)🧠</h1>
271
- <p class="subtitle">Avec Mariam, votre assistante IA</p>
 
 
272
 
273
  <div class="telegram-join-button-container">
274
  <a href="https://t.me/+ic4zemy1E1k0MzQ0" target="_blank" class="telegram-button">
275
- 🚀 Rejoindre le Groupe Telegram pour obtenir le PDF
276
  </a>
277
  </div>
278
-
279
  <div class="container">
280
  <div class="style-selection">
281
  <h3>🎨 Choisissez le style de résolution</h3>
282
  <div class="radio-group">
283
  <div class="radio-option" onclick="selectStyle('light')">
284
  <input type="radio" id="style-light" name="resolution-style" value="light">
285
- <label for="style-light">📝 Résolution Light</label>
 
 
 
286
  </div>
287
- <div class="radio-description">Format simple et épuré, idéal pour une lecture rapide</div>
288
 
289
  <div class="radio-option" onclick="selectStyle('colorful')">
290
  <input type="radio" id="style-colorful" name="resolution-style" value="colorful" checked>
291
- <label for="style-colorful">🌈 Résolution Colorée</label>
 
 
 
292
  </div>
293
- <div class="radio-description">Format richement formaté avec couleurs, boîtes et mise en page élégante</div>
294
  </div>
295
  </div>
296
-
297
  <div id="upload-section" class="upload-section">
298
  <div class="upload-icon">📤</div>
299
  <p>Cliquez ou glissez-déposez une image ici</p>
@@ -302,9 +347,9 @@
302
  <img id="image-preview" src="#" alt="Aperçu de l'image">
303
  </div>
304
  </div>
305
-
306
  <button id="solve-button" class="button" disabled>🔍 Résoudre</button>
307
-
308
  <div id="solving-container">
309
  <div class="status" id="status">En attente de résolution...</div>
310
  <div class="telegram-notice">
@@ -327,14 +372,13 @@
327
  const solveButton = document.getElementById('solve-button');
328
  const solvingContainer = document.getElementById('solving-container');
329
  const responseContainer = document.getElementById('response-container');
330
- const responseDiv = document.getElementById('response'); // Renommé pour clarté
331
  const copyButton = document.getElementById('copy-button');
332
  const statusElement = document.getElementById('status');
333
  const loadingText = document.getElementById('loading-text');
334
 
335
  let selectedFile = null;
336
 
337
- // Fonction pour sélectionner le style de résolution
338
  window.selectStyle = function(style) {
339
  document.getElementById(`style-${style}`).checked = true;
340
  };
@@ -343,19 +387,19 @@
343
 
344
  uploadSection.addEventListener('dragover', (e) => {
345
  e.preventDefault();
346
- uploadSection.style.borderColor = '#3498db';
347
  uploadSection.style.backgroundColor = '#e8f4fb';
348
  });
349
 
350
  uploadSection.addEventListener('dragleave', () => {
351
- uploadSection.style.borderColor = '#bdc3c7';
352
- uploadSection.style.backgroundColor = '#ecf0f1';
353
  });
354
 
355
  uploadSection.addEventListener('drop', (e) => {
356
  e.preventDefault();
357
- uploadSection.style.borderColor = '#bdc3c7';
358
- uploadSection.style.backgroundColor = '#ecf0f1';
359
 
360
  if (e.dataTransfer.files.length) {
361
  handleFileSelection(e.dataTransfer.files[0]);
@@ -376,7 +420,7 @@
376
 
377
  selectedFile = file;
378
  solveButton.disabled = false;
379
- solveButton.textContent = '🔍 Résoudre'; // Réinitialiser le texte du bouton
380
 
381
  const reader = new FileReader();
382
  reader.onload = (e) => {
@@ -385,7 +429,6 @@
385
  };
386
  reader.readAsDataURL(file);
387
 
388
- // Cacher la zone de résolution si une nouvelle image est sélectionnée
389
  solvingContainer.style.display = 'none';
390
  responseContainer.style.display = 'none';
391
  }
@@ -393,21 +436,20 @@
393
  solveButton.addEventListener('click', () => {
394
  if (!selectedFile) return;
395
 
396
- // Récupérer le style sélectionné
397
  const selectedStyle = document.querySelector('input[name="resolution-style"]:checked').value;
398
 
399
  solveButton.disabled = true;
400
  solveButton.textContent = '⏳ Traitement...';
401
  solvingContainer.style.display = 'block';
402
  responseContainer.style.display = 'none';
403
- statusElement.className = 'status'; // Reset class
404
  statusElement.textContent = 'Préparation de la requête...';
405
  loadingText.style.display = 'block';
406
  responseDiv.innerHTML = '';
407
 
408
  const formData = new FormData();
409
  formData.append('image', selectedFile);
410
- formData.append('style', selectedStyle); // Ajouter le style sélectionné
411
 
412
  fetch('/solve', {
413
  method: 'POST',
@@ -433,135 +475,106 @@
433
  const data = JSON.parse(event.data);
434
 
435
  if (data.error) {
436
- statusElement.className = 'status error';
437
- statusElement.textContent = 'Erreur de traitement:';
438
- responseDiv.innerHTML = `<p style="color:red;">${data.error}</p>`;
439
- responseContainer.style.display = 'block';
440
- loadingText.style.display = 'none';
441
- eventSource.close();
442
- solveButton.disabled = false;
443
- solveButton.textContent = '🔍 Résoudre';
444
  return;
445
  }
446
 
447
- if (data.status === 'pending') {
448
- statusElement.textContent = 'En file d\'attente...';
449
- } else if (data.status === 'processing') {
450
- statusElement.innerHTML = '<span class="thinking">Mariam</span> traite votre image... <br><small>La réponse sera également envoyée sur Telegram.</small>';
451
- } else if (data.status === 'completed') {
452
- statusElement.className = 'status completed';
453
- statusElement.textContent = 'Traitement terminé avec succès ! 🎉';
454
- responseContainer.style.display = 'block';
455
- loadingText.style.display = 'none';
456
-
457
- responseDiv.innerHTML = data.response; // Utiliser innerHTML si la réponse contient du HTML
458
- renderMathInElement(responseDiv, {
459
- delimiters: [
460
- {left: '$$', right: '$$', display: true},
461
- {left: '$', right: '$', display: false},
462
- {left: '\\(', right: '\\)', display: false},
463
- {left: '\\[', right: '\\]', display: true}
464
- ]
465
- });
466
-
467
- eventSource.close();
468
- solveButton.disabled = false;
469
- solveButton.textContent = '🔍 Résoudre';
470
- } else if (data.status === 'error') {
471
- statusElement.className = 'status error';
472
- statusElement.textContent = 'Erreur de traitement:';
473
- responseDiv.innerHTML = `<p style="color:red;">${data.error || 'Une erreur inattendue est survenue durant le traitement.'}</p>`;
474
- responseContainer.style.display = 'block';
475
- loadingText.style.display = 'none';
476
-
477
- eventSource.close();
478
- solveButton.disabled = false;
479
- solveButton.textContent = '🔍 Résoudre';
480
- }
481
  };
482
 
483
  eventSource.onerror = function() {
484
  eventSource.close();
485
- fetch('/task/' + taskId)
486
- .then(response => response.json())
487
- .then(taskData => {
488
- if (taskData.status === 'completed') {
489
- statusElement.className = 'status completed';
490
- statusElement.textContent = 'Traitement terminé (récupéré après déconnexion) !';
491
- responseContainer.style.display = 'block';
492
- loadingText.style.display = 'none';
493
-
494
- responseDiv.innerHTML = taskData.response;
495
- renderMathInElement(responseDiv, {
496
- delimiters: [
497
- {left: '$$', right: '$$', display: true},
498
- {left: '$', right: '$', display: false},
499
- {left: '\\(', right: '\\)', display: false},
500
- {left: '\\[', right: '\\]', display: true}
501
- ]
502
- });
503
- } else if (taskData.status === 'error' || (taskData.error && taskData.error !== "Task not found or not completed yet")) {
504
- statusElement.className = 'status error';
505
- statusElement.textContent = 'Erreur (récupéré après déconnexion):';
506
- responseDiv.innerHTML = `<p style="color:red;">${taskData.error || 'Une erreur inattendue est survenue.'}</p>`;
507
- } else {
508
- statusElement.className = 'status error';
509
- statusElement.textContent = 'Erreur de connexion:';
510
- 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.';
511
- }
512
- })
513
- .catch(error => {
514
- statusElement.className = 'status error';
515
- statusElement.textContent = 'Erreur de connexion:';
516
- 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é.';
517
- })
518
- .finally(() => {
519
- responseContainer.style.display = 'block';
520
- loadingText.style.display = 'none';
521
- solveButton.disabled = false;
522
- solveButton.textContent = '🔍 Résoudre';
523
- });
524
  };
525
  })
526
  .catch(error => {
527
- statusElement.className = 'status error';
528
- statusElement.textContent = 'Erreur Initiale:';
529
- responseDiv.innerHTML = `<p style="color:red;">${error.message || 'Une erreur est survenue lors de la communication avec le serveur.'}</p>`;
530
- responseContainer.style.display = 'block';
531
- loadingText.style.display = 'none';
532
- solveButton.disabled = false;
533
- solveButton.textContent = '🔍 Résoudre';
534
  });
535
  });
536
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  copyButton.addEventListener('click', () => {
538
- const textToCopy = responseDiv.innerText || responseDiv.textContent; // Pour mieux copier le texte brut
539
- navigator.clipboard.writeText(textToCopy).then(() => {
540
- copyButton.textContent = '✅ Copié!';
541
- setTimeout(() => {
542
- copyButton.textContent = '📋 Copier la réponse';
543
- }, 2000);
544
- }).catch(err => {
545
- console.error('Erreur de copie: ', err);
546
- // Fallback pour les anciens navigateurs (moins fiable)
547
- const range = document.createRange();
548
- range.selectNode(responseDiv);
549
- window.getSelection().removeAllRanges();
550
- window.getSelection().addRange(range);
551
- try {
552
- document.execCommand('copy');
553
- copyButton.textContent = '✅ Copié! (fallback)';
554
- } catch (e) {
555
- copyButton.textContent = 'Erreur copie';
556
- }
557
- window.getSelection().removeAllRanges();
558
- setTimeout(() => {
559
- copyButton.textContent = '📋 Copier la réponse';
560
- }, 2000);
561
- });
 
562
  });
563
 
564
- // Rendu KaTeX initial pour toute la page (si des formules sont en dehors des réponses)
565
  renderMathInElement(document.body, {
566
  delimiters: [
567
  {left: '$$', right: '$$', display: true},
@@ -569,9 +582,9 @@
569
  {left: '\\(', right: '\\)', display: false},
570
  {left: '\\[', right: '\\]', display: true}
571
  ],
572
- throwOnError: false // Important pour ne pas bloquer le script si KaTeX rencontre une erreur
573
  });
574
  });
575
  </script>
576
  </body>
577
- </html>
 
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
+ :root {
12
+ --primary-color: #3498db;
13
+ --primary-hover: #2980b9;
14
+ --secondary-color: #2ecc71;
15
+ --secondary-hover: #27ae60;
16
+ --background-color: #f4f7f6;
17
+ --text-color: #333;
18
+ --border-color: #e0e0e0;
19
+ --shadow: 0 4px 15px rgba(0,0,0,0.1);
20
+ --spacing-unit: 1rem;
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ margin: 0;
26
+ padding: 0;
27
+ }
28
+
29
  body {
30
+ font-family: 'Segoe UI', system-ui, sans-serif;
31
  max-width: 800px;
32
  margin: 0 auto;
33
+ padding: calc(var(--spacing-unit) * 2);
34
  line-height: 1.6;
35
+ background-color: var(--background-color);
36
+ color: var(--text-color);
37
  }
38
+
39
+ .header {
40
  text-align: center;
41
+ margin-bottom: calc(var(--spacing-unit) * 2);
 
 
42
  }
43
+
44
+ .header h1 {
45
+ font-size: 2.5rem;
46
+ color: #2c3e50;
47
+ margin-bottom: calc(var(--spacing-unit) * 0.5);
48
+ }
49
+
50
+ .header .subtitle {
51
+ font-size: 1.1rem;
52
  color: #555;
 
 
53
  }
54
 
55
  .telegram-join-button-container {
56
  text-align: center;
57
+ margin-bottom: calc(var(--spacing-unit) * 2);
58
  }
59
 
60
  .telegram-button {
61
+ display: inline-block;
62
+ background-color: #0088cc;
63
  color: white;
64
+ padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
65
+ border-radius: 0.5rem;
 
66
  text-decoration: none;
67
+ transition: all 0.3s ease;
 
 
 
 
68
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
69
  }
70
+
71
  .telegram-button:hover {
 
72
  transform: translateY(-2px);
73
+ background-color: #006699;
 
 
74
  }
75
 
76
  .container {
77
+ background-color: white;
78
+ padding: calc(var(--spacing-unit) * 2);
79
+ border-radius: 1rem;
80
+ box-shadow: var(--shadow);
 
 
 
81
  }
82
+
83
  .style-selection {
84
  background-color: #f9f9f9;
85
+ padding: calc(var(--spacing-unit) * 1.5);
86
+ border-radius: 0.75rem;
87
+ border: 1px solid var(--border-color);
88
+ margin-bottom: calc(var(--spacing-unit) * 1.5);
89
  }
90
+
91
  .style-selection h3 {
92
+ margin-bottom: var(--spacing-unit);
93
  color: #2c3e50;
94
+ font-size: 1.2rem;
95
  }
96
+
97
  .radio-group {
98
  display: flex;
99
  flex-direction: column;
100
+ gap: var(--spacing-unit);
101
  }
102
+
103
  .radio-option {
104
  display: flex;
105
+ align-items: flex-start;
106
+ padding: calc(var(--spacing-unit) * 0.75);
107
+ border-radius: 0.5rem;
 
108
  transition: background-color 0.2s;
109
  cursor: pointer;
110
+ border: 1px solid transparent;
111
  }
112
+
113
  .radio-option:hover {
114
  background-color: #f0f4f8;
115
+ border-color: var(--primary-color);
116
  }
117
+
118
  .radio-option input[type="radio"] {
119
+ margin-top: 0.25rem;
120
+ margin-right: calc(var(--spacing-unit) * 0.75);
121
+ width: 1.25rem;
122
+ height: 1.25rem;
123
+ accent-color: var(--primary-color);
124
  }
125
+
126
+ .radio-content {
127
+ flex: 1;
128
+ }
129
+
130
+ .radio-label {
131
  font-weight: 500;
132
+ margin-bottom: calc(var(--spacing-unit) * 0.25);
133
+ display: block;
134
  }
135
+
136
  .radio-description {
137
+ font-size: 0.9rem;
138
  color: #666;
 
 
139
  }
140
+
141
  .upload-section {
142
+ border: 3px dashed var(--border-color);
143
+ padding: calc(var(--spacing-unit) * 2);
144
+ text-align: center;
145
+ border-radius: 0.75rem;
 
 
 
146
  cursor: pointer;
147
  transition: all 0.3s ease;
148
+ background-color: #f8f9fa;
149
+ margin: calc(var(--spacing-unit) * 1.5) 0;
150
  }
151
+
152
  .upload-section:hover {
153
+ border-color: var(--primary-color);
154
  background-color: #e8f4fb;
155
  }
156
+
 
 
 
 
157
  .upload-icon {
158
+ font-size: 2.5rem;
159
+ margin-bottom: var(--spacing-unit);
160
+ color: var(--primary-color);
161
  }
162
+
163
  #file-input {
164
  display: none;
165
  }
166
+
167
  .preview-container {
168
+ margin-top: var(--spacing-unit);
 
 
169
  }
170
+
171
  #image-preview {
172
  max-width: 100%;
173
  max-height: 300px;
174
  display: none;
175
+ border-radius: 0.5rem;
176
+ border: 1px solid var(--border-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }
178
+
179
  .button {
 
 
 
 
 
 
 
 
 
180
  width: 100%;
181
+ padding: var(--spacing-unit);
182
+ border: none;
183
+ border-radius: 0.5rem;
184
+ font-size: 1rem;
185
  cursor: pointer;
186
+ transition: all 0.3s ease;
187
+ margin: var(--spacing-unit) 0;
188
+ background-color: var(--primary-color);
189
+ color: white;
190
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
191
  }
192
+
193
+ .button:hover:not(:disabled) {
194
  transform: translateY(-2px);
195
+ background-color: var(--primary-hover);
196
  }
197
+
 
 
198
  .button:disabled {
199
+ background-color: #bdc3c7;
200
  cursor: not-allowed;
 
 
201
  }
202
+
203
  .copy-button {
204
+ background-color: var(--secondary-color);
 
205
  }
206
+
207
  .copy-button:hover {
208
+ background-color: var(--secondary-hover);
209
+ }
210
+
211
+ #solving-container {
212
+ display: none;
213
+ background-color: #f9f9f9;
214
+ padding: calc(var(--spacing-unit) * 1.5);
215
+ border-radius: 0.75rem;
216
+ border: 1px solid var(--border-color);
217
+ margin-top: calc(var(--spacing-unit) * 1.5);
218
  }
219
+
220
+ .status {
221
+ text-align: center;
222
+ margin-bottom: var(--spacing-unit);
223
+ font-weight: bold;
224
+ color: #2c3e50;
225
+ }
226
+
227
+ .status.error { color: #e74c3c; }
228
+ .status.completed { color: #2ecc71; }
229
+
230
  .telegram-notice {
231
+ background-color: #eaf5ff;
232
+ border-left: 5px solid var(--primary-color);
233
+ padding: var(--spacing-unit);
234
+ margin: var(--spacing-unit) 0;
235
+ border-radius: 0 0.5rem 0.5rem 0;
236
+ }
237
+
238
+ .response-container {
239
+ display: none;
240
+ margin-top: calc(var(--spacing-unit) * 1.5);
241
+ padding: calc(var(--spacing-unit) * 1.5);
242
+ background-color: white;
243
+ border-radius: 0.75rem;
244
+ border: 1px solid var(--border-color);
245
  }
246
+
247
+ #response {
248
+ background-color: #fdfdfd;
249
+ padding: var(--spacing-unit);
250
+ border-radius: 0.5rem;
251
+ border: 1px solid #eee;
252
+ min-height: 50px;
253
+ white-space: pre-wrap;
254
+ word-wrap: break-word;
255
+ }
256
+
257
  .loading {
258
  text-align: center;
259
  font-style: italic;
 
260
  color: #555;
261
+ margin: var(--spacing-unit) 0;
262
  }
263
+
264
  .loading::before {
265
  content: "⏳ ";
266
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
+ @media (max-width: 768px) {
269
+ :root {
270
+ --spacing-unit: 0.875rem;
271
+ }
272
+
273
+ body {
274
+ padding: var(--spacing-unit);
275
+ }
276
+
277
+ .header h1 {
278
+ font-size: 1.75rem;
279
+ }
280
+
281
+ .container {
282
+ padding: var(--spacing-unit);
283
+ }
284
+
285
+ .radio-option {
286
+ padding: calc(var(--spacing-unit) * 0.5);
287
+ }
288
+
289
+ .radio-content {
290
+ font-size: 0.95rem;
291
+ }
292
+
293
+ .radio-description {
294
+ font-size: 0.85rem;
295
+ }
296
+
297
+ .upload-section {
298
+ padding: var(--spacing-unit);
299
+ }
300
+
301
+ .telegram-button {
302
+ padding: calc(var(--spacing-unit) * 0.75) var(--spacing-unit);
303
+ font-size: 0.95rem;
304
+ }
305
+ }
306
  </style>
307
  </head>
308
  <body>
309
+ <div class="header">
310
+ <h1>🖼️ Science (Math, Physique, Chimie) 🧠</h1>
311
+ <p class="subtitle">Avec Mariam, votre assistante IA</p>
312
+ </div>
313
 
314
  <div class="telegram-join-button-container">
315
  <a href="https://t.me/+ic4zemy1E1k0MzQ0" target="_blank" class="telegram-button">
316
+ 🚀 Rejoindre le Groupe Telegram pour obtenir le PDF
317
  </a>
318
  </div>
319
+
320
  <div class="container">
321
  <div class="style-selection">
322
  <h3>🎨 Choisissez le style de résolution</h3>
323
  <div class="radio-group">
324
  <div class="radio-option" onclick="selectStyle('light')">
325
  <input type="radio" id="style-light" name="resolution-style" value="light">
326
+ <div class="radio-content">
327
+ <label class="radio-label" for="style-light">📝 Résolution Light</label>
328
+ <div class="radio-description">Format simple et épuré, idéal pour une lecture rapide</div>
329
+ </div>
330
  </div>
 
331
 
332
  <div class="radio-option" onclick="selectStyle('colorful')">
333
  <input type="radio" id="style-colorful" name="resolution-style" value="colorful" checked>
334
+ <div class="radio-content">
335
+ <label class="radio-label" for="style-colorful">🌈 Résolution Colorée</label>
336
+ <div class="radio-description">Format richement formaté avec couleurs, boîtes et mise en page élégante</div>
337
+ </div>
338
  </div>
 
339
  </div>
340
  </div>
341
+
342
  <div id="upload-section" class="upload-section">
343
  <div class="upload-icon">📤</div>
344
  <p>Cliquez ou glissez-déposez une image ici</p>
 
347
  <img id="image-preview" src="#" alt="Aperçu de l'image">
348
  </div>
349
  </div>
350
+
351
  <button id="solve-button" class="button" disabled>🔍 Résoudre</button>
352
+
353
  <div id="solving-container">
354
  <div class="status" id="status">En attente de résolution...</div>
355
  <div class="telegram-notice">
 
372
  const solveButton = document.getElementById('solve-button');
373
  const solvingContainer = document.getElementById('solving-container');
374
  const responseContainer = document.getElementById('response-container');
375
+ const responseDiv = document.getElementById('response');
376
  const copyButton = document.getElementById('copy-button');
377
  const statusElement = document.getElementById('status');
378
  const loadingText = document.getElementById('loading-text');
379
 
380
  let selectedFile = null;
381
 
 
382
  window.selectStyle = function(style) {
383
  document.getElementById(`style-${style}`).checked = true;
384
  };
 
387
 
388
  uploadSection.addEventListener('dragover', (e) => {
389
  e.preventDefault();
390
+ uploadSection.style.borderColor = 'var(--primary-color)';
391
  uploadSection.style.backgroundColor = '#e8f4fb';
392
  });
393
 
394
  uploadSection.addEventListener('dragleave', () => {
395
+ uploadSection.style.borderColor = 'var(--border-color)';
396
+ uploadSection.style.backgroundColor = '#f8f9fa';
397
  });
398
 
399
  uploadSection.addEventListener('drop', (e) => {
400
  e.preventDefault();
401
+ uploadSection.style.borderColor = 'var(--border-color)';
402
+ uploadSection.style.backgroundColor = '#f8f9fa';
403
 
404
  if (e.dataTransfer.files.length) {
405
  handleFileSelection(e.dataTransfer.files[0]);
 
420
 
421
  selectedFile = file;
422
  solveButton.disabled = false;
423
+ solveButton.textContent = '🔍 Résoudre';
424
 
425
  const reader = new FileReader();
426
  reader.onload = (e) => {
 
429
  };
430
  reader.readAsDataURL(file);
431
 
 
432
  solvingContainer.style.display = 'none';
433
  responseContainer.style.display = 'none';
434
  }
 
436
  solveButton.addEventListener('click', () => {
437
  if (!selectedFile) return;
438
 
 
439
  const selectedStyle = document.querySelector('input[name="resolution-style"]:checked').value;
440
 
441
  solveButton.disabled = true;
442
  solveButton.textContent = '⏳ Traitement...';
443
  solvingContainer.style.display = 'block';
444
  responseContainer.style.display = 'none';
445
+ statusElement.className = 'status';
446
  statusElement.textContent = 'Préparation de la requête...';
447
  loadingText.style.display = 'block';
448
  responseDiv.innerHTML = '';
449
 
450
  const formData = new FormData();
451
  formData.append('image', selectedFile);
452
+ formData.append('style', selectedStyle);
453
 
454
  fetch('/solve', {
455
  method: 'POST',
 
475
  const data = JSON.parse(event.data);
476
 
477
  if (data.error) {
478
+ handleError(data.error);
 
 
 
 
 
 
 
479
  return;
480
  }
481
 
482
+ updateStatus(data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  };
484
 
485
  eventSource.onerror = function() {
486
  eventSource.close();
487
+ handleEventSourceError(taskId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  };
489
  })
490
  .catch(error => {
491
+ handleError(error.message);
 
 
 
 
 
 
492
  });
493
  });
494
 
495
+ function handleError(errorMessage) {
496
+ statusElement.className = 'status error';
497
+ statusElement.textContent = 'Erreur:';
498
+ responseDiv.innerHTML = `<p style="color:red;">${errorMessage}</p>`;
499
+ showResponse();
500
+ }
501
+
502
+ function updateStatus(data) {
503
+ switch(data.status) {
504
+ case 'pending':
505
+ statusElement.textContent = 'En file d\'attente...';
506
+ break;
507
+ case 'processing':
508
+ statusElement.innerHTML = '<span class="thinking">Mariam</span> traite votre image... <br><small>La réponse sera également envoyée sur Telegram.</small>';
509
+ break;
510
+ case 'completed':
511
+ statusElement.className = 'status completed';
512
+ statusElement.textContent = 'Traitement terminé avec succès ! 🎉';
513
+ responseDiv.innerHTML = data.response;
514
+ renderMathInElement(responseDiv);
515
+ showResponse();
516
+ break;
517
+ case 'error':
518
+ handleError(data.error || 'Une erreur inattendue est survenue.');
519
+ break;
520
+ }
521
+ }
522
+
523
+ function showResponse() {
524
+ responseContainer.style.display = 'block';
525
+ loadingText.style.display = 'none';
526
+ solveButton.disabled = false;
527
+ solveButton.textContent = '🔍 Résoudre';
528
+ }
529
+
530
+ function handleEventSourceError(taskId) {
531
+ fetch('/task/' + taskId)
532
+ .then(response => response.json())
533
+ .then(taskData => {
534
+ if (taskData.status === 'completed') {
535
+ updateStatus({
536
+ status: 'completed',
537
+ response: taskData.response
538
+ });
539
+ } else if (taskData.status === 'error' || taskData.error) {
540
+ handleError(taskData.error || 'Une erreur est survenue.');
541
+ } else {
542
+ handleError('La connexion au flux a été perdue. La réponse sera envoyée sur Telegram.');
543
+ }
544
+ })
545
+ .catch(() => {
546
+ handleError('La connexion au flux a été perdue et la récupération a échoué.');
547
+ });
548
+ }
549
+
550
  copyButton.addEventListener('click', () => {
551
+ const textToCopy = responseDiv.innerText || responseDiv.textContent;
552
+ navigator.clipboard.writeText(textToCopy)
553
+ .then(() => {
554
+ copyButton.textContent = '✅ Copié!';
555
+ setTimeout(() => {
556
+ copyButton.textContent = '📋 Copier la réponse';
557
+ }, 2000);
558
+ })
559
+ .catch(() => {
560
+ // Fallback pour les anciens navigateurs
561
+ const range = document.createRange();
562
+ range.selectNode(responseDiv);
563
+ window.getSelection().removeAllRanges();
564
+ window.getSelection().addRange(range);
565
+ try {
566
+ document.execCommand('copy');
567
+ copyButton.textContent = '✅ Copié!';
568
+ } catch (e) {
569
+ copyButton.textContent = '❌ Erreur de copie';
570
+ }
571
+ window.getSelection().removeAllRanges();
572
+ setTimeout(() => {
573
+ copyButton.textContent = '📋 Copier la réponse';
574
+ }, 2000);
575
+ });
576
  });
577
 
 
578
  renderMathInElement(document.body, {
579
  delimiters: [
580
  {left: '$$', right: '$$', display: true},
 
582
  {left: '\\(', right: '\\)', display: false},
583
  {left: '\\[', right: '\\]', display: true}
584
  ],
585
+ throwOnError: false
586
  });
587
  });
588
  </script>
589
  </body>
590
+ </html>