import React, { useState, useEffect, useRef } from 'react'; import { Brain, Send, Loader2, CheckCircle, AlertCircle, ChevronRight, History, ClipboardCheck, RefreshCcw, FileText, Star, Award, Trophy, Trash2 } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useLanguage } from '../../contexts/LanguageContext'; import { useConfirm } from '../../contexts/ConfirmContext'; import { assessmentService, AssessmentSession, AssessmentState } from '../../services/assessmentService'; import { knowledgeGroupService } from '../../services/knowledgeGroupService'; import { templateService } from '../../services/templateService'; import { KnowledgeGroup, AssessmentTemplate } from '../../types'; import { cn } from '../../src/utils/cn'; interface AssessmentViewProps { onLogout: () => void; onNavigate: (path: string) => void; isAdmin: boolean; } export const AssessmentView: React.FC = ({ onLogout, onNavigate, isAdmin }) => { const { language, t } = useLanguage(); const { confirm } = useConfirm(); const [groups, setGroups] = useState([]); const [selectedGroup, setSelectedGroup] = useState(null); const [session, setSession] = useState(null); const [state, setState] = useState(null); const [inputValue, setInputValue] = useState(''); const [isLoading, setIsLoading] = useState(false); const [processStep, setProcessStep] = useState(''); const [error, setError] = useState(null); const [history, setHistory] = useState([]); const [loadingHistoryId, setLoadingHistoryId] = useState(null); const [showBasis, setShowBasis] = useState(false); const [templates, setTemplates] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(null); const messagesEndRef = useRef(null); useEffect(() => { const fetchGroups = async () => { try { const data = await knowledgeGroupService.getGroups(); setGroups(data); } catch (err) { console.error('Failed to fetch groups:', err); } }; const fetchTemplates = async () => { try { const data = await templateService.getAll(); setTemplates(data); } catch (err) { console.error('Failed to fetch templates:', err); } }; fetchGroups(); fetchTemplates(); }, []); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [state?.messages, isLoading]); const fetchHistory = async () => { try { const data = await assessmentService.getHistory(); setHistory(data); } catch (err) { console.error('Failed to fetch history:', err); } }; useEffect(() => { fetchHistory(); }, []); const isZh = language === 'zh'; const isJa = language === 'ja'; const getStatusText = (node: string) => { const mapping: Record = { generator: 'statusGeneratingQuestions', grader: 'statusEvaluatingAnswer', interviewer: 'statusPreparingQuestion', analyzer: 'statusGeneratingReport', }; return t(mapping[node]) || t('statusProcessing'); }; const handleSelectHistory = async (histSession: AssessmentSession) => { if (isLoading) return; setLoadingHistoryId(histSession.id); setIsLoading(true); setError(null); try { const histState = await assessmentService.getSessionState(histSession.id); setState(histState); setSession(histSession); } catch (err: any) { setError(err.message || 'Failed to load historical assessment'); } finally { setIsLoading(false); setLoadingHistoryId(null); } }; const handleDeleteHistory = async (e: React.MouseEvent, histId: string) => { e.stopPropagation(); const confirmed = await confirm(t('confirmDeleteAssessment')); if (!confirmed) return; try { await assessmentService.deleteSession(histId); setHistory(prev => prev.filter(h => h.id !== histId)); if (session?.id === histId) { setSession(null); setState(null); } } catch (err: any) { console.error('Failed to delete history:', err); setError(t('deleteAssessmentFailed')); } }; const handleStartAssessment = async () => { if (!selectedTemplate) return; setIsLoading(true); setError(null); setProcessStep(isZh ? '正在初始化...' : isJa ? '初期化中...' : 'Initializing...'); try { const newSession = await assessmentService.startSession(selectedGroup || undefined, language, selectedTemplate || undefined); setSession(newSession); for await (const event of assessmentService.startSessionStream(newSession.id)) { if (event.type === 'node') { setProcessStep(getStatusText(event.node)); if (event.data) { setState(prev => { if (!prev) return event.data; const prevMessages = prev.messages || []; return { ...prev, ...event.data, messages: event.data.messages ? [...prevMessages, ...event.data.messages.filter((m: any) => !prevMessages.some((pm: any) => pm.content === m.content && pm.role === m.role))] : prevMessages, feedbackHistory: event.data.feedbackHistory ? [...(prev.feedbackHistory || []), ...event.data.feedbackHistory.filter((fh: any) => !(prev.feedbackHistory || []).some((pfh: any) => pfh.content === fh.content))] : (prev.feedbackHistory || []), scores: { ...(prev.scores || {}), ...(event.data.scores || {}) } } as any; }); } } else if (event.type === 'final') { setState(event.data); } } } catch (err: any) { setError(err.message || 'Failed to start assessment'); } finally { setIsLoading(false); setProcessStep(''); } }; const handleRetry = async () => { if (!session) return; setIsLoading(true); setError(null); setProcessStep(isZh ? '正在重新尝试生成...' : isJa ? '再生成中...' : 'Retrying generation...'); try { for await (const event of assessmentService.startSessionStream(session.id)) { if (event.type === 'node') { setProcessStep(getStatusText(event.node)); } else if (event.type === 'final') { setState(event.data); } } } catch (err: any) { setError(err.message || 'Retry failed'); } finally { setIsLoading(false); setProcessStep(''); } }; const handleSubmitAnswer = async () => { if (!session || !inputValue.trim() || isLoading) return; const answer = inputValue.trim(); setInputValue(''); setIsLoading(true); setError(null); setProcessStep(isZh ? '正在准备发送...' : isJa ? '送信準備中...' : 'Preparing to send...'); try { setState(prev => ({ ...prev!, messages: [ ...(prev?.messages || []), { role: 'user' as const, content: answer, timestamp: Date.now() } ] })); for await (const event of assessmentService.submitAnswerStream(session.id, answer, language)) { if (event.type === 'node') { setProcessStep(getStatusText(event.node)); if (event.data) { setState(prev => { if (!prev) return event.data; const prevMessages = prev.messages || []; const mergedMessages = event.data.messages ? [...prevMessages, ...event.data.messages.filter((m: any) => !prevMessages.some((pm: any) => pm.content === m.content && pm.role === m.role))] : prevMessages; return { ...prev, ...event.data, messages: mergedMessages, feedbackHistory: event.data.feedbackHistory ? [...(prev.feedbackHistory || []), ...event.data.feedbackHistory.filter((fh: any) => !(prev.feedbackHistory || []).some((pfh: any) => pfh.content === fh.content))] : (prev.feedbackHistory || []), scores: { ...(prev.scores || {}), ...(event.data.scores || {}) } } as any; }); } } else if (event.type === 'final') { setState(event.data); if (event.data.status === 'COMPLETED') { setSession(prev => prev ? { ...prev, status: 'COMPLETED' } : null); fetchHistory(); } } } } catch (err: any) { setError(err.message || 'Failed to submit answer'); } finally { setIsLoading(false); setProcessStep(''); } }; const renderHeader = () => (

{t('assessmentTitle')}

{t('assessmentDesc')}

{session && (
{session.status === 'IN_PROGRESS' ? t('inProgress') : t('statusReadyFragment')}
)}
); const renderSetup = () => (
{/* Main Setup Content */}

{t('readyForAssessment')}

{t('readyForAssessmentDesc')}

{templates.map(template => ( ))}
{error && (

{error}

)}
{t('aiPoweredAnalysis')}
{t('masteryScoring')}
{/* Assessment History Sidebar */} {history.length > 0 && (

{t('recentAssessments')}

{history.map(hist => (
{hist.knowledgeBase?.name || hist.knowledgeGroup?.name || t('assessmentTitle')}
{hist.finalScore !== null && hist.finalScore !== undefined ? `${Math.round(hist.finalScore * 10) / 10}/10` : t('inProgress')} {new Date(hist.createdAt).toLocaleDateString()}
))}
)}
); const renderAssessment = () => { const currentQuestionNo = (state?.currentQuestionIndex || 0) + 1; const totalQuestions = state?.questions?.length || 0; const progressLabel = totalQuestions > 0 ? t('questionProgress', currentQuestionNo, totalQuestions) : t('initializingQuestion', currentQuestionNo); const messages = state?.messages || []; const filteredMessages = messages.filter(m => m.role !== 'system' && !(m.role === 'assistant' && (m.content?.toString().startsWith('Score:') || m.content?.toString().startsWith('得分:'))) ); const feedbackHistory = state?.feedbackHistory || []; const lastFeedbackMessage = feedbackHistory[feedbackHistory.length - 1]; const feedbackMatch = lastFeedbackMessage?.content?.toString().match(/(?:Score|得分): (\d+)\/10\n\n(?:Feedback|反馈): ([\s\S]*)/i); const latestScore = feedbackMatch ? feedbackMatch[1] : null; const latestFeedback = feedbackMatch ? feedbackMatch[2] : (lastFeedbackMessage?.content || null); return (
{/* Left: Chat Area */}
{progressLabel} {isLoading && (
{processStep || t('aiIsProcessing')} )}
{filteredMessages.map((msg, idx) => (
{msg.content}
{new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
))} {isLoading && (
)}