'use client'; import { useState, useTransition, useMemo, useEffect } from 'react'; import Link from 'next/link'; import { saveCalibrationAnswers, getCalibrationAnswers } from '@/app/actions/calibration'; import { getDialectScoresFromCalibration } from '@/app/actions/calibration-scoring'; import { ExperimentCalibration } from '@/lib/model/experiment_calibration'; import CalibrationEntryView from './CalibrationEntryView'; import CalibrationScoresDisplay from '@/components/CalibrationScoresDisplay'; import { DIALECT_LABELS } from '@/lib/dialects'; interface CalibrationPageViewProps { entries: ExperimentCalibration[]; experimentId: number; } interface CalibrationAnswers { [key: number]: { calibrationItemId: number; dialectLabel: string; confidence: number; }; } export default function CalibrationPageView({ entries, experimentId, }: CalibrationPageViewProps) { const [isPending, startTransition] = useTransition(); const [answers, setAnswers] = useState({}); const [isLoading, setIsLoading] = useState(true); const [currentIndex, setCurrentIndex] = useState(0); const [dialectScores, setDialectScores] = useState>({}); // Load existing answers on component mount useEffect(() => { const loadExistingAnswers = async () => { try { const result = await getCalibrationAnswers(experimentId); if (result.success && result.data) { setAnswers(result.data as CalibrationAnswers); // Find the first unanswered entry const firstUnanswered = entries.findIndex((e) => { const answer = (result.data as CalibrationAnswers)[e.id]; return !answer || !answer.dialectLabel || !answer.confidence; }); if (firstUnanswered !== -1) { setCurrentIndex(firstUnanswered); } } } catch (error) { console.error('Error loading calibration answers:', error); } finally { setIsLoading(false); } }; loadExistingAnswers(); }, [experimentId, entries]); const currentEntry = entries[currentIndex]; // Count only complete answers (both dialect and confidence filled) const completeAnswers = Object.values(answers).filter( (answer) => answer.dialectLabel && answer.confidence > 0 ); const isComplete = entries.length > 0 && completeAnswers.length === entries.length; const progressPct = Math.round((completeAnswers.length / entries.length) * 100); // Load dialect scores when calibration is complete useEffect(() => { if (isComplete && !isLoading) { const loadScores = async () => { try { const scores = await getDialectScoresFromCalibration(experimentId); setDialectScores(scores); } catch (error) { console.error('Error loading dialect scores:', error); } }; loadScores(); } }, [isComplete, isLoading, experimentId]); // Check if current entry has complete answers const currentEntryComplete = currentEntry && answers[currentEntry.id]?.dialectLabel && answers[currentEntry.id]?.confidence > 0; const handlePrevious = () => { if (currentIndex > 0) { setCurrentIndex((i) => i - 1); } }; const handleNext = () => { if (currentIndex + 1 < entries.length) { setCurrentIndex((i) => i + 1); } }; const handleSaveEntry = async (dialectLabel: string, confidence: number) => { startTransition(async () => { // Build updated answers const updatedAnswers = { ...answers }; // Initialize entry if it doesn't exist if (!updatedAnswers[currentEntry.id]) { updatedAnswers[currentEntry.id] = { calibrationItemId: currentEntry.id, dialectLabel: '', confidence: 0, }; } // Update with new values (keep existing values if not provided) if (dialectLabel) { updatedAnswers[currentEntry.id].dialectLabel = dialectLabel; } if (confidence > 0) { updatedAnswers[currentEntry.id].confidence = confidence; } setAnswers(updatedAnswers); // Save to database immediately try { await saveCalibrationAnswers(experimentId, updatedAnswers); } catch (error) { console.error('Error saving calibration answers:', error); } // Auto-advance to next entry only if both answers are selected const entry = updatedAnswers[currentEntry.id]; if (entry.dialectLabel && entry.confidence > 0) { // Don't auto-advance if it's the last entry if (currentIndex < entries.length - 1) { handleNext(); } } }); }; const handleDialectSelect = async (dialect: string) => { const currentConfidence = answers[currentEntry.id]?.confidence || 0; await handleSaveEntry(dialect, currentConfidence); }; const handleConfidenceSelect = async (confidence: number) => { const currentDialect = answers[currentEntry.id]?.dialectLabel || ''; await handleSaveEntry(currentDialect, confidence); }; const handleCompleteCalibration = async () => { startTransition(async () => { // All answers are already saved, just trigger a page reload or redirect // Reload the page to check calibration status again window.location.reload(); }); }; if (isComplete) { // Sort dialect scores from highest to lowest const sortedScores = Object.entries(dialectScores).sort((a, b) => b[1] - a[1]); return (
🎉

Kalibrierung abgeschlossen!

Sie haben alle Kalibrierungssamples bewertet. Vielen Dank! Sie können jetzt mit der Annotation beginnen.

{/* Dialect Scores Summary */} {sortedScores.length > 0 && (

Ihre Kalibrierungsergebnisse

)}
); } return (
{/* Header with progress */}

Kalibrierungsphase

{completeAnswers.length} / {entries.length}
{/* Main content */}
{currentEntry && ( )}
{/* Navigation footer */}
); }