docs: sync README/IDEA.MD with 0.1.0+48

This commit is contained in:
admin committed 2026-10-11 14:07:17 +02:00
1 parent ff09b74f03
commit a95ae7ae5b
2 files changed
+41 -12

No files matched your search

+11 -5
View File
@@ -19,7 +19,7 @@ Telegram-бот, который по запросу пользователя г
## Режимы работы
- **Главное меню** — внутреннее состояние после `/start` или выхода из режима; пользователь не выбирает его как отдельный режим.
- **Обучение** — принимает скриншоты, накапливает образцы, показывает стиль (`/style`); `/clear` очищает профиль и историю, не затрагивая подписку или триал.
- **Обучение** — принимает скриншоты, текст, голос и аудио, накапливает образцы, показывает стиль (`/style`); `/clear` очищает профиль и историю, не затрагивая подписку или триал.
- **Генерация** — создаёт и редактирует посты, предлагает идеи (`/post`, `/idea`); генерация требует активного доступа.
- **Подписка** — при входе показывает активный период и остаток дней; inline-кнопка продления сразу открывает Stars-инвойс (`/balance`, `/subscribe`).
- **Помощь** — справка и навигация по её разделам (`/help`).
@@ -36,13 +36,14 @@ Telegram-бот, который по запросу пользователя г
- Формат: Telegram-бот.
- Стек: Python 3.12, aiogram 3.x, aiohttp, SQLite.
- APInex Voice STT расшифровывает голосовые; Gemini 3.8 Flash через APInex классифицирует намерения, пишет/правит посты и извлекает ToV из скриншотов.
- Coolify env с отдельными модельными настройками: `APINEX_STT_MODEL`, `APINEX_INTENT_CLASSIFIER_MODEL`, `APINEX_INJECTION_CLASSIFIER_MODEL`, `APINEX_POST_WRITER_MODEL`, `APINEX_IMAGE_TONE_MODEL`, `APINEX_RESPONSE_REPAIR_MODEL`; общий секрет `APINEX_API_KEY`. При невалидном JSON допускается одна попытка исправления форматирования до валидации схемы.
- В режиме «Обучение» текстовые заметки, голосовые/аудио и примеры постов дополняют единый накопительный профиль аккаунта; сброс обучения удаляет и заметки, и образцы.
- Coolify env с отдельными модельными настройками: `APINEX_STT_MODEL`, `APINEX_INTENT_CLASSIFIER_MODEL`, `APINEX_INJECTION_CLASSIFIER_MODEL`, `APINEX_POST_WRITER_MODEL`, `APINEX_IMAGE_TONE_MODEL`, `APINEX_RESPONSE_REPAIR_MODEL`; общий секрет `APINEX_API_KEY`. При невалидном JSON допускается одна попытка исправления форматирования до валидации схемы. Обрезанный на середине строки ответ не требует доотправки: `_extract_complete_post_strings` достаёт целые строки и возвращает три полных варианта. Лимит `max_tokens` у вызовов LLM снят.
- В режиме «Обучение» текстовые заметки, голосовые/аудио и примеры постов дополняют единый накопительный профиль аккаунта; сброс обучения удаляет и заметки, и образцы. Флаг `users.is_trained` включает самостоятельную генерацию поста.
- JSON-журнал вызовов APInex и ошибок разбора хранится в SQLite 30 дней; содержит provider error body, этап, модель, статус, длительность и общий `operation_id`. Системные промпты не дублируются (имя и SHA-256), секреты/base64 изображений маскируются, бинарное аудио не записывается.
- Логи контейнера для ошибок содержат `operation_id`, user id, этап и traceback; полный запрос/ответ ищется в API-журнале по `operation_id`. Текст пользовательских запросов и ответов — приватные данные.
- Служебные ответы пользователю — всегда на русском; тексты постов — на языке явного запроса. В production исключения не показываются; техническая диагностика приходит отдельным сообщением только в development.
- Настройки пользователей и распознанные тексты публикаций для накопления Tone of Voice хранятся отдельно по Telegram user id; история диалогов и исходные изображения не сохраняются.
- На явную просьбу поста и на реплай-правку бот возвращает 3 отдельных варианта.
- Кнопка «✍️ Придумать пост» и команда `/post` (режим «Генерация») запускают `_generate_post_from_training`: после проверки подписки проверяется флаг обучения `users.is_trained`; без обученного профиля бот просит зайти в «Обучение» и не вызывает модель. Контекст (профиль аккаунта, образцы постов, обучающие заметки, ≤ 8000 символов) передаётся модели как недоверенные данные, тему выбирает сама модель.
- В режиме «Генерация» отдельный LLM-классификатор проверяет prompt injection перед классификацией намерений; сомнительный запрос или ошибка проверки не пропускаются дальше. Благодарность и сообщения не по теме отвечаются заготовленными фразами без вызова генератора; режим «Обучение» не меняется.
- В режиме «Генерация» статичные изображения и галереи с подписью используются как визуальный контекст для трёх вариантов поста; без подписи бот запрашивает ТЗ и принимает следующий текст или reply. Видео, видеокружки и GIF/анимации отклоняются во всех режимах.
- Во время обучения, чтения изображений, распознавания голосовых и генерации бот редактирует одно сообщение-статус по этапам; перед результатом показывает ✅, при ошибке — ❌. Итог обучения — только краткий статус, без вывода содержимого профиля.
@@ -69,7 +70,7 @@ Telegram-бот, который по запросу пользователя г
- После деплоя файл приложения `/app/data/bot.log` и его ротационные копии хранятся на persistent volume выше и переживают пересоздание контейнера. Пока logging-изменение не задеплоено, используй `docker logs` и API-журнал `api_call_logs` в SQLite. API-логи сохраняются в `/app/data/social_writer.sqlite3` на том же volume.
- Dev-only raw update log: отдельная SQLite `/app/data/bot_updates.sqlite3`, таблица `raw_updates`; поиск по Telegram `update_id` (`SELECT payload FROM raw_updates WHERE update_id = ?`). Middleware подключается только при `DEPLOY_ENV=development`; записи старше 30 дней удаляются при запуске и записи новых событий. Секретные поля/токены маскируются. JSON включает приватный текст и metadata апдейта; production его не пишет.
- Секреты хранятся в переменных окружения приложения Coolify; не сохраняются в репозитории.
- `DEPLOY_ENV` включает dev-поведение только при точном значении `development`; production, пустое и неизвестное значение отключают его. `BOT_ENV` отдельно выбирает Telegram Bot API. `DEV_CHAT_ID` используется для уведомления о версии; версия в файле `VERSION` (сейчас `0.1.0+30`).
- `DEPLOY_ENV` включает dev-поведение только при точном значении `development`; production, пустое и неизвестное значение отключают его. `BOT_ENV` отдельно выбирает Telegram Bot API. `DEV_CHAT_ID` используется для уведомления о версии; версия в файле `VERSION` (сейчас `0.1.0+48`). Ротационный файл лога — `BOT_LOG_PATH` (по умолчанию `/app/data/bot.log`) на том же volume; сырые апдейты — `RAW_UPDATES_DB_PATH` (по умолчанию `/app/data/bot_updates.sqlite3`).
- Вики Gitea: страницы `Home`, `User-Guide`, `Developer-Guide`, `Subscription`, `Deployment`.
## Статус
@@ -102,9 +103,14 @@ Telegram-бот, который по запросу пользователя г
- [x] Исправлен баг: `_send_idea` была потеряна в коммите про подписку — `/idea` падал с `NameError`
- [x] Реальная оплата Stars: `sendInvoice` (XTR) → `pre_checkout_query` → `successful_payment`; подписка активируется только после оплаты, `telegram_payment_charge_id` защищает от двойного начисления
- [x] В `DEPLOY_ENV=development` после обычного сообщения об инвойсе отдельным сообщением доступна кнопка «🧪 Оплатил (dev)»; callback ограничен пользователем инвойса, а все dev-ветви fail-closed выключены вне development
- Версия разработки: `0.1.0+42`
- Версия разработки: `0.1.0+48`
- [x] Справка бота (`/help`) описывает `/balance` и `/subscribe`
- [x] Проект Gitea «Social Post Writer Bot Roadmap» с задачами (выполненные — в «Готово», будущие — в Backlog)
- [x] Ротационный файл лога `/app/data/bot.log` (5 MiB × 3 архива) на persistent volume
- [x] Кнопка «✍️ Придумать пост» и `/post`: самостоятельный пост по обученному профилю, гейт по `users.is_trained`
- [x] Лимит `max_tokens` снят; обрезанный JSON восстанавливается по целым строкам (`_extract_complete_post_strings`)
- [x] Dev-only raw update log: входящие Telegram Update в `/app/data/bot_updates.sqlite3`, поиск по `update_id`, retention 30 дней
- [x] Уведомление о версии показывается вместе с главным меню при старте бота
## План работ (проект Gitea)
Колонки: **Backlog** → **В работе** → **Готово**.
+30 -7
View File
@@ -13,7 +13,7 @@ Telegram-бот для генерации постов для соцсетей.
- [Деплой и конфигурация](#деплой-и-конфигурация)
- [Тесты](#тесты)
Версия разработки: `0.1.0+42`.
Версия разработки: `0.1.0+48`.
---
@@ -68,7 +68,8 @@ onboarding. В «Подписке» сразу отображаются тип
| Поблагодарить или написать не по теме | В режиме «Генерация» отправляет обычное сообщение | Одна фраза из заготовленного набора; генератор постов/идей не вызывается |
| Пост по изображению | В режиме «Генерация» отправляет изображение/галерею с подписью-ТЗ | Vision изучает изображения и бот возвращает 3 варианта поста |
| Изображение без ТЗ | В режиме «Генерация» отправляет изображение/галерею без подписи | Бот спрашивает, о чём написать, затем принимает обычный текст или reply |
| Настроить стиль | В режиме «Обучение» присылает скриншоты постов | Бот распознаёт тексты, накапливает их, обновляет профиль и показывает только статус завершения |
| Настроить стиль | В режиме «Обучение» присылает скриншоты, текст, голос или аудио | Бот накапливает сведения, обновляет профиль аккаунта и показывает только статус завершения |
| Пост по обученным данным | В режиме «Генерация» нажимает «✍️ Придумать пост» или вводит `/post` | Бот сам выбирает тему по накопленному профилю и возвращает 3 варианта; без обучения просит сначала обучить |
| Голосом | В режиме «Генерация» отправляет голосовое | STT расшифровывает → обычный текстовый запрос |
| Узнать статус / продлить | Входит в режим «Подписка» и нажимает «Продлить подписку» | Текущий тип периода, остаток дней и Stars-инвойс на 100 звёзд |
| Очистить данные | В режиме «Обучение» нажимает «Сбросить данные» или вводит `/clear`, затем подтверждает | Очищает стиль и историю, не меняя подписку или триал |
@@ -84,6 +85,14 @@ onboarding. В «Подписке» сразу отображаются тип
### Блокировка по подписке
Если подписка не активна (триал/подписка истекли или `is_active = 0`), генерация блокируется. Переключатели режимов доступны всегда; для оплаты пользователь переходит в режим «Подписка».
### Самостоятельный пост по обученным данным
Кнопка «✍️ Придумать пост» (режим «Генерация») и команда `/post` идут не через классификатор намерений, а в
`_generate_post_from_training`: сначала проверяется подписка, затем флаг обучения `is_user_trained`
(`users.is_trained`). Если профиль не обучен, бот просит зайти в режим «Обучение» и прислать сведения об
аккаунте или примеры постов, не вызывая модель. Иначе собранный контекст (профиль аккаунта, образцы постов,
обучающие заметки, до 8000 символов) передаётся как **недоверенные данные**, и модель сама выбирает одну тему.
Обученный флаг выставляется при сохранении Tone of Voice или обучающих заметок и сбрасывается `/clear`.
---
## Архитектура кода
@@ -114,6 +123,15 @@ handle_text (только в режиме генерации) → _classify_and_
handle_command_text → переключатели режимов доступны всегда; действия проходят режимную проверку
```
**Самостоятельный пост (`/post` или кнопка «✍️ Придумать пост»):**
```
handle_command_text → _generate_post_from_training
├─ _check_subscription
├─ is_user_trained? нет → просьба обучить, модель не вызывается
├─ gather(get_tone_of_voice, get_post_samples, get_training_notes) → контекст ≤ 8000
└─ generate_posts(requirements=контекст как недоверенные данные) → _send_three_posts
```
**Голосовое:**
```
transcribe_voice → _process_voice → transcribe_audio (APInex STT) → _classify_and_respond
@@ -136,6 +154,7 @@ update_tone_from_image → _handle_image_message
| `handle_command_text` | Роутинг глобальных переключателей и профильных команд по режиму |
| `_classify_and_respond` | Ядро: проверка подписки → классификация → генерация |
| `_check_subscription` | Проверяет активную подписку; если нет — показывает статус и блокирует |
| `_generate_post_from_training` | `/post` и кнопка «✍️ Придумать пост»: пост по обученному профилю, с проверкой `is_user_trained` |
| `_show_subscription_info` | Показывает тип периода, остаток дней и inline-кнопку продления в режиме подписки |
| `balance_command` | `/balance` — статус подписки |
| `subscribe_command` | `/subscribe` — отправляет Stars-инвойс на 30 дней (подписка активируется только после оплаты) |
@@ -158,6 +177,7 @@ update_tone_from_image → _handle_image_message
| `deactivate_subscription` | `is_active = 0` |
| `is_subscribed` | Проверяет: запись есть, `is_active = 1`, срок не истёк |
| `subscription_row` | Возвращает строку подписки |
| `is_user_trained` | Флаг `users.is_trained`: обучен ли профиль (гейт для «Придумать пост») |
| `_days_until` | Сколько дней осталось (триал — `trial_started_at + 3 дня`; подписка — `last_paid_at`); `-1` если истёкло/отключено |
---
@@ -204,6 +224,7 @@ SUBSCRIPTION_PRICE_STARS = 100
- **Per-user данные:** профиль аккаунта/ToV, текстовые обучающие заметки, образцы постов со скриншотов, история сгенерированных постов и идеи. В «Обучении» текст/голос/скриншоты накапливают и обновляют профиль, а не запускают генерацию. История диалогов и исходные изображения **не сохраняются**.
- **Пустые ответы APInex:** при пустом `content` бот делает до двух повторов; третий запрос добавляет `reasoning_effort: low`. Если ответ всё ещё пустой, операция завершается ошибкой и каждая попытка попадает в API-журнал. Ошибки HTTP автоматически не повторяются. APInex документирует `low`, но не режим отключения reasoning; поддержка параметра зависит от модели.
- **Починка формата JSON:** при синтаксически невалидном JSON выполняется не более одной отдельной попытки модели `APINEX_RESPONSE_REPAIR_MODEL`; затем исходные проверки структуры/схемы запускаются заново. Ошибки API, пустые ответы и JSON с неверной схемой автоматически не исправляются. В Coolify development задана `APINEX_RESPONSE_REPAIR_MODEL`.
- **Обрезанный JSON:** если модель вернула валидную схему, но текст поста обрывается на середине (незакрытая строка/массив), бот доотправки не требует: `_extract_complete_post_strings` достаёт из ответа завершённые строки и возвращает три полных варианта. Если целых строк меньше трёх или они не найдены — операция завершается ошибкой. Лимит `max_tokens` у вызовов LLM снят: модель пишет столько, сколько нужно, без искусственного обрезания.
- **API-логи:** JSON запросов/ответов APInex, тело ответа провайдера при ошибке и ошибки разбора хранятся в SQLite 30 дней. Записи связаны `operation_id` и содержат этап, модель, HTTP-статус и длительность. Системные промпты не дублируются — только имя и SHA-256; секреты и base64 изображений маскируются, бинарное аудио не пишется. Пользовательские тексты в запросах/ответах — приватные данные.
- **Логи контейнера:** ошибки операций содержат `operation_id`, Telegram user id, этап и полный traceback; ошибки APInex дополнительно показывают модель, статус, длительность и диагностическую структуру ответа без вывода текста пользовательского запроса. Ищи все связанные записи по одному `operation_id`.
- `/clear` (только режим «Обучение») удаляет пользовательские данные (ToV, скриншоты, посты, идеи, настройки), не меняя подписку и триал; **API-логи сохраняются**.
@@ -217,7 +238,7 @@ SUBSCRIPTION_PRICE_STARS = 100
| `/start` | Возврат в главное меню | Любой |
| `/training`, `/generate`, `/subscription`, `/help` | Переключение режима | Любой |
| `/style`, `/clear` | Профиль аккаунта; сброс накопленного обучения | Обучение |
| `/post`, `/idea` | Создать пост или идею | Генерация; для генерации нужна подписка |
| `/post`, `/idea` | Самостоятельный пост по обученному профилю; идея | Генерация; для генерации нужна подписка |
| `/balance`, `/subscribe` | Статус или оплата Stars | Подписка |
В режиме «Генерация» русские фразы («придумай идею», «о чём написать?») работают как `/idea`.
@@ -228,10 +249,10 @@ SUBSCRIPTION_PRICE_STARS = 100
- **Gitea:** `admin/social-post-writer-bot`, ветка `dev`. Push → автодеплой.
- **Coolify:** проект `Social Post Writer Bot`, окружение `development`, приложение `social-post-writer-bot-development` (`d32u5zcq2g4odmdpq9hw91yn`) на `spb-easy-dale`.
- **Деплой:** `.hermes/scripts/deploy.sh` (POST `/api/v1/deploy?uuid=...`).
- **Деплой:** `bash /root/.hermes/scripts/gitea_deploy.sh dev` (POST `/api/v1/deploy?uuid=...&force=false`).
- **Окружение:** `DEPLOY_ENV` включает dev-функции только при точном значении `development`; production, пустое и неизвестное значение отключают их. `BOT_ENV` отдельно выбирает Telegram Bot API и не используется для включения dev-поведения.
- **VERSION:** файл `VERSION` (`0.1.0+30`). При `DEPLOY_ENV=development` и изменении версии бот шлёт уведомление о версии в `DEV_CHAT_ID`.
- **Переменные Coolify:** `BOT_TOKEN`, `APINEX_API_KEY`, `APINEX_STT_MODEL`, `APINEX_INTENT_CLASSIFIER_MODEL`, `APINEX_POST_WRITER_MODEL`, `APINEX_IMAGE_TONE_MODEL`, `APINEX_RESPONSE_REPAIR_MODEL` (optional; default uses the standard model), `BOT_DB_PATH`, `DEPLOY_ENV`, `DEV_CHAT_ID`.
- **VERSION:** файл `VERSION` (`0.1.0+48`). При `DEPLOY_ENV=development` и изменении версии бот шлёт уведомление о версии в `DEV_CHAT_ID`.
- **Переменные Coolify:** `BOT_TOKEN`, `APINEX_API_KEY`, `APINEX_STT_MODEL`, `APINEX_INTENT_CLASSIFIER_MODEL`, `APINEX_INJECTION_CLASSIFIER_MODEL`, `APINEX_POST_WRITER_MODEL`, `APINEX_IMAGE_TONE_MODEL`, `APINEX_RESPONSE_REPAIR_MODEL` (optional; default uses the standard model), `BOT_DB_PATH`, `RAW_UPDATES_DB_PATH` (optional; default `/app/data/bot_updates.sqlite3`), `DEPLOY_ENV`, `DEV_CHAT_ID`, `BOT_LOG_PATH` (optional; default `/app/data/bot.log`).
- **Docker:** `python:3.12-slim`, копирует исходники бота, включая `raw_updates.py`; `CMD ["python", "bot.py"]`.
---
@@ -242,4 +263,6 @@ SUBSCRIPTION_PRICE_STARS = 100
python -m unittest discover -s tests -v
```
Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, **режимные клавиатуры и роутинг** (переключатели, возврат в главное меню), **платежи** (Stars, защита от повторов, dev-подтверждение), **ошибки**, **справка**, **onboarding**, **подписки** (статус при входе, продление, оплата, баланс, блокировка, сохранение подписки при `/clear`).
Покрытие: STT, LLM, vision, ToV, reply-поля, изоляция пользователей, API-логи, **raw update log** (сохранение/чтение, выключение вне development, очистка по retention), **режимные клавиатуры и роутинг** (переключатели, возврат в главное меню), **платежи** (Stars, защита от повторов, dev-подтверждение), **ошибки**, **справка**, **onboarding**, **подписки** (статус при входе, продление, оплата, баланс, блокировка, сохранение подписки при `/clear`), **самостоятельная генерация поста** (гейт по флагу обучения, контекст из профиля), **восстановление обрезанного JSON**.
Запускай через виртуальное окружение проекта: `.venv/bin/python -m unittest discover -s tests` (вне его `aiogram` может быть не установлен).