Spaces:
Sleeping
Sleeping
Ajout des statistiques globales sur la page d'accueil
Browse files- backend/api/routes/questions.py +16 -0
- frontend/app.js +35 -0
- frontend/index.html +15 -0
- frontend/style.css +36 -0
backend/api/routes/questions.py
CHANGED
|
@@ -5,6 +5,7 @@ from models.question import QuestionRequest, QuestionResponse
|
|
| 5 |
from services.question_handler import QuestionHandler
|
| 6 |
|
| 7 |
from models.interaction import InteractionLog
|
|
|
|
| 8 |
|
| 9 |
router = APIRouter()
|
| 10 |
handler = QuestionHandler()
|
|
@@ -30,6 +31,21 @@ def ask_question(request: QuestionRequest, db: Session = Depends(get_db)):
|
|
| 30 |
|
| 31 |
return answer_data
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
@router.get("/stats/{session_id}")
|
| 34 |
def get_stats(session_id: str, db: Session = Depends(get_db)):
|
| 35 |
"""
|
|
|
|
| 5 |
from services.question_handler import QuestionHandler
|
| 6 |
|
| 7 |
from models.interaction import InteractionLog
|
| 8 |
+
from models.document import Document
|
| 9 |
|
| 10 |
router = APIRouter()
|
| 11 |
handler = QuestionHandler()
|
|
|
|
| 31 |
|
| 32 |
return answer_data
|
| 33 |
|
| 34 |
+
@router.get("/stats/global")
|
| 35 |
+
def get_global_stats(db: Session = Depends(get_db)):
|
| 36 |
+
"""
|
| 37 |
+
Retourne les statistiques globales de toute la plateforme pour la page d'accueil.
|
| 38 |
+
"""
|
| 39 |
+
total_sessions = db.query(Document.session_id).distinct().count()
|
| 40 |
+
total_documents = db.query(Document).count()
|
| 41 |
+
total_questions = db.query(InteractionLog).count()
|
| 42 |
+
|
| 43 |
+
return {
|
| 44 |
+
"total_sessions": total_sessions,
|
| 45 |
+
"total_documents": total_documents,
|
| 46 |
+
"total_questions": total_questions
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
@router.get("/stats/{session_id}")
|
| 50 |
def get_stats(session_id: str, db: Session = Depends(get_db)):
|
| 51 |
"""
|
frontend/app.js
CHANGED
|
@@ -338,3 +338,38 @@ function escapeHtml(str) {
|
|
| 338 |
.replace(/>/g, ">")
|
| 339 |
.replace(/"/g, """);
|
| 340 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
.replace(/>/g, ">")
|
| 339 |
.replace(/"/g, """);
|
| 340 |
}
|
| 341 |
+
|
| 342 |
+
function animateValue(id, start, end, duration) {
|
| 343 |
+
if (start === end) return;
|
| 344 |
+
const obj = document.getElementById(id);
|
| 345 |
+
let startTimestamp = null;
|
| 346 |
+
const step = (timestamp) => {
|
| 347 |
+
if (!startTimestamp) startTimestamp = timestamp;
|
| 348 |
+
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
|
| 349 |
+
obj.innerHTML = Math.floor(progress * (end - start) + start);
|
| 350 |
+
if (progress < 1) {
|
| 351 |
+
window.requestAnimationFrame(step);
|
| 352 |
+
}
|
| 353 |
+
};
|
| 354 |
+
window.requestAnimationFrame(step);
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
async function loadGlobalStats() {
|
| 358 |
+
try {
|
| 359 |
+
const resp = await fetch(`${API_BASE}/stats/global`);
|
| 360 |
+
if (resp.ok) {
|
| 361 |
+
const data = await resp.json();
|
| 362 |
+
document.getElementById("global-stats").classList.remove("hidden");
|
| 363 |
+
animateValue("stat-sessions", 0, data.total_sessions, 1500);
|
| 364 |
+
animateValue("stat-docs", 0, data.total_documents, 1500);
|
| 365 |
+
animateValue("stat-questions", 0, data.total_questions, 1500);
|
| 366 |
+
}
|
| 367 |
+
} catch (e) {
|
| 368 |
+
console.error("Erreur stats globales", e);
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
// Initialisation au démarrage
|
| 373 |
+
document.addEventListener("DOMContentLoaded", () => {
|
| 374 |
+
loadGlobalStats();
|
| 375 |
+
});
|
frontend/index.html
CHANGED
|
@@ -30,6 +30,21 @@
|
|
| 30 |
Interrogez vos cours PDF en français ou en langue locale.<br />
|
| 31 |
Réponses sourcées, fonctionne sur smartphone, sans connexion stable.
|
| 32 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
<div id="role-selection" class="role-cards">
|
| 34 |
<button class="role-card" onclick="showTeacherLogin()">
|
| 35 |
<div class="role-icon"></div>
|
|
|
|
| 30 |
Interrogez vos cours PDF en français ou en langue locale.<br />
|
| 31 |
Réponses sourcées, fonctionne sur smartphone, sans connexion stable.
|
| 32 |
</p>
|
| 33 |
+
<div id="global-stats" class="global-stats hidden">
|
| 34 |
+
<div class="stat-item">
|
| 35 |
+
<div class="stat-value" id="stat-sessions">0</div>
|
| 36 |
+
<div class="stat-label">Cours Interactifs Créés</div>
|
| 37 |
+
</div>
|
| 38 |
+
<div class="stat-item">
|
| 39 |
+
<div class="stat-value" id="stat-docs">0</div>
|
| 40 |
+
<div class="stat-label">Ressources Indexées</div>
|
| 41 |
+
</div>
|
| 42 |
+
<div class="stat-item">
|
| 43 |
+
<div class="stat-value" id="stat-questions">0</div>
|
| 44 |
+
<div class="stat-label">Questions Résolues</div>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
<div id="role-selection" class="role-cards">
|
| 49 |
<button class="role-card" onclick="showTeacherLogin()">
|
| 50 |
<div class="role-icon"></div>
|
frontend/style.css
CHANGED
|
@@ -525,3 +525,39 @@ body {
|
|
| 525 |
.message-bubble { max-width: 92%; }
|
| 526 |
.chat-header { padding: 16px; }
|
| 527 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
.message-bubble { max-width: 92%; }
|
| 526 |
.chat-header { padding: 16px; }
|
| 527 |
}
|
| 528 |
+
|
| 529 |
+
/* ========== GLOBAL STATS ========== */
|
| 530 |
+
.global-stats {
|
| 531 |
+
display: flex;
|
| 532 |
+
justify-content: center;
|
| 533 |
+
gap: 32px;
|
| 534 |
+
background: rgba(255, 255, 255, 0.03);
|
| 535 |
+
border: 1px solid var(--border);
|
| 536 |
+
backdrop-filter: blur(10px);
|
| 537 |
+
border-radius: var(--radius);
|
| 538 |
+
padding: 16px 24px;
|
| 539 |
+
margin-bottom: 32px;
|
| 540 |
+
}
|
| 541 |
+
@media(max-width:640px) {
|
| 542 |
+
.global-stats { flex-direction: column; gap: 16px; padding: 16px; }
|
| 543 |
+
}
|
| 544 |
+
.stat-item {
|
| 545 |
+
display: flex;
|
| 546 |
+
flex-direction: column;
|
| 547 |
+
align-items: center;
|
| 548 |
+
text-align: center;
|
| 549 |
+
}
|
| 550 |
+
.stat-value {
|
| 551 |
+
font-size: 1.8rem;
|
| 552 |
+
font-weight: 800;
|
| 553 |
+
color: var(--accent-3);
|
| 554 |
+
margin-bottom: 4px;
|
| 555 |
+
font-variant-numeric: tabular-nums;
|
| 556 |
+
}
|
| 557 |
+
.stat-label {
|
| 558 |
+
font-size: 0.8rem;
|
| 559 |
+
text-transform: uppercase;
|
| 560 |
+
letter-spacing: 0.05em;
|
| 561 |
+
color: var(--text-muted);
|
| 562 |
+
font-weight: 600;
|
| 563 |
+
}
|