371 lines
31 KiB
Markdown
371 lines
31 KiB
Markdown
# Checklist реализации MpakoImageAIbot
|
||
|
||
Источник: `plan.md`. Бот на `python-telegram-bot` v21+ (async) генерирует изображения через удалённый ComfyUI API, ведёт глобальную очередь, показывает позицию/прогресс и хранит запросы в SQLite.
|
||
|
||
Блоки идут в порядке зависимостей. В конце каждого блока — критерий готовности (DoD).
|
||
|
||
## Текущий статус
|
||
|
||
- Реализованы блоки 2–7, базовое хранилище (9), файлы Docker (10), тесты (11), README (12), веб-история (13), улучшение промптов (14), модерация (15) и оплата Stars (16).
|
||
- Вживую проверены: T2I (Z-Image), I2I (Krea2), WebSocket-прогресс, улучшение промптов, Docker, CI и веб-история.
|
||
- 46 тестов проходят: БД, workflows, очередь, веб-история, enhancer, модерация, платежи, вспомогательные функции ComfyUI.
|
||
- Не проверено: реальная оплата Stars в Telegram, очередь из 3+ пользователей, падение ComfyUI, рестарт с `running`-задачей, детекция лица на реальном фото.
|
||
|
||
## 0. Допущения и решения по умолчанию
|
||
|
||
| Вопрос | Решение по умолчанию |
|
||
|---|---|
|
||
| HTTP-клиент | `httpx.AsyncClient`, без блокирующего `requests` |
|
||
| Очередь | Собственная очередь в приложении, по умолчанию 1 воркер; в ComfyUI уходит не более 1 задачи одновременно |
|
||
| Прогресс | WebSocket `/ws` + fallback-опрос `/history/{prompt_id}` |
|
||
| Один активный запрос на пользователя | Проверка в памяти + частичный уникальный индекс в SQLite |
|
||
| Задача в статусе `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/ # SQLite, в .gitignore
|
||
├── tests/
|
||
├── requirements.txt
|
||
├── requirements-dev.txt
|
||
├── Dockerfile
|
||
├── .dockerignore
|
||
├── .env.example
|
||
├── .gitignore
|
||
└── README.md
|
||
```
|
||
|
||
- [ ] `requirements.txt`: `python-telegram-bot`, `httpx`, `aiosqlite`, `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`, `DB_PATH`, `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`)
|
||
|
||
- [ ] Реализовать `init_db()`: подключение `aiosqlite`, `PRAGMA journal_mode=WAL`, `PRAGMA busy_timeout`, `PRAGMA foreign_keys=ON`, `row_factory`.
|
||
- [ ] Создать таблицу `requests` по плану:
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS requests (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL,
|
||
timestamp TEXT NOT NULL,
|
||
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 TEXT`, `seed INTEGER`.
|
||
- [ ] Индексы: `(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`.
|
||
- [ ] Все временные метки — ISO 8601 UTC (`datetime.now(timezone.utc).isoformat()`).
|
||
- [ ] Атомарность: создание запроса и проверка активного — в одной транзакции; при конфликте unique-индекса возвращать понятную ошибку `UserAlreadyQueued`.
|
||
- [ ] Восстановление при старте: `queued` читаются по `id`, `running` → `failed` с текстом «прервано перезапуском».
|
||
- [ ] Тесты: создание, смена статусов, запрет второго активного запроса, выборка очереди, восстановление.
|
||
|
||
**DoD:** тесты БД зелёные; повторный `init_db()` не ломает существующую БД.
|
||
|
||
## 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.<ext>`, `storage/{user_id}/{request_id}_output.<ext>`.
|
||
- [ ] Безопасные имена (только сгенерированные, не из пользовательского ввода), защита от path traversal.
|
||
- [ ] Атомарная запись: `.part` → `os.replace`.
|
||
- [ ] Создание директорий при старте/записи.
|
||
- [ ] (Опционально) очистка файлов старше N дней по расписанию; настройка `FILE_RETENTION_DAYS`.
|
||
- [ ] (Опционально) проверка свободного места и понятная ошибка при переполнении.
|
||
- [ ] Не хранить бинарные данные в SQLite — только пути.
|
||
|
||
**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`: `env_file: .env`, volume для `./storage` и `./data`, `restart: unless-stopped`.
|
||
- [x] `docker-compose.yml` использует готовый образ `mpakoimageaibot:latest`.
|
||
- [x] Gitea Actions: `.gitea/workflows/docker-build.yml` собирает и пушит `mpakoimageaibot:latest` и `:<sha>` в Registry при push в `main`/`master`.
|
||
- [x] Сетевой доступ к ComfyUI: `host.docker.internal` для Windows/macOS или LAN-IP для Linux; зафиксировать в README.
|
||
- [x] Проверить, что SQLite и 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] 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 звёзд, после оплаты задача ставится в очередь; при сбое звёзды возвращаются.
|
||
|
||
## Порядок работ
|
||
|
||
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.
|
||
- Горизонтальное масштабирование и несколько экземпляров бота на одной SQLite.
|
||
- Админ-панель.
|