diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue
index 2756f82..d0a3953 100644
--- a/frontend/src/views/social/VocabLessonView.vue
+++ b/frontend/src/views/social/VocabLessonView.vue
@@ -192,17 +192,17 @@
{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}
-
Tageswiederholung zuerst
-
Ältere Vokabeln stehen hier bewusst vor der Kapitel-Prüfung, damit Wiederholung täglich mitläuft.
+
Wiederholung läuft schrittweise mit
+
Zuerst liegt der Fokus auf den neuen Begriffen dieser Lektion. Mit deinem Fortschritt fließen ältere Vokabeln dann zunehmend mit ein.
Kapitel-Prüfung noch gesperrt
{{ exerciseUnlockHint }}
-
{{ hasPreviousVocab ? 'Starte mit der Wiederholung älterer Vokabeln und arbeite dich dann zur aktuellen Lektion vor.' : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}
+
{{ hasPreviousVocab ? 'Starte mit den neuen Vokabeln dieser Lektion. Mit fortschreitendem Üben mischt der Trainer automatisch passende Wiederholungen ein.' : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}
- {{ hasPreviousVocab ? 'Tageswiederholung starten' : $t('socialnetwork.vocab.courses.startVocabTrainer') }}
+ {{ hasPreviousVocab ? 'Lektion starten' : $t('socialnetwork.vocab.courses.startVocabTrainer') }}
@@ -220,7 +220,7 @@
{{ $t('socialnetwork.vocab.courses.currentLesson') || 'Aktuelle Lektion' }}
- {{ $t('socialnetwork.vocab.courses.mixedReview') || 'Wiederholung' }}
+ {{ $t('socialnetwork.vocab.courses.mixedReview') || 'Gemischt' }}
{{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }}
@@ -230,6 +230,11 @@
{{ $t('socialnetwork.vocab.courses.stopTrainer') }}
+
+ Neue Inhalte: {{ vocabTrainerCurrentAttempts }}/{{ trainerNewFocusTarget }}
+ Wiederholung: {{ vocabTrainerReviewAttempts }}
+ Mischanteil: {{ Math.round(currentReviewShare * 100) }}%
+
@@ -701,6 +706,8 @@ export default {
vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten
vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln
vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase
+ vocabTrainerCurrentAttempts: 0,
+ vocabTrainerReviewAttempts: 0,
exercisePreparationCompleted: false,
currentVocabQuestion: null,
vocabTrainerAnswer: '',
@@ -740,6 +747,42 @@ export default {
hasPreviousVocab() {
return Array.isArray(this.previousVocab) && this.previousVocab.length > 0;
},
+ lessonComplexityWeight() {
+ const lessonType = this.lesson?.lessonType;
+ if (['dialogue', 'phrases', 'survival', 'grammar'].includes(lessonType)) {
+ return 1.2;
+ }
+ if (lessonType === 'review' || lessonType === 'vocab_review') {
+ return 0.9;
+ }
+ return 1;
+ },
+ trainerNewFocusTarget() {
+ const vocabCount = this.importantVocab?.length || 0;
+ const exerciseCount = this.effectiveExercises?.length || 0;
+ const durationBonus = Math.max(0, Math.round((this.lesson?.targetMinutes || 0) / 5) - 1);
+ const baseTarget = Math.ceil((Math.max(vocabCount, 4) * 1.35) + (exerciseCount * 0.35) + durationBonus);
+ const weightedTarget = Math.ceil(baseTarget * this.lessonComplexityWeight);
+ return Math.min(24, Math.max(6, weightedTarget));
+ },
+ trainerReviewBlendStart() {
+ return Math.max(3, Math.ceil(this.trainerNewFocusTarget * 0.4));
+ },
+ trainerReviewRampWindow() {
+ return Math.max(4, this.trainerNewFocusTarget - this.trainerReviewBlendStart);
+ },
+ trainerExerciseUnlockAttempts() {
+ const unlockTarget = this.trainerNewFocusTarget + Math.ceil((this.effectiveExercises?.length || 0) * 0.25);
+ return Math.min(28, Math.max(6, unlockTarget));
+ },
+ currentReviewShare() {
+ if (!this.hasPreviousVocab) {
+ return 0;
+ }
+ const progressPastBlendStart = Math.max(0, this.vocabTrainerCurrentAttempts - this.trainerReviewBlendStart);
+ const normalizedRamp = Math.min(1, progressPastBlendStart / this.trainerReviewRampWindow);
+ return Math.min(0.55, normalizedRamp * 0.55);
+ },
canAccessExercises() {
if (!this.hasExercises) return false;
const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review';
@@ -747,9 +790,9 @@ export default {
},
exerciseUnlockHint() {
if (this.hasPreviousVocab) {
- return 'Beantworte zuerst einige Wiederholungsfragen aus älteren Lektionen richtig. Danach wird die Kapitel-Prüfung freigeschaltet.';
+ return `Lerne zuerst die neuen Inhalte der Lektion und arbeite dich durch ungefähr ${this.trainerExerciseUnlockAttempts} Trainerfragen. Ältere Vokabeln werden dabei nach und nach zugemischt.`;
}
- return 'Arbeite zuerst kurz mit dem Vokabeltrainer der aktuellen Lektion. Danach wird die Kapitel-Prüfung freigeschaltet.';
+ return `Arbeite zuerst durch ungefähr ${this.trainerExerciseUnlockAttempts} Trainerfragen aus dieser Lektion. Danach wird die Kapitel-Prüfung freigeschaltet.`;
},
/** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */
effectiveExercises() {
@@ -878,19 +921,13 @@ export default {
return;
}
- const minimumAttempts = this.hasPreviousVocab ? 8 : 6;
+ const minimumAttempts = this.trainerExerciseUnlockAttempts;
const successRate = this.vocabTrainerTotalAttempts > 0
? (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100
: 0;
+ const currentLessonReady = this.vocabTrainerCurrentAttempts >= this.trainerNewFocusTarget;
- if (this.hasPreviousVocab) {
- if (this.vocabTrainerPhase === 'mixed' && this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) {
- this.exercisePreparationCompleted = true;
- }
- return;
- }
-
- if (this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) {
+ if (currentLessonReady && this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) {
this.exercisePreparationCompleted = true;
}
},
@@ -1052,6 +1089,8 @@ export default {
this.vocabTrainerPool = [];
this.vocabTrainerMixedPool = [];
this.vocabTrainerPhase = 'current';
+ this.vocabTrainerCurrentAttempts = 0;
+ this.vocabTrainerReviewAttempts = 0;
// Reset Flags
this.isCheckingLessonCompletion = false;
this.isNavigatingToNext = false;
@@ -1471,14 +1510,14 @@ export default {
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0;
+ this.vocabTrainerCurrentAttempts = 0;
+ this.vocabTrainerReviewAttempts = 0;
this.vocabTrainerStats = {};
// Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion)
this.vocabTrainerMixedPool = this._buildMixedPool();
- this.vocabTrainerPhase = this.vocabTrainerMixedPool.length > 0 ? 'mixed' : 'current';
+ this.vocabTrainerPhase = 'current';
this.vocabTrainerMixedAttempts = 0;
- this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed'
- ? [...this.vocabTrainerMixedPool]
- : [...this.importantVocab];
+ this.vocabTrainerPool = [...this.importantVocab];
debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
debugLog('[VocabLessonView] Rufe nextVocabQuestion auf');
this.$nextTick(() => {
@@ -1491,6 +1530,8 @@ export default {
this.vocabTrainerAutoSwitchedToTyping = false;
this.vocabTrainerPhase = 'current';
this.vocabTrainerMixedAttempts = 0;
+ this.vocabTrainerCurrentAttempts = 0;
+ this.vocabTrainerReviewAttempts = 0;
this.vocabTrainerMixedPool = [];
this.currentVocabQuestion = null;
this.vocabTrainerAnswer = '';
@@ -1517,44 +1558,16 @@ export default {
return this.vocabTrainerStats[key];
},
checkVocabModeSwitch() {
- const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase
- const MIXED_LIMIT = 40; // Anzahl gemischter Vokabeln aus alten Lektionen
-
this.updateExerciseUnlockState();
- if (this.vocabTrainerPhase === 'current') {
- // Phase 1: Aktuelle Lektion - nach MC_THRESHOLD Versuchen mit 80% → Wechsel zu Mixed oder Typing
- if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MC_THRESHOLD) {
- const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
- if (successRate >= 80) {
- // Wechsel zur Mixed-Phase (falls alte Vokabeln vorhanden)
- if (this.vocabTrainerMixedPool.length > 0) {
- debugLog('[VocabLessonView] Wechsel zu Mixed-Phase mit', this.vocabTrainerMixedPool.length, 'alten Vokabeln');
- this.vocabTrainerPhase = 'mixed';
- this.vocabTrainerPool = [...this.vocabTrainerMixedPool];
- this.vocabTrainerMixedAttempts = 0;
- // Stats zurücksetzen für neue Phase
- this.vocabTrainerCorrect = 0;
- this.vocabTrainerWrong = 0;
- this.vocabTrainerTotalAttempts = 0;
- } else {
- // Kein Mixed-Pool → direkt zu Typing
- this.vocabTrainerMode = 'typing';
- this.vocabTrainerAutoSwitchedToTyping = true;
- this.vocabTrainerPool = [...this.importantVocab];
- this.vocabTrainerCorrect = 0;
- this.vocabTrainerWrong = 0;
- this.vocabTrainerTotalAttempts = 0;
- }
- }
- }
- } else if (this.vocabTrainerPhase === 'mixed') {
- // Phase 2: Gemischte Wiederholung - nach MIXED_LIMIT Versuchen → Wechsel zu Typing mit allen Vokabeln
- if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MIXED_LIMIT) {
+ this.vocabTrainerPhase = this.hasPreviousVocab && this.currentReviewShare > 0 ? 'mixed' : 'current';
+
+ if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= this.trainerExerciseUnlockAttempts) {
+ const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
+ if (successRate >= 80) {
debugLog('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing');
this.vocabTrainerMode = 'typing';
this.vocabTrainerAutoSwitchedToTyping = true;
- // Im Typing: Pool aus aktuellen + alten Vokabeln kombinieren
this.vocabTrainerPool = [...this.importantVocab, ...this.vocabTrainerMixedPool];
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
@@ -1566,12 +1579,9 @@ export default {
// Wechsle zurück zu Multiple Choice
this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false;
- // Zurück zur aktuellen Phase mit passendem Pool
- if (this.vocabTrainerPhase === 'mixed') {
- this.vocabTrainerPool = [...this.vocabTrainerMixedPool];
- } else {
- this.vocabTrainerPool = [...this.importantVocab];
- }
+ this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed'
+ ? [...this.importantVocab, ...this.vocabTrainerMixedPool]
+ : [...this.importantVocab];
// Reset Stats für Multiple Choice Modus
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
@@ -1667,15 +1677,33 @@ export default {
// Prüfe ob Modus-Wechsel nötig ist
this.checkVocabModeSwitch();
- // Wähle zufällige Vokabel
- const randomIndex = Math.floor(Math.random() * this.vocabTrainerPool.length);
- const vocab = this.vocabTrainerPool[randomIndex];
+ let questionSource = 'current';
+ let sourcePool = this.importantVocab;
+
+ if (this.vocabTrainerMode === 'typing') {
+ sourcePool = this.vocabTrainerPool;
+ if (this.vocabTrainerMixedPool.length > 0 && Math.random() < 0.35) {
+ questionSource = 'review';
+ }
+ } else if (this.vocabTrainerMixedPool.length > 0 && this.currentReviewShare > 0 && Math.random() < this.currentReviewShare) {
+ sourcePool = this.vocabTrainerMixedPool;
+ questionSource = 'review';
+ }
+
+ if (!sourcePool || sourcePool.length === 0) {
+ sourcePool = this.importantVocab;
+ questionSource = 'current';
+ }
+
+ const randomIndex = Math.floor(Math.random() * sourcePool.length);
+ const vocab = sourcePool[randomIndex];
this.vocabTrainerDirection = Math.random() < 0.5 ? 'L2R' : 'R2L';
this.currentVocabQuestion = {
vocab: vocab,
prompt: this.vocabTrainerDirection === 'L2R' ? vocab.learning : vocab.reference,
answer: this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning,
- key: this.getVocabKey(vocab)
+ key: this.getVocabKey(vocab),
+ source: questionSource
};
debugLog('[VocabLessonView] Neue Frage erstellt:', this.currentVocabQuestion.prompt);
@@ -1693,7 +1721,7 @@ export default {
// Wichtig: Der Prompt (die Frage) darf nicht in den Optionen erscheinen
this.vocabTrainerChoiceOptions = this.buildChoiceOptions(
this.currentVocabQuestion.answer,
- this.vocabTrainerPool,
+ [...this.importantVocab, ...this.vocabTrainerMixedPool],
this.currentVocabQuestion.prompt // Exkludiere den Prompt
);
debugLog('[VocabLessonView] Choice-Optionen erstellt:', this.vocabTrainerChoiceOptions);
@@ -1736,6 +1764,11 @@ export default {
const stats = this.getVocabStats(this.currentVocabQuestion.vocab);
stats.attempts++;
this.vocabTrainerTotalAttempts++;
+ if (this.currentVocabQuestion.source === 'review') {
+ this.vocabTrainerReviewAttempts++;
+ } else {
+ this.vocabTrainerCurrentAttempts++;
+ }
if (this.vocabTrainerLastCorrect) {
this.vocabTrainerCorrect++;
@@ -2445,6 +2478,11 @@ export default {
margin-bottom: 4px;
}
+.trainer-progress-row {
+ font-size: 0.92em;
+ color: #5b4636;
+}
+
.vocab-trainer-start {
text-align: center;
}