From 79fe05c6304e0d9013bcbc8a7aeb7fbc050add26 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 23 Apr 2026 13:44:31 +0200 Subject: [PATCH] feat(VocabPracticeDialog): enhance vocabulary item expansion logic - Added methods to split phrase alternatives and expand pool item alternatives, allowing for better handling of multiple valid translations and maintaining alignment between learning and reference phrases. - Updated the normalization process to incorporate these new methods, improving the accuracy and flexibility of vocabulary practice sessions. --- .../socialnetwork/VocabPracticeDialog.vue | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index a0dfaca..ba5b6d7 100644 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -600,23 +600,51 @@ export default { return (leftLooksShortFragment && rightLooksSentence) || (rightLooksShortFragment && leftLooksSentence); }, + splitPhraseAlternatives(value) { + const text = String(value || '').trim(); + if (!text) return []; + const parts = text + .split(/\s+\/\s+/) + .map((part) => String(part || '').trim()) + .filter(Boolean); + return parts.length >= 2 ? parts : [text]; + }, + expandPoolItemAlternatives(item) { + const learning = String(item?.learning || '').trim(); + const reference = String(item?.reference || '').trim(); + const learningParts = this.splitPhraseAlternatives(learning); + const referenceParts = this.splitPhraseAlternatives(reference); + + // Common case: one prompt with multiple valid translations. + // Split into separate vocab cards so each answer is trainable. + if (learningParts.length === 1 && referenceParts.length > 1) { + return referenceParts.map((ref) => ({ ...item, learning, reference: ref })); + } + if (referenceParts.length === 1 && learningParts.length > 1) { + return learningParts.map((lrn) => ({ ...item, learning: lrn, reference })); + } + + // If both sides contain parallel alternatives of equal length, keep pairs aligned. + if (learningParts.length > 1 && learningParts.length === referenceParts.length) { + return learningParts.map((lrn, idx) => ({ ...item, learning: lrn, reference: referenceParts[idx] })); + } + + return [{ ...item, learning, 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 (!this.isTrainablePair(learning, reference)) { - return null; - } + .flatMap((item, index) => this.expandPoolItemAlternatives(item).map((candidate, altIndex) => ({ candidate, index, altIndex }))) + .map(({ candidate, index, altIndex }) => { + const learning = String(candidate?.learning || '').trim(); + const reference = String(candidate?.reference || '').trim(); + if (!this.isTrainablePair(learning, reference)) return null; const key = `${this.normalize(learning)}|${this.normalize(reference)}`; - if (seen.has(key)) { - return null; - } + if (seen.has(key)) return null; seen.add(key); return { - ...item, - id: item?.id || item?.itemKey || item?.key || `${key}|${index}`, + ...candidate, + id: candidate?.id || candidate?.itemKey || candidate?.key || `${key}|${index}|${altIndex}`, learning, reference }; @@ -766,10 +794,7 @@ export default { // Handle full-answer alternatives like "A / B" as separate valid answers. // Word-level slash expansion alone does not cover this reliably. - const phraseAlternatives = base - .split(/\s+\/\s+/) - .map((part) => String(part || '').trim()) - .filter(Boolean); + const phraseAlternatives = this.splitPhraseAlternatives(base); if (phraseAlternatives.length >= 2) { const expanded = phraseAlternatives.flatMap((part) => this.expandSingleAnswerVariants(part));