Итоговый промпт в подписи и /restart
Build and push Docker image / tests (push) Successful in 23s Details
Build and push Docker image / docker (push) Successful in 12s Details

В подписи результата показываю итоговый промпт и подсказку /restart. /reset оставил алиасом для совместимости.
This commit is contained in:
MpakobecEx 2026-10-19 22:20:44 +05:00
parent 67d3180ef6
commit d4263c8420
4 changed files with 45 additions and 10 deletions

View File

@ -15,7 +15,7 @@ Telegram-бот для генерации и редактирования изо
- **Баланс и рефералы**: 3 стартовые генерации, +1 ежедневно в 00:00 МСК, +3 за первую оплату друга. - **Баланс и рефералы**: 3 стартовые генерации, +1 ежедневно в 00:00 МСК, +3 за первую оплату друга.
- **Управление пользователями**: в веб-интерфейсе можно выдавать админку, менять баланс и блокировать. - **Управление пользователями**: в веб-интерфейсе можно выдавать админку, менять баланс и блокировать.
- **Подтверждение 18+**: без подтверждения возраста бот не принимает запросы. - **Подтверждение 18+**: без подтверждения возраста бот не принимает запросы.
- **Контекст**: следующий текстовый запрос продолжает предыдущую сцену; `/reset` начинает новую. - **Контекст**: следующий текстовый запрос продолжает предыдущую сцену; `/restart` начинает новую.
- Глобальная очередь с одной активной задачей на пользователя. - Глобальная очередь с одной активной задачей на пользователя.
- Позиция в очереди и прогресс генерации через WebSocket ComfyUI. - Позиция в очереди и прогресс генерации через WebSocket ComfyUI.
- PostgreSQL: запросы, статусы, seed, пути к файлам. - PostgreSQL: запросы, статусы, seed, пути к файлам.
@ -202,7 +202,7 @@ TELEGRAM_HEALTH_INTERVAL=60
- Премиум-очередь всегда оплачивается 15 звёздами; баланс при этом не расходуется. - Премиум-очередь всегда оплачивается 15 звёздами; баланс при этом не расходуется.
- `/balance` — текущий баланс. - `/balance` — текущий баланс.
- `/invite` — реферальная ссылка. - `/invite` — реферальная ссылка.
- `/reset` — сбросить контекст и начать новую сцену. - `/restart` — сбросить контекст и начать новую сцену (алиас `/reset`).
- За первую оплату приглашённого друга начисляется +3 генерации, реферер получает уведомление. - За первую оплату приглашённого друга начисляется +3 генерации, реферер получает уведомление.
Баланс и рефералы хранятся в таблице `users`. Пропущенные дни начисляются при следующем запуске/полуночи. Баланс и рефералы хранятся в таблице `users`. Пропущенные дни начисляются при следующем запуске/полуночи.
@ -211,10 +211,10 @@ TELEGRAM_HEALTH_INTERVAL=60
- Первый текстовый запрос создаёт контекст: базовую сцену и текущий улучшенный промпт. - Первый текстовый запрос создаёт контекст: базовую сцену и текущий улучшенный промпт.
- Следующий текст обрабатывается вместе с предыдущим промптом: бот сохраняет сцену и применяет только запрошенное изменение. - Следующий текст обрабатывается вместе с предыдущим промптом: бот сохраняет сцену и применяет только запрошенное изменение.
- `/reset` очищает контекст; следующий запрос начнёт новую сцену. - `/restart` очищает контекст; следующий запрос начнёт новую сцену.
- Новое фото с подписью автоматически начинает новую цепочку. - Новое фото с подписью автоматически начинает новую цепочку.
- Для точного редактирования конкретной картинки ответь на сообщение бота с изображением. - Для точного редактирования конкретной картинки ответь на сообщение бота с изображением.
- `/reset` не отменяет уже запущенную генерацию: она завершится со своим снимком контекста. - `/restart` не отменяет уже запущенную генерацию: она завершится со своим снимком контекста.
Контекст хранится в таблице `user_contexts` и обновляется только после успешной отправки результата. Контекст хранится в таблице `user_contexts` и обновляется только после успешной отправки результата.

