Снимок состояния до переноса серверного UI
This commit is contained in:
parent
2c2aad1355
commit
c97b3e3ec3
@ -0,0 +1,14 @@
|
|||||||
|
# Диагностика больших voice/audio в Telegram-боте
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
- Бот при большом voice/audio больше не отказывается заранее по метаданным Telegram. Теперь он сначала сообщает, что пробует скачать файл, затем отдельно сообщает об успешном скачивании и только после этого переходит к подготовке аудио и распознаванию через OpenAI.
|
||||||
|
- что именно проверять:
|
||||||
|
- Отправить в бота большой `voice` или `audio`, который раньше попадал под ранний отказ.
|
||||||
|
- Проверить, что сначала приходит сообщение о попытке скачать большой файл.
|
||||||
|
- Проверить два сценария:
|
||||||
|
- скачивание удалось: бот пишет об успешной загрузке и продолжает распознавание;
|
||||||
|
- скачивание не удалось: бот пишет именно о неудачном скачивании из Telegram, без ложной привязки к ошибке OpenAI.
|
||||||
|
- ожидаемый результат:
|
||||||
|
- Пользователь видит понятную поэтапную диагностику: попытка скачивания, результат скачивания и только потом следующий этап обработки.
|
||||||
|
- статус:
|
||||||
|
- pending
|
||||||
@ -57,6 +57,7 @@ python3 SHiNE-agent-bot-coder/py_bot_service.py --selftest-codex "Ответь
|
|||||||
## Длинные voice/audio
|
## Длинные voice/audio
|
||||||
- Если аудио короткое, бот отправляет его в OpenAI как раньше.
|
- Если аудио короткое, бот отправляет его в OpenAI как раньше.
|
||||||
- Если аудио большое или длинное, бот локально пережимает его через `ffmpeg`, при необходимости режет на куски и распознаёт последовательно.
|
- Если аудио большое или длинное, бот локально пережимает его через `ffmpeg`, при необходимости режет на куски и распознаёт последовательно.
|
||||||
|
- Если Telegram заранее сообщает большой размер файла, бот больше не отказывается сразу: сначала явно пишет, что пробует скачать файл, затем отдельно сообщает, удалось ли скачивание, и только после успешной загрузки переходит к подготовке аудио и OpenAI.
|
||||||
- Для очень больших файлов упираемся не только в OpenAI, но и в лимит обычного облачного Telegram Bot API на скачивание файла ботом. Для таких случаев нужно использовать локальный `telegram-bot-api` сервер и указать его через `TELEGRAM_API_BASE_URL`.
|
- Для очень больших файлов упираемся не только в OpenAI, но и в лимит обычного облачного Telegram Bot API на скачивание файла ботом. Для таких случаев нужно использовать локальный `telegram-bot-api` сервер и указать его через `TELEGRAM_API_BASE_URL`.
|
||||||
|
|
||||||
## Запуск как systemd-сервис
|
## Запуск как systemd-сервис
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
from urllib import error, request
|
from urllib import error, request
|
||||||
|
|
||||||
DEFAULT_ALLOWED_PLAYERS = ",".join([
|
DEFAULT_ALLOWED_PLAYERS = ",".join([
|
||||||
@ -1509,7 +1509,10 @@ class ShinePyBotService:
|
|||||||
try:
|
try:
|
||||||
if job.get("type") == "voice":
|
if job.get("type") == "voice":
|
||||||
self._safe_send(chat_id, f"#{job_num}: распознаю голосовое...", reply_to=message_id)
|
self._safe_send(chat_id, f"#{job_num}: распознаю голосовое...", reply_to=message_id)
|
||||||
recognized = self._transcribe_voice_job(job)
|
recognized = self._transcribe_voice_job(
|
||||||
|
job,
|
||||||
|
status_cb=lambda note: self._safe_send(chat_id, f"#{job_num}: {note}", reply_to=message_id),
|
||||||
|
)
|
||||||
job["text"] = recognized
|
job["text"] = recognized
|
||||||
self._append_history(history_path, "voice_transcription", {"jobId": job_id, "jobNum": job_num, "text": recognized})
|
self._append_history(history_path, "voice_transcription", {"jobId": job_id, "jobNum": job_num, "text": recognized})
|
||||||
preview = recognized.strip()
|
preview = recognized.strip()
|
||||||
@ -2206,7 +2209,12 @@ class ShinePyBotService:
|
|||||||
raise VoiceReplyError("OpenAI вернул пустой аудиофайл.")
|
raise VoiceReplyError("OpenAI вернул пустой аудиофайл.")
|
||||||
return audio
|
return audio
|
||||||
|
|
||||||
def _transcribe_voice_job(self, job: dict[str, Any]) -> str:
|
def _transcribe_voice_job(
|
||||||
|
self,
|
||||||
|
job: dict[str, Any],
|
||||||
|
*,
|
||||||
|
status_cb: Callable[[str], Any] | None = None,
|
||||||
|
) -> str:
|
||||||
if not self.cfg.openai_api_key:
|
if not self.cfg.openai_api_key:
|
||||||
raise VoiceTranscriptionError(
|
raise VoiceTranscriptionError(
|
||||||
"не настроен ключ OpenAI для распознавания.",
|
"не настроен ключ OpenAI для распознавания.",
|
||||||
@ -2225,17 +2233,11 @@ class ShinePyBotService:
|
|||||||
media_type = (job.get("telegram_media_type") or "voice").strip()
|
media_type = (job.get("telegram_media_type") or "voice").strip()
|
||||||
duration_seconds = int(job.get("telegram_duration_seconds") or 0)
|
duration_seconds = int(job.get("telegram_duration_seconds") or 0)
|
||||||
telegram_file_size = int(job.get("telegram_file_size") or 0)
|
telegram_file_size = int(job.get("telegram_file_size") or 0)
|
||||||
if self._telegram_cloud_download_is_likely_too_big(telegram_file_size):
|
file_looks_big_for_cloud = self._telegram_cloud_download_is_likely_too_big(telegram_file_size)
|
||||||
limit_mb = self._bytes_to_mb(20 * 1024 * 1024)
|
if file_looks_big_for_cloud and status_cb is not None:
|
||||||
actual_mb = self._bytes_to_mb(telegram_file_size)
|
status_cb(
|
||||||
raise VoiceTranscriptionError(
|
"файл большой, всё равно пробую скачать его из Telegram. "
|
||||||
(
|
f"Предварительный размер около {self._bytes_to_mb(telegram_file_size)} MB."
|
||||||
f"Telegram не даст этому боту скачать такой файл через обычный Bot API "
|
|
||||||
f"(примерно {actual_mb} MB при лимите около {limit_mb} MB). "
|
|
||||||
f"Для очень длинных аудио нужен локальный `telegram-bot-api` сервер или другой способ доставки файла."
|
|
||||||
),
|
|
||||||
stage="telegram_get_file_too_big",
|
|
||||||
retryable=False,
|
|
||||||
)
|
)
|
||||||
started_at = time.time()
|
started_at = time.time()
|
||||||
print(f"[py-bot] transcribe start job={job_id} num={job_num} media={media_type}", flush=True)
|
print(f"[py-bot] transcribe start job={job_id} num={job_num} media={media_type}", flush=True)
|
||||||
@ -2244,6 +2246,11 @@ class ShinePyBotService:
|
|||||||
f"[py-bot] transcribe downloaded job={job_id} filename={filename} size={len(file_bytes)} bytes",
|
f"[py-bot] transcribe downloaded job={job_id} filename={filename} size={len(file_bytes)} bytes",
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
if file_looks_big_for_cloud and status_cb is not None:
|
||||||
|
status_cb(
|
||||||
|
"скачивание из Telegram прошло успешно. "
|
||||||
|
f"Фактический размер около {self._bytes_to_mb(len(file_bytes))} MB, дальше готовлю аудио и отправляю в OpenAI."
|
||||||
|
)
|
||||||
prepared_parts = self._prepare_audio_parts_for_transcription(
|
prepared_parts = self._prepare_audio_parts_for_transcription(
|
||||||
file_bytes,
|
file_bytes,
|
||||||
filename,
|
filename,
|
||||||
@ -2290,7 +2297,9 @@ class ShinePyBotService:
|
|||||||
detail = str(e)
|
detail = str(e)
|
||||||
if "file is too big" in detail.lower():
|
if "file is too big" in detail.lower():
|
||||||
raise VoiceTranscriptionError(
|
raise VoiceTranscriptionError(
|
||||||
"Telegram считает файл слишком большим для скачивания через текущий Bot API. Для такого аудио нужен локальный `telegram-bot-api` сервер или другой способ передать файл боту.",
|
"Файл большой: я попробовал скачать его через текущий Telegram Bot API, "
|
||||||
|
"но Telegram не дал это сделать. Для такого аудио нужен локальный `telegram-bot-api` "
|
||||||
|
"сервер или другой способ передать файл боту.",
|
||||||
stage="telegram_get_file_too_big",
|
stage="telegram_get_file_too_big",
|
||||||
retryable=False,
|
retryable=False,
|
||||||
detail=detail,
|
detail=detail,
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.115
|
client.version=1.2.116
|
||||||
server.version=1.2.107
|
server.version=1.2.108
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
# UI deploy
|
|
||||||
|
|
||||||
Актуальный UI-деплой выполняется одной командой:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew deployUI
|
|
||||||
```
|
|
||||||
|
|
||||||
По умолчанию:
|
|
||||||
|
|
||||||
- хост: `player@93.170.12.154`
|
|
||||||
- домен: `https://shineup.me`
|
|
||||||
- путь: `/home/player/SHiNE/SHiNE-UI`
|
|
||||||
|
|
||||||
Переопределение при необходимости:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
REMOTE_HOST=player@93.170.12.154 REMOTE_BASE_DIR=/home/player/SHiNE bash deploy_shine-PWA.sh
|
|
||||||
```
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# MVP notes: Web Push
|
|
||||||
|
|
||||||
## Временное поведение (сделано для тестового стенда)
|
|
||||||
|
|
||||||
- Клиент отправляет push-подписку на сервер при каждом запуске после авторизации, даже если подписка не изменилась.
|
|
||||||
- Причина: на тестовом сервере/после переустановки БД запись о токене может пропасть, а клиент этого не узнает.
|
|
||||||
|
|
||||||
## Что доработать для production
|
|
||||||
|
|
||||||
- Вернуть режим "отправлять только при изменении подписки" как основной.
|
|
||||||
- Добавить безопасный механизм ресинхронизации:
|
|
||||||
- Вариант 1: периодическая принудительная отправка (например, 1 раз в N дней).
|
|
||||||
- Вариант 2: endpoint на сервере "есть ли подписка", и отправка только при отсутствии/рассинхроне.
|
|
||||||
- В логах разделить обычную отправку и принудительную, чтобы видеть лишний трафик.
|
|
||||||
- Добавить e2e-тесты сценариев:
|
|
||||||
- Переустановка сервера (потеря токена в БД).
|
|
||||||
- Смена браузерной подписки.
|
|
||||||
- Повторный запуск клиента без изменений.
|
|
||||||
Loading…
Reference in New Issue
Block a user