diff --git a/drizzle/0014_groovy_patriot.sql b/drizzle/0014_groovy_patriot.sql new file mode 100644 index 0000000..738b62a --- /dev/null +++ b/drizzle/0014_groovy_patriot.sql @@ -0,0 +1 @@ +ALTER TABLE `annotation` ADD `confidence` integer NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0014_snapshot.json b/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..a1d647b --- /dev/null +++ b/drizzle/meta/0014_snapshot.json @@ -0,0 +1,954 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1413326b-8df5-4c48-9460-9c60528d0393", + "prevId": "a27062e1-cbe8-4669-95fd-2cda4cbaf569", + "tables": { + "annotation": { + "name": "annotation", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "dataset_entry_id": { + "name": "dataset_entry_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "experiment_id": { + "name": "experiment_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confidence": { + "name": "confidence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dialect_label": { + "name": "dialect_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "unique_annotation": { + "name": "unique_annotation", + "columns": [ + "dataset_entry_id", + "experiment_id", + "user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "annotation_dataset_entry_id_dataset_entry_id_fk": { + "name": "annotation_dataset_entry_id_dataset_entry_id_fk", + "tableFrom": "annotation", + "tableTo": "dataset_entry", + "columnsFrom": [ + "dataset_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "annotation_experiment_id_experiment_id_fk": { + "name": "annotation_experiment_id_experiment_id_fk", + "tableFrom": "annotation", + "tableTo": "experiment", + "columnsFrom": [ + "experiment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "admin": { + "name": "admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "dataset_entry": { + "name": "dataset_entry", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "speaker_id": { + "name": "speaker_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "utterance_id": { + "name": "utterance_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "utterance_text": { + "name": "utterance_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dialect": { + "name": "dialect", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iteration": { + "name": "iteration", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rms_value": { + "name": "rms_value", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "longest_pause": { + "name": "longest_pause", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "utmos_score": { + "name": "utmos_score", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wer_score": { + "name": "wer_score", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "dataset_entry_dataset_id_dataset_id_fk": { + "name": "dataset_entry_dataset_id_dataset_id_fk", + "tableFrom": "dataset_entry", + "tableTo": "dataset", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "dataset": { + "name": "dataset", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "experiment_calibration": { + "name": "experiment_calibration", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "experiment_id": { + "name": "experiment_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dialect_label": { + "name": "dialect_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "utterance_id": { + "name": "utterance_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "utterance_text": { + "name": "utterance_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file": { + "name": "file", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "experiment_calibration_experiment_id_experiment_id_fk": { + "name": "experiment_calibration_experiment_id_experiment_id_fk", + "tableFrom": "experiment_calibration", + "tableTo": "experiment", + "columnsFrom": [ + "experiment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "experiment": { + "name": "experiment", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published": { + "name": "published", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotation_tool": { + "name": "annotation_tool", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "onboarding_enabled": { + "name": "onboarding_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "calibration_enabled": { + "name": "calibration_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "experiment_dataset_id_dataset_id_fk": { + "name": "experiment_dataset_id_dataset_id_fk", + "tableFrom": "experiment", + "tableTo": "dataset", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "participant": { + "name": "participant", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "experiment_id": { + "name": "experiment_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "calibration_answers": { + "name": "calibration_answers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "onboarding_answers": { + "name": "onboarding_answers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "unique_participant": { + "name": "unique_participant", + "columns": [ + "experiment_id", + "user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "participant_experiment_id_experiment_id_fk": { + "name": "participant_experiment_id_experiment_id_fk", + "tableFrom": "participant", + "tableTo": "experiment", + "columnsFrom": [ + "experiment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index d6f7b6d..5a0977e 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -99,6 +99,13 @@ "when": 1775796301988, "tag": "0013_mute_black_widow", "breakpoints": true + }, + { + "idx": 14, + "version": "6", + "when": 1775807031115, + "tag": "0014_groovy_patriot", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/actions/annotations.ts b/src/app/actions/annotations.ts index 84113b2..bbc714f 100644 --- a/src/app/actions/annotations.ts +++ b/src/app/actions/annotations.ts @@ -156,6 +156,7 @@ export async function getAnnotationEntries(experimentId: number): Promise a.datasetEntryId === e.id)?.rating || null, + confidence: annotationEntries.find((a) => a.datasetEntryId === e.id)?.confidence || null, utteranceText: e.utteranceText, })); @@ -167,7 +168,7 @@ export async function getAnnotationEntries(experimentId: number): Promise { const session = await auth.api.getSession({ headers: await headers() }); @@ -175,15 +176,16 @@ export async function saveAnnotations( if (ratings.length === 0) return; - for (const { entryId, rating, dialectLabel } of ratings) { + for (const { entryId, rating, dialectLabel, confidence } of ratings) { await db .insert(annotation) - .values({experimentId, datasetEntryId: entryId, userId: session.user.id, rating, dialectLabel }) + .values({experimentId, datasetEntryId: entryId, userId: session.user.id, rating, dialectLabel, confidence }) .onConflictDoUpdate({ target: [annotation.userId, annotation.experimentId, annotation.datasetEntryId], set: { rating, dialectLabel, + confidence } }); } diff --git a/src/components/AnnotationViews/AnnotationPageView.tsx b/src/components/AnnotationViews/AnnotationPageView.tsx index 03eb7b6..2d25aeb 100644 --- a/src/components/AnnotationViews/AnnotationPageView.tsx +++ b/src/components/AnnotationViews/AnnotationPageView.tsx @@ -13,9 +13,10 @@ import CalibrationScoresModal from '@/components/CalibrationScoresModal'; export interface EntryViewProps { entry: DatasetEntryForAnnotation; index: number; - onSave: (rating: number) => Promise; + onSave: (rating: number, confidence: number) => Promise; isSaving: boolean; ratingOptions?: { value: number; label: JSX.Element | string }[]; + confidenceOptions?: { value: number; label: JSX.Element | string }[]; question?: JSX.Element | string; } @@ -68,6 +69,12 @@ export default function AnnotationPageView({ { value: 4, label: `Klingt eindeutig nach ${DIALECT_LABELS[dialectLabel]}` }, ], question:
Wie authentisch klingt diese Aufnahme nach dem Dialekt der Region {DIALECT_LABELS[dialectLabel]}?
, + confidenceOptions: [ + { value: 1, label: 'Sehr unsicher' }, + { value: 2, label: 'Eher unsicher' }, + { value: 3, label: 'Eher sicher' }, + { value: 4, label: 'Sehr sicher' }, + ] }; case 'binary': return { @@ -99,6 +106,7 @@ export default function AnnotationPageView({ isSaving={isPending} ratingOptions={config.ratingOptions} question={config.question} + confidenceOptions={config.confidenceOptions} /> ); case 'binary': @@ -147,12 +155,16 @@ export default function AnnotationPageView({ } }; - const handleSaveEntry = async (rating: number) => { + const handleSaveEntry = async (rating: number, confidence: number) => { startTransition(async () => { - const batch = [{ entryId: currentEntry!.id, rating, dialectLabel: currentEntry!.dialect }]; + const batch = [{ entryId: currentEntry!.id, rating, dialectLabel: currentEntry!.dialect, confidence }]; // Using max confidence for now, can be changed to user input if needed await saveAnnotations(batch, experimentId); + const isNewAnnotation = entries[currentIndex].annotation === null && entries[currentIndex].confidence === null; // Check if this is the first time annotating this entry entries[currentIndex].annotation = rating; // Update local state optimistically - handleNext(); + entries[currentIndex].confidence = confidence; // Update confidence in local state + if (isNewAnnotation) { + handleNext(); + } }); }; // Calculate progress: count already-annotated entries diff --git a/src/components/AnnotationViews/SingleChoiceBinaryEntryView.tsx b/src/components/AnnotationViews/SingleChoiceBinaryEntryView.tsx index 53fd9f0..485ba0e 100644 --- a/src/components/AnnotationViews/SingleChoiceBinaryEntryView.tsx +++ b/src/components/AnnotationViews/SingleChoiceBinaryEntryView.tsx @@ -26,7 +26,7 @@ export default function SingleChoiceBinaryEntryView({ if (answer === entry.annotation) return; // No change, don't save const timer = setTimeout(() => { - onSave(answer); + onSave(answer, -1); // Using max confidence for now, can be changed to user input if needed }, AUTO_ADVANCE_DELAY_MS); return () => clearTimeout(timer); diff --git a/src/components/AnnotationViews/SingleChoiceEntryView.tsx b/src/components/AnnotationViews/SingleChoiceEntryView.tsx index 7be3191..bae752f 100644 --- a/src/components/AnnotationViews/SingleChoiceEntryView.tsx +++ b/src/components/AnnotationViews/SingleChoiceEntryView.tsx @@ -7,42 +7,47 @@ import { type EntryViewProps } from './AnnotationPageView'; const AUTO_ADVANCE_DELAY_MS = 600; + + export default function SingleChoiceEntryView({ entry, index, onSave, isSaving, ratingOptions, + confidenceOptions, question, }: EntryViewProps) { - const [answer, setAnswer] = useState(null); + const [answerRating, setAnswerRating] = useState(null); + const [answerConfidence, setAnswerConfidence] = useState(null); const [fullyPlayed, setFullyPlayed] = useState(false); const fileExt = entry.fileName.substring(entry.fileName.lastIndexOf('.')); const audioSrc = `/public/datasets/${entry.datasetId}/${entry.externalId}${fileExt}`; - // Auto-save when answer is selected + // Auto-save when both answer and confidence are selected useEffect(() => { - if (answer === null || isSaving) return; - if (answer === entry.annotation) return; // No change, don't save + if (answerRating === null || answerConfidence === null || isSaving) return; + if (answerRating === entry.annotation && answerConfidence === entry.confidence) return; // No change, don't save const timer = setTimeout(() => { - onSave(answer); + onSave(answerRating, answerConfidence); }, AUTO_ADVANCE_DELAY_MS); return () => clearTimeout(timer); - }, [answer, onSave, entry.annotation, isSaving]); + }, [answerRating, answerConfidence, onSave, entry.annotation, entry.confidence, isSaving]); useEffect(() => { // Reset state when entry changes - setAnswer(entry.annotation); - setFullyPlayed(entry.annotation !== null); // If already annotated, mark as fully played to allow changing answer + setAnswerRating(entry.annotation); + setAnswerConfidence(entry.confidence); + setFullyPlayed(entry.annotation !== null && entry.confidence !== null); // If already annotated, mark as fully played to allow changing answer }, [entry]); return (
{/* Sample header */} @@ -79,44 +84,83 @@ export default function SingleChoiceEntryView({
)} - {/* Dialect + question */} -
-
- {question} + {/* Questions container */} +
+ {/* Rating question */} +
+
+ {question} +
+
+ {ratingOptions?.map(({ value, label }) => { + const selected = answerRating === value; + const disabled = !fullyPlayed || isSaving; + return ( + + ); + })} +
-
- {/* Rating buttons */} -
- {ratingOptions?.map(({ value, label }) => { - const selected = answer === value; - const disabled = !fullyPlayed || isSaving; - return ( - - ); - })} + {/* Confidence question */} +
+
+ Wie sicher bist du? +
+
+ {confidenceOptions?.map(({ value, label }) => { + const selected = answerConfidence === value; + const disabled = !fullyPlayed || isSaving; + return ( + + ); + })} +
+
{/* Footnote */} diff --git a/src/lib/dialects.ts b/src/lib/dialects.ts index 95f45c1..f6df531 100644 --- a/src/lib/dialects.ts +++ b/src/lib/dialects.ts @@ -28,5 +28,6 @@ export type DatasetEntryForAnnotation = { datasetId: number; experimentId: number; annotation: number | null; + confidence: number | null; utteranceText: string | null; }; diff --git a/src/lib/model/annotation.ts b/src/lib/model/annotation.ts index e06d2fc..7708119 100644 --- a/src/lib/model/annotation.ts +++ b/src/lib/model/annotation.ts @@ -11,7 +11,8 @@ export const annotation = sqliteTable( experimentId: integer('experiment_id') .references(() => experiment.id), userId: text('user_id').notNull(), - rating: integer('rating').notNull(), // 1–5 + rating: integer('rating').notNull(), // 1–4 + confidence: integer('confidence').notNull(), // 1-4 dialectLabel: text('dialect_label').notNull(), // e.g. 'ch_be', 'ch_zh', … createdAt: integer('created_at', { mode: 'timestamp' }) .notNull()