19
bot.py
View File

@ -43,6 +43,19 @@ logger = logging.getLogger(__name__)
MSK = timezone(timedelta(hours=3)) MSK = timezone(timedelta(hours=3))
POLLING_WATCHDOG_INTERVAL = 60.0 POLLING_WATCHDOG_INTERVAL = 60.0
POLLING_WATCHDOG_STUCK_AFTER = 300.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( def polling_is_stuck(
@ -64,7 +77,7 @@ START_TEXT = (
"/balance — баланс генераций.\n" "/balance — баланс генераций.\n"
"/invite — пригласить друга.\n" "/invite — пригласить друга.\n"
"/status — состояние текущей задачи.\n" "/status — состояние текущей задачи.\n"
"/reset — начать новую сцену." "/restart — сброс контекста."
) )
BLOCKED_TEXT = "Запрос нарушает правила. Звёзды не списаны." BLOCKED_TEXT = "Запрос нарушает правила. Звёзды не списаны."
AGE_TEXT = ( AGE_TEXT = (
@ -125,6 +138,7 @@ class MpakoBot:
application.add_handler(CommandHandler("status", self._status)) application.add_handler(CommandHandler("status", self._status))
application.add_handler(CommandHandler("balance", self._balance)) application.add_handler(CommandHandler("balance", self._balance))
application.add_handler(CommandHandler("invite", self._invite)) application.add_handler(CommandHandler("invite", self._invite))
application.add_handler(CommandHandler("restart", self._reset))
application.add_handler(CommandHandler("reset", self._reset)) application.add_handler(CommandHandler("reset", self._reset))
application.add_handler( application.add_handler(
PreCheckoutQueryHandler(self._pre_checkout) PreCheckoutQueryHandler(self._pre_checkout)
@ -1284,7 +1298,8 @@ class MpakoBot:
if self._bot is None: if self._bot is None:
return 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 filename = Path(output_path).name
try: try:
content = await asyncio.to_thread(Path(output_path).read_bytes) content = await asyncio.to_thread(Path(output_path).read_bytes)

View File

@ -8,7 +8,7 @@
- Реализованы блоки 2–7, базовое хранилище (9), файлы Docker (10), тесты (11), README (12), веб-история (13), улучшение промптов (14), модерация (15), оплата Stars (16), баланс/рефералы (17), управление пользователями (18), возрастной gate (19) и надёжность Telegram (20). - Реализованы блоки 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 и веб-история. - Вживую проверены: 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 и первый деплой. - Не проверено: реальная оплата Stars и реферальная выплата в Telegram, очередь из 3+ пользователей, падение ComfyUI, рестарт с `running`-задачей, детекция лица на реальном фото. PostgreSQL-миграция и тесты БД ещё не запускались вживую — это сделает CI и первый деплой.
## 0. Допущения и решения по умолчанию ## 0. Допущения и решения по умолчанию
@ -387,10 +387,11 @@ ON requests(user_id) WHERE status IN ('queued', 'running');
- [x] Снимок контекста сохраняется в `requests` и `pending_payments`. - [x] Снимок контекста сохраняется в `requests` и `pending_payments`.
- [x] Следующий текстовый запрос обрабатывается вместе с предыдущим промптом. - [x] Следующий текстовый запрос обрабатывается вместе с предыдущим промптом.
- [x] Контекст обновляется только после успешной отправки результата. - [x] Контекст обновляется только после успешной отправки результата.
- [x] `/reset` очищает контекст; новое фото начинает новую цепочку. - [x] `/restart` очищает контекст; новое фото начинает новую цепочку.
- [x] Тесты БД, enhancer и очереди. - [x] В подписи результата итоговый промпт и подсказка `/restart`.
- [x] Тесты БД, enhancer, очереди и подписи.
**DoD:** продолжение сцены работает по тексту, `/reset` возвращает к новой сцене. **DoD:** продолжение сцены работает по тексту, `/restart` возвращает к новой сцене.
## 22. Админ-панель и очистка ComfyUI ## 22. Админ-панель и очистка ComfyUI

19
tests/test_caption.py Normal file
View File

@ -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