Docfile commited on
Commit
78cb1ca
·
verified ·
1 Parent(s): ac7a3b5

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +395 -270
templates/index.html CHANGED
@@ -3,22 +3,23 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
6
  <title>Mariam AI - Correcteur d'Exercices</title>
7
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
8
  <style>
9
  :root {
10
- --primary: #4f46e5;
11
  --primary-hover: #4338ca;
12
  --primary-light: #eef2ff;
13
- --success: #10b981;
14
  --success-light: #ecfdf5;
15
- --error: #ef4444;
16
  --error-light: #fef2f2;
17
- --text: #1f2937;
18
- --text-light: #6b7280;
19
- --bg-light: #f9fafb;
20
  --card-bg: #ffffff;
21
- --border: #e5e7eb;
22
  --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
23
  --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
24
  --radius: 0.5rem;
@@ -31,39 +32,43 @@
31
  }
32
 
33
  body {
34
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
35
  line-height: 1.6;
36
  color: var(--text);
37
  background-color: var(--bg-light);
38
  padding: 1.5rem;
 
39
  }
40
 
41
  .container {
42
  max-width: 1000px;
43
- margin: 0 auto;
 
44
  }
45
 
46
  .header {
47
  text-align: center;
48
- margin-bottom: 2rem;
49
  }
50
 
51
  h1 {
52
- font-size: 2rem;
53
  font-weight: 700;
54
  margin-bottom: 0.5rem;
55
  color: var(--primary);
56
  }
57
 
58
  h2 {
59
- font-size: 1.25rem;
60
  font-weight: 600;
61
- margin-bottom: 1rem;
62
  color: var(--text);
 
 
63
  }
64
 
65
  h3 {
66
- font-size: 1rem;
67
  font-weight: 600;
68
  margin-bottom: 0.75rem;
69
  color: var(--text);
@@ -72,19 +77,22 @@
72
  .subheader {
73
  color: var(--text-light);
74
  font-size: 1rem;
 
 
75
  }
76
 
77
  .card {
78
  background: var(--card-bg);
79
  border-radius: var(--radius);
80
  box-shadow: var(--shadow);
81
- padding: 1.5rem;
82
  margin-bottom: 1.5rem;
 
83
  }
84
 
85
  .status-checks {
86
  display: grid;
87
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
88
  gap: 1rem;
89
  }
90
 
@@ -94,33 +102,27 @@
94
  display: flex;
95
  align-items: center;
96
  gap: 0.75rem;
 
97
  }
98
 
99
  .status-success {
100
  background-color: var(--success-light);
101
- border: 1px solid var(--success);
102
  }
103
 
104
  .status-error {
105
  background-color: var(--error-light);
106
- border: 1px solid var(--error);
107
  }
108
 
109
  .status-icon {
110
  font-size: 1.25rem;
111
  }
112
 
113
- .status-success .status-icon {
114
- color: var(--success);
115
- }
116
-
117
- .status-error .status-icon {
118
- color: var(--error);
119
- }
120
 
121
- .status-text {
122
- flex: 1;
123
- }
124
 
125
  .file-upload {
126
  display: flex;
@@ -131,7 +133,7 @@
131
  .file-input-container {
132
  position: relative;
133
  width: 100%;
134
- height: 150px;
135
  border: 2px dashed var(--border);
136
  border-radius: var(--radius);
137
  display: flex;
@@ -142,12 +144,13 @@
142
  padding: 1.5rem;
143
  cursor: pointer;
144
  overflow: hidden;
145
- transition: border-color 0.3s ease, background 0.3s ease;
 
146
  }
147
 
148
  .file-input-container:hover, .file-input-container.dragover {
149
  border-color: var(--primary);
150
- background: var(--primary-light);
151
  }
152
 
153
  .file-input {
@@ -161,12 +164,13 @@
161
  }
162
 
163
  .upload-icon {
164
- font-size: 2rem;
165
  color: var(--primary);
166
  }
167
 
168
  .upload-label {
169
  font-weight: 500;
 
170
  }
171
 
172
  .upload-hint {
@@ -175,112 +179,123 @@
175
  }
176
 
177
  .file-preview {
178
- display: none;
179
  position: absolute;
180
  top: 0;
181
  left: 0;
182
  width: 100%;
183
  height: 100%;
184
  z-index: 1;
185
- background: rgba(255, 255, 255, 0.9);
186
  align-items: center;
187
  justify-content: center;
 
188
  }
189
 
190
  .file-preview img {
191
- max-width: 90%;
192
- max-height: 90%;
193
  object-fit: contain;
 
194
  }
195
 
196
- .preview-active .file-preview {
197
- display: flex;
198
- }
199
 
200
- .file-name {
201
  display: none;
202
  position: absolute;
203
  bottom: 0;
204
  left: 0;
205
  right: 0;
206
- background: rgba(255, 255, 255, 0.8);
207
- padding: 0.5rem;
208
- font-size: 0.875rem;
 
209
  text-align: center;
210
  word-break: break-all;
 
 
 
211
  }
212
 
213
- .preview-active .file-name {
214
- display: block;
215
- }
216
 
217
  .clear-file {
218
  display: none;
219
  position: absolute;
220
- top: 0.5rem;
221
- right: 0.5rem;
222
- background: white;
 
 
223
  border-radius: 50%;
224
- width: 24px;
225
- height: 24px;
226
  align-items: center;
227
  justify-content: center;
228
  cursor: pointer;
229
  box-shadow: var(--shadow);
230
- z-index: 2;
 
 
231
  }
232
-
233
- .preview-active .clear-file {
234
- display: flex;
235
  }
236
 
 
 
237
  .button {
238
  display: inline-flex;
239
  align-items: center;
240
  justify-content: center;
241
- gap: 0.5rem;
242
  background-color: var(--primary);
243
  color: white;
244
- padding: 0.75rem 1.5rem;
245
  border: none;
246
  border-radius: var(--radius);
247
  font-weight: 500;
248
  font-size: 1rem;
249
  cursor: pointer;
250
- transition: background-color 0.3s, transform 0.2s;
251
  width: 100%;
 
252
  }
253
 
254
  .button:hover {
255
  background-color: var(--primary-hover);
 
256
  }
257
 
258
  .button:active {
259
  transform: translateY(1px);
 
260
  }
261
 
262
  .button:disabled {
263
  background-color: var(--text-light);
264
  cursor: not-allowed;
265
  opacity: 0.7;
 
266
  }
267
 
268
- .button-icon {
269
- font-size: 1rem;
270
- }
271
 
272
  .loading {
273
  display: flex;
274
  flex-direction: column;
275
  align-items: center;
276
  gap: 1rem;
277
- padding: 2rem;
 
278
  }
279
 
280
  .spinner {
281
- width: 40px;
282
- height: 40px;
283
- border: 4px solid rgba(79, 70, 229, 0.2);
284
  border-radius: 50%;
285
  border-top-color: var(--primary);
286
  animation: spin 1s linear infinite;
@@ -292,92 +307,116 @@
292
  }
293
 
294
  .message {
295
- padding: 1rem;
296
  border-radius: var(--radius);
297
  margin-bottom: 1.5rem;
298
  display: flex;
299
- align-items: center;
300
  gap: 0.75rem;
 
 
 
 
 
 
 
 
301
  }
302
 
303
  .message-success {
304
  background-color: var(--success-light);
305
- border: 1px solid var(--success);
306
- color: var(--success);
307
  }
308
 
309
  .message-error {
310
  background-color: var(--error-light);
311
- border: 1px solid var(--error);
312
- color: var(--error);
313
- white-space: pre-wrap;
314
  }
315
 
316
  .tab-container {
317
  border: 1px solid var(--border);
318
  border-radius: var(--radius);
319
  overflow: hidden;
 
 
320
  }
321
 
322
  .tabs {
323
  display: flex;
324
  background: var(--bg-light);
 
325
  }
326
 
327
  .tab {
328
- padding: 0.75rem 1.25rem;
329
  cursor: pointer;
330
- border-bottom: 2px solid transparent;
 
331
  font-weight: 500;
332
  color: var(--text-light);
333
  transition: all 0.3s ease;
 
334
  }
 
 
 
 
335
 
336
  .tab.active {
337
  color: var(--primary);
338
  border-bottom-color: var(--primary);
 
339
  }
340
 
341
  .tab-content {
342
  display: none;
343
  padding: 1.5rem;
 
344
  }
345
 
346
- .tab-content.active {
347
- display: block;
348
- }
349
 
350
  #pdf-viewer {
351
  width: 100%;
352
- height: 600px;
353
  border: 1px solid var(--border);
354
  border-radius: var(--radius);
355
  }
