feat(cefr): dual mistake ingestion, 1-tap options breakdown, and 260 curated questions

This commit is contained in:
Antigravity committed 2026-10-06 15:57:07 +03:00
1 parent a00b330f74
commit a2eb26ced5
7 files changed
+1277 -95

No files matched your search

+90 -7
View File
@@ -381,14 +381,15 @@ async function handleOptionTap(questionId, selectedOption, clickedBtn) {
${res.phonetic ? `<span class="res-phonetic">${escapeHtml(res.phonetic)}</span>` : ''}
</div>
${res.explanation ? `<p class="fb-explanation">${escapeHtml(res.explanation)}</p>` : ''}
${renderOptionsBreakdown(res.options_breakdown)}
`;
feedbackBox.classList.remove('hidden');
attachBreakdownListeners(feedbackBox, q.sentence);
}
} else {
sessionStats.mistakes++;
if (res.card_created || res.card_updated) {
sessionStats.ingested++;
}
// Two words were ingested: the correct target word and the clicked distractor
sessionStats.ingested += 2;
clickedBtn.classList.add('opt-wrong');
@@ -413,14 +414,26 @@ async function handleOptionTap(questionId, selectedOption, clickedBtn) {
${res.phonetic ? `<span class="res-phonetic">${escapeHtml(res.phonetic)}</span>` : ''}
</div>
${res.explanation ? `<p class="fb-explanation">${escapeHtml(res.explanation)}</p>` : ''}
<div class="cefr-gap-ingested-badge">
<span class="badge-icon">📥</span>
<div class="badge-text">
<strong>Авто-захват в словарь:</strong> слово добавлено со статусом «В процессе» для закрепления в утреннем SRS!
<div class="cefr-dual-ingested-box">
<div class="dual-ingested-header">
<span class="badge-icon">📥</span>
<strong>В ваш словарь SRS («В процессе») добавлены оба слова:</strong>
</div>
<div class="dual-ingested-chips">
<div class="ingested-chip chip-target">
<span class="chip-label">Верный ответ:</span>
<strong>${escapeHtml(res.correct_option)}</strong> — ${escapeHtml(res.translation || '')}
</div>
<div class="ingested-chip chip-chosen">
<span class="chip-label">Ваш выбор:</span>
<strong>${escapeHtml(res.selected_option)}</strong> — ${escapeHtml(res.selected_translation || '')}
</div>
</div>
</div>
${renderOptionsBreakdown(res.options_breakdown)}
`;
feedbackBox.classList.remove('hidden');
attachBreakdownListeners(feedbackBox, q.sentence);
}
}
@@ -478,3 +491,73 @@ function renderQuizSummary() {
sumSrsBtn.classList.toggle('hidden', ingested === 0 && mistakes === 0);
}
}
// -----------------------------------------------------------------------
// Render Options Breakdown (All 4 options with 1-tap SRS Ingestion)
// -----------------------------------------------------------------------
function renderOptionsBreakdown(breakdown) {
if (!breakdown || breakdown.length === 0) return '';
return `
<div class="cefr-words-breakdown">
<div class="words-breakdown-header">
<span class="wb-title"><i class="ph-books"></i> Слова из этого вопроса:</span>
<span class="wb-hint">Добавьте в словарь слова, перевод которых хотите запомнить</span>
</div>
<div class="words-breakdown-grid">
${breakdown.map(b => {
const isTarget = b.is_correct;
const wasChosenMistake = b.was_selected && !b.is_correct;
const inLib = b.in_library || b.auto_added;
let actionHtml = '';
if (b.auto_added) {
actionHtml = `<span class="wb-badge badge-auto-added"><i class="ph-check-circle-fill"></i> В словаре (SRS)</span>`;
} else if (inLib) {
actionHtml = `<span class="wb-badge badge-in-lib"><i class="ph-check"></i> В библиотеке</span>`;
} else {
actionHtml = `<button type="button" class="btn btn-xs btn-outline btn-add-option-word" data-term="${escapeHtml(b.term)}">+ В словарь</button>`;
}
return `
<div class="word-breakdown-card ${isTarget ? 'is-target' : ''} ${wasChosenMistake ? 'is-chosen-mistake' : ''}">
<div class="wb-card-top">
<strong class="wb-term">${escapeHtml(b.term)}</strong>
${b.pos ? `<span class="wb-pos">${escapeHtml(b.pos)}</span>` : ''}
</div>
<div class="wb-trans">${escapeHtml(b.translation || '—')}</div>
<div class="wb-action-wrap">${actionHtml}</div>
</div>
`;
}).join('')}
</div>
</div>
`;
}
function attachBreakdownListeners(container, contextSentence) {
if (!container) return;
container.querySelectorAll('.btn-add-option-word').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const term = btn.dataset.term;
if (!term) return;
btn.disabled = true;
btn.innerHTML = '...';
try {
await api.cefr.addWord(currentLevel, term, contextSentence || '');
sessionStats.ingested++;
btn.outerHTML = '<span class="wb-badge badge-just-added"><i class="ph-check-circle-fill"></i> Добавлено!</span>';
showToast(`Слово «${term}» добавлено в утренний тренажёр SRS!`, 'success');
} catch (err) {
btn.disabled = false;
btn.textContent = '+ В словарь';
showToast(`Ошибка: ${err.message}`, 'error');
}
});
});
}