Zeggai Abdellah commited on
Commit
778a327
·
1 Parent(s): 3552aba

fix the index.html

Browse files
Files changed (2) hide show
  1. app.py +1 -0
  2. index.html +234 -705
app.py CHANGED
@@ -536,6 +536,7 @@ async def root():
536
  """
537
  Root endpoint that serves the HTML UI from the index.html file.
538
  """
 
539
  return FileResponse("./index.html", media_type="text/html")
540
 
541
  if __name__ == "__main__":
 
536
  """
537
  Root endpoint that serves the HTML UI from the index.html file.
538
  """
539
+ print("Serving index.html") # Debug log to confirm serving
540
  return FileResponse("./index.html", media_type="text/html")
541
 
542
  if __name__ == "__main__":
index.html CHANGED
@@ -3,742 +3,271 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Générateur de Questions sur les Vaccins - Amélioré</title>
7
- <style>
8
- body {
9
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
- line-height: 1.6;
11
- color: #333;
12
- max-width: 1200px;
13
- margin: 0 auto;
14
- padding: 20px;
15
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
16
- min-height: 100vh;
17
- }
18
- h1 {
19
- color: white;
20
- text-align: center;
21
- font-size: 2.5em;
22
- margin-bottom: 30px;
23
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
24
- }
25
- .container {
26
- background: rgba(255, 255, 255, 0.95);
27
- backdrop-filter: blur(10px);
28
- border-radius: 15px;
29
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
30
- padding: 25px;
31
- margin-bottom: 20px;
32
- border: 1px solid rgba(255, 255, 255, 0.2);
33
- }
34
- button {
35
- background: linear-gradient(45deg, #3498db, #2980b9);
36
- color: white;
37
- border: none;
38
- padding: 12px 24px;
39
- border-radius: 8px;
40
- cursor: pointer;
41
- font-size: 16px;
42
- font-weight: 600;
43
- transition: all 0.3s ease;
44
- box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
45
- }
46
- button:hover {
47
- transform: translateY(-2px);
48
- box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
49
- }
50
- button:disabled {
51
- background: #95a5a6;
52
- cursor: not-allowed;
53
- transform: none;
54
- box-shadow: none;
55
- }
56
- .download-btn {
57
- background: linear-gradient(45deg, #27ae60, #219955);
58
- box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
59
- }
60
- .download-btn:hover {
61
- box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
62
- }
63
- .warning-btn {
64
- background: linear-gradient(45deg, #f39c12, #e67e22);
65
- box-shadow: 0 4px 15px rgba(243, 156, 18, 0.3);
66
- }
67
- .danger-btn {
68
- background: linear-gradient(45deg, #e74c3c, #c0392b);
69
- box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
70
- }
71
- .small-btn {
72
- padding: 8px 16px;
73
- font-size: 14px;
74
- margin: 5px;
75
- }
76
-
77
- #statusContainer {
78
- margin-top: 20px;
79
- padding: 20px;
80
- border-left: 5px solid #3498db;
81
- background: linear-gradient(135deg, #e8f4fc 0%, #f0f8ff 100%);
82
- border-radius: 10px;
83
- }
84
-
85
- .api-status {
86
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
87
- padding: 15px;
88
- border-radius: 10px;
89
- margin-bottom: 15px;
90
- border-left: 4px solid #28a745;
91
- }
92
- .api-status.error {
93
- border-left-color: #dc3545;
94
- background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
95
- }
96
-
97
- .loader {
98
- display: inline-block;
99
- width: 20px;
100
- height: 20px;
101
- border: 3px solid rgba(255, 255, 255, 0.3);
102
- border-radius: 50%;
103
- border-top-color: #3498db;
104
- animation: spin 1s ease infinite;
105
- margin-right: 10px;
106
- vertical-align: middle;
107
- }
108
- @keyframes spin {
109
- to { transform: rotate(360deg); }
110
- }
111
-
112
- .stats-container {
113
- display: grid;
114
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
115
- gap: 15px;
116
- margin-top: 20px;
117
- }
118
- .stat-card {
119
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
120
- padding: 20px;
121
- border-radius: 12px;
122
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
123
- text-align: center;
124
- transition: transform 0.3s ease;
125
- }
126
- .stat-card:hover {
127
- transform: translateY(-5px);
128
- }
129
- .stat-value {
130
- font-size: 28px;
131
- font-weight: bold;
132
- color: #2c3e50;
133
- margin: 10px 0;
134
- }
135
- .stat-label {
136
- color: #7f8c8d;
137
- font-size: 14px;
138
- font-weight: 500;
139
- }
140
-
141
- .progress-container {
142
- margin-top: 15px;
143
- background-color: #ecf0f1;
144
- border-radius: 10px;
145
- height: 25px;
146
- overflow: hidden;
147
- position: relative;
148
- }
149
- .progress-bar {
150
- height: 100%;
151
- background: linear-gradient(45deg, #3498db, #2980b9);
152
- transition: width 0.3s ease;
153
- display: flex;
154
- align-items: center;
155
- justify-content: center;
156
- color: white;
157
- font-size: 12px;
158
- font-weight: bold;
159
- position: relative;
160
- }
161
- .progress-bar.complete {
162
- background: linear-gradient(45deg, #27ae60, #219955);
163
- }
164
- .progress-bar.error {
165
- background: linear-gradient(45deg, #e74c3c, #c0392b);
166
- }
167
-
168
- .time-estimate {
169
- background: rgba(52, 152, 219, 0.1);
170
- padding: 10px;
171
- border-radius: 8px;
172
- margin-top: 10px;
173
- font-size: 14px;
174
- color: #2980b9;
175
- }
176
-
177
- .failed-chunks {
178
- background: rgba(231, 76, 60, 0.1);
179
- border: 1px solid rgba(231, 76, 60, 0.2);
180
- border-radius: 8px;
181
- padding: 15px;
182
- margin-top: 15px;
183
- }
184
- .failed-chunks h4 {
185
- color: #e74c3c;
186
- margin-top: 0;
187
- }
188
- .failed-chunk-item {
189
- background: white;
190
- padding: 10px;
191
- margin: 5px 0;
192
- border-radius: 5px;
193
- border-left: 3px solid #e74c3c;
194
- }
195
-
196
- #questionsPreview {
197
- margin-top: 20px;
198
- max-height: 500px;
199
- overflow-y: auto;
200
- background: rgba(255, 255, 255, 0.9);
201
- border-radius: 12px;
202
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
203
- }
204
- table {
205
- width: 100%;
206
- border-collapse: collapse;
207
- }
208
- th, td {
209
- padding: 15px;
210
- text-align: left;
211
- border-bottom: 1px solid #e0e6ed;
212
- }
213
- th {
214
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
215
- font-weight: 600;
216
- color: #495057;
217
- position: sticky;
218
- top: 0;
219
- }
220
- tr:hover {
221
- background-color: rgba(52, 152, 219, 0.05);
222
- }
223
-
224
- .badge {
225
- display: inline-block;
226
- padding: 6px 12px;
227
- border-radius: 20px;
228
- font-size: 11px;
229
- font-weight: 600;
230
- text-transform: uppercase;
231
- color: white;
232
- letter-spacing: 0.5px;
233
- }
234
- .badge-easy { background: linear-gradient(45deg, #27ae60, #2ecc71); }
235
- .badge-medium { background: linear-gradient(45deg, #f39c12, #e67e22); }
236
- .badge-hard { background: linear-gradient(45deg, #e74c3c, #c0392b); }
237
- .badge-factual { background: linear-gradient(45deg, #3498db, #2980b9); }
238
- .badge-conceptual { background: linear-gradient(45deg, #9b59b6, #8e44ad); }
239
- .badge-applied { background: linear-gradient(45deg, #e67e22, #d35400); }
240
-
241
- .control-panel {
242
- display: flex;
243
- flex-wrap: wrap;
244
- gap: 10px;
245
- margin-bottom: 20px;
246
- align-items: center;
247
- }
248
-
249
- .alert {
250
- padding: 15px;
251
- border-radius: 8px;
252
- margin: 10px 0;
253
- }
254
- .alert-success {
255
- background-color: #d4edda;
256
- border: 1px solid #c3e6cb;
257
- color: #155724;
258
- }
259
- .alert-warning {
260
- background-color: #fff3cd;
261
- border: 1px solid #ffeaa7;
262
- color: #856404;
263
- }
264
- .alert-error {
265
- background-color: #f8d7da;
266
- border: 1px solid #f5c6cb;
267
- color: #721c24;
268
- }
269
-
270
- @media (max-width: 768px) {
271
- body {
272
- padding: 10px;
273
- }
274
- .stats-container {
275
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
276
- gap: 10px;
277
- }
278
- .control-panel {
279
- flex-direction: column;
280
- align-items: stretch;
281
- }
282
- button {
283
- width: 100%;
284
- margin: 5px 0;
285
- }
286
- }
287
- </style>
288
  </head>
289
- <body>
290
- <h1>🧬 Générateur de Questions sur les Vaccins</h1>
291
-
292
- <!-- API Status Container -->
293
- <div class="container">
294
- <h3>🔑 État des Clés API</h3>
295
- <div id="apiStatusContainer">
296
- <div class="api-status">
297
- <span>Vérification des clés API...</span>
 
298
  </div>
299
- </div>
300
- <button id="checkApiBtn" class="small-btn">Vérifier les Clés</button>
301
- </div>
302
-
303
- <!-- Generation Control Container -->
304
- <div class="container">
305
- <h2>🚀 Génération de Questions</h2>
306
- <p>Générez des questions à partir du guide de vaccination avec gestion multi-clés et suivi en temps réel.</p>
307
-
308
- <div class="control-panel">
309
- <button id="generateBtn">Générer des Questions</button>
310
- <button id="downloadProgressBtn" class="download-btn small-btn" style="display: none;">Télécharger Progrès</button>
311
- <button id="retryFailedBtn" class="warning-btn small-btn" style="display: none;">Voir Échecs</button>
312
- </div>
313
-
314
- <div id="statusContainer" style="display: none;">
315
- <div id="statusHeader">
316
- <div class="loader"></div>
317
- <span id="statusText">Initialisation de la génération...</span>
318
  </div>
319
-
320
- <div class="progress-container">
321
- <div id="progressBar" class="progress-bar" style="width: 0%">0%</div>
 
322
  </div>
323
-
324
- <div id="statusDetails" style="margin-top: 15px;">
325
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
326
- <div><strong>Chunks traités:</strong> <span id="processedChunks">0</span>/<span id="totalChunks">0</span></div>
327
- <div><strong>Questions générées:</strong> <span id="questionsGenerated">0</span></div>
328
- <div><strong>Clé API actuelle:</strong> <span id="currentApiKey">-</span></div>
329
- <div><strong>Échecs:</strong> <span id="failedChunks">0</span></div>
330
  </div>
 
331
  </div>
332
-
333
- <div id="timeEstimate" class="time-estimate" style="display: none;">
334
- <strong>⏱️ Temps estimé restant:</strong> <span id="estimatedTime">Calcul en cours...</span>
 
 
 
 
335
  </div>
336
-
337
- <div id="failedChunksContainer" class="failed-chunks" style="display: none;">
338
- <h4>❌ Chunks Échoués</h4>
339
- <div id="failedChunksList"></div>
340
  </div>
341
- </div>
342
- </div>
343
-
344
- <!-- Results Container -->
345
- <div class="container" id="resultsContainer" style="display: none;">
346
- <h2>📊 Résultats de Génération</h2>
347
-
348
- <div class="stats-container" id="statsContainer">
349
- <!-- Stats will be populated here -->
350
- </div>
351
-
352
- <div style="margin-top: 25px; text-align: center;">
353
- <button id="downloadBtn" class="download-btn">📥 Télécharger le Dataset Complet</button>
354
- </div>
355
-
356
- <h3 style="margin-top: 30px;">👀 Aperçu des Questions</h3>
357
- <div id="questionsPreview">
358
- <table>
 
 
 
 
 
 
359
  <thead>
360
- <tr>
361
- <th>Question</th>
362
- <th>Type</th>
363
- <th>Difficulté</th>
364
- <th>But d'Entraînement</th>
365
- <th>Chunk ID</th>
366
- <th>Clé API</th>
367
  </tr>
368
  </thead>
369
- <tbody id="questionsTableBody">
370
- <!-- Questions will be populated here -->
371
- </tbody>
372
  </table>
373
- </div>
374
  </div>
375
 
376
  <script>
377
- const apiBaseUrl = window.location.origin;
378
- let generatedDataset = null;
379
- let downloadUrl = '';
380
- let statusCheckInterval = null;
381
- let currentStatus = null;
382
-
383
- // Event listeners
384
- document.getElementById('generateBtn').addEventListener('click', startGeneration);
385
- document.getElementById('downloadBtn').addEventListener('click', downloadDataset);
386
- document.getElementById('downloadProgressBtn').addEventListener('click', downloadProgress);
387
- document.getElementById('checkApiBtn').addEventListener('click', checkApiKeys);
388
- document.getElementById('retryFailedBtn').addEventListener('click', showFailedChunks);
389
-
390
- // Check for ongoing generation and API status when the page loads
391
- window.addEventListener('load', () => {
392
- checkApiKeys();
393
- checkOngoingGeneration();
394
- });
395
-
396
- async function checkApiKeys() {
397
- try {
398
- const response = await fetch(`${apiBaseUrl}/api-keys-status`);
399
- const status = await response.json();
400
-
401
- const container = document.getElementById('apiStatusContainer');
402
-
403
- if (status.status === 'success') {
404
- container.innerHTML = `
405
- <div class="api-status">
406
- <strong>✅ ${status.total_keys} clés API configurées</strong><br>
407
- <small>Clé actuelle: Index ${status.current_key_index} | Rotation automatique activée</small>
408
- </div>
409
- `;
410
- } else {
411
- container.innerHTML = `
412
- <div class="api-status error">
413
- <strong>❌ Erreur de configuration API</strong><br>
414
- <small>${status.message}</small>
415
- </div>
416
- `;
417
- }
418
- } catch (error) {
419
- console.error('Error checking API keys:', error);
420
- document.getElementById('apiStatusContainer').innerHTML = `
421
- <div class="api-status error">
422
- <strong>❌ Impossible de vérifier les clés API</strong><br>
423
- <small>${error.message}</small>
424
- </div>
425
- `;
426
- }
427
- }
428
-
429
- async function checkOngoingGeneration() {
430
- try {
431
- const response = await fetch(`${apiBaseUrl}/generation-status`);
432
- const status = await response.json();
433
- currentStatus = status;
434
-
435
- if (status.is_running || status.completed) {
436
- setupStatusMonitoring();
437
- updateStatusDisplay(status);
438
- }
439
-
440
- if (status.completed && status.result_file) {
441
- downloadUrl = `/download/${status.result_file}`;
442
- await loadResults();
443
- }
444
- } catch (error) {
445
- console.error('Error checking generation status:', error);
446
- }
447
- }
448
-
449
- async function startGeneration() {
450
- const generateBtn = document.getElementById('generateBtn');
451
- const statusContainer = document.getElementById('statusContainer');
452
- const resultsContainer = document.getElementById('resultsContainer');
453
-
454
- generateBtn.disabled = true;
455
- statusContainer.style.display = 'block';
456
- resultsContainer.style.display = 'none';
457
-
458
- try {
459
- const response = await fetch(`${apiBaseUrl}/generate-questions`);
460
-
461
- if (!response.ok) {
462
- throw new Error(`HTTP error! Status: ${response.status}`);
463
- }
464
-
465
- const data = await response.json();
466
- console.log('Generation started:', data);
467
-
468
- // Show success alert
469
- showAlert('Génération démarrée avec succès!', 'success');
470
-
471
- setupStatusMonitoring();
472
- } catch (error) {
473
- console.error('Error starting generation:', error);
474
- showAlert(`Erreur lors du démarrage: ${error.message}`, 'error');
475
- document.getElementById('statusText').textContent = `Erreur: ${error.message}`;
476
- generateBtn.disabled = false;
477
- }
478
- }
479
-
480
- function setupStatusMonitoring() {
481
- if (statusCheckInterval) {
482
- clearInterval(statusCheckInterval);
483
- }
484
-
485
- document.getElementById('statusContainer').style.display = 'block';
486
- document.getElementById('downloadProgressBtn').style.display = 'inline-block';
487
-
488
- statusCheckInterval = setInterval(checkGenerationStatus, 3000);
489
- checkGenerationStatus();
490
- }
491
-
492
- async function checkGenerationStatus() {
493
  try {
494
- const response = await fetch(`${apiBaseUrl}/generation-status`);
495
- const status = await response.json();
496
- currentStatus = status;
497
-
498
- updateStatusDisplay(status);
499
-
500
- if (status.completed) {
501
- clearInterval(statusCheckInterval);
502
- downloadUrl = `/download/${status.result_file}`;
503
- await loadResults();
504
- showAlert('Génération terminée avec succès!', 'success');
505
- }
506
-
507
- if (status.error) {
508
- clearInterval(statusCheckInterval);
509
- document.getElementById('generateBtn').disabled = false;
510
- showAlert(`Erreur durant la génération: ${status.error}`, 'error');
511
- }
512
  } catch (error) {
513
- console.error('Error checking status:', error);
514
- }
515
- }
516
-
517
- function updateStatusDisplay(status) {
518
- const statusText = document.getElementById('statusText');
519
- const progressBar = document.getElementById('progressBar');
520
- const processedChunks = document.getElementById('processedChunks');
521
- const totalChunks = document.getElementById('totalChunks');
522
- const questionsGenerated = document.getElementById('questionsGenerated');
523
- const currentApiKey = document.getElementById('currentApiKey');
524
- const failedChunks = document.getElementById('failedChunks');
525
- const timeEstimate = document.getElementById('timeEstimate');
526
- const estimatedTime = document.getElementById('estimatedTime');
527
-
528
- // Update status text
529
- if (status.error) {
530
- statusText.textContent = `❌ Erreur: ${status.error}`;
531
- progressBar.classList.add('error');
532
- } else if (status.completed) {
533
- statusText.textContent = '✅ Génération terminée avec succès !';
534
- progressBar.classList.add('complete');
535
- } else if (status.is_running) {
536
- statusText.textContent = '⚡ Génération des questions en cours...';
537
- }
538
-
539
- // Update counts and progress
540
- processedChunks.textContent = status.processed_chunks || 0;
541
- totalChunks.textContent = status.total_chunks || 0;
542
- questionsGenerated.textContent = status.questions_generated || 0;
543
- currentApiKey.textContent = status.current_api_key_index || 0;
544
- failedChunks.textContent = (status.failed_chunks || []).length;
545
-
546
- // Update progress bar
547
- if (status.total_chunks > 0) {
548
- const percentage = Math.round((status.processed_chunks / status.total_chunks) * 100);
549
- progressBar.style.width = `${percentage}%`;
550
- progressBar.textContent = `${percentage}%`;
551
- }
552
-
553
- // Show time estimate
554
- if (status.estimated_remaining_minutes) {
555
- timeEstimate.style.display = 'block';
556
- const minutes = Math.round(status.estimated_remaining_minutes);
557
- estimatedTime.textContent = `${minutes} minutes`;
558
- }
559
-
560
- // Show failed chunks
561
- if (status.failed_chunks && status.failed_chunks.length > 0) {
562
- document.getElementById('retryFailedBtn').style.display = 'inline-block';
563
- updateFailedChunksDisplay(status.failed_chunks);
564
- }
565
- }
566
-
567
- function updateFailedChunksDisplay(failedChunks) {
568
- const container = document.getElementById('failedChunksContainer');
569
- const list = document.getElementById('failedChunksList');
570
-
571
- if (failedChunks.length > 0) {
572
- container.style.display = 'block';
573
- list.innerHTML = failedChunks.map(chunk => `
574
- <div class="failed-chunk-item">
575
- <strong>Chunk ${chunk.chunk_id}:</strong> ${chunk.error}<br>
576
- <small>Tentatives: ${chunk.attempts}</small>
577
- </div>
578
- `).join('');
579
  }
580
  }
581
-
582
- async function loadResults() {
583
- if (!downloadUrl) return;
584
-
585
- try {
586
- const response = await fetch(`${apiBaseUrl}${downloadUrl}`);
587
- if (!response.ok) {
588
- throw new Error(`HTTP error! Status: ${response.status}`);
589
- }
590
-
591
- const dataset = await response.json();
592
- generatedDataset = dataset;
593
- displayResults(dataset);
594
-
595
- document.getElementById('generateBtn').disabled = false;
596
- } catch (error) {
597
- console.error('Error loading results:', error);
598
- showAlert(`Erreur lors du chargement des résultats: ${error.message}`, 'error');
599
  }
600
  }
601
-
602
- function displayResults(dataset) {
603
- const resultsContainer = document.getElementById('resultsContainer');
604
- const statsContainer = document.getElementById('statsContainer');
605
- const questionsTableBody = document.getElementById('questionsTableBody');
606
-
607
- // Clear and populate stats
608
- statsContainer.innerHTML = '';
609
-
610
- // Main stats
611
- addStatCard(statsContainer, dataset.dataset_info.total_questions, 'Questions Totales');
612
- addStatCard(statsContainer, dataset.dataset_info.successful_chunks || 0, 'Chunks Réussis');
613
- addStatCard(statsContainer, dataset.dataset_info.failed_chunks || 0, 'Chunks Échoués');
614
-
615
- // Type distribution
616
- const typeCount = countByProperty(dataset.questions, 'type');
617
- for (const [type, count] of Object.entries(typeCount)) {
618
- addStatCard(statsContainer, count, `${capitalizeFirstLetter(type)}`);
619
- }
620
-
621
- // Difficulty distribution
622
- const difficultyCount = countByProperty(dataset.questions, 'difficulty');
623
- for (const [difficulty, count] of Object.entries(difficultyCount)) {
624
- addStatCard(statsContainer, count, `${capitalizeFirstLetter(difficulty)}`);
 
 
 
625
  }
626
-
627
- // Success rate
628
- if (dataset.dataset_info.total_chunks_processed) {
629
- const successRate = Math.round(((dataset.dataset_info.successful_chunks || 0) / dataset.dataset_info.total_chunks_processed) * 100);
630
- addStatCard(statsContainer, `${successRate}%`, 'Taux de Réussite');
 
 
 
 
 
 
 
 
631
  }
632
-
633
- // Populate questions table
634
- questionsTableBody.innerHTML = '';
635
- dataset.questions.forEach((question, index) => {
636
- if (index < 50) { // Limit display for performance
637
- const row = document.createElement('tr');
638
-
639
- row.innerHTML = `
640
- <td>${question.question}</td>
641
- <td><span class="badge badge-${question.type}">${question.type}</span></td>
642
- <td><span class="badge badge-${question.difficulty}">${question.difficulty}</span></td>
643
- <td>${question.training_purpose}</td>
644
- <td>${question.chunk_id}</td>
645
- <td>${question.api_key_used || 'N/A'}</td>
646
- `;
647
-
648
- questionsTableBody.appendChild(row);
649
- }
650
- });
651
-
652
- if (dataset.questions.length > 50) {
653
- const row = document.createElement('tr');
654
- row.innerHTML = `<td colspan="6" style="text-align: center; font-style: italic; color: #666;">... et ${dataset.questions.length - 50} autres questions</td>`;
655
- questionsTableBody.appendChild(row);
656
  }
657
-
658
- resultsContainer.style.display = 'block';
659
- }
660
-
661
- function addStatCard(container, value, label) {
662
- const card = document.createElement('div');
663
- card.className = 'stat-card';
664
-
665
- card.innerHTML = `
666
- <div class="stat-value">${value}</div>
667
- <div class="stat-label">${label}</div>
668
- `;
669
-
670
- container.appendChild(card);
671
- }
672
-
673
- function countByProperty(array, property) {
674
- const counts = {};
675
- array.forEach(item => {
676
- const value = item[property];
677
- counts[value] = (counts[value] || 0) + 1;
678
- });
679
- return counts;
680
- }
681
-
682
- function capitalizeFirstLetter(string) {
683
- return string.charAt(0).toUpperCase() + string.slice(1);
684
- }
685
-
686
- function downloadDataset() {
687
- if (downloadUrl) {
688
- window.open(`${apiBaseUrl}${downloadUrl}`, '_blank');
689
  }
 
 
 
690
  }
691
-
692
- async function downloadProgress() {
693
- try {
694
- const response = await fetch(`${apiBaseUrl}/download-progress`);
695
- if (response.ok) {
696
- const blob = await response.blob();
697
- const url = window.URL.createObjectURL(blob);
698
- const a = document.createElement('a');
699
- a.href = url;
700
- a.download = `progress_${new Date().getTime()}.json`;
701
- document.body.appendChild(a);
702
- a.click();
703
- window.URL.revokeObjectURL(url);
704
- document.body.removeChild(a);
705
- showAlert('Fichier de progrès téléchargé!', 'success');
706
- } else {
707
- throw new Error('Pas de fichier de progrès disponible');
708
- }
709
- } catch (error) {
710
- showAlert(`Erreur de téléchargement: ${error.message}`, 'error');
711
  }
 
 
 
 
 
 
 
 
 
 
 
 
712
  }
713
-
714
- async function showFailedChunks() {
715
- try {
716
- const response = await fetch(`${apiBaseUrl}/retry-failed`);
717
- const data = await response.json();
718
-
719
- if (data.failed_chunks && data.failed_chunks.length > 0) {
720
- updateFailedChunksDisplay(data.failed_chunks);
721
- document.getElementById('failedChunksContainer').style.display = 'block';
722
- showAlert(`${data.failed_chunks.length} chunks ont échoué. Voir les détails ci-dessous.`, 'warning');
723
- } else {
724
- showAlert('Aucun chunk échoué trouvé!', 'success');
725
- }
726
- } catch (error) {
727
- showAlert(`Erreur: ${error.message}`, 'error');
 
 
 
 
 
 
 
 
 
728
  }
729
  }
730
-
731
- function showAlert(message, type) {
732
- const alertDiv = document.createElement('div');
733
- alertDiv.className = `alert alert-${type}`;
734
- alertDiv.textContent = message;
735
-
736
- document.body.insertBefore(alertDiv, document.body.firstChild);
737
-
738
- setTimeout(() => {
739
- alertDiv.remove();
740
- }, 5000);
741
  }
 
 
 
 
 
 
 
 
 
 
 
 
742
  </script>
743
  </body>
744
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Générateur de Questions sur les Vaccins</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  </head>
9
+ <body class="bg-gray-100 font-sans">
10
+ <div class="container mx-auto p-6">
11
+ <!-- Header -->
12
+ <h1 class="text-3xl font-bold text-center mb-8">🧬 Générateur de Questions sur les Vaccins</h1>
13
+
14
+ <!-- API Keys Status Section -->
15
+ <section class="bg-white p-6 rounded-lg shadow-md mb-8">
16
+ <h2 class="text-2xl font-semibold mb-4">🔑 État des Clés API</h2>
17
+ <div id="api-keys-status" class="text-gray-700">
18
+ Vérification des clés API...
19
  </div>
20
+ <button id="check-api-keys" class="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
21
+ Vérifier les Clés
22
+ </button>
23
+ </section>
24
+
25
+ <!-- Question Generation Section -->
26
+ <section class="bg-white p-6 rounded-lg shadow-md mb-8">
27
+ <h2 class="text-2xl font-semibold mb-4">🚀 Génération de Questions</h2>
28
+ <p class="mb-4">Générez des questions à partir du guide de vaccination avec gestion multi-clés et suivi en temps réel.</p>
29
+ <div class="flex space-x-4">
30
+ <button id="generate-questions" class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
31
+ Générer des Questions
32
+ </button>
33
+ <a id="download-progress" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600" href="#" style="display: none;">
34
+ Télécharger Progrès
35
+ </a>
36
+ <button id="view-failed-chunks" class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600">
37
+ Voir Échecs
38
+ </button>
39
  </div>
40
+
41
+ <!-- Generation Status -->
42
+ <div id="generation-status" class="mt-4 text-gray-700">
43
+ Initialisation de la génération...
44
  </div>
45
+
46
+ <!-- Progress Bar -->
47
+ <div class="mt-4">
48
+ <div class="w-full bg-gray-200 rounded-full h-4">
49
+ <div id="progress-bar" class="bg-blue-500 h-4 rounded-full" style="width: 0%;"></div>
 
 
50
  </div>
51
+ <p id="progress-text" class="text-center mt-2">0%</p>
52
  </div>
53
+
54
+ <!-- Generation Details -->
55
+ <div id="generation-details" class="mt-4 text-gray-700">
56
+ <p>Chunks traités: <span id="processed-chunks">0</span>/<span id="total-chunks">0</span></p>
57
+ <p>Questions générées: <span id="questions-generated">0</span></p>
58
+ <p>Clé API actuelle: <span id="current-api-key">-</span></p>
59
+ <p>Échecs: <span id="failed-chunks-count">0</span></p>
60
  </div>
61
+
62
+ <!-- Estimated Time -->
63
+ <div id="estimated-time" class="mt-4 text-gray-700">
64
+ ⏱️ Temps estimé restant: Calcul en cours...
65
  </div>
66
+
67
+ <!-- Failed Chunks -->
68
+ <div id="failed-chunks" class="mt-4 hidden">
69
+ <h3 class="text-lg font-semibold">❌ Chunks Échoués</h3>
70
+ <ul id="failed-chunks-list" class="list-disc pl-5"></ul>
71
+ <button id="retry-failed" class="mt-4 bg-yellow-500 text-white px-4 py-2 rounded hover:bg-yellow-600">
72
+ Réessayer les Chunks Échoués
73
+ </button>
74
+ </div>
75
+ </section>
76
+
77
+ <!-- Results Section -->
78
+ <section class="bg-white p-6 rounded-lg shadow-md">
79
+ <h2 class="text-2xl font-semibold mb-4">📊 Résultats de Génération</h2>
80
+ <div id="results-status" class="text-gray-700 mb-4">
81
+ Aucun résultat disponible pour le moment.
82
+ </div>
83
+ <a id="download-results" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" href="#" style="display: none;">
84
+ 📥 Télécharger le Dataset Complet
85
+ </a>
86
+
87
+ <!-- Questions Preview -->
88
+ <h3 class="text-lg font-semibold mt-6">👀 Aperçu des Questions</h3>
89
+ <table class="w-full mt-4 border-collapse">
90
  <thead>
91
+ <tr class="bg-gray-200">
92
+ <th class="border p-2">Question</th>
93
+ <th class="border p-2">Type</th>
94
+ <th class="border p-2">Difficulté</th>
95
+ <th class="border p-2">But d'Entraînement</th>
96
+ <th class="border p-2">Chunk ID</th>
97
+ <th class="border p-2">Clé API</th>
98
  </tr>
99
  </thead>
100
+ <tbody id="questions-table"></tbody>
 
 
101
  </table>
102
+ </section>
103
  </div>
104
 
105
  <script>
106
+ // Utility function to fetch data from API
107
+ async function fetchData(url) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  try {
109
+ const response = await fetch(url);
110
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
111
+ return await response.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  } catch (error) {
113
+ console.error('Error fetching data:', error);
114
+ return null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
116
  }
117
+
118
+ // Update API Keys Status
119
+ async function updateApiKeysStatus() {
120
+ const statusDiv = document.getElementById('api-keys-status');
121
+ const data = await fetchData('/api-keys-status');
122
+ if (data && data.status === 'success') {
123
+ statusDiv.innerHTML = `${data.total_keys} clés API configurées pour rotation. Clé actuelle: ${data.current_key_index}`;
124
+ } else {
125
+ statusDiv.innerHTML = `Erreur: ${data?.message || 'Impossible de vérifier les clés API'}`;
 
 
 
 
 
 
 
 
 
126
  }
127
  }
128
+
129
+ // Update Generation Status
130
+ async function updateGenerationStatus() {
131
+ const data = await fetchData('/generation-status');
132
+ if (!data) return;
133
+
134
+ const statusDiv = document.getElementById('generation-status');
135
+ const progressBar = document.getElementById('progress-bar');
136
+ const progressText = document.getElementById('progress-text');
137
+ const processedChunks = document.getElementById('processed-chunks');
138
+ const totalChunks = document.getElementById('total-chunks');
139
+ const questionsGenerated = document.getElementById('questions-generated');
140
+ const currentApiKey = document.getElementById('current-api-key');
141
+ const failedChunksCount = document.getElementById('failed-chunks-count');
142
+ const estimatedTime = document.getElementById('estimated-time');
143
+ const downloadProgress = document.getElementById('download-progress');
144
+ const failedChunksDiv = document.getElementById('failed-chunks');
145
+ const failedChunksList = document.getElementById('failed-chunks-list');
146
+
147
+ if (data.is_running) {
148
+ statusDiv.innerHTML = 'Génération en cours...';
149
+ } else if (data.completed) {
150
+ statusDiv.innerHTML = 'Génération terminée!';
151
+ } else if (data.error) {
152
+ statusDiv.innerHTML = `Erreur: ${data.error}`;
153
+ } else {
154
+ statusDiv.innerHTML = 'Prêt à générer des questions.';
155
  }
156
+
157
+ progressBar.style.width = `${data.progress_percentage || 0}%`;
158
+ progressText.innerText = `${data.progress_percentage || 0}%`;
159
+ processedChunks.innerText = data.processed_chunks || 0;
160
+ totalChunks.innerText = data.total_chunks || 0;
161
+ questionsGenerated.innerText = data.questions_generated || 0;
162
+ currentApiKey.innerText = data.current_api_key_index || '-';
163
+ failedChunksCount.innerText = data.failed_chunks?.length || 0;
164
+
165
+ if (data.estimated_remaining_minutes) {
166
+ estimatedTime.innerHTML = `⏱️ Temps estimé restant: ${data.estimated_remaining_minutes} minutes`;
167
+ } else {
168
+ estimatedTime.innerHTML = '⏱️ Temps estimé restant: Calcul en cours...';
169
  }
170
+
171
+ // Update download progress link
172
+ if (data.progress_file) {
173
+ downloadProgress.href = `/download/${data.progress_file}`;
174
+ downloadProgress.style.display = 'inline-block';
175
+ } else {
176
+ downloadProgress.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }
178
+
179
+ // Update failed chunks
180
+ if (data.failed_chunks?.length > 0) {
181
+ failedChunksDiv.classList.remove('hidden');
182
+ failedChunksList.innerHTML = data.failed_chunks.map(chunk =>
183
+ `<li>Chunk ${chunk.chunk_id}: ${chunk.error} (Tentatives: ${chunk.attempts})</li>`
184
+ ).join('');
185
+ } else {
186
+ failedChunksDiv.classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
+
189
+ // Update results
190
+ updateResults(data);
191
  }
192
+
193
+ // Update Results and Questions Preview
194
+ async function updateResults(statusData) {
195
+ const resultsStatus = document.getElementById('results-status');
196
+ const downloadResults = document.getElementById('download-results');
197
+ const questionsTable = document.getElementById('questions-table');
198
+
199
+ if (statusData.completed && statusData.result_file) {
200
+ resultsStatus.innerHTML = `Dataset généré avec ${statusData.questions_generated} questions.`;
201
+ downloadResults.href = `/download/${statusData.result_file}`;
202
+ downloadResults.style.display = 'inline-block';
203
+ } else if (statusData.partial_results?.length > 0) {
204
+ resultsStatus.innerHTML = `Résultats partiels disponibles (${statusData.partial_results.length} questions).`;
205
+ downloadResults.style.display = 'none';
206
+ } else {
207
+ resultsStatus.innerHTML = 'Aucun résultat disponible pour le moment.';
208
+ downloadResults.style.display = 'none';
 
 
 
209
  }
210
+
211
+ // Update questions preview
212
+ questionsTable.innerHTML = statusData.partial_results?.slice(0, 10).map(q => `
213
+ <tr>
214
+ <td class="border p-2">${q.question}</td>
215
+ <td class="border p-2">${q.type}</td>
216
+ <td class="border p-2">${q.difficulty}</td>
217
+ <td class="border p-2">${q.training_purpose}</td>
218
+ <td class="border p-2">${q.chunk_id}</td>
219
+ <td class="border p-2">${q.api_key_used}</td>
220
+ </tr>
221
+ `).join('') || '';
222
  }
223
+
224
+ // Start Generation
225
+ async function startGeneration() {
226
+ const generateButton = document.getElementById('generate-questions');
227
+ generateButton.disabled = true;
228
+ generateButton.innerText = 'Génération en cours...';
229
+
230
+ const response = await fetchData('/generate-questions');
231
+ if (response && response.status === 'started') {
232
+ alert(response.message);
233
+ // Start polling for status
234
+ const interval = setInterval(() => {
235
+ updateGenerationStatus().then(() => {
236
+ if (!response.is_running && response.completed) {
237
+ clearInterval(interval);
238
+ generateButton.disabled = false;
239
+ generateButton.innerText = 'Générer des Questions';
240
+ }
241
+ });
242
+ }, 5000);
243
+ } else {
244
+ alert(response?.message || 'Erreur lors du démarrage de la génération.');
245
+ generateButton.disabled = false;
246
+ generateButton.innerText = 'Générer des Questions';
247
  }
248
  }
249
+
250
+ // Retry Failed Chunks
251
+ async function retryFailedChunks() {
252
+ const retryButton = document.getElementById('retry-failed');
253
+ retryButton.disabled = true;
254
+ const response = await fetchData('/retry-failed');
255
+ alert(response.message);
256
+ retryButton.disabled = false;
257
+ updateGenerationStatus();
 
 
258
  }
259
+
260
+ // Event Listeners
261
+ document.getElementById('check-api-keys').addEventListener('click', updateApiKeysStatus);
262
+ document.getElementById('generate-questions').addEventListener('click', startGeneration);
263
+ document.getElementById('view-failed-chunks').addEventListener('click', () => {
264
+ document.getElementById('failed-chunks').classList.toggle('hidden');
265
+ });
266
+ document.getElementById('retry-failed').addEventListener('click', retryFailedChunks);
267
+
268
+ // Initial Load
269
+ updateApiKeysStatus();
270
+ updateGenerationStatus();
271
  </script>
272
  </body>
273
  </html>