356
 
357
  .code-area {
358
- background-color: #f8fafc;
359
  border: 1px solid var(--border);
360
- border-radius: 0.25rem;
361
- padding: 1rem;
362
- max-height: 400px;
363
  overflow-y: auto;
 
 
 
 
364
  }
365
 
366
  .code-area pre {
367
  margin: 0;
368
  white-space: pre-wrap;
369
  word-wrap: break-word;
370
- font-family: Consolas, Monaco, 'Andale Mono', monospace;
371
- font-size: 0.875rem;
372
- line-height: 1.5;
 
 
373
  }
374
 
375
  .download-button {
 
376
  display: inline-flex;
377
  align-items: center;
378
  justify-content: center;
379
- gap: 0.5rem;
380
- background-color: var(--primary);
381
  color: white;
382
  padding: 0.75rem 1.5rem;
383
  border: none;
@@ -385,33 +424,37 @@
385
  font-weight: 500;
386
  font-size: 1rem;
387
  cursor: pointer;
388
- transition: background-color 0.3s, transform 0.2s;
389
- margin: 1.5rem auto;
390
- width: auto;
391
  }
392
-
393
- .hidden {
394
- display: none !important;
395
  }
 
 
 
 
 
 
 
 
 
396
 
397
  /* Responsive design */
398
  @media (max-width: 768px) {
399
- .status-checks {
400
- grid-template-columns: 1fr;
401
- }
402
-
403
- .file-upload-container {
404
- height: 120px;
405
- }
406
-
407
- .tab {
408
- padding: 0.5rem 1rem;
409
- font-size: 0.875rem;
410
- }
411
-
412
- #pdf-viewer {
413
- height: 400px;
414
- }
415
  }
416
  </style>
417
  </head>
@@ -419,11 +462,11 @@
419
  <div class="container">
420
  <div class="header">
421
  <h1>Mariam AI</h1>
422
- <p class="subheader">Correcteur Intelligent d'Exercices Mathématiques/physique/chimie.</p>
423
  </div>
424
 
425
  <div class="card">
426
- <h2>État du système</h2>
427
  <div class="status-checks">
428
  <div id="latex-status" class="status-item">
429
  <span class="status-icon"><i class="fas fa-spinner fa-spin"></i></span>
@@ -437,47 +480,57 @@
437
  </div>
438
 
439
  <div class="card">
440
- <h2>Soumettre un exercice</h2>
441
  <div class="file-upload">
442
  <div id="file-input-container" class="file-input-container">
443
- <span class="upload-icon"><i class="fas fa-cloud-upload-alt"></i></span>
444
- <span class="upload-label">Déposez votre image ou cliquez pour sélectionner</span>
445
- <span class="upload-hint">Formats acceptés: JPG, PNG, GIF</span>
446
- <input type="file" id="image-input" class="file-input" accept="image/*">
447
-
 
 
 
 
 
448
  <div class="file-preview">
449
- <img id="image-preview" src="" alt="Aperçu">
450
  </div>
451
- <div class="file-name" id="file-name"></div>
452
- <div class="clear-file" id="clear-file"><i class="fas fa-times"></i></div>
453
  </div>
454
-
455
  <button id="process-button" class="button" disabled>
456
- <span class="button-icon"><i class="fas fa-magic"></i></span>
457
  <span>Générer la solution</span>
458
  </button>
459
  </div>
460
  </div>
461
 
 
462
  <div id="loading" class="card loading hidden">
463
  <div class="spinner"></div>
464
- <p>Mariam AI analyse l'exercice et génère la solution...</p>
465
- <p class="upload-hint">Cette opération peut prendre jusqu'à une minute.</p>
466
  </div>
467
 
468
- <div id="messages" class="message hidden"></div>
 
469
 
 
470
  <div id="results" class="card hidden">
471
- <h2>Résultat de l'analyse</h2>
472
-
473
  <div class="tab-container">
474
  <div class="tabs">
475
- <div class="tab active" data-tab="pdf">Aperçu PDF</div>
476
- <div class="tab" data-tab="latex">Code LaTeX</div>
477
- <div class="tab" data-tab="thinking">Processus de réflexion</div>
478
  </div>
479
-
 
480
  <div id="pdf-tab" class="tab-content active">
 
481
  <iframe id="pdf-viewer" title="Aperçu du PDF de la solution"></iframe>
482
  <div class="download-container">
483
  <button id="download-button" class="download-button">
@@ -485,16 +538,20 @@
485
  </button>
486
  </div>
487
  </div>
488
-
 
489
  <div id="latex-tab" class="tab-content">
 
490
  <div class="code-area">
491
  <pre id="latex-output"></pre>
492
  </div>
493
  </div>
494
-
 
495
  <div id="thinking-tab" class="tab-content">
 
496
  <div class="code-area">
497
- <pre id="thinking-output"></pre>
498
  </div>
499
  </div>
500
  </div>
@@ -503,13 +560,13 @@
503
 
504
  <script>
