diff --git a/README.md b/README.md index de88d00..f6bd85b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Telegram-бот для генерации и редактирования изо - **Баланс и рефералы**: 3 стартовые генерации, +1 ежедневно в 00:00 МСК, +3 за первую оплату друга. - **Управление пользователями**: в веб-интерфейсе можно выдавать админку, менять баланс и блокировать. - **Подтверждение 18+**: без подтверждения возраста бот не принимает запросы. -- **Контекст**: следующий текстовый запрос продолжает предыдущую сцену; `/reset` начинает новую. +- **Контекст**: следующий текстовый запрос продолжает предыдущую сцену; `/restart` начинает новую. - Глобальная очередь с одной активной задачей на пользователя. - Позиция в очереди и прогресс генерации через WebSocket ComfyUI. - PostgreSQL: запросы, статусы, seed, пути к файлам. @@ -202,7 +202,7 @@ TELEGRAM_HEALTH_INTERVAL=60 - Премиум-очередь всегда оплачивается 15 звёздами; баланс при этом не расходуется. - `/balance` — текущий баланс. - `/invite` — реферальная ссылка. -- `/reset` — сбросить контекст и начать новую сцену. +- `/restart` — сбросить контекст и начать новую сцену (алиас `/reset`). - За первую оплату приглашённого друга начисляется +3 генерации, реферер получает уведомление. Баланс и рефералы хранятся в таблице `users`. Пропущенные дни начисляются при следующем запуске/полуночи. @@ -211,10 +211,10 @@ TELEGRAM_HEALTH_INTERVAL=60 - Первый текстовый запрос создаёт контекст: базовую сцену и текущий улучшенный промпт. - Следующий текст обрабатывается вместе с предыдущим промптом: бот сохраняет сцену и применяет только запрошенное изменение. -- `/reset` очищает контекст; следующий запрос начнёт новую сцену. +- `/restart` очищает контекст; следующий запрос начнёт новую сцену. - Новое фото с подписью автоматически начинает новую цепочку. - Для точного редактирования конкретной картинки ответь на сообщение бота с изображением. -- `/reset` не отменяет уже запущенную генерацию: она завершится со своим снимком контекста. +- `/restart` не отменяет уже запущенную генерацию: она завершится со своим снимком контекста. Контекст хранится в таблице `user_contexts` и обновляется только после успешной отправки результата. diff --git a/bot.py b/bot.py index b208fd1..eeaec2b 100644 --- a/bot.py +++ b/bot.py @@ -43,6 +43,19 @@ logger = logging.getLogger(__name__) MSK = timezone(timedelta(hours=3)) POLLING_WATCHDOG_INTERVAL = 60.0 POLLING_WATCHDOG_STUCK_AFTER = 300.0 +RESTART_HINT = "/restart — сброс контекста" +CAPTION_LIMIT = 1024 + + +def result_caption(prompt: str, *, limit: int = CAPTION_LIMIT) -> str: + value = (prompt or "").strip() + suffix = f"\n\n{RESTART_HINT}" + available = max(0, limit - len(suffix)) + if len(value) > available: + value = value[: max(0, available - 1)].rstrip() + if value: + value += "…" + return f"{value}{suffix}" if value else RESTART_HINT def polling_is_stuck( @@ -64,7 +77,7 @@ START_TEXT = ( "/balance — баланс генераций.\n" "/invite — пригласить друга.\n" "/status — состояние текущей задачи.\n" - "/reset — начать новую сцену." + "/restart — сброс контекста." ) BLOCKED_TEXT = "Запрос нарушает правила. Звёзды не списаны." AGE_TEXT = ( @@ -125,6 +138,7 @@ class MpakoBot: application.add_handler(CommandHandler("status", self._status)) application.add_handler(CommandHandler("balance", self._balance)) application.add_handler(CommandHandler("invite", self._invite)) + application.add_handler(CommandHandler("restart", self._reset)) application.add_handler(CommandHandler("reset", self._reset)) application.add_handler( PreCheckoutQueryHandler(self._pre_checkout) @@ -1284,7 +1298,8 @@ class MpakoBot: if self._bot is None: return - caption = f"Готово. Seed: {request.seed}" if request.seed is not None else "Готово." + prompt = request.enhanced_prompt or request.prompt_text + caption = result_caption(prompt) filename = Path(output_path).name try: content = await asyncio.to_thread(Path(output_path).read_bytes) diff --git a/checklist.md b/checklist.md index 8a2f9a8..3b14041 100644 --- a/checklist.md +++ b/checklist.md @@ -8,7 +8,7 @@ - Реализованы блоки 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 и веб-история. -- 66 тестов: 39 проходят локально без БД, 27 тестов БД выполняются в CI на PostgreSQL через `TEST_DATABASE_URL`. Покрыты workflows, очередь, веб-история, enhancer, модерация, платежи, премиум-очередь, баланс/рефералы, управление пользователями, retry, миграция, watchdog, контекст, админ-панель, скрытие истории. +- 69 тестов: 42 проходят локально без БД, 27 тестов БД выполняются в CI на PostgreSQL через `TEST_DATABASE_URL`. Покрыты workflows, очередь, веб-история, enhancer, модерация, платежи, премиум-очередь, баланс/рефералы, управление пользователями, retry, миграция, watchdog, контекст, админ-панель, скрытие истории. - Не проверено: реальная оплата Stars и реферальная выплата в Telegram, очередь из 3+ пользователей, падение ComfyUI, рестарт с `running`-задачей, детекция лица на реальном фото. PostgreSQL-миграция и тесты БД ещё не запускались вживую — это сделает CI и первый деплой. ## 0. Допущения и решения по умолчанию @@ -387,10 +387,11 @@ ON requests(user_id) WHERE status IN ('queued', 'running'); - [x] Снимок контекста сохраняется в `requests` и `pending_payments`. - [x] Следующий текстовый запрос обрабатывается вместе с предыдущим промптом. - [x] Контекст обновляется только после успешной отправки результата. -- [x] `/reset` очищает контекст; новое фото начинает новую цепочку. -- [x] Тесты БД, enhancer и очереди. +- [x] `/restart` очищает контекст; новое фото начинает новую цепочку. +- [x] В подписи результата итоговый промпт и подсказка `/restart`. +- [x] Тесты БД, enhancer, очереди и подписи. -**DoD:** продолжение сцены работает по тексту, `/reset` возвращает к новой сцене. +**DoD:** продолжение сцены работает по тексту, `/restart` возвращает к новой сцене. ## 22. Админ-панель и очистка ComfyUI diff --git a/tests/test_caption.py b/tests/test_caption.py new file mode 100644 index 0000000..0c52e23 --- /dev/null +++ b/tests/test_caption.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from bot import RESTART_HINT, result_caption + + +def test_result_caption_contains_prompt_and_restart_hint() -> None: + caption = result_caption("A red apple on a table") + assert caption == f"A red apple on a table\n\n{RESTART_HINT}" + + +def test_result_caption_truncates_long_prompt() -> None: + caption = result_caption("x" * 2000, limit=100) + assert len(caption) <= 100 + assert caption.endswith(RESTART_HINT) + assert "…" in caption + + +def test_result_caption_empty_prompt() -> None: + assert result_caption("") == RESTART_HINT