MpakoImageAIbot/checklist.md

29 KiB
Raw Blame History

Checklist реализации MpakoImageAIbot

Источник: plan.md. Бот на python-telegram-bot v21+ (async) генерирует изображения через удалённый ComfyUI API, ведёт глобальную очередь, показывает позицию/прогресс и хранит запросы в SQLite.

Блоки идут в порядке зависимостей. В конце каждого блока — критерий готовности (DoD).

Текущий статус

  • Реализованы блоки 2–7, базовое хранилище (9), файлы Docker (10), тесты (11), README (12), веб-история (13) и улучшение промптов (14).
  • Вживую проверены: T2I (Z-Image), I2I (Krea2), WebSocket-прогресс, переключение T2I → I2I.
  • 25 тестов проходят: БД, workflows, очередь, веб-история, enhancer, вспомогательные функции ComfyUI.
  • Не проверено: сборка Docker (демон не запущен), очередь из 3+ пользователей, падение ComfyUI, рестарт с running-задачей, реальная отправка в Telegram (нет токена).

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

  • Открыть ComfyUI, включить Settings → Dev mode Options, чтобы появился пункт Save (API Format).
  • Собрать t2i-граф: UnetLoaderGGUF → CLIPLoaderGGUF → CLIPTextEncode + EmptySD3LatentImage → KSampler → VAEDecode → SaveImage.
  • Собрать i2i-граф: LoadImage → TextEncodeQwenImageEdit → KSamplerAdvanced → VAEDecode → SaveImage.
  • Экспортировать оба графа кнопкой Save (API Format) в workflows/txt2img.json и workflows/img2img.json.
  • Убедиться, что в JSON только API-узлы (class_type, inputs), без UI-метаданных и без extra-полей.
  • Определить ID нужных узлов. Удобно: выделить узел → Properties → Node ID, либо найти по class_type в JSON (CLIPTextEncode, KSampler, LoadImage).
  • Заполнить workflows/mapping.json (ID соответствуют текущим шаблонам):
{
  "txt2img": {
    "positive": "67",
    "negative": "71",
    "seed": "70",
    "latent": "81",
    "save": "9"
  },
  "img2img": {
    "positive": "25",
    "negative": "10",
    "seed": "3",
    "input_image": "20",
    "save": "24"
  }
}
  • Проверить t2i вручную: отправить JSON на POST /prompt и получить prompt_id, затем забрать результат через /history и /view.
  • Проверить i2i: загрузить тестовое фото через /upload/image, подставить имя в LoadImage, выполнить генерацию.
  • Проверить i2i с фото произвольного размера (портрет/ландшафт); при необходимости добавить ImageScale в граф.
  • Зафиксировать дефолтные steps, cfg, denoise, negative prompt и размер изображения.

1.2 Окружение

  • Убедиться, что Python 3.10+ доступен локально и в Docker-образе.
  • Проверить сетевую доступность 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. Каркас проекта и конфигурация

  • Создать структуру:
.
├── 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 по плану:
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).
  • Частичный уникальный индекс «один активный запрос на пользователя»:
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

  • Положить txt2img.json и img2img.json в workflows/.
  • 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 и деплой

  • Dockerfile на python:3.13-slim, непривилегированный пользователь, PYTHONUNBUFFERED=1.
  • Копирование кода и установка requirements.txt; без лишних build-зависимостей.
  • .dockerignore: .env, storage/, data/, __pycache__/, tests/, .git/.
  • docker-compose.yml: env_file: .env, volume для ./storage и ./data, restart: unless-stopped.
  • docker-compose.yml использует готовый образ mpakoimageaibot:latest.
  • Gitea Actions: .gitea/workflows/docker-build.yml собирает и пушит mpakoimageaibot:latest и :<sha> в Registry при push в main/master.
  • Сетевой доступ к ComfyUI: host.docker.internal для Windows/macOS или LAN-IP для Linux; зафиксировать в README.
  • Проверить, что SQLite и storage переживают пересоздание контейнера.
  • (Опционально) healthcheck.
  • Логи только в 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. Веб-история

  • web.py: FastAPI + Jinja2, отдельный контейнер web.
  • Список запросов с фильтрами user_id, status, mode и пагинацией.
  • Отображение @username с fallback на Telegram ID; миграция старых БД.
  • Превью результата в списке, генерация и кэш в storage/.thumbnails.
  • Карточка запроса: промпт, seed, ошибка, comfy_prompt_id, вход/результат.
  • Отдача изображений только из STORAGE_DIR (защита от path traversal).
  • Доступ только из локальной сети: публикация на WEB_BIND_IP, без авторизации.
  • Тесты: фильтры БД, страницы, media.
  • Compose-сервис на порту 8099 → 8080 контейнера.

DoD: история открывается по http://IP:8099, фильтр по пользователю работает, изображения доступны.

14. Улучшение промптов

  • workflows/prompt_enhance.json: CLIPLoader + TextGenerate + PreviewAny на существующей qwen3vl_4b.
  • prompt_enhancer.py: ChatML-промпт для T2I/I2I, очистка ответа.
  • Улучшение до генерации, с fallback на исходный промпт.
  • Исходный и улучшенный промпты хранятся в БД, отображаются в веб-истории.
  • Событие «Улучшаю промпт…» в статусе бота.
  • Тесты enhancer, очереди и миграции БД.

DoD: русский запрос превращается в английский промпт, изображение генерируется по нему; при сбое LLM используется оригинал.

Порядок работ

  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.
  • Админ-панель.