chore: initial project plan and agent rules

This commit is contained in:
MpakobecEx 2026-10-18 17:03:50 +05:00
commit d94e8dd48f
4 changed files with 417 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
.env
.env.*
!.env.example
__pycache__/
*.py[cod]
.venv/
venv/
.pytest_cache/
storage/
data/
*.db
*.db-wal
*.db-shm
.idea/
.vscode/

44
AGENT.md Normal file
View File

@ -0,0 +1,44 @@
# AGENT.md — правила работы
## Ответы
- **Коротко и по делу.** Без вступлений, извинений и повторов.
- Сначала результат, потом минимальные пояснения.
- Язык — русский, если не попросили иначе.
- Не пересказывать очевидное; ссылаться на конкретные файлы.
- Нужны уточнения — задать 1–3 конкретных вопроса.
- Не выдумывать факты: проверить или спросить.
## Проект
- Корень: `D:\!Projects\MpakoImageAIbot`.
- Стек: Python 3.10+, python-telegram-bot 21+, httpx, aiosqlite, SQLite, Docker.
- Требования: `plan.md`. План работ: `checklist.md`; отмечать выполненные пункты.
- Следовать структуре, именованию и стилю существующего кода.
- Секреты — только в `.env`. Не логировать токены, не коммитить `.env`, `storage/`, `data/`.
## Git / Gitea
- Remote: `ssh://git@home:2222/mpakobecex/MpakoImageAIbot.git`
(алиас `home` → `192.168.31.158`).
- Основная ветка — `main`.
- Перед коммитом: `git diff`, тесты/линтер, если есть.
- Коммиты атомарные: `feat:`, `fix:`, `docs:`, `chore:`.
- Не делать force push и не переписывать историю.
- Push — после успешной проверки.
## Работа с кодом
- Сначала прочитать существующий код, затем менять.
- Изменения минимальные и точечные; без попутного рефакторинга.
- Не удалять и не перезаписывать пользовательские файлы без подтверждения.
- Сетевые вызовы — async, event loop не блокировать.
- Ошибки обрабатывать явно: понятное сообщение пользователю + лог.
- После изменений проверять запуск/тесты и сообщать результат.
- Не оставлять проект в нерабочем состоянии.
## Безопасность
- Не выводить содержимое `.env` и приватные ключи.
- Не выполнять деструктивные команды без подтверждения.
- Не добавлять зависимости без необходимости.

311
checklist.md Normal file
View File

