Docfile commited on
Commit
10970c2
·
verified ·
1 Parent(s): 9275c03

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +139 -452
templates/index.html CHANGED
@@ -4,457 +4,134 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Solveur Expert IA - Maths, Physique, Chimie</title>
 
 
7
  <!-- Google Fonts -->
8
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Fira+Code&display=swap" rel="stylesheet">
9
  <!-- Font Awesome Icons -->
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
11
  <style>
 
12
  :root {
13
- --primary-color: #2c3e50; /* Bleu Nuit - Élégant et Professionnel */
14
- --secondary-color: #1abc9c; /* Turquoise - Dynamique */
15
- --accent-color: #e74c3c; /* Rouge Doux - Pour les erreurs subtiles */
16
- --success-color: #27ae60; /* Vert Succès */
17
- --light-bg: #f4f6f8; /* Fond Général Très Clair */
18
- --card-bg: #ffffff; /* Fond des Cartes */
19
- --text-color: #34495e; /* Texte Principal */
20
- --subtle-text-color: #7f8c8d; /* Texte Secondaire/Discret */
21
- --border-color: #dfe4ea; /* Bordures Claires */
22
- --shadow: 0 8px 25px rgba(44, 62, 80, 0.1); /* Ombre plus douce */
23
- --border-radius: 12px; /* Rayon de bordure plus arrondi */
24
- --font-main: 'Montserrat', sans-serif;
25
- --font-code: 'Fira Code', monospace;
26
  }
