'use client'; import { useState, useRef, useEffect } from 'react'; import { Box, Typography, Chip, Paper, Button } from '@mui/material'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import ReactMarkdown from 'react-markdown'; import { detailStyles } from '../detailStyles'; import { useTranslation } from 'react-i18next'; import 'github-markdown-css/github-markdown-light.css'; // 答题状态常量 const EVAL_STATUS = { SUCCESS: 0, FORMAT_ERROR: 1, API_ERROR: 2 }; // 状态标签配置 const STATUS_CONFIG = { [EVAL_STATUS.SUCCESS]: { label: 'evalTasks.statusSuccess', color: 'success' }, [EVAL_STATUS.FORMAT_ERROR]: { label: 'evalTasks.statusFormatError', color: 'warning' }, [EVAL_STATUS.API_ERROR]: { label: 'evalTasks.statusApiError', color: 'error' } }; export default function QuestionCard({ result, index, task }) { const { t } = useTranslation(); const { evalDataset, modelAnswer, isCorrect, score, judgeResponse, duration = 0, status = 0, errorMessage = '' } = result; const { question, questionType, options, correctAnswer } = evalDataset; const [isExpanded, setIsExpanded] = useState(false); const [shouldShowExpand, setShouldShowExpand] = useState(false); const contentRef = useRef(null); const [isCorrectExpanded, setIsCorrectExpanded] = useState(false); const [shouldShowCorrectExpand, setShouldShowCorrectExpand] = useState(false); const correctContentRef = useRef(null); // 检查内容是否超过高度限制 useEffect(() => { if (contentRef.current) { const hasOverflow = contentRef.current.scrollHeight > 200; setShouldShowExpand(hasOverflow); } }, [modelAnswer]); useEffect(() => { if (correctContentRef.current) { const hasOverflow = correctContentRef.current.scrollHeight > 200; setShouldShowCorrectExpand(hasOverflow); } }, [correctAnswer]); // 解析选项 let parsedOptions = []; if (questionType === 'single_choice' || questionType === 'multiple_choice') { try { parsedOptions = JSON.parse(options); } catch (e) { parsedOptions = options ? [options] : []; } } else if (questionType === 'true_false') { parsedOptions = ['True', 'False']; } // 格式化答案显示 const formatAnswer = ans => { if (!ans) return '-'; return String(ans); }; // 判断选项状态 const getOptionStatus = (optionText, idx) => { const letter = String.fromCharCode(65 + idx); const normModelAns = String(modelAnswer).trim(); const normCorrectAns = String(correctAnswer).trim(); let isSelected = false; let isCorrectOption = false; if (questionType === 'true_false') { // 判断题:A 对应 ✅/True,B 对应 ❌/False const isTrueOption = idx === 0; const isFalseOption = idx === 1; isSelected = (isTrueOption && (normModelAns === '✅' || normModelAns.toUpperCase() === 'TRUE')) || (isFalseOption && (normModelAns === '❌' || normModelAns.toUpperCase() === 'FALSE')); isCorrectOption = (isTrueOption && (normCorrectAns === '✅' || normCorrectAns.toUpperCase() === 'TRUE')) || (isFalseOption && (normCorrectAns === '❌' || normCorrectAns.toUpperCase() === 'FALSE')); } else { // 选择题逻辑 const normModelAnsUpper = normModelAns.toUpperCase(); const normCorrectAnsUpper = normCorrectAns.toUpperCase(); const normOptionText = String(optionText).toUpperCase(); isSelected = normModelAnsUpper.includes(letter) || normModelAnsUpper.includes(normOptionText); isCorrectOption = normCorrectAnsUpper.includes(letter) || normCorrectAnsUpper.includes(normOptionText); } return { isSelected, isCorrectOption }; }; // 解析 AI 点评内容 const getJudgeDisplayContent = content => { if (!content) return ''; try { // 尝试从 markdown 代码块中提取 JSON const jsonMatch = content.match(/\{[\s\S]*?\}/); if (jsonMatch) { const parsed = JSON.parse(jsonMatch[0]); if (parsed.reason) return parsed.reason; } // 尝试直接解析 const parsed = JSON.parse(content); if (parsed.reason) return parsed.reason; } catch (e) { // 解析失败,返回原内容 } return content; }; return ( {/* 判卷标记 (红勾/红叉) - 绝对定位 */} {isCorrect ? : } {/* 题号与类型标签 */} {index + 1} {/* 答题耗时 */} {duration > 0 && ( } label={duration >= 1000 ? `${(duration / 1000).toFixed(1)}s` : `${duration}ms`} size="small" variant="outlined" sx={{ height: 24, '& .MuiChip-label': { px: 0.75, fontSize: '0.75rem' } }} /> )} {/* 答题状态 */} {status !== EVAL_STATUS.SUCCESS && ( } label={t( STATUS_CONFIG[status]?.label || 'evalTasks.statusUnknown', status === EVAL_STATUS.FORMAT_ERROR ? t('evalTasks.statusFormatError') : t('evalTasks.statusApiError') )} size="small" color={STATUS_CONFIG[status]?.color || 'default'} variant="outlined" sx={{ height: 24, '& .MuiChip-label': { px: 0.75, fontSize: '0.75rem' } }} /> )} {/* 题目内容 */} {question} {/* 选项区域 (仅选择题/判断题) */} {parsedOptions.length > 0 && ( {parsedOptions.map((opt, idx) => { const letter = String.fromCharCode(65 + idx); const { isSelected, isCorrectOption } = getOptionStatus(opt, idx); return ( {letter}. {opt} ); })} )} {/* 答案对比区域 */} {t('evalTasks.modelAnswer')} {questionType === 'open_ended' || questionType === 'short_answer' ? (
{modelAnswer || ''}
) : ( {formatAnswer(modelAnswer)} )} {/* 展开/收起 遮罩和按钮 */} {shouldShowExpand && !isExpanded && ( )}
{isExpanded && shouldShowExpand && ( )} {t('evalTasks.correctAnswer')} {questionType === 'open_ended' || questionType === 'short_answer' ? (
{correctAnswer || ''}
) : ( {formatAnswer(correctAnswer)} )} {/* 展开/收起 遮罩和按钮 */} {shouldShowCorrectExpand && !isCorrectExpanded && ( )}
{isCorrectExpanded && shouldShowCorrectExpand && ( )}
{/* 错误信息显示 */} {errorMessage && ( {errorMessage} )} {/* 教师点评 (气泡样式) */} {judgeResponse && ( {t('evalTasks.judgeComment')} {getJudgeDisplayContent(judgeResponse)} {/* 得分显示(如果是主观题) */} {(questionType === 'short_answer' || questionType === 'open_ended') && ( {(score * 100).toFixed(0)} {t('evalTasks.scoreUnit')} )} )}
); }