505
  document.addEventListener('DOMContentLoaded', () => {
506
- // Éléments DOM
507
  const latexStatusEl = document.getElementById('latex-status');
508
  const apiStatusEl = document.getElementById('api-status');
509
  const fileInputContainer = document.getElementById('file-input-container');
510
  const imageInput = document.getElementById('image-input');
511
  const imagePreview = document.getElementById('image-preview');
512
- const fileName = document.getElementById('file-name');
513
  const clearFile = document.getElementById('clear-file');
514
  const processButton = document.getElementById('process-button');
515
  const loadingEl = document.getElementById('loading');
@@ -518,50 +575,53 @@
518
  const pdfViewer = document.getElementById('pdf-viewer');
519
  const downloadButton = document.getElementById('download-button');
520
  const latexOutputEl = document.getElementById('latex-output');
521
- const thinkingOutputEl = document.getElementById('thinking-output');
522
  const tabs = document.querySelectorAll('.tab');
523
  const tabContents = document.querySelectorAll('.tab-content');
 
524
 
525
- let currentPdfBase64 = null; // Stockage des données PDF
 
526
 
527
  // --- Gestion des onglets ---
528
  tabs.forEach(tab => {
529
  tab.addEventListener('click', () => {
530
- // Désactiver tous les onglets
531
- tabs.forEach(t => t.classList.remove('active'));
532
- tabContents.forEach(c => c.classList.remove('active'));
533
-
534
- // Activer l'onglet sélectionné
535
- tab.classList.add('active');
536
- document.getElementById(`${tab.dataset.tab}-tab`).classList.add('active');
537
  });
538
  });
539
 
 
 
 
 
 
 
 
 
 
 
 
540
  // --- Gestion du chargement des images ---
541
  imageInput.addEventListener('change', handleFileSelect);
542
-
543
  // Gestion du drag and drop
544
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
545
  fileInputContainer.addEventListener(eventName, preventDefaults, false);
546
  });
547
 
548
  ['dragenter', 'dragover'].forEach(eventName => {
549
- fileInputContainer.addEventListener(eventName, () => {
550
- fileInputContainer.classList.add('dragover');
551
- }, false);
552
  });
553
 
554
  ['dragleave', 'drop'].forEach(eventName => {
555
- fileInputContainer.addEventListener(eventName, () => {
556
- fileInputContainer.classList.remove('dragover');
557
- }, false);
558
  });
559
 
560
  fileInputContainer.addEventListener('drop', handleDrop, false);
561
-
562
  // Gestion du bouton de suppression de fichier
563
  clearFile.addEventListener('click', (e) => {
564
- e.stopPropagation();
565
  clearFileInput();
566
  });
567
 
@@ -573,38 +633,67 @@
573
  function handleDrop(e) {
574
  const dt = e.dataTransfer;
575
  const files = dt.files;
576
-
577
- if (files && files[0]) {
578
- imageInput.files = files;
579
- handleFileSelect();
580
- }
581
  }
582
 
583
  function handleFileSelect() {
584
- const file = imageInput.files[0];
585
-
586
- if (file) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  const reader = new FileReader();
588
-
589
  reader.onload = function(e) {
590
  imagePreview.src = e.target.result;
591
- fileName.textContent = file.name;
 
592
  fileInputContainer.classList.add('preview-active');
 
593
  processButton.disabled = false;
 
 
594
  };
595
-
 
 
 
596
  reader.readAsDataURL(file);
597
  } else {
598
  clearFileInput();
599
  }
600
  }
601
 
 
602
  function clearFileInput() {
603
- imageInput.value = '';
604
  imagePreview.src = '';
605
- fileName.textContent = '';
 
606
  fileInputContainer.classList.remove('preview-active');
 
607
  processButton.disabled = true;
 
 
 
 
 
 
 
608
  }
609
 
610
  // --- Fonctions Utilitaires ---
@@ -613,100 +702,112 @@
613
  processButton.disabled = true;
614
  messagesEl.classList.add('hidden');
615
  resultsEl.classList.add('hidden');
616
- pdfViewer.src = 'about:blank'; // Vider l'aperçu PDF
 
617
  latexOutputEl.textContent = '';
618
- thinkingOutputEl.textContent = '';
619
  currentPdfBase64 = null;
620
  }
621
 
622
  function hideLoading() {
623
  loadingEl.classList.add('hidden');
 
624
  processButton.disabled = !imageInput.files[0];
625
  }
626
 
627
  function showMessage(message, isError = false) {
628
  messagesEl.innerHTML = `
629
- <i class="fas ${isError ? 'fa-exclamation-circle' : 'fa-check-circle'}"></i>
630
  <div>${message}</div>
631
  `;
632
  messagesEl.className = `message ${isError ? 'message-error' : 'message-success'}`;
633
  messagesEl.classList.remove('hidden');
 
 
634
  }
635
 
 
 
 
 
 
 
636
  function displayResults(data) {
637
- resultsEl.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
 
639
- // Gestion du PDF
640
- if (data.pdf_base64) {
641
- pdfViewer.src = `data:application/pdf;base64,${data.pdf_base64}`;
642
- currentPdfBase64 = data.pdf_base64;
643
- downloadButton.classList.remove('hidden');
644
- } else {
645
- pdfViewer.src = 'about:blank';
646
- downloadButton.classList.add('hidden');
647
- }
 
 
 
 
 
 
648
 
649
- // Gestion du LaTeX
650
- latexOutputEl.textContent = data.latex || "Aucun code LaTeX disponible.";
651
-
652
- // Gestion du processus de réflexion
653
- thinkingOutputEl.textContent = data.thinking || "Aucun processus de réflexion disponible.";
654
-
655
- // Activer l'onglet approprié par défaut
656
- if (data.pdf_base64) {
657
- activateTab('pdf');
658
- } else if (data.latex) {
659
- activateTab('latex');
660
- } else if (data.thinking) {
661
- activateTab('thinking');
662
- }
663
- }
664
-
665
- function activateTab(tabName) {
666
- tabs.forEach(t => t.classList.remove('active'));
667
- tabContents.forEach(c => c.classList.remove('active'));
668
-
669
- document.querySelector(`.tab[data-tab="${tabName}"]`).classList.add('active');
670
- document.getElementById(`${tabName}-tab`).classList.add('active');
671
- }
672
 
673
  // --- Vérifications Initiales ---
674
  async function checkStatus() {
 
675
  try {
676
  const latexRes = await fetch('/check-latex');
 
677
  const latexData = await latexRes.json();
678
-
679
- latexStatusEl.innerHTML = `
680
- <span class="status-icon"><i class="fas ${latexData.success ? 'fa-check-circle' : 'fa-times-circle'}"></i></span>
681
- <span class="status-text">${latexData.message}</span>
682
- `;
683
- latexStatusEl.className = `status-item ${latexData.success ? 'status-success' : 'status-error'}`;
684
  } catch (error) {
685
- latexStatusEl.innerHTML = `
686
- <span class="status-icon"><i class="fas fa-times-circle"></i></span>
687
- <span class="status-text">Erreur lors de la vérification de LaTeX: ${error}</span>
688
- `;
689
- latexStatusEl.className = 'status-item status-error';
690
  }
691
 
 
692
  try {
693
  const apiRes = await fetch('/check-api');
 
694
  const apiData = await apiRes.json();
695
-
696
- apiStatusEl.innerHTML = `
697
- <span class="status-icon"><i class="fas ${apiData.success ? 'fa-check-circle' : 'fa-times-circle'}"></i></span>
698
- <span class="status-text">${apiData.message}</span>
699
- `;
700
- apiStatusEl.className = `status-item ${apiData.success ? 'status-success' : 'status-error'}`;
701
  } catch (error) {
702
- apiStatusEl.innerHTML = `
703
- <span class="status-icon"><i class="fas fa-times-circle"></i></span>
704
- <span class="status-text">Erreur lors de la vérification de l'API: ${error}</span>
705
- `;
706
- apiStatusEl.className = 'status-item status-error';
707
  }
708
  }
709
 
 
 
 
 
 
 
 
 
710
  // --- Traitement de l'Image ---
