# Checklist реализации MpakoImageAIbot Источник: `plan.md`. Бот на `python-telegram-bot` v21+ (async) генерирует изображения через удалённый ComfyUI API, ведёт глобальную очередь, показывает позицию/прогресс и хранит запросы в PostgreSQL. Блоки идут в порядке зависимостей. В конце каждого блока — критерий готовности (DoD). ## Текущий статус - Реализованы блоки 2–7, базовое хранилище (9), файлы Docker (10), тесты (11), README (12), веб-история (13), улучшение промптов (14), модерация (15), оплата Stars (16), баланс/рефералы (17), управление пользователями (18), возрастной gate (19) и надёжность Telegram (20). - Вживую проверены: T2I (Z-Image), I2I (Krea2), WebSocket-прогресс, улучшение промптов, Docker, CI и веб-история. - 78 тестов: 50 проходят локально без БД, 28 тестов БД выполняются в CI на PostgreSQL через `TEST_DATABASE_URL`. Покрыты workflows, очередь, веб-история, enhancer, модерация, платежи, премиум-очередь, баланс/рефералы, управление пользователями, retry, миграция, watchdog, контекст, админ-панель, скрытие истории, настройки Krea2. - Не проверено: реальная оплата Stars и реферальная выплата в Telegram, очередь из 3+ пользователей, падение ComfyUI, рестарт с `running`-задачей, детекция лица на реальном фото. PostgreSQL-миграция и тесты БД ещё не запускались вживую — это сделает CI и первый деплой. ## 0. Допущения и решения по умолчанию | Вопрос | Решение по умолчанию | |---|---| | HTTP-клиент | `httpx.AsyncClient`, без блокирующего `requests` | | Очередь | Собственная очередь в приложении, по умолчанию 1 воркер; в ComfyUI уходит не более 1 задачи одновременно | | Прогресс | WebSocket `/ws` + fallback-опрос `/history/{prompt_id}` | | Один активный запрос на пользователя | Проверка в памяти + частичный уникальный индекс в PostgreSQL | | Задача в статусе `running` при рестарте | Пометить `failed` (не перезапускать автоматически) | | Задачи `queued` при рестарте | Восстановить из БД и поставить в очередь заново | | i2i без подписи | Отклонить и попросить промпт (не использовать дефолтный) | | Чаты | Приватные; `user_id` считается id чата | | Workflow | JSON в API-формате (экспорт «Save (API Format)» из ComfyUI) | | T2I-модель | Z-Image Turbo GGUF, `EmptySD3LatentImage`, 1024×1024, 8 steps, cfg 1, denoise 1.0 | | I2I-модель | Krea2 edit GGUF, `LoadImage`, 8 steps, cfg 1 | | ComfyUI | `http://192.168.31.113:8188`, v0.40.0, RTX 4060 Ti 8 GB | | Язык интерфейса | Русский | Согласовано: **1 воркер**, задача `running` при рестарте → **`failed`**, workflow готовим вместе (см. блок 1). Для i2i используем **Krea2 edit**. ## 1. Подготовка workflow и окружения (до кода) ### 1.1 Экспорт workflow - [x] Открыть ComfyUI, включить **Settings → Dev mode Options**, чтобы появился пункт **Save (API Format)**. - [x] Собрать t2i-граф: `UnetLoaderGGUF → CLIPLoaderGGUF → CLIPTextEncode` + `EmptySD3LatentImage → KSampler → VAEDecode → SaveImage`. - [x] Собрать i2i-граф: `LoadImage → TextEncodeQwenImageEdit → KSamplerAdvanced → VAEDecode → SaveImage`. - [x] Экспортировать оба графа кнопкой **Save (API Format)** в `workflows/txt2img.json` и `workflows/img2img.json`. - [x] Убедиться, что в JSON только API-узлы (`class_type`, `inputs`), без UI-метаданных и без `extra`-полей. - [x] Определить ID нужных узлов. Удобно: выделить узел → `Properties` → `Node ID`, либо найти по `class_type` в JSON (`CLIPTextEncode`, `KSampler`, `LoadImage`). - [x] Заполнить `workflows/mapping.json` (ID соответствуют текущим шаблонам): ```json { "txt2img": { "positive": "67", "negative": "71", "seed": "70", "latent": "81", "save": "9" }, "img2img": { "positive": "25", "negative": "10", "seed": "3", "input_image": "20", "save": "24" } } ``` - [x] Проверить t2i вручную: отправить JSON на `POST /prompt` и получить `prompt_id`, затем забрать результат через `/history` и `/view`. - [x] Проверить i2i: загрузить тестовое фото через `/upload/image`, подставить имя в `LoadImage`, выполнить генерацию. - [ ] Проверить i2i с фото произвольного размера (портрет/ландшафт); при необходимости добавить `ImageScale` в граф. - [x] Зафиксировать дефолтные `steps`, `cfg`, `denoise`, negative prompt и размер изображения. ### 1.2 Окружение - [x] Убедиться, что Python 3.10+ доступен локально и в Docker-образе. - [x] Проверить сетевую доступность ComfyUI: `GET http://IP_WINDOWS:PORT/system_stats`. - [ ] Получить токен бота у `@BotFather`. - [ ] Определить значения лимитов: максимальная длина промпта, максимальный размер фото, размер очереди, таймаут генерации. - [ ] Решить, где будет запускаться бот (локально / Docker) и как он видит ComfyUI (LAN, VPN, `host.docker.internal`). **DoD:** есть токен, два валидных workflow JSON, заполненный `mapping.json`; тестовый `POST /prompt` возвращает `prompt_id`, результат скачивается. T2I проверен (~52 с), i2i — (~61 с). ## 2. Каркас проекта и конфигурация - [ ] Создать структуру: ```text . ├── bot.py ├── comfy_client.py ├── db.py ├── queue_manager.py ├── config.py ├── workflows/ │ ├── txt2img.json │ ├── img2img.json │ └── mapping.json ├── storage/ # создаётся автоматически, в .gitignore ├── data/ # PostgreSQL и старый SQLite, в .gitignore ├── tests/ ├── requirements.txt ├── requirements-dev.txt ├── Dockerfile ├── .dockerignore ├── .env.example ├── .gitignore └── README.md ``` - [ ] `requirements.txt`: `python-telegram-bot`, `httpx`, `psycopg[binary]`, `psycopg-pool`, `websockets`, `python-dotenv` (версии — диапазонами, например `>=21,<23`). - [ ] `requirements-dev.txt`: `pytest`, `pytest-asyncio`, `respx`. - [ ] `config.py`: dataclass/`Settings`, загрузка `.env`, валидация обязательных полей, вычисление WS-URL из base URL. - [ ] Переменные окружения: `BOT_TOKEN`, `COMFY_BASE_URL`, `WORKFLOW_T2I`, `WORKFLOW_I2I`, `WORKFLOW_MAPPING`, `STORAGE_DIR`, `DATABASE_URL`, `DB_POOL_MIN_SIZE`, `DB_POOL_MAX_SIZE`, `WORKER_CONCURRENCY=1`, `MAX_QUEUE_SIZE`, `ALLOWED_USER_IDS`, `PROGRESS_INTERVAL`, `GENERATION_TIMEOUT`, `LOG_LEVEL`. - [ ] Настроить логирование в stdout: уровень из env, формат с timestamp/level/logger, никогда не логировать токен и содержимое `.env`. - [ ] `.env.example` со всеми переменными и комментариями. - [ ] `.gitignore`: `.env`, `storage/`, `data/`, `__pycache__/`, `.pytest_cache/`. - [ ] Проверить импорт конфига и падение с понятной ошибкой при отсутствии `BOT_TOKEN`. **DoD:** `python -c "from config import get_settings; print(get_settings())"` работает; секреты не попадают в репозиторий. ## 3. Слой хранения (`db.py`) - [ ] Реализовать `connect()`: пул `psycopg_pool.AsyncConnectionPool`, `row_factory=dict_row`, схема и миграции. - [ ] Создать таблицу `requests` по плану: ```sql CREATE TABLE IF NOT EXISTS requests ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT now(), prompt_text TEXT NOT NULL, input_file_path TEXT, output_file_path TEXT, status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'running', 'done', 'failed')), comfy_prompt_id TEXT ); ``` - [ ] Добавить расширения схемы (согласовать): `mode TEXT NOT NULL CHECK (mode IN ('t2i','i2i'))`, `error_text TEXT`, `finished_at TIMESTAMPTZ`, `seed BIGINT`. - [ ] Индексы: `(user_id, status)`, `(status, id)`. - [ ] Частичный уникальный индекс «один активный запрос на пользователя»: ```sql CREATE UNIQUE INDEX IF NOT EXISTS uq_requests_one_active ON requests(user_id) WHERE status IN ('queued', 'running'); ``` - [ ] CRUD: `create_request`, `get_request`, `get_active_by_user`, `set_status`, `set_comfy_prompt_id`, `set_output_path`, `set_error`, `list_queued`, `count_ahead`, `mark_running_as_failed`. - [ ] Все временные метки — `TIMESTAMPTZ`, в Python — `datetime.now(UTC)`. - [ ] Атомарность: создание запроса и проверка активного — в одной транзакции; при конфликте unique-индекса возвращать понятную ошибку `UserAlreadyQueued`. - [ ] Восстановление при старте: `queued` читаются по `id`, `running` → `failed` с текстом «прервано перезапуском». - [ ] Тесты: создание, смена статусов, запрет второго активного запроса, выборка очереди, восстановление. **DoD:** тесты БД зелёные на PostgreSQL; повторный `connect()` не ломает существующую БД. ## 4. Клиент ComfyUI (`comfy_client.py`) - [ ] Класс `ComfyClient` с ленивой инициализацией `httpx.AsyncClient` и явным `close()`. - [ ] `submit_workflow(workflow, client_id) -> prompt_id`: `POST /prompt` с телом `{"prompt": workflow, "client_id": client_id}`. - [ ] Обработка ответа `/prompt`: HTTP-ошибки, `error`, `node_errors` — поднимать `ComfyError` с человекочитаемым текстом. - [ ] `upload_image(path, request_id) -> image_ref`: `POST /upload/image` (multipart), уникальное имя файла, `type=input`; вернуть имя/подпапку для `LoadImage`. - [ ] `get_history(prompt_id) -> dict`: `GET /history/{prompt_id}`; извлекать список изображений из `outputs[*]["images"]`. - [ ] `download_image(filename, subfolder, type, dest_path)`: `GET /view` потоково, запись через временный файл + `os.replace`. - [ ] `wait_for_result(prompt_id, timeout)`: fallback-опрос истории с интервалом 2 с; таймаут → `ComfyTimeout`. - [ ] WebSocket-прогресс: `ws://.../ws?clientId=...`, подключение **до** `POST /prompt`, чтобы не потерять ранние события. - [ ] Разбор событий: `progress` (`value`/`max`, при наличии `prompt_id` — фильтровать), `executing`, `execution_success`, `execution_error`, `status`. - [ ] Переподключение WS с backoff; при недоступности WS — не падать, переходить на polling. - [ ] Таймауты: connect 10 с, запросы 30 с, ожидание генерации — `GENERATION_TIMEOUT`. - [ ] Не ретраить слепо `POST /prompt` (риск дублей); ретраить только идемпотентные GET/upload. - [ ] Типы ошибок: `ComfyConnectionError`, `ComfyHTTPError`, `ComfyExecutionError`, `ComfyTimeout`. - [ ] Тесты с `respx`/fake-сервером: успешный submit, `node_errors`, таймаут, скачивание, парсинг progress. **DoD:** отдельный скрипт/тест отправляет тестовый workflow на реальный ComfyUI, получает `prompt_id`, progress и сохраняет картинку. ## 5. Шаблоны workflow - [x] Положить `txt2img.json` и `img2img.json` в `workflows/`. - [x] `workflows/mapping.json`: соответствие «роль узла → node id» для каждого workflow (positive, negative, seed, latent/input_image). - [ ] Валидация mapping при старте: все указанные узлы существуют, у них ожидаемые поля; иначе — падение с понятной ошибкой. - [ ] `build_txt2img(prompt, negative, seed) -> dict`: глубокое копирование шаблона, подстановка prompt/negative/seed. - [ ] `build_img2img(prompt, negative, seed, image_name) -> dict`: то же + имя загруженного изображения. - [ ] Генерация seed: `secrets.randbelow(2**32)`, сохранять использованный seed в БД. - [ ] Обрезка/нормализация промпта (strip, лимит длины), значение negative prompt по умолчанию из конфига. - [ ] Гарантировать, что исходный шаблон не мутируется между запросами. - [ ] Тесты: подстановка значений, отсутствующий узел, отсутствующий файл workflow. **DoD:** юнит-тесты подтверждают корректную подстановку; при старте приложения шаблоны и mapping валидны. ## 6. Очередь и менеджер задач (`queue_manager.py`) - [ ] `QueueManager` с зависимостями: `db`, `ComfyClient`, `WorkflowBuilder`, `Settings`. - [ ] Внутренняя очередь `collections.deque` + `asyncio.Event` (не `asyncio.Queue`, чтобы уметь считать позицию). - [ ] `asyncio.Lock` для всех операций с очередью и реестром активных пользователей. - [ ] `enqueue(user_id, mode, prompt, input_path) -> EnqueueResult(request, position)`: - [ ] проверить активный запрос пользователя (память + БД); - [ ] проверить `MAX_QUEUE_SIZE`; - [ ] создать запись в БД, добавить id в очередь, вернуть позицию. - [ ] Позиция: количество задач впереди + выполняющаяся задача; считать уникальных пользователей (по плану — «X человек»). - [ ] Воркер: цикл `while not stopping`, брать задачу, никогда не умирать из-за ошибки одной задачи. - [ ] Жизненный цикл задачи: `queued → running → done/failed`; обновлять `comfy_prompt_id`, `output_file_path`, `error_text`, `finished_at`. - [ ] Для i2i: upload изображения → подстановка имени → submit. - [ ] Уникальный `client_id` на задачу; WS-подключение до submit, закрытие после завершения. - [ ] Прогресс: throttle (не чаще `PROGRESS_INTERVAL`, шаг ≥ 5%), вычисление процента из `value/max`. - [ ] События наружу через callback/event bus: `request_started`, `progress`, `request_done`, `request_failed` — без импорта Telegram в менеджере. - [ ] Обработка ошибок: любое исключение → `failed`, лог с `request_id`, событие для бота. - [ ] `start()`: восстановить `queued` из БД, пометить `running` как `failed`, запустить воркер. - [ ] `stop()`: остановить приём, дождаться воркера, пометить незавершённые задачи, закрыть клиент. - [ ] Тесты с fake-клиентом: порядок FIFO, позиции, дубль пользователя, ошибка генерации, отмена при остановке. **DoD:** два пользователя получают корректные позиции; повторная заявка отклоняется; прогресс доходит до подписчика; после рестарта `queued` восстанавливаются. ## 7. Telegram-слой (`bot.py`) - [ ] Инициализация `Application` (PTB v21+, async), регистрация `post_init`/`post_shutdown` для старта/остановки `QueueManager`. - [ ] Команды `/start` и `/help`: краткая инструкция по двум режимам и формату ввода. - [ ] (Опционально) `/status`: позиция и статус текущего запроса. - [ ] Текстовый хендлер (`filters.TEXT & ~filters.COMMAND`): создать t2i-запрос, поставить в очередь. - [ ] Фото-хендлер (`filters.PHOTO`): взять `photo[-1]`, скачать через `get_file().download_to_drive()`, сохранить в `storage/{user_id}/`. - [ ] Для i2i: caption обязателен; пустой caption → подсказка «пришлите фото с описанием». - [ ] Валидация: пустой/слишком длинный промпт, размер фото, поддерживаемые форматы; лимит caption 1024 символа. - [ ] Ответ при дубле: «Ты уже в очереди» + текущая позиция (по плану). - [ ] Ответ при постановке: «Принято. Перед тобой X человек» / «Ты первый, начинаю…». - [ ] Статусное сообщение: сохранить `message_id` в памяти; редактировать при `request_started` («Генерация… 0%») и `progress`. - [ ] Throttle редактирования статуса (не чаще 3 с), игнорировать `Message is not modified`. - [ ] По завершении: отправить результат как фото (`FSInputFile`), статусное сообщение удалить или заменить на «Готово». - [ ] По ошибке: редактировать статус на понятный текст, логировать техническую причину с `request_id`. - [ ] (Опционально) `ALLOWED_USER_IDS`: отказ остальным. - [ ] (Опционально) `ChatAction.UPLOAD_PHOTO` при отправке результата. - [ ] Аккуратно обрабатывать `TelegramError`/`BadRequest`, не ронять приложение. - [ ] Проверить, что команды не перехватываются текстовым хендлером. **DoD:** вручную проходят сценарии t2i и i2i; при дубле приходит корректный отказ; прогресс обновляется без флуда. ## 8. Сквозная интеграция - [ ] Связать `bot.py → QueueManager → db → ComfyClient → storage`. - [ ] t2i: текст → картинка → файл в `storage/{user_id}/` → отправка в чат → `done` в БД. - [ ] i2i: фото + caption → upload → workflow → картинка → отправка → `done`. - [ ] Очередь из 3 пользователей: позиции 1/2/3, затем корректное продвижение. - [ ] ComfyUI недоступен: задача → `failed`, пользователь получает понятную ошибку, воркер жив. - [ ] Ошибка выполнения workflow (`execution_error`): сообщение пользователю без стектрейса. - [ ] Рестарт с задачами в очереди: `queued` продолжают выполняться, `running` помечены `failed`. - [ ] Повторный запуск после падения: БД и файлы консистентны. - [ ] Проверить отсутствие блокировки event loop (все I/O — async). **DoD:** все сценарии выше пройдены на реальном ComfyUI. ## 9. Файловое хранилище - [ ] Структура: `storage/{user_id}/{request_id}_input.`, `storage/{user_id}/{request_id}_output.`. - [ ] Безопасные имена (только сгенерированные, не из пользовательского ввода), защита от path traversal. - [ ] Атомарная запись: `.part` → `os.replace`. - [ ] Создание директорий при старте/записи. - [ ] (Опционально) очистка файлов старше N дней по расписанию; настройка `FILE_RETENTION_DAYS`. - [ ] (Опционально) проверка свободного места и понятная ошибка при переполнении. - [ ] Не хранить бинарные данные в PostgreSQL — только пути. **DoD:** входные и выходные файлы лежат по ожидаемым путям; повторный запуск не конфликтует по именам. ## 10. Docker и деплой - [x] `Dockerfile` на `python:3.13-slim`, непривилегированный пользователь, `PYTHONUNBUFFERED=1`. - [x] Копирование кода и установка `requirements.txt`; без лишних build-зависимостей. - [x] `.dockerignore`: `.env`, `storage/`, `data/`, `__pycache__/`, `tests/`, `.git/`. - [x] `docker-compose.yml`: сервис `db` (PostgreSQL 17), healthcheck, volume `./data/postgres`, `env_file: .env`, volume `./storage`, `restart: unless-stopped`. - [x] `docker-compose.yml` использует готовый образ `mpakoimageaibot:latest`. - [x] Gitea Actions: `.gitea/workflows/docker-build.yml` собирает и пушит `mpakoimageaibot:latest` и `:` в Registry при push в `main`/`master`. - [x] Сетевой доступ к ComfyUI: `host.docker.internal` для Windows/macOS или LAN-IP для Linux; зафиксировать в README. - [x] Проверить, что PostgreSQL и storage переживают пересоздание контейнера. - [ ] (Опционально) healthcheck. - [x] Логи только в stdout/stderr. **DoD:** `docker compose up -d` поднимает бота, генерация работает, данные сохраняются после `down/up`. ## 11. Тесты и приёмка - [ ] `pytest` + `pytest-asyncio`, общая фикстура временной БД и storage. - [ ] Юнит-тесты: `db.py`, подстановка workflow, расчёт позиции, throttle прогресса, парсинг WS-событий. - [ ] Интеграционный тест: fake ComfyUI (HTTP + WS) → полный цикл `enqueue → running → done`, файл создан. - [ ] Тест ошибок: ComfyUI 500, `node_errors`, таймаут, обрыв WS. - [ ] Тест конкуренции: одновременные сообщения одного пользователя → только один запрос. - [ ] Ручной e2e на реальном ComfyUI (t2i, i2i, очередь, рестарт). - [ ] Проверка лимитов Telegram: размер результата, длина caption, частота редактирований. **DoD:** тесты зелёные; ручной чек-лист из блока 8 пройден. ## 12. Документация - [ ] `README.md`: описание, схема работы, требования. - [ ] Инструкция по экспорту workflow в API-формате и заполнению `mapping.json`. - [ ] Таблица переменных окружения с примерами. - [ ] Запуск локально и через Docker. - [ ] Раздел troubleshooting: ComfyUI недоступен, WS не подключается, «ты уже в очереди», ошибки workflow. - [ ] Краткое описание архитектуры и границ MVP. **DoD:** новый разработчик поднимает проект по README без устных пояснений. ## 13. Веб-история - [x] `web.py`: FastAPI + Jinja2, отдельный контейнер `web`. - [x] Список запросов с фильтрами `user_id`, `status`, `mode` и пагинацией. - [x] Отображение `@username` с fallback на Telegram ID; миграция старых БД. - [x] Превью результата в списке, генерация и кэш в `storage/.thumbnails`. - [x] Карточка запроса: промпт, seed, ошибка, `comfy_prompt_id`, вход/результат. - [x] Отдача изображений только из `STORAGE_DIR` (защита от path traversal). - [x] Доступ только из локальной сети: публикация на `WEB_BIND_IP`, без авторизации. - [x] Тесты: фильтры БД, страницы, media. - [x] Compose-сервис на порту `8099` → `8080` контейнера. **DoD:** история открывается по `http://IP:8099`, фильтр по пользователю работает, изображения доступны. ## 14. Улучшение промптов - [x] `workflows/prompt_enhance.json`: `CLIPLoader` + `TextGenerate` + `PreviewAny` на существующей `qwen3vl_4b`. - [x] `prompt_enhancer.py`: ChatML-промпт для T2I/I2I, очистка ответа. - [x] Улучшение до генерации, с fallback на исходный промпт. - [x] Исходный и улучшенный промпты хранятся в БД, отображаются в веб-истории. - [x] Событие «Улучшаю промпт…» в статусе бота. - [x] Тесты enhancer, очереди и миграции БД. **DoD:** русский запрос превращается в английский промпт, изображение генерируется по нему; при сбое LLM используется оригинал. ## 15. Фильтрация промптов - [x] `is_prompt_allowed`: нормализация, hard/context-блоки, обходные паттерны. - [x] Админы из `ADMIN_USER_IDS` обходят фильтр. - [x] Логирование блокировок в `blocked_requests`. - [x] Ответ «Запрос нарушает правила. Звёзды не списаны.». - [x] i2i: OpenCV-детекция при наличии; иначе консервативный ручной флаг. - [x] Тесты правил и очереди. **DoD:** небезопасный промпт не попадает в очередь, причина пишется в БД, админ может отправлять без фильтра. ## 16. Оплата Stars - [x] `PAYMENTS_ENABLED` / `STARS_PRICE`, админы бесплатно. - [x] Премиум-очередь: `PREMIUM_STARS_PRICE`, чередование с обычной, счётчики очередей до оплаты. - [x] Invoice `XTR` + `pre_checkout_query` + `successful_payment`. - [x] `pending_payments`: payload, сумма, статус, charge_id, request_id. - [x] Возврат звёзд при ошибке постановки в очередь и генерации. - [x] `payment_charge_id` и `payment_refunded` в `requests`. - [x] Тесты жизненного цикла платежа. **DoD:** не-админ получает счёт на 5 звёзд, после оплаты задача ставится в очередь; при сбое звёзды возвращаются. ## 17. Баланс и рефералы - [x] `users`: 3 стартовые генерации, ежедневное +1 в 00:00 МСК. - [x] Списание 1 генерации за обычную очередь, премиум — только звёзды. - [x] `/balance`, `/invite`, реферальная ссылка `?start=ref_`. - [x] +3 генерации рефереру за первую оплату друга и уведомление. - [x] Тесты баланса, ежедневного начисления и рефералов. **DoD:** новый пользователь получает 3 генерации, ежедневно +1, друг после первой оплаты приносит рефереру +3. ## 18. Управление пользователями в веб - [x] Страница `/users`: поиск, баланс, админ-флаг, блокировка. - [x] POST-действия: изменить баланс, назначить/снять админа, заблокировать. - [x] Блокировка проверяется ботом при запросах, оплате и callback-выборе. - [x] Админы из env сидируются один раз; дальше источник истины — БД. - [x] Тесты страницы и действий. **DoD:** изменения в веб-интерфейсе применяются к боту без рестарта. ## 19. Подтверждение 18+ - [x] Возрастной gate в `/start` с кнопками «18+»/«меньше 18». - [x] Флаг `age_confirmed`/`age_declined` в `users`. - [x] Без подтверждения запросы, оплата и выбор очереди недоступны. - [x] Существующие пользователи проходят подтверждение один раз. - [x] Статус 18+ отображается в веб-управлении пользователями. **DoD:** несовершеннолетний пользователь не может пользоваться ботом; подтверждение сохраняется. ## 20. Надёжность Telegram - [x] `_retry` с настраиваемыми попытками, backoff и режимом «до успеха». - [x] Health-check `getMe` с повторами. - [x] Ретраи критичных отправок и загрузок файлов. - [x] Watchdog polling: если pending updates не убывают 5 минут, updater перезапускается, при неудаче перезапускается контейнер. - [x] Тесты retry и watchdog. **DoD:** при таймаутах прокси операции повторяются, health-check фиксирует восстановление связи. ## 21. Контекст между сообщениями - [x] Таблица `user_contexts`: `base_prompt`, `current_prompt`, `last_request_id`, `last_mode`, `updated_at`. - [x] Снимок контекста сохраняется в `requests` и `pending_payments`. - [x] Следующий текстовый запрос обрабатывается вместе с предыдущим промптом. - [x] Контекст обновляется только после успешной отправки результата. - [x] `/restart` очищает контекст; новое фото начинает новую цепочку. - [x] В подписи результата итоговый промпт и подсказка `/restart`. - [x] Тесты БД, enhancer, очереди и подписи. **DoD:** продолжение сцены работает по тексту, `/restart` возвращает к новой сцене. ## 22. Админ-панель и очистка ComfyUI - [x] Публичная страница `/` показывает только видимую историю. - [x] `/admin` и `/admin/users` защищены HTTP Basic (`WEB_ADMIN_USERNAME`/`WEB_ADMIN_PASSWORD`). - [x] Управление пользователями перенесено в `/admin/users`. - [x] Скрытие/показ запросов из публичной истории; админ видит скрытые. - [x] Скрытые карточки и медиа недоступны публично (`404`). - [x] После успешного скачивания бот удаляет историю ComfyUI по `prompt_id`. - [x] Скрипт очистки файлов `scripts/cleanup_comfy_assets.ps1`. - [x] Тесты БД и веб-маршрутов. **DoD:** публичная история не показывает скрытые запросы, админ-панель требует пароль, история ComfyUI чистится после генерации. ## 23. Настройки Krea2 - [x] `users.generation_quality`: `standard` (Q4) / `improved` (Q6). - [x] `/settings` — inline-клавиатура, у выбранного пункта галочка справа. - [x] Нажатие меняет только галочку и сохраняет выбор, без запуска генерации. - [x] Очередь Krea2 подставляет Q4 или Q6 в `unet_name`. - [x] Тесты настройки и workflow. **DoD:** выбор сохраняется между запросами, стандартная использует Q4, улучшенная — Q6. ## 24. Команды и меню - [x] При старте бот получает `getMyCommands` и обновляет список при расхождении. - [x] `setMyCommands` для `/start`, `/help`, `/balance`, `/invite`, `/status`, `/settings`, `/restart`. - [x] `setChatMenuButton` включает `MenuButtonCommands` — кнопка меню слева снизу. - [x] Ошибки настройки команд не блокируют запуск бота. - [x] Тесты сравнения команд и идемпотентной настройки. **DoD:** после старта в меню бота видны команды, повторный запуск не дублирует настройку. ## Порядок работ 1. Блок 1 (подготовка) — без него нельзя валидировать клиент и workflow. 2. Блок 2 (каркас) → Блок 3 (БД) → Блок 4 (ComfyUI-клиент) → Блок 5 (workflow). 3. Блок 6 (очередь) → Блок 7 (бот) → Блок 8 (интеграция). 4. Блоки 9 (storage) и 10 (Docker) можно делать параллельно после блока 2. 5. Блоки 11 (тесты) и 12 (документация) — параллельно с 6–10, финализация после 8. ## Открытые вопросы Согласовано: 1 воркер; `running` при рестарте → `failed`; T2I — Z-Image, i2i — Krea2 edit. Осталось уточнить: 1. i2i без подписи: отклонять (по умолчанию) или использовать фиксированный промпт? 2. Нужны ли `/cancel`, удаление из очереди и ограничение доступа `ALLOWED_USER_IDS` в MVP? 3. Сколько хранить входные/выходные файлы? Нужна ли автоочистка? 4. Где будет хоститься бот и как он видит ComfyUI (LAN/VPN/интернет)? 5. Добавлять ли в схему `mode`, `seed`, `error_text`, `finished_at` (сейчас они предложены как расширение плана)? 6. Уведомлять ли пользователя, если его `running`-задача была помечена `failed` после рестарта? 7. Нужно ли масштабировать входное фото перед i2i (сейчас подаётся как есть; проверен только квадрат 1024×1024)? ## Вне скоупа MVP - Платежи, подписки, квоты. - Несколько моделей/чекпоинтов и выбор модели пользователем. - Галерея, история генераций в боте, повтор генерации. - LoRA/ControlNet и другие продвинутые workflow. - Горизонтальное масштабирование и несколько экземпляров бота на одной PostgreSQL. - Админ-панель.