feat: verbessere SRS-Logik und füge serverseitige Zählung für fällige Items hinzu
Some checks failed
Deploy to production / deploy (push) Failing after 1m50s

This commit is contained in:
Torsten Schulz (local)
2026-06-03 16:39:12 +02:00
parent a4c053df3e
commit 8b0aa94715
2 changed files with 53 additions and 4 deletions

View File

@@ -928,6 +928,16 @@ export default class VocabService {
if (!reference || !learning) { if (!reference || !learning) {
return; 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)) { if (!glossByReference.has(reference)) {
glossByReference.set(reference, learning); glossByReference.set(reference, learning);
} }
@@ -1885,6 +1895,11 @@ export default class VocabService {
const rows = validDueRows.slice(0, limit); const rows = validDueRows.slice(0, limit);
const totalDueCount = validDueRows.length; 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 { return {
courseId: course.id, courseId: course.id,
dueAt: now.toISOString(), dueAt: now.toISOString(),
@@ -1992,6 +2007,18 @@ export default class VocabService {
} }
await item.save(); 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 { return {
itemKey: item.itemKey, itemKey: item.itemKey,
correct, correct,

View File

@@ -28,7 +28,7 @@
<div v-else-if="pool.length === 0"> <div v-else-if="pool.length === 0">
{{ $t('socialnetwork.vocab.practice.noPool') }} {{ $t('socialnetwork.vocab.practice.noPool') }}
</div> </div>
<div v-else-if="srsMode && srsQueueIds.length === 0" class="srs-finished"> <div v-else-if="srsMode && srsRemainingCount === 0" class="srs-finished">
<div class="srs-finished__title">{{ $t('socialnetwork.vocab.practice.srsFinishedTitle') }}</div> <div class="srs-finished__title">{{ $t('socialnetwork.vocab.practice.srsFinishedTitle') }}</div>
<div class="srs-finished__desc">{{ $t('socialnetwork.vocab.practice.srsFinishedDesc') }}</div> <div class="srs-finished__desc">{{ $t('socialnetwork.vocab.practice.srsFinishedDesc') }}</div>
</div> </div>
@@ -180,6 +180,7 @@ export default {
// Stored per day and course so the user can close/reopen without starting over. // Stored per day and course so the user can close/reopen without starting over.
srsSession: null, // { version, dateKey, courseId, initialTotalDue, initialDueIds, doneIds, correctCount, wrongCount } srsSession: null, // { version, dateKey, courseId, initialTotalDue, initialDueIds, doneIds, correctCount, wrongCount }
srsQueueIds: [], // remaining due ids for this session, in due order srsQueueIds: [], // remaining due ids for this session, in due order
srsServerTotalDue: null,
// session stats // session stats
correctCount: 0, correctCount: 0,
@@ -517,7 +518,8 @@ export default {
version: SRS_SESSION_STORAGE_VERSION, version: SRS_SESSION_STORAGE_VERSION,
dateKey: this.getLocalDateKey(), dateKey: this.getLocalDateKey(),
courseId: this.openParams.courseId, 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, initialDueIds: dueIds,
doneIds: [], doneIds: [],
correctCount: 0, correctCount: 0,
@@ -764,6 +766,26 @@ export default {
this.loading = true; this.loading = true;
try { try {
let res; 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.openParams.lessonId) {
if (this.allVocabs && this.openParams.courseId) { if (this.allVocabs && this.openParams.courseId) {
res = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/completed-lesson-vocabs`, { res = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/completed-lesson-vocabs`, {