711
  processButton.addEventListener('click', async () => {
712
  const file = imageInput.files[0];
@@ -716,6 +817,7 @@
716
  }
717
 
718
  showLoading();
 
719
 
720
  const formData = new FormData();
721
  formData.append('image', file);
@@ -726,68 +828,91 @@
726
  body: formData
727
  });
728
 
729
- const data = await response.json();
730
  hideLoading();
731
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
732
  if (data.success) {
733
  showMessage('Solution générée avec succès !');
734
  displayResults(data);
735
  } else {
736
- showMessage(`Erreur : ${data.message}`, true);
737
- // Afficher le LaTeX/Thinking même en cas d'erreur de compilation
738
- if(data.latex || data.thinking) {
 
739
  displayResults(data);
740
  }
741
  }
742
 
743
  } catch (error) {
 
744
  hideLoading();
745
- showMessage(`Erreur de communication avec le serveur : ${error}`, true);
746
  console.error("Fetch Error:", error);
747
  }
748
  });
749
 
750
- // --- Téléchargement du PDF ---
751
- downloadButton.addEventListener('click', async () => {
752
- if (!currentPdfBase64) {
753
- showMessage('Aucune donnée PDF à télécharger.', true);
754
- return;
755
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
 
757
- try {
758
- const response = await fetch('/download-pdf', {
759
- method: 'POST',
760
- headers: {
761
- 'Content-Type': 'application/json',
762
- },
763
- body: JSON.stringify({ pdf_data: currentPdfBase64 }),
764
- });
765
-
766
- if (response.ok) {
767
- const blob = await response.blob();
768
- const url = window.URL.createObjectURL(blob);
769
- const a = document.createElement('a');
770
- a.style.display = 'none';
771
- a.href = url;
772
- a.download = response.headers.get('Content-Disposition')?.split('filename=')[1]?.replaceAll('"', '') || 'solution_mariam_ai.pdf';
773
- document.body.appendChild(a);
774
- a.click();
775
- window.URL.revokeObjectURL(url);
776
- a.remove();
777
- showMessage('Téléchargement démarré.');
778
- } else {
779
- let errorMsg = `Échec du téléchargement (code ${response.status}).`;
780
- try {
781
- const errorData = await response.json();
782
- if(errorData.message) errorMsg += ` Raison: ${errorData.message}`;
783
- } catch(e) { /* Ignorer l'erreur si la réponse n'est pas JSON */ }
784
- showMessage(errorMsg, true);
785
- }
786
- } catch (error) {
787
- showMessage(`Erreur lors de la tentative de téléchargement : ${error}`, true);
788
- console.error("Download Error:", error);
789
- }
790
- });
791
 
792
  // Exécuter les vérifications au chargement
793
  checkStatus();
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <!-- Titre mis à jour pour refléter l'IA sous-jacente -->
7
  <title>Mariam AI - Correcteur d'Exercices</title>
8
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
9
  <style>
10
  :root {
11
+ --primary: #4f46e5; /* Indigo */
12
  --primary-hover: #4338ca;
13
  --primary-light: #eef2ff;
14
+ --success: #10b981; /* Emerald */
15
  --success-light: #ecfdf5;
16
+ --error: #ef4444; /* Red */
17
  --error-light: #fef2f2;
18
+ --text: #1f2937; /* Gray 800 */
19
+ --text-light: #6b7280; /* Gray 500 */
20
+ --bg-light: #f9fafb; /* Gray 50 */
21
  --card-bg: #ffffff;
22
+ --border: #e5e7eb; /* Gray 200 */
23
  --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
24
  --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
25
  --radius: 0.5rem;
 
32
  }
33
 
34
  body {
35
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
36
  line-height: 1.6;
37
  color: var(--text);
38
  background-color: var(--bg-light);
39
  padding: 1.5rem;
40
+ font-size: 16px; /* Base font size */
41
  }
42
 
43
  .container {
44
  max-width: 1000px;
45
+ margin: 1.5rem auto; /* Added top margin */
46
+ padding: 0 1rem; /* Padding on smaller screens */
47
  }
48
 
49
  .header {
50
  text-align: center;
51
+ margin-bottom: 2.5rem; /* Increased margin */
52
  }
53
 
54
  h1 {
55
+ font-size: 2.25rem; /* Larger */
56
  font-weight: 700;
57
  margin-bottom: 0.5rem;
58
  color: var(--primary);
59
  }
60
 
61
  h2 {
62
+ font-size: 1.5rem; /* Larger */
63
  font-weight: 600;
64
+ margin-bottom: 1.25rem; /* Increased margin */
65
  color: var(--text);
66
+ border-bottom: 1px solid var(--border); /* Subtle separator */
67
+ padding-bottom: 0.5rem;
68
  }
69
 
70
  h3 {
71
+ font-size: 1.125rem; /* Slightly larger */
72
  font-weight: 600;
73
  margin-bottom: 0.75rem;
74
  color: var(--text);
 
77
  .subheader {
78
  color: var(--text-light);
79
  font-size: 1rem;
80
+ max-width: 600px; /* Limit width */
81
+ margin: 0 auto; /* Center */
82
  }
83
 
84
  .card {
85
  background: var(--card-bg);
86
  border-radius: var(--radius);
87
  box-shadow: var(--shadow);
88
+ padding: 1.5rem; /* Standard padding */
89
  margin-bottom: 1.5rem;
90
+ border: 1px solid var(--border); /* Subtle border */
91
  }
92
 
93
  .status-checks {
94
  display: grid;
95
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* Increased min-width */
96
  gap: 1rem;
97
  }
98
 
 
102
  display: flex;
103
  align-items: center;
104
  gap: 0.75rem;
105
+ font-size: 0.9rem; /* Slightly smaller text */
106
  }
107
 
108
  .status-success {
109
  background-color: var(--success-light);
110
+ border-left: 4px solid var(--success); /* Accent border */
111
  }
112
 
113
  .status-error {
114
  background-color: var(--error-light);
115
+ border-left: 4px solid var(--error); /* Accent border */
116
  }
117
 
118
  .status-icon {
119
  font-size: 1.25rem;
120
  }
121
 
122
+ .status-success .status-icon { color: var(--success); }
123
+ .status-error .status-icon { color: var(--error); }
 
 
 
 
 
124
 
125
+ .status-text { flex: 1; }
 
 
126
 
127
  .file-upload {
128
  display: flex;
 
133
  .file-input-container {
134
  position: relative;
135
  width: 100%;
136
+ min-height: 150px; /* Use min-height */
137
  border: 2px dashed var(--border);
138
  border-radius: var(--radius);
139
  display: flex;
 
144
  padding: 1.5rem;
145
  cursor: pointer;
146
  overflow: hidden;
147
+ transition: border-color 0.3s ease, background-color 0.3s ease; /* Corrected property name */
148
+ text-align: center; /* Ensure text is centered */
149
  }
150
 
151
  .file-input-container:hover, .file-input-container.dragover {
152
  border-color: var(--primary);
153
+ background-color: var(--primary-light); /* Corrected property name */
154
  }
155
 
156
  .file-input {
 
164
  }
165
 
166
  .upload-icon {
167
+ font-size: 2.5rem; /* Larger icon */
168
  color: var(--primary);
169
  }
170
 
171
  .upload-label {
172
  font-weight: 500;
173
+ color: var(--text); /* Ensure label color */
174
  }
175
 
176
  .upload-hint {
 
179
  }
180
 
181
  .file-preview {
182
+ display: none; /* Initially hidden */
183
  position: absolute;
184
  top: 0;
185
  left: 0;
186
  width: 100%;
187
  height: 100%;
188
  z-index: 1;
189
+ background: rgba(255, 255, 255, 0.95); /* Slightly more opaque */
190
  align-items: center;
191
  justify-content: center;
192
+ padding: 0.5rem; /* Add padding */
193
  }
194
 
195
  .file-preview img {
196
+ max-width: 100%; /* Use 100% */
197
+ max-height: 100%; /* Use 100% */
198
  object-fit: contain;
199
+ border-radius: calc(var(--radius) / 2); /* Slight radius */
200
  }
201
 
202
+ .preview-active .file-preview { display: flex; }
 
 
203
 
204
+ .file-name-display { /* Renamed for clarity */
205
  display: none;
206
  position: absolute;
207
  bottom: 0;
208
  left: 0;
209
  right: 0;
210
+ background: rgba(79, 70, 229, 0.8); /* Primary color background */
211
+ color: white;
212
+ padding: 0.5rem 1rem;
213
+ font-size: 0.8rem;
214
  text-align: center;
215
  word-break: break-all;
216
+ z-index: 2; /* Above preview image */
217
+ border-bottom-left-radius: var(--radius); /* Match container */
218
+ border-bottom-right-radius: var(--radius);
219
  }
220
 
221
+ .preview-active .file-name-display { display: block; } /* Corrected class name */
 
 
222
 
223
  .clear-file {
224
  display: none;
225
  position: absolute;
226
+ top: 0.75rem; /* Adjusted position */
227
+ right: 0.75rem;
228
+ background: rgba(255, 255, 255, 0.8);
229
+ backdrop-filter: blur(2px); /* Frosted glass effect */
230
+ border: 1px solid var(--border);
231
  border-radius: 50%;
232
+ width: 28px; /* Larger */
233
+ height: 28px;
234
  align-items: center;
235
  justify-content: center;
236
  cursor: pointer;
237
  box-shadow: var(--shadow);
238
+ z-index: 3; /* Above name display */
239
+ color: var(--text-light);
240
+ transition: background-color 0.2s, color 0.2s;
241
  }
242
+ .clear-file:hover {
243
+ background-color: var(--error);
244
+ color: white;
245
  }
246
 
247
+ .preview-active .clear-file { display: flex; }
248
+
249
  .button {
250
  display: inline-flex;
251
  align-items: center;
252
  justify-content: center;
253
+ gap: 0.6rem; /* Increased gap */
254
  background-color: var(--primary);
255
  color: white;
256
+ padding: 0.8rem 1.75rem; /* Adjusted padding */
257
  border: none;
258
  border-radius: var(--radius);
259
  font-weight: 500;
260
  font-size: 1rem;
261
  cursor: pointer;
262
+ transition: background-color 0.3s, transform 0.2s, box-shadow 0.2s;
263
  width: 100%;
264
+ box-shadow: var(--shadow); /* Add shadow */
265
  }
266
 
267
  .button:hover {
268
  background-color: var(--primary-hover);
269
+ box-shadow: var(--shadow-md); /* Enhance shadow */
270
  }
271
 
272
  .button:active {
273
  transform: translateY(1px);
274
+ box-shadow: none; /* Remove shadow on click */
275
  }
276
 
277
  .button:disabled {
278
  background-color: var(--text-light);
279
  cursor: not-allowed;
280
  opacity: 0.7;
281
+ box-shadow: none;
282
  }
283
 
284
+ .button-icon { font-size: 1.1rem; } /* Slightly larger */
 
 
285
 
286
  .loading {
287
  display: flex;
288
  flex-direction: column;
289
  align-items: center;
290
  gap: 1rem;
291
+ padding: 2.5rem 1.5rem; /* Increased padding */
292
+ text-align: center;
293
  }
294
 
295
  .spinner {
296
+ width: 48px; /* Larger */
297
+ height: 48px;
298
+ border: 5px solid rgba(79, 70, 229, 0.2); /* Thicker border */
299
  border-radius: 50%;
300
  border-top-color: var(--primary);
301
  animation: spin 1s linear infinite;
 
307
  }
308
 
309
  .message {
310
+ padding: 1rem 1.25rem; /* Adjusted padding */
311
  border-radius: var(--radius);
312
  margin-bottom: 1.5rem;
313
  display: flex;
314
+ align-items: flex-start; /* Align icon top */
315
  gap: 0.75rem;
316
+ font-size: 0.95rem; /* Adjust font size */
317
+ border-left-width: 4px;
318
+ border-left-style: solid;
319
+ }
320
+
321
+ .message i {
322
+ margin-top: 2px; /* Align icon better with text */
323
+ font-size: 1.1rem;
324
  }
325
 
326
  .message-success {
327
  background-color: var(--success-light);
328
+ border-left-color: var(--success);
329
+ color: var(--success); /* Adjust text color for consistency */
330
  }
331
 
332
  .message-error {
333
  background-color: var(--error-light);
334
+ border-left-color: var(--error);
335
+ color: #c01d1d; /* Darker red for text */
336
+ white-space: pre-wrap; /* Preserve line breaks in errors */
337
  }
338
 
339
  .tab-container {
340
  border: 1px solid var(--border);
341
  border-radius: var(--radius);
342
  overflow: hidden;
343
+ box-shadow: var(--shadow); /* Add shadow */
344
+ margin-top: 1.5rem; /* Add margin */
345
  }
346
 
347
  .tabs {
348
  display: flex;
349
  background: var(--bg-light);
350
+ border-bottom: 1px solid var(--border); /* Separator line */
351
  }
352
 
353
  .tab {
354
+ padding: 0.85rem 1.5rem; /* Adjusted padding */
355
  cursor: pointer;
356
+ border-bottom: 3px solid transparent; /* Thicker border */
357
+ margin-bottom: -1px; /* Overlap border */
358
  font-weight: 500;
359
  color: var(--text-light);
360
  transition: all 0.3s ease;
361
+ font-size: 0.95rem;
362
  }
363
+ .tab:hover {
364
+ background-color: #f3f4f6; /* Light gray hover */
365
+ color: var(--text);
366
+ }
367
 
368
  .tab.active {
369
  color: var(--primary);
370
  border-bottom-color: var(--primary);
371
+ background-color: var(--card-bg); /* Match content background */
372
  }
373
 
374
  .tab-content {
375
  display: none;
376
  padding: 1.5rem;
377
+ background-color: var(--card-bg);
378
  }
379
 
380
+ .tab-content.active { display: block; }
 
 
381
 
382
  #pdf-viewer {
383
  width: 100%;
384
+ height: 650px; /* Increased height */
385
  border: 1px solid var(--border);
386
  border-radius: var(--radius);
387
  }
388
 
389
  .code-area {
390
+ background-color: #f8fafc; /* Lighter background */
391
  border: 1px solid var(--border);
392
+ border-radius: var(--radius); /* Use consistent radius */
393
+ padding: 1rem 1.25rem; /* Adjust padding */
394
+ max-height: 500px; /* Increased max-height */
395
  overflow-y: auto;
396
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
397
+ font-size: 0.875rem;
398
+ line-height: 1.6; /* Improved line spacing */
399
+ color: #334155; /* Darker code text */
400
  }
401
 
402
  .code-area pre {
403
  margin: 0;
404
  white-space: pre-wrap;
405
  word-wrap: break-word;
406
+ }
407
+
408
+ .download-container {
409
+ text-align: center; /* Center the button */
410
+ margin-top: 1.5rem; /* Add space above button */
411
  }
412
 
413
  .download-button {
414
+ /* Reuse button styles and adjust */
415
  display: inline-flex;
416
  align-items: center;
417
  justify-content: center;
418
+ gap: 0.6rem;
419
+ background-color: var(--success); /* Use success color */
420
  color: white;
421
  padding: 0.75rem 1.5rem;
422
  border: none;
 
424
  font-weight: 500;
425
  font-size: 1rem;
426
  cursor: pointer;
427
+ transition: background-color 0.3s, transform 0.2s, box-shadow 0.2s;
428
+ width: auto; /* Don't force full width */
429
+ box-shadow: var(--shadow);
430
  }
431
+ .download-button:hover {
432
+ background-color: #059669; /* Darker success */
433
+ box-shadow: var(--shadow-md);
434
  }
435
+ .download-button:active {
436
+ transform: translateY(1px);
437
+ box-shadow: none;
438
+ }
439
+ .download-button i {
440
+ font-size: 1rem; /* Match icon size */
441
+ }
442
+
443
+ .hidden { display: none !important; }
444
 
445
  /* Responsive design */
446
  @media (max-width: 768px) {
447
+ body { padding: 1rem; }
448
+ .container { margin-top: 1rem; padding: 0 0.5rem; }
449
+ h1 { font-size: 1.75rem; }
450
+ h2 { font-size: 1.25rem; }
451
+ .status-checks { grid-template-columns: 1fr; }
452
+ .file-input-container { min-height: 120px; }
453
+ .tabs { flex-wrap: wrap; } /* Allow tabs to wrap */
454
+ .tab { padding: 0.7rem 1rem; font-size: 0.9rem; flex-grow: 1; text-align: center; } /* Make tabs responsive */
455
+ #pdf-viewer { height: 500px; } /* Adjust height */
456
+ .code-area { max-height: 400px; }
457
+ .button, .download-button { font-size: 0.95rem; padding: 0.7rem 1.25rem; }
 
 
 
 
 
458
  }
459
  </style>
460
  </head>
 
462
  <div class="container">
463
  <div class="header">
464
  <h1>Mariam AI</h1>
465
+ <p class="subheader">Votre assistante IA pour la correction détaillée d'exercices de Mathématiques, Physique et Chimie.</p>
466
  </div>
467
 
468
  <div class="card">
469
+ <h2><i class="fas fa-power-off fa-fw" style="margin-right: 0.5rem; color: var(--primary);"></i>État du système</h2>
470
  <div class="status-checks">
471
  <div id="latex-status" class="status-item">
472
  <span class="status-icon"><i class="fas fa-spinner fa-spin"></i></span>
 
480
  </div>
481
 
482
  <div class="card">
483
+ <h2><i class="fas fa-upload fa-fw" style="margin-right: 0.5rem; color: var(--primary);"></i>Soumettre un exercice</h2>
484
  <div class="file-upload">
485
  <div id="file-input-container" class="file-input-container">
486
+ <input type="file" id="image-input" class="file-input" accept="image/jpeg, image/png, image/gif, image/webp"> <!-- Added webp -->
487
+
488
+ <!-- Default content -->
489
+ <div class="default-content">
490
+ <span class="upload-icon"><i class="fas fa-cloud-upload-alt"></i></span>
491
+ <span class="upload-label">Déposez votre image ici ou cliquez pour sélectionner</span>
492
+ <span class="upload-hint">Formats: JPG, PNG, GIF, WebP. Taille max: 10Mo</span> <!-- Clarify formats -->
493
+ </div>
494
+
495
+ <!-- Preview content (managed by JS) -->
496
  <div class="file-preview">
497
+ <img id="image-preview" src="" alt="Aperçu de l'exercice">
498
  </div>
499
+ <div class="file-name-display" id="file-name-display"></div>
500
+ <div class="clear-file" id="clear-file" title="Retirer l'image"><i class="fas fa-times"></i></div>
501
  </div>
502
+
503
  <button id="process-button" class="button" disabled>
504
+ <span class="button-icon"><i class="fas fa-wand-magic-sparkles"></i></span> <!-- Updated icon -->
505
  <span>Générer la solution</span>
506
  </button>
507
  </div>
508
  </div>
509
 
510
+ <!-- Loading Indicator -->
511
  <div id="loading" class="card loading hidden">
512
  <div class="spinner"></div>
513
+ <p style="font-weight: 500; font-size: 1.1rem;">Mariam AI travaille...</p>
514
+ <p class="upload-hint">Analyse de l'image et génération de la solution en cours. Cela peut prendre un moment.</p>
515
  </div>
516
 
517
+ <!-- Messages Area -->
518
+ <div id="messages" class="message hidden"></div> <!-- Will be populated by JS -->
519
 
520
+ <!-- Results Area -->
521
  <div id="results" class="card hidden">
522
+ <h2><i class="fas fa-lightbulb fa-fw" style="margin-right: 0.5rem; color: var(--primary);"></i>Résultat de l'analyse</h2>
523
+
524
  <div class="tab-container">
525
  <div class="tabs">
526
+ <div class="tab active" data-tab="pdf"><i class="fas fa-file-pdf fa-fw"></i> Aperçu PDF</div>
527
+ <div class="tab" data-tab="latex"><i class="fas fa-code fa-fw"></i> Code LaTeX</div>
528
+ <div class="tab" data-tab="thinking"><i class="fas fa-brain fa-fw"></i> Processus</div> <!-- NEW TAB -->
529
  </div>
530
+
531
+ <!-- PDF Tab Content -->
532
  <div id="pdf-tab" class="tab-content active">
533
+ <p class="upload-hint" style="margin-bottom: 1rem;">Aperçu de la solution générée au format PDF.</p>
534
  <iframe id="pdf-viewer" title="Aperçu du PDF de la solution"></iframe>
535
  <div class="download-container">
536
  <button id="download-button" class="download-button">
 
538
  </button>
539
  </div>
540
  </div>
541
+
542
+ <!-- LaTeX Tab Content -->
543
  <div id="latex-tab" class="tab-content">
544
+ <p class="upload-hint" style="margin-bottom: 1rem;">Code source LaTeX généré par l'IA.</p>
545
  <div class="code-area">
546
  <pre id="latex-output"></pre>
547
  </div>
548
  </div>
549
+
550
+ <!-- Thinking Tab Content -->
551
  <div id="thinking-tab" class="tab-content">
552
+ <p class="upload-hint" style="margin-bottom: 1rem;">Étapes de raisonnement suivies par l'IA pour générer la solution.</p>
553
  <div class="code-area">
554
+ <pre id="thinking-output"></pre> <!-- NEW PRE -->
555
  </div>
556
  </div>
557
  </div>
 
560
 
561
  <script>
562
  document.addEventListener('DOMContentLoaded', () => {
563
+ // --- Éléments DOM ---
564
  const latexStatusEl = document.getElementById('latex-status');
565
  const apiStatusEl = document.getElementById('api-status');
566
  const fileInputContainer = document.getElementById('file-input-container');
567
  const imageInput = document.getElementById('image-input');
568
  const imagePreview = document.getElementById('image-preview');
569
+ const fileNameDisplay = document.getElementById('file-name-display'); // Updated ID
570
  const clearFile = document.getElementById('clear-file');
571
  const processButton = document.getElementById('process-button');
572
  const loadingEl = document.getElementById('loading');
 
575
  const pdfViewer = document.getElementById('pdf-viewer');
576
  const downloadButton = document.getElementById('download-button');
577
  const latexOutputEl = document.getElementById('latex-output');
578
+ const thinkingOutputEl = document.getElementById('thinking-output'); // NEW: Thinking output element
579
  const tabs = document.querySelectorAll('.tab');
580
  const tabContents = document.querySelectorAll('.tab-content');
581
+ const defaultUploadContent = fileInputContainer.querySelector('.default-content'); // Get default content area
582
 
583
+ let currentPdfBase64 = null; // Stockage des données PDF base64
584
+ let currentFileName = null; // Store filename for download
585
 
586
  // --- Gestion des onglets ---
587
  tabs.forEach(tab => {
588
  tab.addEventListener('click', () => {
589
+ activateTab(tab.dataset.tab); // Use activateTab function
 
 
 
 
 
 
590
  });
591
  });
592
 
593
+ function activateTab(tabName) {
594
+ tabs.forEach(t => t.classList.remove('active'));
595
+ tabContents.forEach(c => c.classList.remove('active'));
596
+
597
+ const activeTab = document.querySelector(`.tab[data-tab="${tabName}"]`);
598
+ const activeContent = document.getElementById(`${tabName}-tab`);
599
+
600
+ if(activeTab) activeTab.classList.add('active');
601
+ if(activeContent) activeContent.classList.add('active');
602
+ }
603
+
604
  // --- Gestion du chargement des images ---
605
  imageInput.addEventListener('change', handleFileSelect);
606
+
607
  // Gestion du drag and drop
608
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
609
  fileInputContainer.addEventListener(eventName, preventDefaults, false);
610
  });
611
 
612
  ['dragenter', 'dragover'].forEach(eventName => {
613
+ fileInputContainer.addEventListener(eventName, () => fileInputContainer.classList.add('dragover'), false);
 
 
614
  });
615
 
616
  ['dragleave', 'drop'].forEach(eventName => {
617
+ fileInputContainer.addEventListener(eventName, () => fileInputContainer.classList.remove('dragover'), false);
 
 
618
  });
619
 
620
  fileInputContainer.addEventListener('drop', handleDrop, false);
621
+
622
  // Gestion du bouton de suppression de fichier
623
  clearFile.addEventListener('click', (e) => {
624
+ e.stopPropagation(); // Prevent triggering file input click
625
  clearFileInput();
626
  });
627
 
 
633
  function handleDrop(e) {
634
  const dt = e.dataTransfer;
635
  const files = dt.files;
636
+ handleFiles(files); // Use a common handler
 
 
 
 
637
  }
638
 
639
  function handleFileSelect() {
640
+ handleFiles(imageInput.files); // Use a common handler
641
+ }
642
+
643
+ function handleFiles(files) {
644
+ if (files && files[0]) {
645
+ const file = files[0];
646
+ // Simple size check (e.g., 10MB)
647
+ if (file.size > 10 * 1024 * 1024) {
648
+ showMessage('Le fichier est trop volumineux (max 10Mo).', true);
649
+ clearFileInput();
650
+ return;
651
+ }
652
+ // Check file type (more robustly)
653
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
654
+ if (!allowedTypes.includes(file.type)) {
655
+ showMessage('Format de fichier non supporté. Utilisez JPG, PNG, GIF ou WebP.', true);
656
+ clearFileInput();
657
+ return;
658
+ }
659
+
660
  const reader = new FileReader();
 
661
  reader.onload = function(e) {
662
  imagePreview.src = e.target.result;
663
+ fileNameDisplay.textContent = file.name;
664
+ currentFileName = file.name.split('.').slice(0, -1).join('.') || 'solution'; // Store base name
665
  fileInputContainer.classList.add('preview-active');
666
+ if(defaultUploadContent) defaultUploadContent.style.display = 'none'; // Hide default text
667
  processButton.disabled = false;
668
+ hideMessage(); // Clear previous messages
669
+ resultsEl.classList.add('hidden'); // Hide previous results
670
  };
671
+ reader.onerror = function() {
672
+ showMessage("Erreur lors de la lecture du fichier.", true);
673
+ clearFileInput();
674
+ }
675
  reader.readAsDataURL(file);
676
  } else {
677
  clearFileInput();
678
  }
679
  }
680
 
681
+
682
  function clearFileInput() {
683
+ imageInput.value = ''; // Important to clear the input
684
  imagePreview.src = '';
685
+ fileNameDisplay.textContent = '';
686
+ currentFileName = null;
687
  fileInputContainer.classList.remove('preview-active');
688
+ if(defaultUploadContent) defaultUploadContent.style.display = 'flex'; // Show default text
689
  processButton.disabled = true;
690
+ resultsEl.classList.add('hidden'); // Hide results on clear
691
+ hideMessage(); // Hide any messages
692
+ // Reset PDF viewer and outputs
693
+ pdfViewer.src = 'about:blank';
694
+ latexOutputEl.textContent = '';
695
+ thinkingOutputEl.textContent = ''; // Clear thinking output
696
+ currentPdfBase64 = null;
697
  }
698
 
699
  // --- Fonctions Utilitaires ---
 
702
  processButton.disabled = true;
703
  messagesEl.classList.add('hidden');
704
  resultsEl.classList.add('hidden');
705
+ // Clear previous results before loading
706
+ pdfViewer.src = 'about:blank';
707
  latexOutputEl.textContent = '';
708
+ thinkingOutputEl.textContent = ''; // Clear thinking output too
709
  currentPdfBase64 = null;
710
  }