27
-
28
- * {
29
- box-sizing: border-box;
30
- margin: 0;
31
- padding: 0;
32
- }
33
-
34
  body {
35
- font-family: var(--font-main);
36
- background-color: var(--light-bg);
37
- color: var(--text-color);
38
- display: flex;
39
- flex-direction: column;
40
- align-items: center;
41
- min-height: 100vh;
42
- padding: 20px;
43
- line-height: 1.7;
44
- }
45
-
46
- .main-container {
47
- background-color: var(--card-bg);
48
- padding: 35px 45px;
49
- border-radius: var(--border-radius);
50
- box-shadow: var(--shadow);
51
- width: 100%;
52
- max-width: 750px;
53
- text-align: center;
54
- transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
55
- }
56
-
57
- h1 {
58
- color: var(--primary-color);
59
- font-weight: 700;
60
- font-size: 2.2em;
61
- margin-bottom: 10px;
62
- display: flex;
63
- align-items: center;
64
- justify-content: center;
65
- }
66
- h1 .logo-icon {
67
- margin-right: 12px;
68
- font-size: 1.3em;
69
- color: var(--secondary-color);
70
- }
71
- .subtitle {
72
- font-size: 1.1em;
73
- color: var(--subtle-text-color);
74
- margin-bottom: 35px;
75
- font-weight: 400;
76
- }
77
-
78
-
79
- .upload-section {
80
- border: 2.5px dashed var(--border-color);
81
- border-radius: var(--border-radius);
82
- padding: 35px;
83
- cursor: pointer;
84
- transition: all 0.3s ease;
85
- background-color: #fdfdfe;
86
- margin-bottom: 30px;
87
- position: relative; /* Pour le positionnement absolu de l'input */
88
- overflow: hidden; /* Pour cacher l'input */
89
- }
90
- .upload-section.highlight-drag { /* Style quand on drag par-dessus */
91
- border-color: var(--secondary-color);
92
- background-color: #e8f8f5;
93
- }
94
- .upload-content {
95
- display: flex;
96
- flex-direction: column;
97
- align-items: center;
98
- justify-content: center;
99
- }
100
- .upload-section .upload-icon {
101
- font-size: 3.5em;
102
- color: var(--secondary-color);
103
- margin-bottom: 15px;
104
- transition: transform 0.3s ease;
105
- }
106
- .upload-section:hover .upload-icon {
107
- transform: scale(1.1) translateY(-5px);
108
- }
109
- .upload-section p {
110
- margin: 0 0 10px 0;
111
- font-size: 1.15em;
112
- font-weight: 500;
113
- color: var(--text-color);
114
- }
115
- .upload-section small {
116
- font-size: 0.9em;
117
- color: var(--subtle-text-color);
118
- }
119
- #file-input { /* Caché mais accessible pour la sémantique et l'accessibilité */
120
- position: absolute;
121
- left: 0;
122
- top: 0;
123
- width: 100%;
124
- height: 100%;
125
- opacity: 0;
126
- cursor: pointer;
127
- }
128
- #image-preview-container {
129
- margin-top: 20px;
130
- text-align: center;
131
- }
132
- #image-preview {
133
- max-width: 100%;
134
- max-height: 280px;
135
- border-radius: var(--border-radius);
136
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
137
- display: none; /* Caché initialement */
138
- border: 1px solid var(--border-color);
139
- }
140
-
141
- .options-panel {
142
- background-color: #f8f9fa;
143
- padding: 20px 25px;
144
- border-radius: var(--border-radius);
145
- margin-bottom: 30px;
146
- text-align: left;
147
- border: 1px solid var(--border-color);
148
- }
149
- .options-panel h3 {
150
- font-weight: 600;
151
- color: var(--primary-color);
152
- margin-bottom: 15px;
153
- font-size: 1.2em;
154
- display: flex;
155
- align-items: center;
156
- }
157
- .options-panel h3 i {
158
- margin-right: 10px;
159
- color: var(--secondary-color);
160
- }
161
-
162
- .prompt-selector label {
163
- font-weight: 500;
164
- margin-bottom: 8px;
165
- display: block;
166
- color: var(--text-color);
167
- font-size: 1em;
168
- }
169
- .prompt-selector select {
170
- width: 100%;
171
- padding: 14px 18px;
172
- border-radius: var(--border-radius);
173
- border: 1.5px solid var(--border-color);
174
- font-size: 1em;
175
- font-family: var(--font-main);
176
- background-color: var(--card-bg);
177
- transition: border-color 0.3s ease, box-shadow 0.3s ease;
178
- appearance: none; /* Pour styliser la flèche */
179
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%232c3e50' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
180
- background-repeat: no-repeat;
181
- background-position: right 18px center;
182
- background-size: 16px;
183
- }
184
- .prompt-selector select:focus {
185
- border-color: var(--secondary-color);
186
- outline: none;
187
- box-shadow: 0 0 0 3px rgba(26, 188, 156, 0.2);
188
- }
189
-
190
- .button {
191
- background-image: linear-gradient(to right, var(--secondary-color) 0%, #16a085 100%);
192
- color: white;
193
- border: none;
194
- padding: 15px 30px;
195
- font-size: 1.15em;
196
- font-weight: 600;
197
- letter-spacing: 0.5px;
198
- border-radius: var(--border-radius);
199
- cursor: pointer;
200
- transition: all 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
201
- display: inline-flex;
202
- align-items: center;
203
- justify-content: center;
204
- gap: 10px;
205
- box-shadow: 0 4px 15px rgba(26, 188, 156, 0.2);
206
- text-transform: uppercase;
207
- }
208
- .button:hover {
209
- transform: translateY(-3px) scale(1.02);
210
- box-shadow: 0 7px 20px rgba(26, 188, 156, 0.3);
211
- }
212
- .button:active {
213
- transform: translateY(-1px) scale(0.98);
214
- box-shadow: 0 2px 10px rgba(26, 188, 156, 0.2);
215
- }
216
- .button:disabled {
217
- background-image: none;
218
- background-color: #bdc3c7;
219
- cursor: not-allowed;
220
- transform: none;
221
- box-shadow: none;
222
- color: #7f8c8d;
223
- }
224
- .button.copy-button { /* Style un peu différent pour le bouton copier */
225
- background-image: linear-gradient(to right, var(--primary-color) 0%, #34495e 100%);
226
- box-shadow: 0 4px 15px rgba(44, 62, 80, 0.2);
227
- font-size: 1em;
228
- padding: 12px 25px;
229
- text-transform: none;
230
- }
231
- .button.copy-button:hover {
232
- box-shadow: 0 7px 20px rgba(44, 62, 80, 0.3);
233
- }
234
-
235
-
236
- #solving-container {
237
- display: none; /* Caché initialement */
238
- margin-top: 35px;
239
- padding: 30px;
240
- background-color: #fbfcfd;
241
- border-radius: var(--border-radius);
242
- border: 1px solid var(--border-color);
243
  }
244
- .status-section {
245
- margin-bottom: 20px;
246
- text-align: center;
247
- }
248
- .status-message {
249
- font-size: 1.2em;
250
- font-weight: 500;
251
- color: var(--primary-color);
252
- display: flex;
253
- align-items: center;
254
- justify-content: center;
255
- flex-wrap: wrap;
256
- }
257
- .status-message i {
258
- margin-right: 10px;
259
- font-size: 1.3em;
260
- transition: color 0.3s ease;
261
  }
262
- .status-message .task-info {
263
- font-size: 0.85em;
264
- color: var(--subtle-text-color);
265
- margin-left: 8px;
266
- font-weight: 400;
267
  }
268
-
269
- .loading-spinner {
270
- width: 40px;
271
- height: 40px;
272
- margin: 25px auto;
273
- border: 5px solid rgba(44, 62, 80, 0.15);
274
- border-left-color: var(--secondary-color);
275
- border-radius: 50%;
276
- animation: spin 0.8s linear infinite;
277
- display: none; /* Caché initialement */
278
  }
 
279
  @keyframes spin {
280
  to { transform: rotate(360deg); }
281
  }
282
-
283
- .telegram-notice {
284
- background-color: #eaf2f8; /* Bleu très clair */
285
- border-left: 5px solid var(--secondary-color);
286
- padding: 15px 20px;
287
- margin: 25px 0;
288
- font-size: 1em;
289
- border-radius: var(--border-radius);
290
- color: var(--text-color);
291
- display: flex;
292
- align-items: center;
293
- }
294
- .telegram-notice i {
295
- margin-right: 12px;
296
- color: var(--secondary-color);
297
- font-size: 1.4em;
298
- }
299
-
300
- .response-container {
301
- margin-top: 25px;
302
- padding: 25px;
303
- border: 1px solid var(--border-color);
304
- border-radius: var(--border-radius);
305
- background-color: var(--card-bg);
306
- display: none; /* Caché initialement */
307
- text-align: left;
308
- }
309
- .response-container h3 {
310
- font-weight: 600;
311
- color: var(--primary-color);
312
- margin-bottom: 15px;
313
- font-size: 1.25em;
314
- display: flex;
315
- align-items: center;
316
- }
317
- .response-container h3 i {
318
- margin-right: 10px;
319
- color: var(--secondary-color);
320
- }
321
- #response-output { /* Renommé pour plus de clarté */
322
- font-family: var(--font-code);
323
- background-color: #2d2d2d; /* Fond sombre pour code */
324
- color: #d4d4d4; /* Texte clair pour code */
325
- padding: 20px;
326
- border-radius: var(--border-radius);
327
- overflow-x: auto;
328
- white-space: pre-wrap;
329
- word-wrap: break-word;
330
- max-height: 450px;
331
- margin-bottom: 20px;
332
- border: 1px solid #444;
333
- line-height: 1.6;
334
- }
335
- /* Style pour les commentaires dans le code LaTeX (si possible à faire via JS) */
336
- /* .latex-comment { color: #6a9955; font-style: italic; } */
337
-
338
- .error-display {
339
- color: var(--accent-color);
340
- background-color: #fbecec;
341
- border: 1.5px solid var(--accent-color);
342
- padding: 18px;
343
- border-radius: var(--border-radius);
344
- margin: 20px 0;
345
- font-weight: 500;
346
- display: flex;
347
- align-items: center;
348
- }
349
- .error-display i {
350
- margin-right: 12px;
351
- font-size: 1.3em;
352
- }
353
- .error-display small {
354
- display: block;
355
- font-weight: 400;
356
- color: #c0392b; /* Rouge plus foncé pour détails */
357
- font-size: 0.9em;
358
- margin-top: 5px;
359
- }
360
-
361
-
362
- .footer {
363
- margin-top: 50px;
364
- padding-bottom: 20px;
365
- font-size: 0.95em;
366
- color: var(--subtle-text-color);
367
- }
368
- .footer a {
369
- color: var(--secondary-color);
370
- text-decoration: none;
371
- font-weight: 500;
372
- }
373
- .footer a:hover {
374
- text-decoration: underline;
375
- }
376
-
377
- /* Responsive adjustments */
378
- @media (max-width: 768px) {
379
- body { padding: 15px; }
380
- .main-container { padding: 25px 30px; max-width: 95%; }
381
- h1 { font-size: 1.9em; }
382
- .subtitle { font-size: 1em; margin-bottom: 25px; }
383
- .upload-section { padding: 25px; }
384
- .upload-section p { font-size: 1.05em; }
385
- .upload-section .upload-icon { font-size: 3em; }
386
- .options-panel { padding: 15px 20px; }
387
- .button { padding: 14px 25px; font-size: 1.05em; }
388
  }
389
- @media (max-width: 480px) {
390
- h1 { font-size: 1.7em; }
391
- .subtitle { font-size: 0.95em; }
392
- .upload-section { padding: 20px; }
393
- .main-container { padding: 20px; }
394
  }
395
-
396
  </style>
397
  </head>
398
- <body>
399
- <div class="main-container">
400
- <h1><i class="fas fa-atom logo-icon"></i>Solveur Expert IA</h1>
401
- <p class="subtitle">Solutions LaTeX précises pour Maths, Physique et Chimie.</p>
 
 
 
402
 
403
- <div id="upload-section" class="upload-section">
 
404
  <div class="upload-content">
405
- <i class="fas fa-file-arrow-up upload-icon"></i>
406
- <p>Déposez l'image de votre exercice ici</p>
407
- <small>ou cliquez pour sélectionner un fichier (PNG, JPG)</small>
408
  </div>
409
- <input type="file" id="file-input" accept="image/png, image/jpeg, image/webp">
410
- <div id="image-preview-container">
411
- <img id="image-preview" src="#" alt="Aperçu de l'énoncé">
412
  </div>
413
  </div>
414
 
415
- <div class="options-panel">
416
- <h3><i class="fas fa-cogs"></i>Options de Formatage</h3>
 
 
 
417
  <div class="prompt-selector">
418
- <label for="prompt-type">Style de la correction LaTeX :</label>
419
- <select id="prompt-type" name="prompt-type">
420
- <option value="refined">Format Raffiné & Complet (mise en page avancée)</option>
421
- <option value="light">Format Léger & Essentiel (LaTeX standard)</option>
422
- </select>
 
 
 
 
 
423
  </div>
424
  </div>
425
 
426
- <button id="solve-button" class="button" disabled>
427
- <i class="fas fa-rocket"></i>Obtenir la Solution
 
428
  </button>
429
 
430
- <div id="solving-container">
431
- <div class="status-section">
432
- <div class="status-message" id="status-message-element">
433
- <i class="fas fa-hourglass-start"></i>Prêt à résoudre votre exercice...
 
 
434
  </div>
435
  </div>
436
- <div class="loading-spinner" id="loading-spinner-element"></div>
437
 
438
- <div class="telegram-notice">
439
- <i class="fab fa-telegram"></i>Une copie de la solution sera envoyée sur Telegram pour archivage.
 
 
 
 
 
440
  </div>
441
 
442
- <div class="response-container" id="response-container-element">
443
- <h3><i class="fas fa-file-code"></i>Correction LaTeX Détaillée :</h3>
444
- <div id="response-output"></div>
445
- <button id="copy-button" class="button copy-button">
446
- <i class="fas fa-copy"></i>Copier le code LaTeX
 
 
 
447
  </button>
448
  </div>
449
 
450
- <div id="error-display-element" class="error-display" style="display: none;">
 
451
  <!-- Les erreurs seront affichées ici -->
452
  </div>
453
  </div>
454
  </div>
455
 
456
- <footer class="footer">
457
- Solutions générées par <a href="#" target="_blank">Mariam IA</a> © 2025 - Précision garantie.
458
  </footer>
459
 
460
  <script>
@@ -462,7 +139,6 @@
462
  const uploadSection = document.getElementById('upload-section');
