This commit is contained in:
averel10
2026-04-10 10:11:21 +02:00
5 changed files with 166 additions and 10 deletions

View File

@@ -2,11 +2,11 @@ import { redirect, notFound } from 'next/navigation';
import { headers } from 'next/headers';
import Link from 'next/link';
import { auth } from '@/lib/auth';
import { getAnnotationEntries} from '@/app/actions/annotations';
import { getAnnotationEntries, getAnnotationProgress } from '@/app/actions/annotations';
import { isCalibrationDone } from '@/app/actions/calibration-scoring';
import { isOnboardingDone } from '@/app/actions/onboarding';
import { getExperimentById } from '@/app/actions/experiment';
import AnnotationPageView from '@/components/AnnotationViews/AnnotationPageView';
import AnnotationPhase from '@/components/AnnotationViews/AnnotationPhase';
import OnboardingPhase from '@/components/OnboardingViews/OnboardingPhase';
import CalibrationPhase from '@/components/CalibrationViews/CalibrationPhase';
@@ -44,8 +44,11 @@ export default async function AnnotatePage({ params }: Props) {
return <CalibrationPhase experimentId={experimentId} />;
}
const prototype = await getExperimentById(experimentId).then(exp => exp?.annotationTool || null);
const entries = await getAnnotationEntries(experimentId);
const [prototype, entries, { done }] = await Promise.all([
getExperimentById(experimentId).then(exp => exp?.annotationTool || null),
getAnnotationEntries(experimentId),
getAnnotationProgress(experimentId),
]);
if (entries.length === 0) {
return (
@@ -68,10 +71,11 @@ export default async function AnnotatePage({ params }: Props) {
}
return (
<AnnotationPageView
entries={entries}
experimentId={experimentId}
viewType={prototype || ''}
<AnnotationPhase
entries={entries}
experimentId={experimentId}
viewType={prototype || ''}
hasExistingAnswers={done > 0}
/>
);
}

View File

@@ -0,0 +1,119 @@
'use client';
import { useEffect } from 'react';
interface AnnotationInfoViewProps {
onContinue: () => void;
hasExistingAnswers: boolean;
}
export default function AnnotationInfoView({
onContinue,
hasExistingAnswers,
}: AnnotationInfoViewProps) {
useEffect(() => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
}, []);
return (
<div className="flex flex-col h-full bg-white">
<div className="flex-1 flex items-center justify-center px-4">
<div className="max-w-[700px] w-full">
{/* Header */}
<div className="text-center mb-4">
<h1 className="text-3xl font-bold text-gray-900 mb-8 mt-4">Bewertungsphase</h1>
</div>
{/* Info Section */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-8 mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Was ist deine Aufgabe?</h2>
<p className="text-gray-700 mb-4">
Du hörst kurze Audioaufnahmen, die von einem KI-Modell generiert wurden. Pro Aufnahme bewertest du zwei Dinge:
</p>
<div className="space-y-3 text-gray-700 mb-8">
<div>
<p className="font-semibold mb-1">Authentizität</p>
<p>Klingt es so, wie jemand aus dieser Region wirklich sprechen würde, mit der typischen Ausdrucksweise dieser Region? Oder klingt es eher wie eine Imitation?</p>
</div>
<div>
<p className="font-semibold mb-1 mt-4">Sicherheit</p>
<p>Wie sicher bist du dir bei deiner Einschätzung?</p>
</div>
</div>
<div className="border-t border-blue-200 pt-4 text-gray-700">
<p className="font-semibold mb-1 mt-1">Keine Trickfragen</p>
<p>Jede Aufnahme wurde tatsächlich im angegebenen Dialekt generiert.</p>
</div>
</div>
{/* Instructions Section */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Wie funktioniert es?</h3>
<ol className="space-y-3 text-gray-700">
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-semibold">
1
</span>
<span>Du hörst eine synthetische Audioaufnahme</span>
</li>
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-semibold">
2
</span>
<span>Du bewertest, wie authentisch die Aufnahme nach dem angegebenen Dialekt klingt</span>
</li>
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-semibold">
3
</span>
<span>Du gibst an, wie sicher du dir bei deiner Wahl bist</span>
</li>
<li className="flex gap-3">
<span className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-semibold">
4
</span>
<span>Dies wiederholst du für weitere Aufnahmen</span>
</li>
</ol>
</div>
{/* Hints Section */}
<div className="mb-12">
<ul className="space-y-2 text-sm text-gray-500">
<li className="flex gap-2">
<span className="flex-shrink-0"></span>
<span>Die Aufnahmen sind aufgrund deiner Kalibrierungsergebnisse sortiert du siehst zuerst Dialekte, die du gut erkennst. Du kannst aber jederzeit vor- und zurückspringen.</span>
</li>
<li className="flex gap-2">
<span className="flex-shrink-0"></span>
<span>Du kannst die Bewertung jederzeit unterbrechen und später dort weitermachen, wo du aufgehört hast.</span>
</li>
</ul>
</div>
{/* Status */}
{hasExistingAnswers && (
<div className="bg-green-50 border border-green-200 rounded-lg p-6 mb-8">
<p className="text-green-800 text-sm">
Du hast diese Einführung bereits gesehen. Du kannst direkt mit der Bewertung weitermachen.
</p>
</div>
)}
{/* Action Button */}
<div className="flex justify-center">
<button
onClick={onContinue}
className="px-8 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-colors text-lg"
>
{hasExistingAnswers ? 'Bewertung fortsetzen' : 'Bewertung starten'}
</button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,33 @@
'use client';
import { useState } from 'react';
import { type DatasetEntryForAnnotation } from '@/lib/dialects';
import AnnotationInfoView from './AnnotationInfoView';
import AnnotationPageView from './AnnotationPageView';
interface AnnotationPhaseProps {
entries: DatasetEntryForAnnotation[];
experimentId: number;
viewType: string;
hasExistingAnswers: boolean;
}
export default function AnnotationPhase({
entries,
experimentId,
viewType,
hasExistingAnswers,
}: AnnotationPhaseProps) {
const [showInfoPage, setShowInfoPage] = useState(true);
if (showInfoPage) {
return (
<AnnotationInfoView
onContinue={() => setShowInfoPage(false)}
hasExistingAnswers={hasExistingAnswers}
/>
);
}
return <AnnotationPageView entries={entries} experimentId={experimentId} viewType={viewType} />;
}

View File

@@ -72,7 +72,7 @@ export default function CalibrationInfoView({
{/* Participation Section */}
<div className="mb-8">
<div className="mb-12">
<p className="text-gray-700 mb-4">
Die Kalibrierung umfasst 28 Aufnahmen und dauert etwa 510 Minuten.
</p>

View File

@@ -60,7 +60,7 @@ export default function OnboardingInfoView({
{/* Participation Section */}
<div className="mb-8">
<div className="mb-12">
<p className="text-gray-700 mb-4">
Du entscheidest selbst, wie viele Aufnahmen du bewertest. Die gesamte Teilnahme inklusive Formular, Kalibrierung und 50 bewerteten Aufnahmen dauert etwa 30 Minuten. Unter allen Teilnehmenden, die 50 oder mehr Aufnahmen bewertet haben, <strong>verlosen wir 3x [Platzhalter]-Gutscheine im Wert von je [x].- CHF!</strong>
</p>