@ -0,0 +1,311 @@
# Checklist реализации MpakoImageAIbot
Источник: `plan.md`. Бот на `python-telegram-bot` v21+ (async) генерирует изображения через удалённый ComfyUI API, ведёт глобальную очередь, показывает позицию/прогресс и хранит запросы в SQLite.
Блоки идут в порядке зависимостей. В конце каждого блока — критерий готовности (DoD).
## 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) |
| Язык интерфейса | Русский |
Согласовано: **1 воркер**, задача `running` при рестарте → **`failed`**, workflow готовим вместе (см. блок 1).
## 1. Подготовка workflow и окружения (до кода)
### 1.1 Экспорт workflow
- [ ] Открыть ComfyUI, включить **Settings → Dev mode Options**, чтобы появился пункт **Save (API Format)**.
- [ ] Собрать t2i-граф: `CheckpointLoaderSimple → CLIPTextEncode (positive) → CLIPTextEncode (negative) → KSampler → VAEDecode → SaveImage`.
- [ ] Собрать i2i-граф: `LoadImage → VAEEncode → KSampler (denoise 0.4–0.7) → VAEDecode → SaveImage`.
- [ ] Экспортировать оба графа кнопкой **Save (API Format)** в `workflows/txt2img_api.json` и `workflows/img2img_api.json`.
- [ ] Убедиться, что в JSON только API-узлы (`class_type`, `inputs`), без UI-метаданных и без `extra`-полей.
- [ ] Определить ID нужных узлов. Удобно: выделить узел → `Properties` → `Node ID`, либо найти по `class_type` в JSON (`CLIPTextEncode`, `KSampler`, `LoadImage`).
- [ ] Заполнить `workflows/mapping.json` (ID ниже — примеры, заменить на реальные):
```json
{
"txt2img": {
"positive": "6",
"negative": "7",
"seed": "3",
"checkpoint": "4"
},
"img2img": {
"positive": "6",
"negative": "7",
"seed": "3",
"input_image": "10",
"denoise": "3"
}
}
```
- [ ] Проверить 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`, результат скачивается.
## 2. Каркас проекта и конфигурация
- [ ] Создать структуру:
```text
.
├── bot.py
├── comfy_client.py
├── db.py
├── queue_manager.py
├── config.py
├── workflows/
│ ├── txt2img_api.json
│ ├── img2img_api.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
- [ ] Положить `txt2img_api.json` и `img2img_api.json` в `workflows/`.
- [ ] `workflows/mapping.json`: соответствие «роль узла → node id» для каждого workflow (positive, negative, seed, input_image, denoise).
- [ ] Валидация mapping при старте: все указанные узлы существуют, у них ожидаемые поля; иначе — падение с понятной ошибкой.
- [ ] `build_txt2img(prompt, negative, seed) -> dict`: глубокое копирование шаблона, подстановка prompt/negative/seed.
- [ ] `build_img2img(prompt, negative, seed, image_name, denoise) -> 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.11-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`.
- [ ] Сетевой доступ к 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 без устных пояснений.
## Порядок работ
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`; workflow готовим вместе. Осталось уточнить:
1. Какой чекпоинт/модель используется (SD 1.5, SDXL, Flux) и какие дефолтные `steps`/`cfg`/`denoise` и размер изображения?
2. Какая версия ComfyUI? В старых сборках событие `progress` может не содержать `prompt_id` — нужен другой способ корреляции.
3. i2i без подписи: отклонять (по умолчанию) или использовать фиксированный промпт?
4. Нужны ли `/cancel`, удаление из очереди и ограничение доступа `ALLOWED_USER_IDS` в MVP?
5. Сколько хранить входные/выходные файлы? Нужна ли автоочистка?
6. Где будет хоститься бот и как он видит ComfyUI (LAN/VPN/интернет)?
7. Добавлять ли в схему `mode`, `seed`, `error_text`, `finished_at` (сейчас они предложены как расширение плана)?
8. Уведомлять ли пользователя, если его `running`-задача была помечена `failed` после рестарта?
## Вне скоупа MVP
- Платежи, подписки, квоты.
- Несколько моделей/чекпоинтов и выбор модели пользователем.
- Галерея, история генераций в боте, повтор генерации.
- LoRA/ControlNet и другие продвинутые workflow.
- Горизонтальное масштабирование и несколько экземпляров бота на одной SQLite.
- Админ-панель.

44
plan.md Normal file
View File

@ -0,0 +1,44 @@
Ты — senior Python-разработчик. Нужно написать Telegram-бота для генерации изображений через удалённый ComfyUI API.
## Стек
- Python 3.10+
- python-telegram-bot (v21+, async) [citation:3]
- SQLite (через aiosqlite или стандартный sqlite3 с осторожностью)
- requests / httpx для вызова ComfyUI API
- Docker для финальной упаковки
## Функционал (два режима)
1. **Text-to-Image**: пользователь отправляет текст → бот шлёт POST на ComfyUI `/prompt` с подставленным промптом [citation:14].
2. **Image-to-Image**: пользователь отправляет фото + текст → бот скачивает фото, загружает его в ComfyUI (через `/upload/image` или временную папку), подставляет в воркфлоу как input image, шлёт POST.
## Очередь (критично)
- Один пользователь = одна активная задача. Если он пишет ещё раз, пока висит его задача — бот отвечает «ты уже в очереди».
- Нужна глобальная очередь. Показывай:
- Если в очереди: «Перед тобой X человек».
- Если генерация идёт: «Генерация… Y%» (прогресс брать из WebSocket ComfyUI, события `progress` [citation:25][citation:35]).
## Хранение (SQLite)
Таблица `requests`:
- id (PK)
- user_id (telegram id)
- timestamp (ISO)
- prompt_text
- input_file_path (nullable)
- output_file_path (nullable)
- status (queued/running/done/failed)
- comfy_prompt_id (для отслеживания)
Файлы (входные фото и результаты) сохраняй локально в папку `./storage/{user_id}/`. В БД только пути.
## Вызов ComfyUI
- База: `http://IP_WINDOWS:PORT`
- POST `/prompt` → получаешь `prompt_id` [citation:2][citation:14].
- Опрос статуса: либо через GET `/history/{prompt_id}` (проще, но медленнее), либо через WebSocket на `/ws` (для прогресса в реальном времени) [citation:30][citation:35].
- После завершения скачай изображение через `/view?filename=...`.
## Что сделать сейчас
1. Создай структуру проекта: `bot.py`, `comfy_client.py`, `db.py`, `queue_manager.py`.
2. Напиши `comfy_client.py` с функциями submit_workflow (POST /prompt) и get_result (скачивание).
3. Напиши `db.py` с init_db() и CRUD для requests.
4. Напиши `bot.py` с хендлерами для текста и фото, но очередь пока сделай простейшей (asyncio.Queue).
5. Добавь `requirements.txt` и `Dockerfile`.