fix: resolve bugs 01 through 08 and set Ollama TTL to 1h

This commit is contained in:
Antigravity committed 2026-10-07 19:19:49 +03:00
1 parent a01bf64445
commit 9bbc9473de
14 files changed
+366 -108

No files matched your search

+36 -53
View File
@@ -472,60 +472,43 @@
---
## 🐛 Зафиксированные баги (Ждут команды на исправление)
### 🐛 Устранённые баги (Решены и протестированы)
- [ ] **BUG-01: Не открывается окно редактирования слова в словаре на главной**:
- **Симптом**: при клике на иконку ✏️ у карточки слова в списке словаря ничего не происходит.
- **Диагностика**: в `app/static/js/modules/dictionary.js` функция `openEditModal` ищет элемент по id `'edit-card-modal'` (`document.getElementById('edit-card-modal')`), в то время как в `app/static/index.html` модальное окно объявлено с id `'edit-modal'` (`<div id="edit-modal" class="modal-backdrop hidden">`). Из-за несовпадения ID селектор возвращает `null`, и вызов `openModal(null)` тихо игнорируется.
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-02: Отсутствует возможность прослушать слово и предложение из словаря**:
- **Симптом**: в списке карточек словаря на главной странице нет кнопок 🔊 для воспроизведения аудио слова и контекстного предложения.
- **Диагностика**: в шаблоне рендеринга `renderCardsList` (`app/static/js/modules/dictionary.js`) внутри `.card-top` и `.card-context` не отрисовываются кнопки аудиовызова с вызовом `playAudio()` / `playTextSpeech()`, хотя модуль `ui.js` полностью поддерживает их воспроизведение.
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-03: Спойлер корня слова в переводе грамматических форм в тестах (например, `(прош. вр. от explain)`)**:
- **Симптом**: при добавлении словоформ в словарь (например, `explained`) в переводе сохраняются пометки словообразования: `объяснять, объяснить (прош. вр. от explain)`. Во время теста (SRS / Урок) отображение этого перевода напрямую спойлерит английское слово `explain`.
- **Диагностика**: в `app/dictionary.py` функция `clean_russian_translation` (строки 235–277) намеренно защищает грамматические пометки вида `(прош. вр. от ...)`, и они попадают в поле `card.translation`. Во время тестирования в `srs.js` и `lesson.js` эта строка выводится в вопросе теста целиком без фильтрации.
- **Варианты решения**: по умолчанию скрывать пометку под интерактивный кликабельный спойлер `[+ грамматика]` или вырезать английский корень из вопроса теста через regex `/\s*\([^)]*от\s+[a-zA-Z\x27-]+\)/gi`.
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-04: В квизе «4 варианта» правильный ответ всегда находится на 1-м месте**:
- **Симптом**: в комплексном уроке и SRS при выборе режима «4 варианта» правильный ответ всегда гарантированно оказывается первым вариантом.
- **Диагностика**: в `app/static/js/modules/ui.js` (строка 28) функция `shuffleArray(array)` возвращает новый скопированный массив (`return arr;`), не мутируя оригинал на месте. В `app/static/js/modules/lesson.js` (строка 778) и `app/static/js/modules/srs.js` (строка 461) написано `shuffleArray(options);`, но результат возврата игнорируется (`options` не перезаписывается), из-за чего правильный ответ, добавленный первым в `[correctText, ...]`, всегда остаётся на позиции 0.
- **Варианты решения**: переписать вызовы на `options = shuffleArray(options);` (или сделать `shuffleArray` мутирующим).
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-05: Поломка верстки и отображение сырых HTML-тегов/JSON при импорте статей Википедии (например, Linux)**:
- **Симптом**: при открытии импортированной из Википедии статьи «Linux» в читалке в 1-й главе («Overview») отображается фрагмент сырого JSON (`"},"title":{"wt":"Linux"}...`), а в главе «Pronunciation» — искажённый тег `<tdTimes New Roman...` и сырые теги цитат `{{ cite newsgroup...`.
- **Диагностика**:
1. В `app/reader.py` (`clean_wiki_html`): MediaWiki Parsoid HTML вставляет метаданные шаблонов в атрибут `data-mw='{"parts":...}'` (например, у `<span class="mw-empty-elt">` и `<table class="infobox">`). Внутри значения этого атрибута содержатся незаэкранированные угловые скобки (например, `<ref name="...">`). Регулярное выражение удаления HTML-тегов `re.sub(r'<[^>]+>', ' ', s)` встречает первый же символ `>` внутри кавычек атрибута, преждевременно обрывает удаление тега, и остаток JSON-строки вываливается в текст статьи.
2. В `clean_data_table` regex удаления стилей `style=["'][^"']*["']` спотыкается о вложенные одинарные кавычки в `style="font-family:'Times New Roman'..."`, оставляя сломанный HTML `<tdTimes New Roman...`.
3. В `app/static/js/modules/reader.js` повреждённые теги не распознаются парсером таблиц и выводятся как обычный экранированный текст.
- **Варианты решения**: предварительно полностью вырезать атрибуты `data-mw` и `about` регулярным выражением или через санитизацию до удаления тегов, улучшить regex очистки стилей таблиц и перезагрузить повреждённые статьи.
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-06: Не работает поиск и обратный перевод с русского на английский в словаре**:
- **Симптом**: при попытке ввести в поле перевода словаря русское слово (например, «собака» или «яблоко») перевод не находится, возвращается пустой результат или ошибка.
- **Диагностика**:
1. В `app/dictionary.py` функция `lookup_term` жестко закодирована только под направление «English -> Russian»:
- В локальной БД поиск ведется только по полю английского слова `WHERE term = ? COLLATE NOCASE` (русские переводы хранятся в поле `translation` и по ним поиск не производится).
- Во внешнем сервисе `fetch_mymemory` жестко зашит параметр `langpair="en|ru"`, из-за чего сервис пытается переводить русское слово как английское и возвращает ошибку или пустую строку.
- Запросы к Wiktionary и FreeDictionaryAPI идут в англоязычные эндпоинты (`en.wiktionary.org`, `api.dictionaryapi.dev`), где нет русских словарных статей.
2. В интерфейсе [`app/static/js/modules/dictionary.js`](file:///home/ai-projects/services/study.qpid.pro/app/static/js/modules/dictionary.js) результат выводится в поле карточки без учёта обратного направления перевода (когда найденный английский эквивалент должен стать `term`, а введенное русское слово — `translation`).
- **Варианты решения**: автоматически определять язык ввода по наличию кириллицы `[\u0400-\u04FF]`. При кириллице: переключать `langpair` MyMemory на `"ru|en"`, выполнять обратный поиск в `offline_dictionary` по `translation`, и корректно заполнять форму добавления карточки (найденное английское слово в `term`, русский запрос в `translation`).
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-07: Ошибка загрузки этапа аудирования: `undefined is not an object (evaluating 'scenario.topic')`**:
- **Симптом**: при переходе к шагу аудирования в комплексном уроке возникает ошибка: `Ошибка загрузки этапа: undefined is not an object (evaluating 'scenario.topic')`, шаг невозможно пройти.
- **Диагностика**: в `app/static/js/modules/lesson.js` (строка 1658) после вызова `api.listening.getExercise()` клиент ожидает поле `data.scenario` (`const scenario = data.scenario;`), в то время как эндпоинт `app/routers/listening.py` (строка 47) возвращает объект упражнения напрямую (`safe_ex`). Из-за этого `scenario` равен `undefined`, и чтение `scenario.topic` выбрасывает исключение `TypeError`.
- **Варианты решения**: использовать `const scenario = data.scenario || data;` с опциональной цепочкой (`scenario?.topic`).
- **Статус**: зафиксировано, ожидает команды на исправление.
- [ ] **BUG-08: Бесконечный спиннер («Анализ ИИ длится бесконечно») на этапе дебрифинга урока**:
- **Симптом**: после прохождения всех этапов урока экран «🤖 Локальный ИИ Qwen 2.5 формирует ваш персональный разбор...» висит бесконечно долго без отображения результата.
- **Диагностика**:
1. Холодный запуск Ollama для модели Qwen 2.5 3B (после разгрузки памяти) занимает ~18.5 секунд только на загрузку весов в RAM. Генерация 450 токенов JSON на CPU суммарно требует 45-60 секунд.
2. В `app/ai.py` (строка 579) таймаут httpx к Ollama выставлен в 25 секунд (`timeout=25.0`), из-за чего запрос рвется, а во фронтенде в `api.lessons.submitDebrief` нет клиентского таймаута с аварийным выходом.
3. Короткий TTL (`keep_alive: 5m`) приводит к тому, что к моменту окончания 10–15 минутного урока модель гарантированно выгружается из памяти, вызывая холодный старт прямо на финальном шаге.
- **Варианты решения**:
1. Увеличить TTL процесса нейросети с `5m` до `1h` (`OLLAMA_KEEP_ALIVE=1h` в `docker-compose.yml`, `config.py` и `ai.py`) — во время активной учебной сессии модель всегда «горячая» в памяти и отвечает за доли секунды, а при простое > 1 часа память автоматически освобождается.
2. Добавить жесткий клиентский таймаут (8-10 сек) на фронтенде с мгновенным отображением сверхбыстрого детерминированного отчета (`generate_deterministic_debrief`, работающего за 1 мс).
3. Рассмотреть использование легкой модели `qwen2.5:1.5b` для ультрабыстрого ответа.
- **Статус**: зафиксировано, ожидает команды на исправление.
- [x] **BUG-01: Не открывается окно редактирования слова в словаре на главной**:
- **Симптом**: при клике на иконку ✏️ у карточки слова в списке словаря ничего не происходило.
- **Решение**: исправлен селектор ID в `app/static/js/modules/dictionary.js` с `'edit-card-modal'` на `'edit-modal'`. Модальное окно теперь стабильно открывается.
- **Статус**: [x] Решено.
- [x] **BUG-02: Отсутствует возможность прослушать слово и предложение из словаря**:
- **Симптом**: в списке карточек словаря на главной странице не было кнопок 🔊 для воспроизведения аудио слова и контекстного предложения.
- **Решение**: добавлены кнопки озвучки 🔊 в `app/static/js/modules/dictionary.js` для `term` (`playAudio`) и `context_sentence` (`playTextSpeech`) с адаптивными стилями в `dictionary.css`.
- **Статус**: [x] Решено.
- [x] **BUG-03: Спойлер корня слова в переводе грамматических форм в тестах (например, `(прош. вр. от explain)`)**:
- **Симптом**: при добавлении словоформ в словарь (например, `explained`) в переводе сохранялись пометки: `(прош. вр. от explain)`, спойлеря правильный ответ во время тестов.
- **Решение**: в `app/static/js/modules/ui.js` добавлены утилиты `cleanPromptTranslation` (вырезает подсказки корня в квизах, тайпинге, клоуз-тестах и диктантах в `srs.js` и `lesson.js`) и `formatTranslationWithSpoiler` (оборачивает пометку в интерактивный кликабельный бейдж-спойлер `[+ грамматика]` в карточках словаря).
- **Статус**: [x] Решено.
- [x] **BUG-04: В квизе «4 варианта» правильный ответ всегда находится на 1-м месте**:
- **Симптом**: в комплексном уроке и SRS при выборе режима «4 варианта» правильный ответ всегда гарантированно оказывался первым.
- **Решение**: в `app/static/js/modules/ui.js` функция `shuffleArray` переведена в мутирующий режим in-place, а в `lesson.js` и `srs.js` вызовы обновлены на явное переприсваивание `options = shuffleArray(options)`. Правильный ответ теперь распределяется равномерно случайно.
- **Статус**: [x] Решено.
- [x] **BUG-05: Поломка верстки и отображение сырых HTML-тегов/JSON при импорте статей Википедии (например, Linux)**:
- **Симптом**: при открытии статьи «Linux» отображался сырой JSON шаблонов MediaWiki (`"},"title":{"wt":"Linux"}...`) и сломанные теги `<tdTimes New Roman...`.
- **Решение**: в `app/reader.py` внедрена предварительная очистка атрибутов MediaWiki Parsoid (`data-mw`, `data-parsoid`, `about`), содержащих вложенные `<ref>` и литеральные `>`, ломавшие регулярные выражения тегов. В `clean_data_table` исправлен regex для поддержки вложенных кавычек `(?:"[^"]*"|\'[^\']*\')`. Статья 19 (Linux) в `data/notes.db` перепарсена и сохранена в идеально чистом виде. Добавлен регрессионный тест в `tests/test_api.py`.
- **Статус**: [x] Решено.
- [x] **BUG-06: Не работает поиск и обратный перевод с русского на английский в словаре**:
- **Симптом**: при вводе в поле поиска словаря русского слова перевод не находился, возвращался пустой результат.
- **Решение**: в `app/dictionary.py` реализовано автоматическое определение кириллицы `[\u0400-\u04FF]`. При русском вводе выполняется обратный поиск в `offline_dictionary` с приоритизацией одиночных слов и запрос MyMemory с `langpair="ru|en"`, обогащаемый фонетикой и частью речи. В `app/routers/dictionary.py` и `dictionary.js` поддержано заполнение карточки в обратном направлении (найденный английский термин в `term`, русский запрос в `translation`). Добавлен регрессионный тест в `tests/test_api.py`.
- **Статус**: [x] Решено.
- [x] **BUG-07: Ошибка загрузки этапа аудирования: `undefined is not an object (evaluating 'scenario.topic')`**:
- **Симптом**: при переходе к шагу аудирования в уроке возникала ошибка загрузки этапа из-за чтения `scenario.topic`.
- **Решение**: в `app/static/js/modules/lesson.js` строка 1659 обновлена на безопасное извлечение `const scenario = (data && (data.scenario || data)) || {};`, поддерживающее как прямой, так и вложенный формат ответа.
- **Статус**: [x] Решено.
- [x] **BUG-08: Бесконечный спиннер («Анализ ИИ длится бесконечно») на этапе дебрифинга урока + TTL 1h**:
- **Симптом**: после прохождения урока анализ ИИ висел бесконечно долго из-за холодного старта Ollama и обрыва HTTP-соединения.
- **Решение**:
1. Увеличен TTL модели в памяти с `5m` до `1h` (`OLLAMA_KEEP_ALIVE=1h` в `config.py` и `docker-compose.yml`) — модель остается теплой всю учебную сессию.
2. В `app/ai.py` таймаут к Ollama ограничен 10 секундами с мгновенным автоматическим фолбэком на детерминированный локальный разбор `generate_deterministic_debrief` (< 1 мс).
3. В `app/static/js/modules/api.js` добавлен жесткий 12-секундный клиентский таймаут с `AbortController`.
- **Статус**: [x] Решено.
---