463
  const fileInput = document.getElementById('file-input');
464
  const imagePreview = document.getElementById('image-preview');
465
- const imagePreviewContainer = document.getElementById('image-preview-container');
466
  const solveButton = document.getElementById('solve-button');
467
  const solvingContainer = document.getElementById('solving-container');
468
  const responseContainer = document.getElementById('response-container-element');
@@ -474,9 +150,9 @@
474
  const errorDisplay = document.getElementById('error-display-element');
475
 
476
  let selectedFile = null;
477
- let currentTaskId = null; // Pour suivre l'ID de la tâche en cours
478
 
479
- // --- Drag and Drop Logic ---
480
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
481
  uploadSection.addEventListener(eventName, preventDefaults, false);
482
  });
@@ -484,19 +160,29 @@
484
  e.preventDefault();
485
  e.stopPropagation();
486
  }
 
487
  ['dragenter', 'dragover'].forEach(eventName => {
488
- uploadSection.addEventListener(eventName, () => uploadSection.classList.add('highlight-drag'), false);
 
 
 
 
489
  });
 
490
  ['dragleave', 'drop'].forEach(eventName => {
491
- uploadSection.addEventListener(eventName, () => uploadSection.classList.remove('highlight-drag'), false);
 
 
 
 
492
  });
 
493
  uploadSection.addEventListener('drop', (e) => {
494
  if (e.dataTransfer.files.length) {
495
  handleFileSelection(e.dataTransfer.files[0]);
496
  }
497
  });
498
- // --- Fin Drag and Drop ---
499
-
500
  fileInput.addEventListener('change', (e) => {
501
  if (e.target.files.length) {
502
  handleFileSelection(e.target.files[0]);
@@ -509,45 +195,43 @@
509
  displayError('Format de fichier non supporté.', 'Veuillez utiliser PNG, JPG ou WEBP.');
510
  selectedFile = null;
511
  solveButton.disabled = true;
512
- imagePreview.style.display = 'none';
513
- imagePreviewContainer.style.display = 'none';
514
  return;
515
  }
516
 
517
  selectedFile = file;
518
  solveButton.disabled = false;
519
- errorDisplay.style.display = 'none';
520
 
521
  const reader = new FileReader();
522
  reader.onload = (e) => {
523
  imagePreview.src = e.target.result;
524
- imagePreview.style.display = 'block';
525
- imagePreviewContainer.style.display = 'block';
526
  };
527
  reader.readAsDataURL(file);
528
  }
529
 
530
  function displayError(message, details = null) {
531
- let fullMessage = `<i class="fas fa-shield-halved"></i> ${message}`;
532
  if (details) {
533
- fullMessage += `<br><small>${escapeHtml(details)}</small>`;
534
  }
535
  errorDisplay.innerHTML = fullMessage;
536
- errorDisplay.style.display = 'block';
537
- responseContainer.style.display = 'none';
538
- loadingSpinner.style.display = 'none';
539
- updateStatusUI('error_user', null); // Statut spécifique pour erreurs d'input utilisateur
540
  }
541
 
542
  solveButton.addEventListener('click', () => {
543
  if (!selectedFile) return;
544
 
545
  solveButton.disabled = true;
546
- solvingContainer.style.display = 'block';
547
- responseContainer.style.display = 'none';
548
  responseOutputDiv.textContent = '';
549
- errorDisplay.style.display = 'none';
550
- loadingSpinner.style.display = 'block';
551
  updateStatusUI('pending', null, 'Préparation de la résolution...');
552
 
553
  const formData = new FormData();
@@ -581,21 +265,21 @@
581
 
582
  if (streamData.error) {
583
  displayError(streamData.error, streamData.error_detail);
584
- if (streamData.response) { // Afficher LaTeX partiel si dispo
585
  responseOutputDiv.textContent = streamData.response;
586
- responseContainer.style.display = 'block';
587
  }
588
  eventSource.close();
589
  solveButton.disabled = false;
590
- loadingSpinner.style.display = 'none';
591
  return;
592
  }
593
 
594
  updateStatusUI(streamData.status, currentTaskId);
595
 
596
  if (streamData.status === 'completed' || streamData.status === 'completed_tex_only' || streamData.status === 'pdf_error') {
597
- responseContainer.style.display = 'block';
598
- loadingSpinner.style.display = 'none';
599
 
600
  if (streamData.response) {
601
  responseOutputDiv.textContent = streamData.response;
@@ -603,7 +287,7 @@
603
 
604
  if (streamData.status === 'pdf_error' && streamData.error_detail) {
605
  const statusEl = statusMessageElement.querySelector('.status-text');
606
- if(statusEl) statusEl.innerHTML += `<br><small class="pdf-error-detail"><i class="fas fa-file-invoice"></i> Erreur PDF: ${escapeHtml(streamData.error_detail)}</small>`;
607
  }
608
 
609
  eventSource.close();
@@ -613,18 +297,18 @@
613
 
614
  eventSource.onerror = function() {
615
  eventSource.close();
616
- fetch('/task/' + currentTaskId) // Utiliser currentTaskId
617
  .then(resp => resp.json())
618
  .then(taskData => {
619
  updateStatusUI(taskData.status, currentTaskId);
620
  if (taskData.status === 'completed' || taskData.status === 'completed_tex_only' || taskData.status === 'pdf_error') {
621
- responseContainer.style.display = 'block';
622
  if (taskData.response) {
623
  responseOutputDiv.textContent = taskData.response;
624
  }
625
  if (taskData.status === 'pdf_error' && taskData.error_detail) {
626
  const statusEl = statusMessageElement.querySelector('.status-text');
627
- if(statusEl) statusEl.innerHTML += `<br><small class="pdf-error-detail"><i class="fas fa-file-invoice"></i> Erreur PDF: ${escapeHtml(taskData.error_detail)}</small>`;
628
  }
629
  } else if (taskData.status === 'error') {
630
  displayError(taskData.error || 'Erreur inattendue lors de la récupération de la tâche.', taskData.error_detail);
@@ -637,14 +321,14 @@
637
  })
638
  .finally(() => {
639
  solveButton.disabled = false;
640
- loadingSpinner.style.display = 'none';
641
  });
642
  };
643
  })
644
  .catch(error => {
645
  displayError(error.message || 'Erreur de communication serveur.');
646
  solveButton.disabled = false;
647
- loadingSpinner.style.display = 'none';
648
  });
649
  });
650
 
@@ -659,40 +343,43 @@
659
  case 'pending': statusMsg = "Lancement de l'analyse par l'IA..."; iconClass = 'fas fa-play-circle'; break;
660
  case 'processing': statusMsg = "L'IA déchiffre votre exercice..."; iconClass = 'fas fa-brain'; iconColor = 'var(--secondary-color)'; break;
661
  case 'generating_latex': statusMsg = "Construction de la solution LaTeX..."; iconClass = 'fas fa-scroll'; break;
662
- case 'cleaning_latex': statusMsg = "Peaufinage du code LaTeX..."; iconClass = 'fas fa-magic-wand-sparkles'; break;
663
- case 'generating_pdf': statusMsg = "Compilation du document PDF final..."; iconClass = 'fas fa-file-pdf'; iconColor = '#e74c3c'; break; // Rouge pour PDF
664
  case 'completed': statusMsg = "Solution Complète et Précise Générée !"; iconClass = 'fas fa-check-double'; iconColor = 'var(--success-color)'; break;
665
  case 'completed_tex_only': statusMsg = "Solution LaTeX Précise Générée ! (PDF non requis/dispo)"; iconClass = 'fas fa-check-circle'; iconColor = 'var(--success-color)'; break;
666
- case 'pdf_error': statusMsg = "Solution LaTeX Précise Générée ! (Erreur PDF)"; iconClass = 'fas fa-file-excel'; iconColor = '#f39c12'; break; // Orange pour erreur PDF
667
  case 'error': statusMsg = "Une anomalie technique est survenue."; iconClass = 'fas fa-times-circle'; iconColor = 'var(--accent-color)'; break;
668
  case 'error_user': statusMsg = "Veuillez vérifier votre image."; iconClass = 'fas fa-exclamation-triangle'; iconColor = 'var(--accent-color)'; break;
669
- default: statusMsg = `Progression: ${status}`; iconClass = 'fas fa-spinner fa-spin'; // spinner pour statut inconnu
670
  }
671
  }
