From bab7d95968c0b5f737279cb49d0cb1104b806dc6 Mon Sep 17 00:00:00 2001 From: Reviflow Bot Date: Thu, 5 Feb 2026 22:26:29 +0100 Subject: [PATCH] Fix: Prevent recurring quiz title suffixes and improve dashboard widget loading --- backend/app/modules/quiz/router.py | 12 +++--- backend/app/modules/quiz/service.py | 8 +++- .../dashboard/RecentRevisionsWidget.tsx | 42 +++++++++++++++---- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/backend/app/modules/quiz/router.py b/backend/app/modules/quiz/router.py index f040f4a..e6c0aa6 100644 --- a/backend/app/modules/quiz/router.py +++ b/backend/app/modules/quiz/router.py @@ -189,9 +189,9 @@ async def save_score( # --- Remediation Logic --- # 1. If this IS a remediation quiz (detected by topic), close old pending errors - # Robust check: case insensitive, check for 'remedia' + # Robust check: case insensitive, check for 'remedia' or 'correct' topic_lower = score_data.topic.lower() - if "remedia" in topic_lower: + if "remedia" in topic_lower or "correct" in topic_lower: # Mark pending items for this revision/topic as REVIEWED # We assume that taking the quiz counts as reviewing them. # If they fail again, the code below (step 2) will add new items. @@ -531,9 +531,9 @@ async def generate_remediation_quiz( # Force topic to indicate remediation/revision for reliable detection downstream topic_lower = quiz_content.get("topic", "").lower() - if "remedia" not in topic_lower and "révision" not in topic_lower: + if "remedia" not in topic_lower and "correct" not in topic_lower: original_topic = quiz_content.get("topic", "Révision") - quiz_content["topic"] = f"{original_topic} (Révision)" + quiz_content["topic"] = f"{original_topic} (Correction)" # Inject revision_id if present so it persists through the quiz lifecycle if revision_id: @@ -572,7 +572,7 @@ async def get_mastery_stats( for s in scores: if not s.topic: continue # Normalize topic - clean_topic = s.topic.replace(" (Remediation)", "").replace(" (Remédiation)", "").replace(" (Révision)", "").strip() + clean_topic = s.topic.replace(" (Remediation)", "").replace(" (Correction)", "").replace(" (Remédiation)", "").replace(" (Révision)", "").strip() if clean_topic not in topic_map: topic_map[clean_topic] = [] @@ -595,7 +595,7 @@ async def get_mastery_stats( if not e.topic: continue # Also clean topic for errors if needed, or rely on exact match? # Ideally errors should also be grouped by clean topic. - t_key = e.topic.replace(" (Remediation)", "").replace(" (Remédiation)", "").replace(" (Révision)", "").strip() + t_key = e.topic.replace(" (Remediation)", "").replace(" (Correction)", "").replace(" (Remédiation)", "").replace(" (Révision)", "").strip() error_counts[t_key] = error_counts.get(t_key, 0) + 1 for topic, topic_scores in topic_map.items(): diff --git a/backend/app/modules/quiz/service.py b/backend/app/modules/quiz/service.py index 84ed831..4540f68 100644 --- a/backend/app/modules/quiz/service.py +++ b/backend/app/modules/quiz/service.py @@ -138,7 +138,11 @@ async def generate_remediation_quiz_service(context_items: List[Dict[str, Any]], # Extract topics from context or questions to guide the LLM # This prevents it from thinking the topic is "Remediation" - topics_context = list(set([item['context'] or "General" for item in context_items if item['context']])) + def clean_topic_name(t): + if not t: return "General" + return t.replace(" (Remediation)", "").replace(" (Correction)", "").replace(" (Remédiation)", "").replace(" (Révision)", "").strip() + + topics_context = list(set([clean_topic_name(item['context']) for item in context_items if item['context']])) joined_topics = ", ".join(topics_context) errors_desc = "\n".join([ @@ -163,7 +167,7 @@ async def generate_remediation_quiz_service(context_items: List[Dict[str, Any]], 2. Identify the underlying concept for EACH failed question. 3. Generate a NEW multiple-choice question to test that SAME concept, but phrased differently or using a different example. 4. You MUST generate exactly one new question for every failed question provided in the input list. If there are 10 errors, generate 10 new questions. - 5. The "topic" field in the JSON should be: "{joined_topics} (Remediation)". + 5. The "topic" field in the JSON should be: "{joined_topics} (Correction)". 6. **CRITICAL**: The questions must be about the SUBJECT MATTER ({joined_topics}). Do NOT generate questions about "remediation", "learning strategies", or "translation". 7. **Language**: output MUST be in French. 8. Provide a clear, helpful "explanation" for the correct answer. diff --git a/frontend/src/features/dashboard/RecentRevisionsWidget.tsx b/frontend/src/features/dashboard/RecentRevisionsWidget.tsx index a1a138c..b3ddd62 100644 --- a/frontend/src/features/dashboard/RecentRevisionsWidget.tsx +++ b/frontend/src/features/dashboard/RecentRevisionsWidget.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Play, BookOpen, ArrowRight, Book, AlertTriangle, X, Trash2, Sparkles, Lightbulb, Brain } from 'lucide-react'; import { useAuth } from '../../stores/useAuth'; import { api } from '../../lib/api'; -import { useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Skeleton } from '../../components/ui/Skeleton'; import { AnimatePresence, motion } from 'framer-motion'; @@ -51,18 +51,24 @@ export const RecentRevisionsWidget = () => { }); useEffect(() => { - if (!activeLearner) return; + if (!activeLearner?.id) { + setLoading(false); + return; + } - api.get(`/quiz/stats/activity?learner_id=${activeLearner.id}`) + setLoading(true); + api.get('/quiz/stats/activity', { params: { learner_id: activeLearner.id } }) .then(res => { const allRevisions = res.data.history .flatMap((day: any) => day.items) .filter((item: any) => item.type === 'REVISION'); setRevisions(allRevisions); - setLoading(false); }) - .catch(() => setLoading(false)); - }, [activeLearner]); + .catch((err) => { + console.error("Error fetching activity:", err); + }) + .finally(() => setLoading(false)); + }, [activeLearner?.id]); const getSubjectStyle = (subject?: string) => { if (subject === 'Mathématiques') return { color: 'text-blue-500', bg: 'bg-blue-50', icon: }; @@ -129,8 +135,30 @@ export const RecentRevisionsWidget = () => { } }; + const renderEmptyState = () => ( +
+
+ 📚 +
+

+ Aucun cours récent +

+

+ Commencez une révision pour la voir apparaître ici ! +

+ navigate('/upload')} + className="px-6 py-3 bg-indigo-600 text-white font-bold rounded-xl shadow-lg shadow-indigo-200 hover:bg-indigo-700 transition-all" + > + Ajouter un cours + +
+ ); + if (loading) return ; - if (revisions.length === 0) return null; + if (revisions.length === 0) return renderEmptyState(); return ( <>