From 29fc270c0e9f3724e43515bbd25e8ba6ca774023 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 24 Jun 2026 22:36:29 +0300 Subject: [PATCH] =?UTF-8?q?fix(assistant):=20=C2=AB=D0=97=D0=B0=D0=B1?= =?UTF-8?q?=D1=8B=D1=82=D1=8C=20=D0=B2=D1=81=D1=91=C2=BB=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D1=81=D0=B1=D1=80=D0=B0=D1=81=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=20=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B8?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D0=B4=D0=BD=D1=8B=D0=B9=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clearMemory ставит точку отсчёта asst_forget_ (datetime now); слабые предметы/темы в _studentProfile считаются только по активности после неё, так что панель памяти видимо очищается. Кнопка «Забыть всё» в виджете показывается лишь при наличии заметок/слабых тем, профиль помечен как авто-обновляемый. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/controllers/assistantController.js | 21 ++++++++++++------- frontend/js/assistant.js | 5 +++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/backend/src/controllers/assistantController.js b/backend/src/controllers/assistantController.js index 258b5ca..d4bf384 100644 --- a/backend/src/controllers/assistantController.js +++ b/backend/src/controllers/assistantController.js @@ -156,13 +156,16 @@ function weakSubject(uid) { // Производный профиль (без LLM) — из уже накопленных сигналов. function _studentProfile(uid) { const out = { weakSubjects: [], weakTopics: [], exam: null, streak: 0 }; + // «Забыть всё» ставит точку отсчёта: производный профиль учитывает только активность ПОСЛЕ неё. + let forget = null; + try { const fr = db.prepare("SELECT value FROM app_settings WHERE key = ?").get('asst_forget_' + uid); forget = (fr && fr.value) || null; } catch (e) {} try { out.weakSubjects = db.prepare(` SELECT s.name AS name, ROUND(AVG(ts.score * 100.0 / ts.total)) AS avg, COUNT(*) AS n FROM test_sessions ts JOIN subjects s ON s.id = ts.subject_id - WHERE ts.user_id = ? AND ts.status = 'completed' AND ts.total > 0 + WHERE ts.user_id = ? AND ts.status = 'completed' AND ts.total > 0${forget ? ' AND ts.finished_at > ?' : ''} GROUP BY ts.subject_id HAVING n >= 2 AND avg < 70 ORDER BY avg ASC LIMIT 3 - `).all(uid).map(r => ({ name: r.name, avg: r.avg })); + `).all(...(forget ? [uid, forget] : [uid])).map(r => ({ name: r.name, avg: r.avg })); } catch (e) {} try { const cand = {}; // трудные темы по ВСЕМ предметам: банк тестов + экзамен @@ -170,17 +173,17 @@ function _studentProfile(uid) { db.prepare(` SELECT t.name AS topic, COUNT(*) AS attempts, SUM(ua.is_correct) AS correct FROM user_answers ua JOIN questions q ON q.id = ua.question_id JOIN topics t ON t.id = q.topic_id - WHERE ua.session_id IN (SELECT id FROM test_sessions WHERE user_id = ? AND status = 'completed') + WHERE ua.session_id IN (SELECT id FROM test_sessions WHERE user_id = ? AND status = 'completed')${forget ? ' AND ua.answered_at > ?' : ''} GROUP BY q.topic_id HAVING attempts >= 3 - `).all(uid).forEach(r => { cand[r.topic] = { topic: r.topic, attempts: r.attempts, correct: r.correct || 0 }; }); + `).all(...(forget ? [uid, forget] : [uid])).forEach(r => { cand[r.topic] = { topic: r.topic, attempts: r.attempts, correct: r.correct || 0 }; }); } catch (e) {} try { db.prepare(` SELECT et.topic AS topic, COUNT(*) AS attempts, SUM(ea.is_correct) AS correct FROM exam_attempts ea JOIN exam_tasks et ON et.id = ea.exam_task_id - WHERE ea.user_id = ? AND et.topic IS NOT NULL AND et.topic <> '' + WHERE ea.user_id = ? AND et.topic IS NOT NULL AND et.topic <> ''${forget ? ' AND ea.created_at > ?' : ''} GROUP BY et.topic HAVING attempts >= 3 - `).all(uid).forEach(r => { + `).all(...(forget ? [uid, forget] : [uid])).forEach(r => { const c = cand[r.topic]; if (c) { c.attempts += r.attempts; c.correct += (r.correct || 0); } else cand[r.topic] = { topic: r.topic, attempts: r.attempts, correct: r.correct || 0 }; @@ -319,7 +322,11 @@ function clearMemory(req, res) { const uid = req.user.id, id = req.params.id ? Number(req.params.id) : null; try { if (id) db.prepare('DELETE FROM assistant_memory WHERE id = ? AND user_id = ?').run(id, uid); - else db.prepare('DELETE FROM assistant_memory WHERE user_id = ?').run(uid); + else { + db.prepare('DELETE FROM assistant_memory WHERE user_id = ?').run(uid); + // «Забыть всё»: сбрасываем и точку отсчёта производного профиля (слабые предметы/темы) + db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, datetime('now'))").run('asst_forget_' + uid); + } } catch (e) {} res.json({ ok: true }); } diff --git a/frontend/js/assistant.js b/frontend/js/assistant.js index 5cb7767..4aa2701 100644 --- a/frontend/js/assistant.js +++ b/frontend/js/assistant.js @@ -631,12 +631,13 @@ var cat = MEM_CAT[n.kind] || 'заметка'; return '
' + esc(cat) + '' + esc(n.text) + '
'; }).join(''); + var forgettable = (m.notes && m.notes.length) || (p.weakSubjects && p.weakSubjects.length) || (p.weakTopics && p.weakTopics.length); var body = m.enabled === false ? '
Персональная память выключена администратором.
' : '
' + - (prof.length ? '
' + prof.map(function (x) { return '
• ' + x + '
'; }).join('') + '
' : '') + + (prof.length ? '
' + prof.map(function (x) { return '
• ' + x + '
'; }).join('') + '
Считается по твоей активности и обновляется автоматически.
' : '') + (notes ? '
Заметки
' + notes : (prof.length ? '' : '
Пока я ничего не запомнил — позанимайся, и здесь появятся слабые темы и заметки.
')) + - ((notes || prof.length) ? '' : '') + + (forgettable ? '' : '') + '
'; openBubble( '
' + faceSVG('happy') + 'Что я о тебе помню' +