Кнопка «🌐 Перевести» в карточках кандидатов и отобранных переводит пост
локальной моделью (qwen3:8b-nothink), результат в posts.translated_text
(идемпотентный ALTER при старте). Метка «Оригинальный пост» стала закладкой,
при готовом переводе рядом появляется закладка «Перевод» (активна по умолчанию),
переключение на vanilla JS. Повторный клик не тратит токены: перевод в БД.
call_ollama из classifier не подошла (парсит ответ как JSON) — прямой запрос
к Ollama /v1/chat/completions. POST /posts/{id}/translate, лимит 6000 символов.
- parse_address(address, crawler) в sources/sources.py: telegram (t.me/name, @name,
https://t.me/s/name) → slug/channel/url; rss → feed_url/url/slug из домена.
- GET /sources/preview: telegram — название через Telethon get_entity (title),
rss — через feedparser (feed.title); slug и name возвращаются в JSON.
- _form_source: единое поле address вместо url/channel/feed_url; slug из адреса.
- /sources: direction datalist из DIRECTIONS + уникальные значения из БД,
свободный ввод остаётся.
- Форма: поле Address + кнопка «Найти» (JS fetch → заполнение slug/name),
direction datalist динамический.
Также в sources.py входит незакоммиченная ранее веб-CRUD-логика (upsert_source_yaml,
delete_source_yaml, sync_yaml_to_db_and_back и др.) — работала на проде, теперь в git.
Форма добавления не имела поля channel, валидатор требует его для
crawler=telegram → 'telegram-источнику нужен channel'.
Добавлено поле в add-форму (required для telegram через JS), показ channel
в таблице у telegram-источников. Backend не менялся.
web/templates/sources.html ранее отсутствовал в git — добавлен.
- новый статус selected; в кандидатах только «⭐ Отобрать» (single+bulk)
- /selected — отдельный список: вся обработка (классификация/переписывание/комментарий/публикация)
- классификация только по кнопке (токены на кандидатов не тратятся)
- рядом с оригиналом — поле «✍️ Мой пост» (rewritten_text всегда виден), туда ложится черновик «Переписать»
- все обработчики учитывают src (candidates|selected) для редиректов
- reject/reject-old (single + bulk): редирект на /candidates с текущей
группировкой вместо /candidates?status=rejected — не прерываем отбор.
Отклонённые доступны через фильтр статуса.
- candidates.html: Обработать/Переписать/Отклонить — один ряд над полем
комментария (после переноса Опубликовать в approve-форму кнопки съехали).
changes: candidates-buttons-order, reject-stay-candidates (skip_specs, валидны).
- _fetch_candidates: жёсткое status='new' только при пустом status; явный
статус (rejected/published) — единственный фильтр статуса → ?status=rejected
больше не даёт пустой список
- формы approve/reclassify/rewrite/reject/comment: скрытые поля
group_by/direction/own/q — контекст страницы
- POST-роуты: редирект через _cand_back() сохраняет группировку и фильтры
(reject → ?status=rejected&group_by=...; approve → ?status=published&...)
- _fetch_candidates: в WHERE всегда is_own=0 AND status='new' → свои посты
канала @dedinit (is_own=1, backfill) и published/rejected больше не
показываются как кандидаты
- counts табов: считаются по внешним постам (не по жёсткому where), чтобы
табы статусов показывали реальные счётчики
- change: candidates-only-external-new (skip_specs)
- _fetch_candidates: статус группы = доминирующий (new>rejected>published),
а не первого поста → при group_by=date группа «Ранее на этой неделе»
больше не выглядит опубликованной (openchange fix-date-group-status)
- base.html: padding-top 1.5rem→4.5rem — заголовок не перекрывается fixed-top меню
- published.html: ID поста на карточках (#N · направление)
- AGENT.MD: бэкапы автоматические по крону, вручную не запускать