From b5f377b1bf361f7f9963f9532b804f4a2909e69e Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 4 Oct 2026 16:12:53 +0300 Subject: [PATCH] =?UTF-8?q?feat(lesson-ai):=20=D0=BD=D0=B0=D1=87=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=20=D1=84=D1=80=D0=B0=D0=B7=D1=8B=20=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D1=91=D1=82=D0=B0=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8?= =?UTF-8?q?=D1=82=20=D0=BE=D1=82=20=D0=BD=D0=BE=D0=BC=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Отчёт о занятии теперь начинается по-разному в зависимости от номера темы: «начали знакомство» для первого занятия модуля (1/N), «продолжили изучение» для промежуточного (2…N-1 из N) и «завершили изучение» для последнего (N/N). Раньше первое предложение всегда звучало одинаково, из-за чего первое и последнее занятие модуля читались в отчёте для родителя одинаково. Позицию разбирает воркер, а не модель. На локальной модели (qwen2.5-1.5b) проверка показала, что с одной только подсказкой в промпте «2/2» уверенно читается как «продолжали» вместо «завершили», а в отчёте без номера темы модель выдумывала начало и подмешивала «Scratch Jr» из примера. Поэтому номер разбирается детерминированно и передаётся готовой строкой. Что добавлено: - lessonTopicPosition в worker.js — разбор «N/M» в конце свободного текста темы: { kind, n, total, label }, kind = first | middle | last. N = M проверяется раньше N = 1, поэтому «1/1» — это last. Мусорные значения (N > M, M = 0, пустая тема) дают null, и строка подсказки не добавляется. Хелпер экспортирован ради юнит-проверок - в контекст запроса к модели добавлена строка «Позиция темы: последнее занятие модуля (2 из 2)»; модель только выбирает формулировку по ней Промпт (LESSON_AI_DEFAULT_PROMPT, 2348 → 4523 симв.): новый блок «НАЧАЛО ФРАЗЫ» с обязательными глаголами для каждого случая, приоритет системной подсказки над «N/M», нейтральное начало без номера, пример с тремя вариантами и запрет переносить «модуль» и цифры в текст отчёта. Четыре копии промпта (server.js, db/init.sql, db/migration.sql INSERT и UPDATE) синхронизированы побайтово; в value IN миграции добавлен прежний дефолт, иначе правка не доехала бы до существующих БД. Проверено на живой модели: 1/2 → «начали знакомство», 2/2 → «завершили изучение», 3/5 → «продолжили», 1/7 → «начали знакомство», без номера — нейтрально и без утечки из примера; e2e через POST /api/lesson-reports с темой «2/2» дал ai_status = done. AGENTS.md §3d: описание lesson_ai_prompt всё ещё называло его «шаблоном делового сообщения», а список контекста воркера не упоминал тему — исправлено, добавлены два пункта про позицию темы. --- AGENTS.md | 6 ++- db/init.sql | 20 +++++++-- db/migration.sql | 107 +++++++++++++++++++++++++++++++++++++---------- server.js | 20 +++++++-- worker.js | 20 ++++++++- 5 files changed, 141 insertions(+), 32 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 860095a..879be87 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,11 +113,13 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo ### 3d. Отчёты о занятии и проверка по шаблону (`server.js`, `worker.js`, `public/`) - **Таблицы**: `lesson_reports` (+ `text_original`, `text_ai`, `ai_status`, `ai_checked_at`, `ai_error`) и `lesson_report_versions` (история версий: `text`, `source` = `manual` | `ai` | `restore`). Схема — в `db/init.sql`, `db/migration.sql` и `ensureLessonReportsTable()` -- **Настройки**: `lesson_ai_enabled` (`'true'` / `'false'` — общий выклюжатель) и `lesson_ai_prompt` (шаблон делового сообщения + правила, пример вставляется в `db/init.sql`, `db/migration.sql` и в `LESSON_AI_DEFAULT_PROMPT` в `server.js`). Раздел в UI — `sec-lesson-ai` на `public/settings.html` +- **Настройки**: `lesson_ai_enabled` (`'true'` / `'false'` — общий выклюжатель) и `lesson_ai_prompt` (промпт редактора сообщений тьютора + правила, пример вставляется в `db/init.sql`, `db/migration.sql` и в `LESSON_AI_DEFAULT_PROMPT` в `server.js`). Раздел в UI — `sec-lesson-ai` на `public/settings.html` Лимит промпта в `PUT /api/settings` — 8000 символов +- **Начало фразы зависит от номера темы**: `lesson_reports.topic` — свободный текст, номер вида `N/M` тьютор пишет в конце строки (`Photoshop 3/5`). Позицию разбирает **воркер**, а не модель: `lessonTopicPosition()` в `worker.js` возвращает `{ kind, n, total, label }` (`kind` = `first` | `middle` | `last`; `N = M` проверяется раньше `N = 1`, поэтому `1/1` — это `last`) и добавляет в контекст готовую строку `Позиция темы: