Docfile commited on
Commit
2854e12
·
verified ·
1 Parent(s): 992f38a

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +87 -780
templates/index.html CHANGED
@@ -1,797 +1,104 @@
1
- <!DOCTYPE html>
2
- <html lang="fr">
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;
25
- }
26
-
27
- * {
28
- box-sizing: border-box;
29
- margin: 0;
30
- padding: 0;
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);
70
- }
71
-
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
-
91
- .status-item {
92
- padding: 1rem;
93
- border-radius: var(--radius);
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;
127
- flex-direction: column;
128
- gap: 1rem;
129
- }
130
-
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;
138
- flex-direction: column;
139
- justify-content: center;
140
- align-items: center;
141
- gap: 0.75rem;
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 {
154
- position: absolute;
155
- top: 0;
156
- left: 0;
157
- width: 100%;
158
- height: 100%;
159
- opacity: 0;
160
- cursor: pointer;
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 {
173
- font-size: 0.875rem;
174
- color: var(--text-light);
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;
287
- }
288
-
289
- @keyframes spin {
290
- 0% { transform: rotate(0deg); }
291
- 100% { transform: rotate(360deg); }
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;
384
- border-radius: var(--radius);
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>
418
- <body>
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>
430
- <span class="status-text">Vérification de LaTeX...</span>
431
- </div>
432
- <div id="api-status" class="status-item">
433
- <span class="status-icon"><i class="fas fa-spinner fa-spin"></i></span>
434
- <span class="status-text">Vérification de l'API Mariam AI...</span>
435
- </div>
436
- </div>
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">
484
- <i class="fas fa-download"></i> Télécharger le PDF
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>
501
- </div>
 
 
 
502
  </div>
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');
516
- const messagesEl = document.getElementById('messages');
517
- const resultsEl = document.getElementById('results');
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
-
568
- function preventDefaults(e) {
569
- e.preventDefault();
570
- e.stopPropagation();
571
  }
 
 
572
 
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 ---
611
- function showLoading() {
612
- loadingEl.classList.remove('hidden');
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];
713
- if (!file) {
714
- showMessage('Veuillez sélectionner un fichier image.', true);
715
- return;
716
- }
717
-
718
- showLoading();
719
-
720
- const formData = new FormData();
721
- formData.append('image', file);
722
-
723
- try {
724
- const response = await fetch('/process', {
725
- method: 'POST',
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();
794
- });
795
- </script>
796
- </body>
797
- </html>
 
1
+ {% extends "base.html" %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ {% block title %}Accueil - Podcasts{% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
+ {% block content %}
6
+ <h2>Podcasts Disponibles</h2>
 
 
 
7
 
8
+ <div class="search-bar">
9
+ <input type="text" id="searchInput" onkeyup="filterPodcasts()" placeholder="Rechercher par nom ou matière...">
10
+ </div>
11
 
12
+ <div id="podcastList">
13
+ {% if podcasts %}
14
+ {% for podcast in podcasts %}
15
+ <div class="podcast-item" data-name="{{ podcast.name.lower() }}" data-subject="{{ podcast.subject.lower() }}">
16
+ <h3>{{ podcast.name }}</h3>
17
+ <p><strong>Matière :</strong> {{ podcast.subject }}</p>
18
+ <button onclick="playPodcast('{{ podcast.id }}', this)">Écouter</button>
19
+ <div id="player-{{ podcast.id }}"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  </div>
21
+ {% endfor %}
22
+ {% else %}
23
+ <p>Aucun podcast disponible. Ajoutez-en depuis la page de <a href="{{ url_for('gestion') }}">gestion</a>.</p>
24
+ {% endif %}
25
  </div>
26
+ {% endblock %}
27
+
28
+ {% block scripts %}
29
+ <script>
30
+ function filterPodcasts() {
31
+ let input = document.getElementById('searchInput');
32
+ let filter = input.value.toLowerCase();
33
+ let podcastList = document.getElementById('podcastList');
34
+ let items = podcastList.getElementsByClassName('podcast-item');
35
+
36
+ for (let i = 0; i < items.length; i++) {
37
+ let name = items[i].getAttribute('data-name');
38
+ let subject = items[i].getAttribute('data-subject');
39
+ if (name.includes(filter) || subject.includes(filter)) {
40
+ items[i].style.display = "";
41
+ } else {
42
+ items[i].style.display = "none";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
+ }
45
+ }
46
 
47
+ function playPodcast(podcastId, buttonElement) {
48
+ // Désactiver tous les autres boutons "Écouter" et réinitialiser leur texte
49
+ document.querySelectorAll('.podcast-item button').forEach(btn => {
50
+ if (btn !== buttonElement) {
51
+ btn.disabled = false;
52
+ btn.textContent = 'Écouter';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
+ });
55
+ // Vider les autres lecteurs audio
56
+ document.querySelectorAll('div[id^="player-"]').forEach(playerDiv => {
57
+ if (playerDiv.id !== `player-${podcastId}`) {
58
+ playerDiv.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
+ });
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ let playerDiv = document.getElementById('player-' + podcastId);
64
+
65
+ // Si le lecteur est déjà là, on le supprime (pour "arrêter")
66
+ if (playerDiv.querySelector('audio')) {
67
+ playerDiv.innerHTML = '';
68
+ buttonElement.textContent = 'Écouter';
69
+ buttonElement.disabled = false;
70
+ return;
71
+ }
 
72
 
73
+ // Afficher "Chargement..." et désactiver le bouton
74
+ buttonElement.textContent = 'Chargement...';
75
+ buttonElement.disabled = true;
 
 
 
76
 
77
+ fetch(`/play/${podcastId}`)
78
+ .then(response => {
79
+ if (!response.ok) {
80
+ throw new Error(`Erreur HTTP: ${response.status}`);
 
81
  }
82
+ // Le serveur envoie l'URL du fichier audio mis en cache
83
+ return response.json();
84
+ })
85
+ .then(data => {
86
+ if (data.audio_url) {
87
+ playerDiv.innerHTML = `<audio controls autoplay src="${data.audio_url}">Votre navigateur ne supporte pas l'élément audio.</audio>`;
88
+ buttonElement.textContent = 'Arrêter'; // Ou "Lecture en cours"
89
+ buttonElement.disabled = false; // Réactiver pour permettre d'arrêter
90
+ } else if (data.error) {
91
+ playerDiv.innerHTML = `<p style="color: red;">Erreur: ${data.error}</p>`;
92
+ buttonElement.textContent = 'Écouter'; // Réinitialiser
93
+ buttonElement.disabled = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
+ })
96
+ .catch(error => {
97
+ console.error('Erreur lors de la lecture du podcast:', error);
98
+ playerDiv.innerHTML = `<p style="color: red;">Impossible de charger le podcast. Vérifiez la console.</p>`;
99
+ buttonElement.textContent = 'Écouter'; // Réinitialiser
100
+ buttonElement.disabled = false;
101
  });
102
+ }
103
+ </script>
104
+ {% endblock %}