feat(trainer): H — 3-уровневые подсказки + расширенная библиотека ошибок

3-уровневая подсказка (trainer.html, переиспользует solution[]):
кнопка «Подсказка» раскрывает по клику: идею (текст шага без формулы) →
первый шаг целиком → полное решение. Подпись кнопки меняется
(«Ещё подсказка» / «Показать решение» / «Решение показано»), уровень
сбрасывается на новой задаче. Пошаговый режим — без изменений.

Библиотека ошибок (analyzeMistake в _trainer_engine.js) — новые диагностики
по соотношению ответа к верному (универсально, без данных по генератору):
- ×2 → «забыли разделить на 2» (площадь треуг./трапеции/ромба, среднее);
- ÷2 → «лишний раз поделили пополам»;
- ×10 / ÷10 → «ошибка разряда: проверьте запятую».
Дополняет прежние nodivide/sign/arith; всплывает в разборе неверного ответа.

Смоук: hint-логика (inline parse + наличие узлов) OK; analyzeMistake 8/8
(2C→half, C/2→extra-half, 10C и C/10→decimal-place, −C→sign, верный→null).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-29 22:29:38 +03:00
parent b79c7c4b5f
commit 686be1faef
2 changed files with 40 additions and 7 deletions
+10
View File
@@ -561,6 +561,16 @@
// перепутан знак ответа
if (correct !== 0 && Math.abs(value + correct) <= Math.max(tol, 1e-6 * Math.abs(correct)))
return { type: 'sign', hint: 'Кажется, перепутан знак. Проверь знаки при переносе слагаемых через знак «=».' };
// забыл разделить на 2 (половина в формуле: площадь треугольника/трапеции/ромба, среднее)
if (correct !== 0 && Math.abs(value - 2 * correct) <= Math.max(tol, 1e-6 * Math.abs(2 * correct)))
return { type: 'half', hint: 'Похоже, вы забыли разделить на 2. Во многих формулах есть деление пополам (площадь треугольника, трапеции, ромба, среднее значение).' };
// лишний раз поделил пополам
if (correct !== 0 && Math.abs(value - correct / 2) <= Math.max(tol, 1e-6 * Math.abs(correct / 2)))
return { type: 'extra-half', hint: 'Кажется, вы лишний раз поделили на 2. Перечитайте формулу — возможно, делить пополам здесь не нужно.' };
// ошибка разряда (в 10 раз) — типично в десятичных и процентах
if (correct !== 0 && (Math.abs(value - 10 * correct) <= Math.max(tol, 1e-6 * Math.abs(10 * correct)) ||
Math.abs(value - correct / 10) <= Math.max(tol, 1e-6 * Math.abs(correct / 10))))
return { type: 'decimal-place', hint: 'Ответ отличается примерно в 10 раз — проверьте разряд: куда поставить запятую и на сколько умножать или делить.' };
// близкая арифметическая ошибка
if (Math.abs(value - correct) <= Math.max(1, Math.abs(correct) * 0.2))
return { type: 'arith', hint: 'Очень близко — похоже на арифметическую ошибку в вычислениях. Пересчитай аккуратно.' };
+30 -7
View File
@@ -516,7 +516,7 @@
</button>
<button class="tr-btn tr-ghost" id="tr-hint" type="button">
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7c.6.5 1 1.3 1 2.1h6c0-.8.4-1.6 1-2.1A7 7 0 0 0 12 2Z"/></svg>
Подсказка
<span id="tr-hint-label">Подсказка</span>
</button>
<button class="tr-btn tr-ai-btn" id="tr-ai" type="button" title="ИИ-репетитор объяснит">
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 4.6L18.5 9l-4.7 1.4L12 15l-1.8-4.6L5.5 9l4.7-1.4z"/><path d="M19 14l.7 1.8L21.5 16.5l-1.8.7L19 19l-.7-1.8L16.5 16.5l1.8-.7z"/></svg>
@@ -1112,6 +1112,7 @@
$('tr-solution').style.display = 'none'; $('tr-solution').innerHTML = '';
var aiBox = $('tr-ai-box'); if (aiBox) { aiBox.style.display = 'none'; aiBox.innerHTML = ''; }
lastWrong = false;
hintLevel = 0; setHintLabel('Подсказка');
var card = $('tr-card'); if (card) { card.classList.remove('tr-correct'); card.classList.remove('tr-wrong'); }
var pv = $('tr-preview'); if (pv) pv.innerHTML = '';
setMode(false);
@@ -1464,19 +1465,41 @@
}).catch(function () { bodyEl.textContent = 'ИИ-репетитор недоступен — смотри решение ниже.'; revealSolution(); });
}
$('tr-ai').addEventListener('click', aiExplain);
// 3-уровневая подсказка: идея (без формулы) → первый шаг целиком → полное решение.
// Каждый клик «Подсказка» раскрывает следующий уровень (лестница строится из solution[]).
var hintLevel = 0;
function setHintLabel(t) { var el = $('tr-hint-label'); if (el) el.textContent = t; }
$('tr-hint').addEventListener('click', function () {
if (!cur) return;
if (stepMode) {
var sol = cur.solution || [];
var idx = Math.min(stepList.length, Math.max(0, sol.length - 1));
var st = sol[idx];
var sol0 = cur.solution || [];
var idx0 = Math.min(stepList.length, Math.max(0, sol0.length - 1));
var st0 = sol0[idx0];
var fb = $('tr-stepfb'); fb.className = 'tr-feedback warn';
fb.innerHTML = 'Подсказка: ' + (st && st.latex ? (kat(st.latex, false) || esc(st.tex || '')) : esc((st && (st.tex || st.note)) || ('x = ' + fmt(cur.answer))));
fb.innerHTML = 'Подсказка: ' + (st0 && st0.latex ? (kat(st0.latex, false) || esc(st0.tex || '')) : esc((st0 && (st0.tex || st0.note)) || ('x = ' + fmt(cur.answer))));
return;
}
var sol = cur.solution || [];
// лестница уровней: [идея (если есть текст), первый шаг, полное решение]
var stages = [];
if (sol.length && sol[0].note) stages.push('concept');
if (sol.length) stages.push('firststep');
stages.push('full');
var i = Math.min(hintLevel, stages.length - 1);
var stage = stages[i];
var s = $('tr-solution');
s.innerHTML = '<h4>Подсказка</h4>' + stepHtml((cur.solution || [])[0] || { note: '', tex: 'x = ' + fmt(cur.answer), latex: null }, 1);
s.style.display = 'block';
if (stage === 'concept') {
s.innerHTML = '<h4>Подсказка</h4><div class="tr-step"><span class="tr-step-note"><span class="tr-step-n">1</span>' + renderMixed(sol[0].note) + '</span></div>';
s.style.display = 'block';
} else if (stage === 'firststep') {
s.innerHTML = '<h4>Подсказка</h4>' + stepHtml(sol[0] || { note: '', tex: 'x = ' + fmt(cur.answer), latex: null }, 1);
s.style.display = 'block';
} else {
revealSolution();
}
hintLevel = i + 1;
if (hintLevel >= stages.length) setHintLabel('Решение показано');
else setHintLabel(stages[hintLevel] === 'full' ? 'Показать решение' : 'Ещё подсказка');
});
$('tr-solve').addEventListener('click', function () { if (cur) revealAnswer(true); });
$('tr-input').addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); check(); } });