711
 
712
  function hideLoading() {
713
  loadingEl.classList.add('hidden');
714
+ // Re-enable button only if a file is still selected
715
  processButton.disabled = !imageInput.files[0];
716
  }
717
 
718
  function showMessage(message, isError = false) {
719
  messagesEl.innerHTML = `
720
+ <i class="fas ${isError ? 'fa-times-circle' : 'fa-check-circle'}"></i>
721
  <div>${message}</div>
722
  `;
723
  messagesEl.className = `message ${isError ? 'message-error' : 'message-success'}`;
724
  messagesEl.classList.remove('hidden');
725
+ // Scroll to the message for visibility
726
+ messagesEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
727
  }
728
 
729
+ function hideMessage() {
730
+ messagesEl.classList.add('hidden');
731
+ messagesEl.innerHTML = ''; // Clear content
732
+ }
733
+
734
+
735
  function displayResults(data) {
736
+ resultsEl.classList.remove('hidden');
737
+ let defaultTab = 'thinking'; // Default to thinking if nothing else
738
+
739
+ // PDF Handling
740
+ if (data.pdf_base64) {
741
+ try {
742
+ // Test if base64 is valid (basic check)
743
+ atob(data.pdf_base64);
744
+ pdfViewer.src = `data:application/pdf;base64,${data.pdf_base64}`;
745
+ currentPdfBase64 = data.pdf_base64;
746
+ downloadButton.classList.remove('hidden');
747
+ defaultTab = 'pdf'; // Set PDF as default if available
748
+ } catch(e) {
749
+ console.error("Invalid base64 data for PDF:", e);
750
+ showMessage("Erreur: Les données PDF reçues sont invalides.", true);
751
+ pdfViewer.src = 'about:blank';
752
+ downloadButton.classList.add('hidden');
753
+ currentPdfBase64 = null;
754
+ }
755
+ } else {
756
+ pdfViewer.src = 'about:blank'; // Ensure it's blank if no PDF
757
+ document.getElementById('pdf-tab').innerHTML = '<p class="upload-hint">Aucun aperçu PDF n\'a pu être généré (vérifiez les onglets LaTeX ou Processus).</p>'; // Inform user
758
+ downloadButton.classList.add('hidden');
759
+ }
760
 
761
+ // LaTeX Handling
762
+ latexOutputEl.textContent = data.latex || "Aucun code LaTeX disponible.";
763
+ if (data.latex && defaultTab === 'thinking') defaultTab = 'latex'; // Prefer LaTeX over thinking if PDF failed
764
+
765
+
766
+ // Thinking Process Handling (NEW)
767
+ thinkingOutputEl.textContent = data.thinking || "Aucun processus de réflexion disponible.";
768
+ // No need to change defaultTab here, it's the fallback
769
+
770
+ // Activate the determined default tab
771
+ activateTab(defaultTab);
772
+
773
+ // Scroll to results
774
+ resultsEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
775
+ }
776
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
777
 
