Add language assistant settings and related features: Introduce new routes and controller methods for managing language assistant settings, including retrieval and saving of LLM configurations. Update navigation structure to include language assistant options. Enhance vocab course model to support additional learning attributes such as learning goals and core patterns. Update SQL scripts to reflect new database schema changes for vocab courses. Improve localization for language assistant settings in German and English.
This commit is contained in:
@@ -14,6 +14,13 @@ import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'
|
||||
import VocabCourse from '../models/community/vocab_course.js';
|
||||
import User from '../models/community/user.js';
|
||||
|
||||
function withTypeName(exerciseTypeName, exercise) {
|
||||
return {
|
||||
...exercise,
|
||||
exerciseTypeName
|
||||
};
|
||||
}
|
||||
|
||||
// Bisaya-spezifische Übungen basierend auf Lektionsthemen
|
||||
const BISAYA_EXERCISES = {
|
||||
// Lektion 1: Begrüßungen & Höflichkeit
|
||||
@@ -62,6 +69,35 @@ const BISAYA_EXERCISES = {
|
||||
correctAnswer: 0
|
||||
},
|
||||
explanation: '"Salamat" bedeutet "Danke" auf Bisaya.'
|
||||
},
|
||||
withTypeName('dialog_completion', {
|
||||
title: 'Begrüßungsdialog ergänzen',
|
||||
instruction: 'Ergänze die passende Antwort im Mini-Dialog.',
|
||||
questionData: {
|
||||
type: 'dialog_completion',
|
||||
question: 'Welche Antwort passt auf die Begrüßung?',
|
||||
dialog: ['A: Kumusta ka?', 'B: ...']
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Maayo ko, salamat.',
|
||||
correct: ['Maayo ko, salamat.', 'Maayo ko. Salamat.']
|
||||
},
|
||||
explanation: 'Eine typische kurze Antwort ist "Maayo ko, salamat."'
|
||||
}),
|
||||
{
|
||||
exerciseTypeId: 8,
|
||||
title: 'Begrüßung frei sprechen',
|
||||
instruction: 'Sprich eine kurze Begrüßung mit Frage und Antwort frei nach.',
|
||||
questionData: {
|
||||
type: 'speaking_from_memory',
|
||||
question: 'Begrüße eine Person und antworte kurz auf "Kumusta ka?".',
|
||||
expectedText: 'Kumusta ka? Maayo ko, salamat.',
|
||||
keywords: ['kumusta', 'maayo', 'salamat']
|
||||
},
|
||||
answerData: {
|
||||
type: 'speaking_from_memory'
|
||||
},
|
||||
explanation: 'Wichtig sind hier die Schlüsselwörter für Begrüßung, Antwort und Höflichkeit.'
|
||||
}
|
||||
],
|
||||
|
||||
@@ -188,7 +224,92 @@ const BISAYA_EXERCISES = {
|
||||
alternatives: ['Mama', 'Nanay', 'Inahan']
|
||||
},
|
||||
explanation: '"Nanay" oder "Mama" bedeutet "Mutter" auf Bisaya.'
|
||||
}
|
||||
},
|
||||
{
|
||||
exerciseTypeId: 3,
|
||||
title: 'Familiensatz bauen',
|
||||
instruction: 'Bilde aus den Wörtern einen kurzen Satz.',
|
||||
questionData: {
|
||||
type: 'sentence_building',
|
||||
question: 'Baue einen Satz: "Das ist meine Mutter."',
|
||||
tokens: ['Si', 'Nanay', 'nako', 'ni']
|
||||
},
|
||||
answerData: {
|
||||
correct: ['Si Nanay nako ni.', 'Si Nanay ni nako.']
|
||||
},
|
||||
explanation: 'Mit "Si Nanay nako ni." stellst du deine Mutter kurz vor.'
|
||||
},
|
||||
withTypeName('situational_response', {
|
||||
title: 'Familie vorstellen',
|
||||
instruction: 'Antworte kurz auf die Situation.',
|
||||
questionData: {
|
||||
type: 'situational_response',
|
||||
question: 'Jemand fragt dich nach deiner Familie. Stelle kurz Mutter und älteren Bruder vor.',
|
||||
keywords: ['nanay', 'kuya']
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Si Nanay ug si Kuya.',
|
||||
keywords: ['nanay', 'kuya']
|
||||
},
|
||||
explanation: 'Für diese Aufgabe reichen kurze, klare Familiennennungen.'
|
||||
})
|
||||
],
|
||||
|
||||
'Essen & Fürsorge': [
|
||||
{
|
||||
exerciseTypeId: 2,
|
||||
title: 'Fürsorgefrage verstehen',
|
||||
instruction: 'Wähle die richtige Bedeutung.',
|
||||
questionData: {
|
||||
type: 'multiple_choice',
|
||||
question: 'Was bedeutet "Nikaon na ka?"?',
|
||||
options: ['Hast du schon gegessen?', 'Bist du müde?', 'Kommst du nach Hause?', 'Möchtest du Wasser?']
|
||||
},
|
||||
answerData: { type: 'multiple_choice', correctAnswer: 0 },
|
||||
explanation: '"Nikaon na ka?" ist eine sehr fürsorgliche Alltagsfrage.'
|
||||
},
|
||||
{
|
||||
exerciseTypeId: 1,
|
||||
title: 'Essensdialog ergänzen',
|
||||
instruction: 'Fülle die Lücken mit den passenden Wörtern.',
|
||||
questionData: {
|
||||
type: 'gap_fill',
|
||||
text: 'Nikaon {gap} ka? {gap} ta!',
|
||||
gaps: 2
|
||||
},
|
||||
answerData: {
|
||||
answers: ['na', 'Kaon']
|
||||
},
|
||||
explanation: '"na" markiert hier den bereits eingetretenen Zustand; "Kaon ta!" heißt "Lass uns essen!".'
|
||||
},
|
||||
withTypeName('dialog_completion', {
|
||||
title: 'Einladung zum Essen ergänzen',
|
||||
instruction: 'Ergänze die passende Antwort.',
|
||||
questionData: {
|
||||
type: 'dialog_completion',
|
||||
question: 'Welche Antwort passt auf die Einladung?',
|
||||
dialog: ['A: Kaon ta!', 'B: ...']
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Oo, gusto ko.',
|
||||
correct: ['Oo, gusto ko.', 'Oo, mokaon ko.']
|
||||
},
|
||||
explanation: 'Eine natürliche kurze Reaktion ist "Oo, gusto ko."'
|
||||
}),
|
||||
withTypeName('situational_response', {
|
||||
title: 'Fürsorglich reagieren',
|
||||
instruction: 'Reagiere passend auf die Situation.',
|
||||
questionData: {
|
||||
type: 'situational_response',
|
||||
question: 'Jemand sieht hungrig aus. Frage fürsorglich nach und biete Essen an.',
|
||||
keywords: ['nikaon', 'kaon']
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Nikaon na ka? Kaon ta.',
|
||||
keywords: ['nikaon', 'kaon']
|
||||
},
|
||||
explanation: 'Die Übung trainiert einen sehr typischen fürsorglichen Mini-Dialog.'
|
||||
})
|
||||
],
|
||||
|
||||
// Lektion: Haus & Familie (Balay, Kwarto, Kusina, Pamilya)
|
||||
@@ -424,6 +545,34 @@ const BISAYA_EXERCISES = {
|
||||
answers: ['Ni', 'Mo']
|
||||
},
|
||||
explanation: 'Ni- für Vergangenheit, Mo- für Zukunft.'
|
||||
},
|
||||
withTypeName('pattern_drill', {
|
||||
title: 'Zeitmuster anwenden',
|
||||
instruction: 'Bilde mit demselben Muster einen Zukunftssatz.',
|
||||
questionData: {
|
||||
type: 'pattern_drill',
|
||||
question: 'Verwende das Muster für "gehen".',
|
||||
pattern: 'Mo- + Verb + ko'
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Mo-adto ko.',
|
||||
correct: ['Mo-adto ko.', 'Moadto ko.']
|
||||
},
|
||||
explanation: 'Mit "Mo-" kannst du ein einfaches Zukunftsmuster bilden.'
|
||||
}),
|
||||
{
|
||||
exerciseTypeId: 3,
|
||||
title: 'Vergangenheit und Zukunft bauen',
|
||||
instruction: 'Schreibe beide Formen nacheinander auf.',
|
||||
questionData: {
|
||||
type: 'sentence_building',
|
||||
question: 'Formuliere: "Ich habe gegessen. Ich werde essen."',
|
||||
tokens: ['Ni-kaon', 'ko', 'Mo-kaon', 'ko']
|
||||
},
|
||||
answerData: {
|
||||
correct: ['Ni-kaon ko. Mo-kaon ko.', 'Nikaon ko. Mokaon ko.']
|
||||
},
|
||||
explanation: 'Die Übung trainiert den direkten Wechsel zwischen den beiden Zeitmarkern.'
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1103,7 +1252,35 @@ const BISAYA_EXERCISES = {
|
||||
},
|
||||
answerData: { type: 'multiple_choice', correctAnswer: 0 },
|
||||
explanation: '"Wala ko kasabot" bedeutet "Ich verstehe nicht".'
|
||||
}
|
||||
},
|
||||
{
|
||||
exerciseTypeId: 3,
|
||||
title: 'Woche 1: Minisatz bauen',
|
||||
instruction: 'Schreibe eine kurze Sequenz aus Begrüßung und Fürsorge.',
|
||||
questionData: {
|
||||
type: 'sentence_building',
|
||||
question: 'Baue: "Wie geht es dir? Hast du schon gegessen?"',
|
||||
tokens: ['Kumusta', 'ka', 'Nikaon', 'na', 'ka']
|
||||
},
|
||||
answerData: {
|
||||
correct: ['Kumusta ka? Nikaon na ka?', 'Kumusta ka. Nikaon na ka?']
|
||||
},
|
||||
explanation: 'Hier kombinierst du zwei wichtige Muster aus Woche 1.'
|
||||
},
|
||||
withTypeName('dialog_completion', {
|
||||
title: 'Woche 1: Dialog ergänzen',
|
||||
instruction: 'Ergänze die passende liebevolle Reaktion.',
|
||||
questionData: {
|
||||
type: 'dialog_completion',
|
||||
question: 'Welche Antwort passt?',
|
||||
dialog: ['A: Mingaw ko nimo.', 'B: ...']
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Palangga taka.',
|
||||
correct: ['Palangga taka.']
|
||||
},
|
||||
explanation: 'Die Kombination klingt im Familienkontext warm und natürlich.'
|
||||
})
|
||||
],
|
||||
|
||||
// Woche 1 - Vokabeltest (Lektion 10)
|
||||
@@ -1167,10 +1344,48 @@ const BISAYA_EXERCISES = {
|
||||
},
|
||||
answerData: { type: 'multiple_choice', correctAnswer: 0 },
|
||||
explanation: '"Mingaw ko nimo" bedeutet "Ich vermisse dich".'
|
||||
}
|
||||
},
|
||||
withTypeName('situational_response', {
|
||||
title: 'Woche 1: Situative Kurzantwort',
|
||||
instruction: 'Reagiere passend auf die Situation.',
|
||||
questionData: {
|
||||
type: 'situational_response',
|
||||
question: 'Jemand fragt: "Kumusta ka?" Antworte kurz und höflich.',
|
||||
keywords: ['maayo', 'salamat']
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer: 'Maayo ko, salamat.',
|
||||
keywords: ['maayo', 'salamat']
|
||||
},
|
||||
explanation: 'Eine kurze höfliche Antwort reicht hier völlig aus.'
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
async function resolveExerciseTypeId(exercise) {
|
||||
if (exercise.exerciseTypeId) {
|
||||
return exercise.exerciseTypeId;
|
||||
}
|
||||
|
||||
if (!exercise.exerciseTypeName) {
|
||||
throw new Error(`Kein exerciseTypeId oder exerciseTypeName für "${exercise.title}" definiert`);
|
||||
}
|
||||
|
||||
const [type] = await sequelize.query(
|
||||
`SELECT id FROM community.vocab_grammar_exercise_type WHERE name = :name LIMIT 1`,
|
||||
{
|
||||
replacements: { name: exercise.exerciseTypeName },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
if (!type) {
|
||||
throw new Error(`Übungstyp "${exercise.exerciseTypeName}" nicht gefunden`);
|
||||
}
|
||||
|
||||
return Number(type.id);
|
||||
}
|
||||
|
||||
async function findOrCreateSystemUser() {
|
||||
let systemUser = await User.findOne({
|
||||
where: {
|
||||
@@ -1270,10 +1485,14 @@ async function createBisayaCourseContent() {
|
||||
const replacePlaceholders = [
|
||||
'Woche 1 - Wiederholung',
|
||||
'Woche 1 - Vokabeltest',
|
||||
'Begrüßungen & Höflichkeit',
|
||||
'Familienwörter',
|
||||
'Essen & Fürsorge',
|
||||
'Alltagsgespräche - Teil 1',
|
||||
'Alltagsgespräche - Teil 2',
|
||||
'Haus & Familie',
|
||||
'Ort & Richtung'
|
||||
'Ort & Richtung',
|
||||
'Zeitformen - Grundlagen'
|
||||
].includes(lesson.title);
|
||||
const existingCount = await VocabGrammarExercise.count({
|
||||
where: { lessonId: lesson.id }
|
||||
@@ -1292,9 +1511,10 @@ async function createBisayaCourseContent() {
|
||||
// Erstelle Übungen
|
||||
let exerciseNumber = 1;
|
||||
for (const exerciseData of exercises) {
|
||||
const exerciseTypeId = await resolveExerciseTypeId(exerciseData);
|
||||
await VocabGrammarExercise.create({
|
||||
lessonId: lesson.id,
|
||||
exerciseTypeId: exerciseData.exerciseTypeId,
|
||||
exerciseTypeId,
|
||||
exerciseNumber: exerciseNumber++,
|
||||
title: exerciseData.title,
|
||||
instruction: exerciseData.instruction,
|
||||
|
||||
Reference in New Issue
Block a user