From f64c923db69551e85fd5bac7d9147b072f08a79f Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 20 Apr 2026 08:30:58 +0200 Subject: [PATCH] feat(VocabService, VocabPracticeDialog): enhance text analysis and SRS validation - Improved text analysis methods in VocabService to better identify instructional-like texts and task-related prompts, enhancing vocabulary training quality. - Updated VocabPracticeDialog to include a new method for checking instruction-like text, ensuring more accurate filtering of trainable pairs. - Incremented SRS session storage version to reflect changes in session management and data handling. - Refactored normalization and validation logic to maintain consistency across vocabulary entries and improve user experience. --- backend/services/vocabService.js | 12 +++++++- .../socialnetwork/VocabPracticeDialog.vue | 28 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index e7df88d..8ecf39e 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -72,7 +72,17 @@ export default class VocabService { return false; } - return /^(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|beschreibe|bilde|wähle|ordne|übersetze|nenne|nenn|beginne|verwende|reagiere|kombiniere|spiele|löse|beantworte|ergänze|formuliere)\b/i.test(text); + const normalized = text.toLowerCase().normalize('NFKC'); + const startsWithTaskVerb = /^(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|erzaehle|beschreibe|bilde|wähle|waehle|ordne|übersetze|uebersetze|nenne|nenn|beginne|verwende|nutze|reagiere|kombiniere|spiele|löse|loese|beantworte|ergänze|ergaenze|formuliere|lies|entscheide|zeige)\b/i.test(normalized); + const startsWithTakeTask = /^nimm\b/i.test(normalized) + && ( + /\b(ein|eine|einen|zwei|drei|vier|fünf|fuenf|sechs|sieben|acht|neun|zehn|\d+)\b/i.test(normalized) + || /\b(w[oö]rter|verben|gegenstände|gegenstaende|sätze|saetze|muster|beispiele)\b/i.test(normalized) + ); + const containsTaskChain = /\b(und|,)\s*(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|erzaehle|beschreibe|bilde|wähle|waehle|ordne|übersetze|uebersetze|nenne|nenn|verwende|nutze|reagiere|kombiniere|spiele|löse|loese|beantworte|ergänze|ergaenze|formuliere|lies|entscheide|zeige)\b/i.test(normalized); + const containsPracticeMarker = /\b(laut|jeweils|zu jedem|zu jeder|umgebung|alltagsszene|rollenspiel|mini-dialog|szene)\b/i.test(normalized); + + return startsWithTaskVerb || startsWithTakeTask || (containsTaskChain && containsPracticeMarker); } _isTrainableSrsPair(entry) { diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index de39619..7341c00 100644 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -138,7 +138,7 @@ import DialogWidget from '@/components/DialogWidget.vue'; import apiClient from '@/utils/axios.js'; const PRACTICE_MIN_EXPOSURES = 3; -const SRS_SESSION_STORAGE_VERSION = 1; +const SRS_SESSION_STORAGE_VERSION = 2; export default { name: 'VocabPracticeDialog', @@ -407,13 +407,37 @@ export default { .trim(); return normalized.replace(/\s+/g, ''); }, + isInstructionLikeText(value) { + const text = String(value || '').trim(); + if (!text) return false; + const wordCount = text.split(/\s+/).filter(Boolean).length; + if (wordCount < 3) return false; + + const normalized = text.toLowerCase().normalize('NFKC'); + const startsWithTaskVerb = /^(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|erzaehle|beschreibe|bilde|wähle|waehle|ordne|übersetze|uebersetze|nenne|nenn|beginne|verwende|nutze|reagiere|kombiniere|spiele|löse|loese|beantworte|ergänze|ergaenze|formuliere|lies|entscheide|zeige)\b/i.test(normalized); + const startsWithTakeTask = /^nimm\b/i.test(normalized) + && ( + /\b(ein|eine|einen|zwei|drei|vier|fünf|fuenf|sechs|sieben|acht|neun|zehn|\d+)\b/i.test(normalized) + || /\b(w[oö]rter|verben|gegenstände|gegenstaende|sätze|saetze|muster|beispiele)\b/i.test(normalized) + ); + const containsTaskChain = /\b(und|,)\s*(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|erzaehle|beschreibe|bilde|wähle|waehle|ordne|übersetze|uebersetze|nenne|nenn|verwende|nutze|reagiere|kombiniere|spiele|löse|loese|beantworte|ergänze|ergaenze|formuliere|lies|entscheide|zeige)\b/i.test(normalized); + const containsPracticeMarker = /\b(laut|jeweils|zu jedem|zu jeder|umgebung|alltagsszene|rollenspiel|mini-dialog|szene)\b/i.test(normalized); + + return startsWithTaskVerb || startsWithTakeTask || (containsTaskChain && containsPracticeMarker); + }, + isTrainablePair(learning, reference) { + if (!learning || !reference || this.normalize(learning) === this.normalize(reference)) { + return false; + } + return !this.isInstructionLikeText(learning) && !this.isInstructionLikeText(reference); + }, normalizePool(items = []) { const seen = new Set(); return (Array.isArray(items) ? items : []) .map((item, index) => { const learning = String(item?.learning || '').trim(); const reference = String(item?.reference || '').trim(); - if (!learning || !reference || this.normalize(learning) === this.normalize(reference)) { + if (!this.isTrainablePair(learning, reference)) { return null; } const key = `${this.normalize(learning)}|${this.normalize(reference)}`;