778
  // --- Vérifications Initiales ---
779
  async function checkStatus() {
780
+ // Check LaTeX
781
  try {
782
  const latexRes = await fetch('/check-latex');
783
+ if (!latexRes.ok) throw new Error(`HTTP error! status: ${latexRes.status}`);
784
  const latexData = await latexRes.json();
785
+ updateStatus(latexStatusEl, latexData.success, latexData.message);
 
 
 
 
 
786
  } catch (error) {
787
+ console.error("LaTeX check failed:", error);
788
+ updateStatus(latexStatusEl, false, `Erreur lors de la vérification de LaTeX: ${error.message}`);
 
 
 
789
  }
790
 
791
+ // Check API
792
  try {
793
  const apiRes = await fetch('/check-api');
794
+ if (!apiRes.ok) throw new Error(`HTTP error! status: ${apiRes.status}`);
795
  const apiData = await apiRes.json();
796
+ updateStatus(apiStatusEl, apiData.success, apiData.message);
 
 
 
 
 
797
  } catch (error) {
798
+ console.error("API check failed:", error);
799
+ updateStatus(apiStatusEl, false, `Erreur lors de la vérification de l'API: ${error.message}`);
 
 
 
800
  }
801
  }
802
 
803
+ function updateStatus(element, success, message) {
804
+ element.innerHTML = `
805
+ <span class="status-icon"><i class="fas ${success ? 'fa-check-circle' : 'fa-times-circle'}"></i></span>
806
+ <span class="status-text">${message}</span>
807
+ `;
808
+ element.className = `status-item ${success ? 'status-success' : 'status-error'}`;
809
+ }
810
+
811
  // --- Traitement de l'Image ---
