mirror of
https://github.com/R0m1k3/Reviflow.git
synced 2026-10-24 07:28:08 +02:00
Fix: Prevent recurring quiz title suffixes and improve dashboard widget loading
This commit is contained in:
1 parent
6531e3318d
commit
bab7d95968
3 files changed
+47
-15
No files matched your search
@@ -189,9 +189,9 @@ async def save_score(
|
|||||||
|
|
||||||
# --- Remediation Logic ---
|
# --- Remediation Logic ---
|
||||||
# 1. If this IS a remediation quiz (detected by topic), close old pending errors
|
# 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()
|
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
|
# Mark pending items for this revision/topic as REVIEWED
|
||||||
# We assume that taking the quiz counts as reviewing them.
|
# We assume that taking the quiz counts as reviewing them.
|
||||||
# If they fail again, the code below (step 2) will add new items.
|
# 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
|
# Force topic to indicate remediation/revision for reliable detection downstream
|
||||||
topic_lower = quiz_content.get("topic", "").lower()
|
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")
|
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
|
# Inject revision_id if present so it persists through the quiz lifecycle
|
||||||
if revision_id:
|
if revision_id:
|
||||||
@@ -572,7 +572,7 @@ async def get_mastery_stats(
|
|||||||
for s in scores:
|
for s in scores:
|
||||||
if not s.topic: continue
|
if not s.topic: continue
|
||||||
# Normalize topic
|
# 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:
|
if clean_topic not in topic_map:
|
||||||
topic_map[clean_topic] = []
|
topic_map[clean_topic] = []
|
||||||
@@ -595,7 +595,7 @@ async def get_mastery_stats(
|
|||||||
if not e.topic: continue
|
if not e.topic: continue
|
||||||
# Also clean topic for errors if needed, or rely on exact match?
|
# Also clean topic for errors if needed, or rely on exact match?
|
||||||
# Ideally errors should also be grouped by clean topic.
|
# 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
|
error_counts[t_key] = error_counts.get(t_key, 0) + 1
|
||||||
|
|
||||||
for topic, topic_scores in topic_map.items():
|
for topic, topic_scores in topic_map.items():
|
||||||
|
|||||||
@@ -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
|
# Extract topics from context or questions to guide the LLM
|
||||||
# This prevents it from thinking the topic is "Remediation"
|
# 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)
|
joined_topics = ", ".join(topics_context)
|
||||||
|
|
||||||
errors_desc = "\n".join([
|
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.
|
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.
|
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.
|
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".
|
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.
|
7. **Language**: output MUST be in French.
|
||||||
8. Provide a clear, helpful "explanation" for the correct answer.
|
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 { Play, BookOpen, ArrowRight, Book, AlertTriangle, X, Trash2, Sparkles, Lightbulb, Brain } from 'lucide-react';
|
||||||
import { useAuth } from '../../stores/useAuth';
|
import { useAuth } from '../../stores/useAuth';
|
||||||
import { api } from '../../lib/api';
|
import { api } from '../../lib/api';
|
||||||
import { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Skeleton } from '../../components/ui/Skeleton';
|
import { Skeleton } from '../../components/ui/Skeleton';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
|
||||||
@@ -51,18 +51,24 @@ export const RecentRevisionsWidget = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
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 => {
|
.then(res => {
|
||||||
const allRevisions = res.data.history
|
const allRevisions = res.data.history
|
||||||
.flatMap((day: any) => day.items)
|
.flatMap((day: any) => day.items)
|
||||||
.filter((item: any) => item.type === 'REVISION');
|
.filter((item: any) => item.type === 'REVISION');
|
||||||
setRevisions(allRevisions);
|
setRevisions(allRevisions);
|
||||||
setLoading(false);
|
|
||||||
})
|
})
|
||||||
.catch(() => setLoading(false));
|
.catch((err) => {
|
||||||
}, [activeLearner]);
|
console.error("Error fetching activity:", err);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [activeLearner?.id]);
|
||||||
|
|
||||||
const getSubjectStyle = (subject?: string) => {
|
const getSubjectStyle = (subject?: string) => {
|
||||||
if (subject === 'Mathématiques') return { color: 'text-blue-500', bg: 'bg-blue-50', icon: <BookOpen className="w-5 h-5" /> };
|
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 (loading) return <Skeleton className="h-48 w-full rounded-[2rem]" />;
|
||||||
if (revisions.length === 0) return null;
|
if (revisions.length === 0) return renderEmptyState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in new issue
Block a user