672
 
673
  let taskInfoHtml = '';
674
  if (taskId) {
675
- taskInfoHtml = `<span class="task-info">(Tâche ${taskId.substring(0,6)} | Style: ${selectedPromptText})</span>`;
676
  }
677
 
678
- statusMessageElement.innerHTML = `<i class="${iconClass}" style="color:${iconColor};"></i> <span class="status-text">${statusMsg}</span> ${taskInfoHtml}`;
679
  }
680
 
681
  function escapeHtml(unsafe) {
682
  if (typeof unsafe !== 'string') return '';
683
- return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
684
  }
685
 
686
  copyButton.addEventListener('click', () => {
687
  const textToCopy = responseOutputDiv.textContent;
688
  navigator.clipboard.writeText(textToCopy).then(() => {
689
  const originalIcon = copyButton.querySelector('i').className;
690
- const originalText = copyButton.childNodes[1] ? copyButton.childNodes[1].nodeValue : ' Copier le code LaTeX'; // Safer access
691
- copyButton.innerHTML = `<i class="fas fa-check"></i> Code Copié !`;
692
- copyButton.style.backgroundColor = 'var(--success-color)'; // Feedback visuel
 
 
693
  setTimeout(() => {
694
- copyButton.innerHTML = `<i class="${originalIcon}"></i>${originalText}`;
695
- copyButton.style.backgroundColor = ''; // Reset style
 
696
  }, 2500);
697
  }).catch(err => {
698
  console.error('Erreur de copie: ', err);
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Solveur Expert IA - Maths, Physique, Chimie</title>
7
+ <!-- Tailwind CSS -->
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.js"></script>
9
  <!-- Google Fonts -->
10
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Fira+Code&display=swap" rel="stylesheet">
11
  <!-- Font Awesome Icons -->
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
13
  <style>
14
+ /* Styles personnalisés complémentaires */
15
  :root {
16
+ --primary-color: #2c3e50;
17
+ --secondary-color: #1abc9c;
18
+ --accent-color: #e74c3c;
19
+ --success-color: #27ae60;
 
 
 
 
 
 
 
 
 
20
  }
21
+
 
 
 
 
 
 
22
  body {
23
+ font-family: 'Montserrat', sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
+
26
+ .font-code {
27
+ font-family: 'Fira Code', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
+
30
+ .gradient-primary {
31
+ background-image: linear-gradient(to right, #1abc9c 0%, #16a085 100%);
 
 
32
  }
33
+
34
+ .gradient-secondary {
35
+ background-image: linear-gradient(to right, #2c3e50 0%, #34495e 100%);
 
 
 
 
 
 
 
36
  }
37
+
38
  @keyframes spin {
39
  to { transform: rotate(360deg); }
40
  }
41
+
42
+ .animate-spin-custom {
43
+ animation: spin 0.8s linear infinite;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  }
45
+
46
+ .upload-highlight {
47
+ border-color: #1abc9c;
48
+ background-color: #e8f8f5;
 
49
  }
 
50
  </style>
51
  </head>
52
+ <body class="bg-gray-50 text-gray-700 min-h-screen flex flex-col items-center py-6 px-4">
53
+ <div class="bg-white rounded-xl shadow-lg w-full max-w-2xl p-6 md:p-8">
54
+ <h1 class="flex items-center justify-center text-3xl font-bold text-center mb-2 text-primary-color">
55
+ <i class="fas fa-atom text-4xl mr-3 text-secondary-color"></i>
56
+ Solveur Expert IA
57
+ </h1>
58
+ <p class="text-center text-gray-500 mb-8">Solutions LaTeX précises pour Maths, Physique et Chimie.</p>
59
 
60
+ <!-- Zone de dépôt de fichier améliorée -->
61
+ <div id="upload-section" class="relative border-3 border-dashed border-gray-300 rounded-xl p-8 mb-6 cursor-pointer transition-all duration-300 bg-gray-50 text-center group hover:border-secondary-color">
62
  <div class="upload-content">
63
+ <i class="fas fa-file-arrow-up text-5xl mb-4 text-secondary-color transition-transform duration-300 transform group-hover:scale-110 group-hover:-translate-y-1"></i>
64
+ <p class="text-lg font-medium mb-1">Déposez l'image de votre exercice ici</p>
65
+ <p class="text-sm text-gray-500">ou cliquez pour sélectionner un fichier (PNG, JPG)</p>
66
  </div>
67
+ <input type="file" id="file-input" accept="image/png, image/jpeg, image/webp" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
68
+ <div id="image-preview-container" class="mt-4">
69
+ <img id="image-preview" src="#" alt="Aperçu de l'énoncé" class="hidden max-w-full max-h-64 rounded-lg mx-auto border border-gray-300 shadow-sm">
70
  </div>
71
  </div>
72
 
73
+ <!-- Options de formatage -->
74
+ <div class="bg-gray-50 rounded-xl p-5 mb-6 border border-gray-200">
75
+ <h3 class="flex items-center font-semibold text-primary-color mb-4">
76
+ <i class="fas fa-cogs mr-2 text-secondary-color"></i>Options de Formatage
77
+ </h3>
78
  <div class="prompt-selector">
79
+ <label for="prompt-type" class="block font-medium mb-2">Style de la correction LaTeX :</label>
80
+ <div class="relative">
81
+ <select id="prompt-type" name="prompt-type" class="w-full p-3 pr-10 rounded-xl border-2 border-gray-300 appearance-none bg-white focus:outline-none focus:border-secondary-color focus:ring-2 focus:ring-secondary-color focus:ring-opacity-20 transition-all">
82
+ <option value="refined">Format Raffiné & Complet (mise en page avancée)</option>
83
+ <option value="light">Format Léger & Essentiel (LaTeX standard)</option>
84
+ </select>
85
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-gray-700">
86
+ <i class="fas fa-chevron-down"></i>
87
+ </div>
88
+ </div>
89
  </div>
90
  </div>
91
 
92
+ <!-- Bouton principal -->
93
+ <button id="solve-button" class="gradient-primary w-full py-4 px-6 rounded-xl text-white font-semibold text-lg tracking-wide flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-gray-400 disabled:shadow-none transition duration-300 transform hover:translate-y-px disabled:transform-none" disabled>
94
+ <i class="fas fa-rocket mr-2"></i>Obtenir la Solution
95
  </button>
96
 
97
+ <!-- Conteneur de résolution (initialement caché) -->
98
+ <div id="solving-container" class="hidden mt-8">
99
+ <!-- Section de statut -->
100
+ <div class="text-center mb-5">
101
+ <div id="status-message-element" class="flex items-center justify-center flex-wrap text-lg font-medium text-primary-color">
102
+ <i class="fas fa-hourglass-start mr-2"></i>Prêt à résoudre votre exercice...
103
  </div>
104
  </div>
 
105
 
106
+ <!-- Spinner de chargement -->
107
+ <div id="loading-spinner-element" class="hidden w-10 h-10 mx-auto my-6 border-4 border-gray-300 border-l-secondary-color rounded-full animate-spin-custom"></div>
108
+
109
+ <!-- Notice Telegram -->
110
+ <div class="flex items-center bg-blue-50 border-l-4 border-secondary-color p-4 rounded-xl my-6">
111
+ <i class="fab fa-telegram text-2xl text-secondary-color mr-3"></i>
112
+ <span>Une copie de la solution sera envoyée sur Telegram pour archivage.</span>
113
  </div>
114
 
115
+ <!-- Conteneur de réponse -->
116
+ <div id="response-container-element" class="hidden mt-6 p-6 border border-gray-300 rounded-xl bg-white">
117
+ <h3 class="flex items-center font-semibold text-xl text-primary-color mb-4">
118
+ <i class="fas fa-file-code mr-3 text-secondary-color"></i>Correction LaTeX Détaillée :
119
+ </h3>
120
+ <div id="response-output" class="font-code bg-gray-900 text-gray-200 p-5 rounded-xl overflow-x-auto whitespace-pre-wrap word-break-word max-h-96 mb-5 border border-gray-700"></div>
121
+ <button id="copy-button" class="gradient-secondary px-6 py-3 rounded-xl text-white font-medium flex items-center justify-center transition duration-300 transform hover:translate-y-px">
122
+ <i class="fas fa-copy mr-2"></i>Copier le code LaTeX
123
  </button>
124
  </div>
125
 
126
+ <!-- Affichage des erreurs -->
127
+ <div id="error-display-element" class="hidden bg-red-50 border-2 border-accent-color text-accent-color p-4 rounded-xl my-5 font-medium">
128
  <!-- Les erreurs seront affichées ici -->
129
  </div>
130
  </div>
131
  </div>
132
 
133
+ <footer class="mt-12 mb-5 text-gray-500 text-sm">
134
+ Solutions générées par <a href="#" target="_blank" class="text-secondary-color font-medium hover:underline">Mariam IA</a> © 2025 - Précision garantie.
135
  </footer>
136
 
137
  <script>
 
139
  const uploadSection = document.getElementById('upload-section');
140
  const fileInput = document.getElementById('file-input');
141
  const imagePreview = document.getElementById('image-preview');
 
142
  const solveButton = document.getElementById('solve-button');
143
  const solvingContainer = document.getElementById('solving-container');
144
  const responseContainer = document.getElementById('response-container-element');
 
150
  const errorDisplay = document.getElementById('error-display-element');
151
 
152
  let selectedFile = null;
153
+ let currentTaskId = null;
154
 
155
+ // --- Gestion du Drag and Drop améliorée ---
156
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
157
  uploadSection.addEventListener(eventName, preventDefaults, false);
158
  });
 
160
  e.preventDefault();
161
  e.stopPropagation();
162
  }
163
+
164
  ['dragenter', 'dragover'].forEach(eventName => {
165
+ uploadSection.addEventListener(eventName, () => {
166
+ uploadSection.classList.add('upload-highlight');
167
+ uploadSection.classList.add('border-secondary-color');
168
+ uploadSection.classList.add('bg-green-50');
169
+ }, false);
170
  });
171
+
172
  ['dragleave', 'drop'].forEach(eventName => {
173
+ uploadSection.addEventListener(eventName, () => {
174
+ uploadSection.classList.remove('upload-highlight');
175
+ uploadSection.classList.remove('border-secondary-color');
176
+ uploadSection.classList.remove('bg-green-50');
177
+ }, false);
178
  });
179
+
180
  uploadSection.addEventListener('drop', (e) => {
181
  if (e.dataTransfer.files.length) {
182
  handleFileSelection(e.dataTransfer.files[0]);
183
  }
184
  });
185
+
 
186
  fileInput.addEventListener('change', (e) => {
187
  if (e.target.files.length) {
188
  handleFileSelection(e.target.files[0]);
 
195
  displayError('Format de fichier non supporté.', 'Veuillez utiliser PNG, JPG ou WEBP.');
196
  selectedFile = null;
197
  solveButton.disabled = true;
198
+ imagePreview.classList.add('hidden');
 
199
  return;
200
  }
201
 
202
  selectedFile = file;
203
  solveButton.disabled = false;
204
+ errorDisplay.classList.add('hidden');
205
 
206
  const reader = new FileReader();
207
  reader.onload = (e) => {
208
  imagePreview.src = e.target.result;
209
+ imagePreview.classList.remove('hidden');
 
210
  };
211
  reader.readAsDataURL(file);
212
  }
213
 
214
  function displayError(message, details = null) {
215
+ let fullMessage = `<i class="fas fa-shield-halved mr-2"></i> ${message}`;
216
  if (details) {
217
+ fullMessage += `<br><small class="block mt-1 text-red-700 font-normal">${escapeHtml(details)}</small>`;
218
  }
219
  errorDisplay.innerHTML = fullMessage;
220
+ errorDisplay.classList.remove('hidden');
221
+ responseContainer.classList.add('hidden');
222
+ loadingSpinner.classList.add('hidden');
223
+ updateStatusUI('error_user', null);
224
  }
225
 
226
  solveButton.addEventListener('click', () => {
227
  if (!selectedFile) return;
228
 
229
  solveButton.disabled = true;
230
+ solvingContainer.classList.remove('hidden');
231
+ responseContainer.classList.add('hidden');
232
  responseOutputDiv.textContent = '';
233
+ errorDisplay.classList.add('hidden');
234
+ loadingSpinner.classList.remove('hidden');
235
  updateStatusUI('pending', null, 'Préparation de la résolution...');
236
 
237
  const formData = new FormData();
 
265
 
266
  if (streamData.error) {
267
  displayError(streamData.error, streamData.error_detail);
268
+ if (streamData.response) {
269
  responseOutputDiv.textContent = streamData.response;
270
+ responseContainer.classList.remove('hidden');
271
  }
272
  eventSource.close();
273
  solveButton.disabled = false;
274
+ loadingSpinner.classList.add('hidden');
275
  return;
276
  }
277
 
278
  updateStatusUI(streamData.status, currentTaskId);
279
 
280
  if (streamData.status === 'completed' || streamData.status === 'completed_tex_only' || streamData.status === 'pdf_error') {
281
+ responseContainer.classList.remove('hidden');
282
+ loadingSpinner.classList.add('hidden');
283
 
284
  if (streamData.response) {
285
  responseOutputDiv.textContent = streamData.response;
 
287
 
288
  if (streamData.status === 'pdf_error' && streamData.error_detail) {
289
  const statusEl = statusMessageElement.querySelector('.status-text');
290
+ if(statusEl) statusEl.innerHTML += `<br><small class="pdf-error-detail block mt-1 text-orange-500"><i class="fas fa-file-invoice mr-1"></i> Erreur PDF: ${escapeHtml(streamData.error_detail)}</small>`;
291
  }
292
 
293
  eventSource.close();
 
297
 
298
  eventSource.onerror = function() {
299
  eventSource.close();
300
+ fetch('/task/' + currentTaskId)
301
  .then(resp => resp.json())
302
  .then(taskData => {
303
  updateStatusUI(taskData.status, currentTaskId);
304
  if (taskData.status === 'completed' || taskData.status === 'completed_tex_only' || taskData.status === 'pdf_error') {
305
+ responseContainer.classList.remove('hidden');
306
  if (taskData.response) {
307
  responseOutputDiv.textContent = taskData.response;
308
  }
309
  if (taskData.status === 'pdf_error' && taskData.error_detail) {
310
  const statusEl = statusMessageElement.querySelector('.status-text');
311
+ if(statusEl) statusEl.innerHTML += `<br><small class="pdf-error-detail block mt-1 text-orange-500"><i class="fas fa-file-invoice mr-1"></i> Erreur PDF: ${escapeHtml(taskData.error_detail)}</small>`;
312
  }
313
  } else if (taskData.status === 'error') {
314
  displayError(taskData.error || 'Erreur inattendue lors de la récupération de la tâche.', taskData.error_detail);
 
321
  })
322
  .finally(() => {
323
  solveButton.disabled = false;
324
+ loadingSpinner.classList.add('hidden');
325
  });
326
  };
327
  })
328
  .catch(error => {
329
  displayError(error.message || 'Erreur de communication serveur.');
330
  solveButton.disabled = false;
331
+ loadingSpinner.classList.add('hidden');
332
  });
333
  });
334
 
 
343
  case 'pending': statusMsg = "Lancement de l'analyse par l'IA..."; iconClass = 'fas fa-play-circle'; break;
344
  case 'processing': statusMsg = "L'IA déchiffre votre exercice..."; iconClass = 'fas fa-brain'; iconColor = 'var(--secondary-color)'; break;
345
  case 'generating_latex': statusMsg = "Construction de la solution LaTeX..."; iconClass = 'fas fa-scroll'; break;
346
+ case 'cleaning_latex': statusMsg = "Peaufinage du code LaTeX..."; iconClass = 'fas fa-magic'; break;
347
+ case 'generating_pdf': statusMsg = "Compilation du document PDF final..."; iconClass = 'fas fa-file-pdf'; iconColor = '#e74c3c'; break;
348
  case 'completed': statusMsg = "Solution Complète et Précise Générée !"; iconClass = 'fas fa-check-double'; iconColor = 'var(--success-color)'; break;
349
  case 'completed_tex_only': statusMsg = "Solution LaTeX Précise Générée ! (PDF non requis/dispo)"; iconClass = 'fas fa-check-circle'; iconColor = 'var(--success-color)'; break;
350
+ case 'pdf_error': statusMsg = "Solution LaTeX Précise Générée ! (Erreur PDF)"; iconClass = 'fas fa-file-excel'; iconColor = '#f39c12'; break;
351
  case 'error': statusMsg = "Une anomalie technique est survenue."; iconClass = 'fas fa-times-circle'; iconColor = 'var(--accent-color)'; break;
352
  case 'error_user': statusMsg = "Veuillez vérifier votre image."; iconClass = 'fas fa-exclamation-triangle'; iconColor = 'var(--accent-color)'; break;
353
+ default: statusMsg = `Progression: ${status}`; iconClass = 'fas fa-spinner fa-spin';
354
  }
355
  }
356
 
357
  let taskInfoHtml = '';
358
  if (taskId) {
359
+ taskInfoHtml = `<span class="text-sm text-gray-500 ml-2">(Tâche ${taskId.substring(0,6)} | Style: ${selectedPromptText})</span>`;
360
  }
361
 
362
+ statusMessageElement.innerHTML = `<i class="${iconClass} mr-2" style="color:${iconColor};"></i> <span class="status-text">${statusMsg}</span> ${taskInfoHtml}`;
363
  }
364
 
365
  function escapeHtml(unsafe) {
366
  if (typeof unsafe !== 'string') return '';
367
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
368
  }
369
 
370
  copyButton.addEventListener('click', () => {
371
  const textToCopy = responseOutputDiv.textContent;
372
  navigator.clipboard.writeText(textToCopy).then(() => {
373
  const originalIcon = copyButton.querySelector('i').className;
374
+ const originalText = copyButton.childNodes[1] ? copyButton.childNodes[1].nodeValue : ' Copier le code LaTeX';
375
+ copyButton.innerHTML = `<i class="fas fa-check mr-2"></i> Code Copié !`;
376
+ copyButton.classList.remove('gradient-secondary');
377
+ copyButton.style.backgroundColor = 'var(--success-color)';
378
+
379
  setTimeout(() => {
380
+ copyButton.innerHTML = `<i class="${originalIcon} mr-2"></i>${originalText}`;
381
+ copyButton.classList.add('gradient-secondary');
382
+ copyButton.style.backgroundColor = '';
383
  }, 2500);
384
  }).catch(err => {
385
  console.error('Erreur de copie: ', err);