812
  processButton.addEventListener('click', async () => {
813
  const file = imageInput.files[0];
 
817
  }
818
 
819
  showLoading();
820
+ hideMessage(); // Hide previous messages
821
 
822
  const formData = new FormData();
823
  formData.append('image', file);
 
828
  body: formData
829
  });
830
 
831
+ // Always hide loading indicator regardless of success/failure
832
  hideLoading();
833
 
834
+ if (!response.ok) {
835
+ // Try to get error message from JSON response, otherwise use status text
836
+ let errorMsg = `Erreur HTTP ${response.status}: ${response.statusText}`;
837
+ try {
838
+ const errorData = await response.json();
839
+ if (errorData && errorData.message) {
840
+ errorMsg = `Erreur: ${errorData.message}`;
841
+ // Display partial results even on error if available
842
+ if (errorData.latex || errorData.thinking) {
843
+ displayResults(errorData); // Pass the whole error data
844
+ }
845
+ }
846
+ } catch (e) {
847
+ // Ignore if response is not JSON
848
+ console.warn("Response was not JSON:", await response.text());
849
+ }
850
+ showMessage(errorMsg, true);
851
+ return; // Stop processing
852
+ }
853
+
854
+ // If response is OK (2xx)
855
+ const data = await response.json();
856
+
857
  if (data.success) {
858
  showMessage('Solution générée avec succès !');
859
  displayResults(data);
860
  } else {
861
+ // Handle cases where backend reports success:false but returns 200 OK
862
+ showMessage(`Erreur : ${data.message || 'Une erreur inconnue est survenue.'}`, true);
863
+ // Display partial results if available (e.g., PDF failed but LaTeX is OK)
864
+ if (data.latex || data.thinking) {
865
  displayResults(data);
866
  }
867
  }
