diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index 2227d4f..acd920b 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -928,6 +928,16 @@ export default class VocabService { if (!reference || !learning) { return; } + + // Heuristik: Vermeide, einzelne sehr kurze Ziel-Token (z.B. "ko") + // automatisch mit mehrwortigen Glosses (z.B. "Ich arbeite") zu koppeln. + // Das verhindert fehlerhafte Glosszuweisungen für Partikeln/Pronomina. + const compactRefLen = reference.replace(/\s+/g, '').length; + const learningWordCount = this._wordCount(learning); + if (compactRefLen <= 3 && learningWordCount > 1) { + return; + } + if (!glossByReference.has(reference)) { glossByReference.set(reference, learning); } @@ -1885,6 +1895,11 @@ export default class VocabService { const rows = validDueRows.slice(0, limit); const totalDueCount = validDueRows.length; + // Debug: Logge Anzahl fälliger SRS-Items (nur in Entwicklung sichtbar) + try { + console.debug('[VocabService] getCourseSrsDue', { userId: user.id, courseId: course.id, totalDueCount }); + } catch (_) {} + return { courseId: course.id, dueAt: now.toISOString(), @@ -1992,6 +2007,18 @@ export default class VocabService { } await item.save(); + // Debug: Logge SRS-Updates, damit wir sehen, ob Reviews ankommen + try { + console.debug('[VocabService] reviewSrsItem saved', { + userId: user.id, + courseId: courseId, + itemKey: item.itemKey, + correct: correct, + nextDueAt: this._normalizeIsoDate(item.nextDueAt), + stage: item.stage + }); + } catch (_) {} + return { itemKey: item.itemKey, correct, diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index f938ca3..45581eb 100644 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -28,7 +28,7 @@
{{ $t('socialnetwork.vocab.practice.noPool') }}
-
+
{{ $t('socialnetwork.vocab.practice.srsFinishedTitle') }}
{{ $t('socialnetwork.vocab.practice.srsFinishedDesc') }}
@@ -180,6 +180,7 @@ export default { // Stored per day and course so the user can close/reopen without starting over. srsSession: null, // { version, dateKey, courseId, initialTotalDue, initialDueIds, doneIds, correctCount, wrongCount } srsQueueIds: [], // remaining due ids for this session, in due order + srsServerTotalDue: null, // session stats correctCount: 0, @@ -517,7 +518,8 @@ export default { version: SRS_SESSION_STORAGE_VERSION, dateKey: this.getLocalDateKey(), courseId: this.openParams.courseId, - initialTotalDue: dueIds.length, + // Prefer server-reported total due count when available (shows full-course due count) + initialTotalDue: Number.isFinite(Number(this.srsServerTotalDue)) ? Number(this.srsServerTotalDue) : dueIds.length, initialDueIds: dueIds, doneIds: [], correctCount: 0, @@ -761,9 +763,29 @@ export default { this.next(); return; } - this.loading = true; + this.loading = true; try { let res; + let courseDueRes = null; + // Wenn SRS-Modus auf Kurs-Ebene, lade kursweite fällige Items (Server liefert totalDueCount) + if (this.srsMode && this.openParams.courseId && !this.openParams.lessonId) { + try { + courseDueRes = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/srs/due`, { params: { limit: 100 } }); + } catch (e) { + // ignore, fallback to lesson/chapters/vocabs endpoints + courseDueRes = null; + } + } + if (courseDueRes && Array.isArray(courseDueRes.data?.items) && courseDueRes.data.items.length > 0) { + // Map server items to pool shape (use itemKey as id) + this.pool = this.normalizePool((courseDueRes.data.items || []).map((it) => ({ + id: it.itemKey, + learning: it.learning, + reference: it.reference, + lessonId: it.lessonId || null + }))); + this.srsServerTotalDue = Number.isFinite(Number(courseDueRes.data?.totalDueCount)) ? Number(courseDueRes.data.totalDueCount) : null; + } else { if (this.openParams.lessonId) { if (this.allVocabs && this.openParams.courseId) { res = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/completed-lesson-vocabs`, { @@ -782,7 +804,7 @@ export default { } else { res = await apiClient.get(`/api/vocab/chapters/${this.openParams.chapterId}/vocabs`); this.pool = this.normalizePool(res.data?.vocabs || []); - } + } } catch (e) { console.error('Reload pool failed:', e); this.pool = [];