feat(web): перевод поста на русский локальной моделью (openspec post-translate)

Кнопка «🌐 Перевести» в карточках кандидатов и отобранных переводит пост
локальной моделью (qwen3:8b-nothink), результат в posts.translated_text
(идемпотентный ALTER при старте). Метка «Оригинальный пост» стала закладкой,
при готовом переводе рядом появляется закладка «Перевод» (активна по умолчанию),
переключение на vanilla JS. Повторный клик не тратит токены: перевод в БД.
call_ollama из classifier не подошла (парсит ответ как JSON) — прямой запрос
к Ollama /v1/chat/completions. POST /posts/{id}/translate, лимит 6000 символов.
This commit is contained in:
kpa39l
2026-09-18 05:40:59 +00:00
parent 745680a3a0
commit 4a166fa9d6
9 changed files with 530 additions and 29 deletions
@@ -0,0 +1,58 @@
# Spec — post-translate
## ADDED Requirements
### Requirement: Перевод поста на русский в карточках кандидатов и отобранных
Модератор может перевести оригинальный пост на русский язык одной кнопкой,
не покидая карточку. Перевод выполняет локальная модель (Ollama qwen3:8b-nothink);
результат сохраняется в БД и показывается в отдельной закладке «Перевод» рядом
с закладкой «Оригинал».
#### Scenario: Кнопка «Перевести» у поста без перевода
Given карточка кандидата или отобранного поста, у которого ещё нет перевода
When модератор нажимает «🌐 Перевести»
Then текст поста переводится локальной моделью на русский
And перевод сохраняется в `posts.translated_text`
And страница перезагружается, и рядом с «Оригинал» появляется закладка «Перевод»
#### Scenario: Перевод уже готов
Given у поста уже сохранён перевод (`translated_text` заполнен)
When модератор открывает карточку
Then кнопка «Перевести» скрыта
And доступны две закладки: «Оригинал» и «Перевод»
#### Scenario: Ошибка модели при переводе
Given локальная модель недоступна или вернула пустой ответ
When модератор нажимает «🌐 Перевести»
Then перевод не сохраняется, пост не меняется
And модератор остаётся на той же карточке с сообщением об ошибке (параметр error)
#### Scenario: Повторный клик «Перевести» не тратит токены
Given перевод уже сохранён
When модератор нажимает «🌐 Перевести» (если кнопка видна — например, после ошибки)
Then сервер видит заполненный `translated_text` и не вызывает модель
And просто возвращает на ту же карточку
### Requirement: Закладки «Оригинал» / «Перевод»
Оригинальный пост и его перевод показываются в карточке как две переключаемые
закладки (вкладки), переключение — без перезагрузки страницы.
#### Scenario: Переключение между оригиналом и переводом
Given карточка с готовым переводом
When модератор кликает «Перевод»
Then показывается текст перевода (перевод отображается тем же стилем, что оригинал)
When модератор кликает «Оригинал»
Then снова показывается исходный текст с медиа
#### Scenario: Перевод отсутствует — показывается только оригинал
Given у поста нет перевода
Then карточка показывает закладку/блок «Оригинал» и кнопку «Перевести»
And закладки «Перевод» нет