feat: judgment-anchored grading and per-question results
- Grader: inject judgment as pass criteria anchor in LLM prompt - Grader: use followupHints for follow-up direction (not generic text) - Grader: follow-up limit from followupHints.length instead of hardcoded 2 - Session: correctAnswer/judgment stored in questions, stripped during assessment - Frontend: per-question results panel with choice ✅/❌ + judgment display
This commit is contained in:
@@ -13,7 +13,8 @@ import {
|
||||
Star,
|
||||
Award,
|
||||
Trophy,
|
||||
Trash2
|
||||
Trash2,
|
||||
XCircle
|
||||
} from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
@@ -823,6 +824,65 @@ export const AssessmentView: React.FC<AssessmentViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{state?.questions && state.questions.length > 0 && (
|
||||
<div>
|
||||
<h4 className="flex items-center gap-2.5 text-lg font-black text-slate-900 mb-4">
|
||||
<CheckCircle size={20} className="text-indigo-600" />
|
||||
每题详情
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
{state.questions.map((q: any, i: number) => {
|
||||
const score = state.scores?.[q.id || (i + 1).toString()];
|
||||
const isChoice = q.questionType === 'MULTIPLE_CHOICE';
|
||||
const isCorrect = isChoice && q.correctAnswer && score >= 10;
|
||||
return (
|
||||
<div key={q.id || i} className="bg-white border border-slate-200 rounded-2xl p-5">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={cn(
|
||||
"w-10 h-10 rounded-xl flex items-center justify-center shrink-0",
|
||||
isChoice
|
||||
? (isCorrect ? "bg-emerald-100 text-emerald-600" : "bg-red-100 text-red-600")
|
||||
: score !== undefined ? "bg-indigo-100 text-indigo-600" : "bg-slate-100 text-slate-400"
|
||||
)}>
|
||||
{isChoice
|
||||
? (isCorrect ? <CheckCircle size={20} /> : <XCircle size={20} />)
|
||||
: <span className="text-sm font-black">{score !== undefined ? score : '?'}</span>
|
||||
}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-bold text-slate-800 text-sm leading-relaxed">{q.questionText}</p>
|
||||
{isChoice && (
|
||||
<div className="mt-2 flex flex-wrap gap-2 text-xs">
|
||||
{q.options?.map((opt: string, oi: number) => {
|
||||
const letter = String.fromCharCode(65 + oi);
|
||||
const isAnswer = letter === q.correctAnswer;
|
||||
return (
|
||||
<span key={oi} className={cn(
|
||||
"px-3 py-1 rounded-lg font-medium",
|
||||
isAnswer ? "bg-emerald-100 text-emerald-700 border border-emerald-200" : "bg-slate-50 text-slate-500"
|
||||
)}>
|
||||
{letter}. {opt}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{q.judgment && (
|
||||
<div className="mt-3 bg-blue-50/50 border border-blue-100 rounded-xl p-3">
|
||||
<p className="text-xs text-slate-600 leading-relaxed">{q.judgment}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isChoice && score !== undefined && (
|
||||
<span className="inline-block mt-2 text-xs text-slate-400">得分: {score}/10</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h4 className="flex items-center gap-2.5 text-lg font-black text-slate-900 mb-4">
|
||||
<FileText size={20} className="text-indigo-600" />
|
||||
|
||||
Reference in New Issue
Block a user