Fix: Prevent recurring quiz title suffixes and improve dashboard widget loading

This commit is contained in:
Reviflow Bot committed 2026-02-05 22:26:29 +01:00
1 parent 6531e3318d
commit bab7d95968
3 files changed
+47 -15

No files matched your search

+6 -6
View File
@@ -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():
+6 -2
View File
@@ -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.
@@ -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<any>(`/quiz/stats/activity?learner_id=${activeLearner.id}`)
setLoading(true);
api.get<any>('/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: <BookOpen className="w-5 h-5" /> };
@@ -129,8 +135,30 @@ export const RecentRevisionsWidget = () => {
}
};
const renderEmptyState = () => (
<div className="bg-white dark:bg-slate-800 rounded-[2rem] p-8 shadow-xl shadow-indigo-100/20 dark:shadow-none border border-slate-100 dark:border-slate-700 text-center">
<div className="w-16 h-16 bg-slate-50 dark:bg-slate-700/50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-3xl">
📚
</div>
<h3 className="font-black text-lg text-slate-800 dark:text-white mb-2">
Aucun cours récent
</h3>
<p className="text-slate-400 text-sm font-medium mb-6">
Commencez une révision pour la voir apparaître ici !
</p>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => 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
</motion.button>
</div>
);
if (loading) return <Skeleton className="h-48 w-full rounded-[2rem]" />;
if (revisions.length === 0) return null;
if (revisions.length === 0) return renderEmptyState();
return (
<>