868
 
869
  } catch (error) {
870
+ // Network errors or other unexpected issues
871
  hideLoading();
872
+ showMessage(`Erreur de communication : ${error.message || error}. Vérifiez votre connexion ou contactez l'administrateur.`, true);
873
  console.error("Fetch Error:", error);
874
  }
875
  });
876
 
877
+ // --- Téléchargement du PDF ---
878
+ downloadButton.addEventListener('click', () => { // Removed async as it's not needed here
879
+ if (!currentPdfBase64) {
880
+ showMessage('Aucune donnée PDF à télécharger.', true);
881
+ return;
882
+ }
883
+
884
+ try {
885
+ // Decode base64 string to binary data
886
+ const byteCharacters = atob(currentPdfBase64);
887
+ const byteNumbers = new Array(byteCharacters.length);
888
+ for (let i = 0; i < byteCharacters.length; i++) {
889
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
890
+ }
891
+ const byteArray = new Uint8Array(byteNumbers);
892
+ const blob = new Blob([byteArray], {type: 'application/pdf'});
893
+
894
+ // Create a temporary link to trigger download
895
+ const url = window.URL.createObjectURL(blob);
896
+ const a = document.createElement('a');
897
+ a.style.display = 'none';
898
+ a.href = url;
899
+ // Use the stored filename or a default
900
+ const downloadFilename = `${currentFileName || 'solution_mariam_ai'}.pdf`;
901
+ a.download = downloadFilename;
902
+ document.body.appendChild(a);
903
+ a.click();
904
+
905
+ // Clean up
906
+ window.URL.revokeObjectURL(url);
907
+ a.remove();
908
+ showMessage('Téléchargement démarré.');
909
+
910
+ } catch (error) {
911
+ showMessage(`Erreur lors de la préparation du téléchargement : ${error}`, true);
912
+ console.error("Download Prep Error:", error);
913
+ }
914
+ });
915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916
 
917
  // Exécuter les vérifications au chargement
918
  checkStatus();