smart tdee

This commit is contained in:
2026-06-16 04:38:23 +00:00
parent f2e98942ff
commit a3f01cd850
56 changed files with 2519 additions and 591 deletions
+26
View File
@@ -130,6 +130,32 @@ class HaClient:
):
yield chunk
async def send_message_with_image_stream(
self,
session_id: int,
content: str,
image_bytes: bytes,
*,
filename: str = "photo.jpg",
content_type: str = "image/jpeg",
) -> AsyncIterator[SseChunk]:
url = f"{self.base_url}/chat/sessions/{session_id}/messages"
files = {"image": (filename, image_bytes, content_type)}
data = {"content": content}
async with httpx.AsyncClient(timeout=None) as client:
async with client.stream(
"POST",
url,
headers=self._headers({"Accept": "text/event-stream"}),
files=files,
data=data,
) as response:
if response.status_code >= 400:
body = await response.aread()
raise HaApiError(body.decode("utf-8", errors="replace") or f"HTTP {response.status_code}", response.status_code)
async for chunk in iter_sse(response):
yield chunk
async def stream_generation(self, session_id: int) -> AsyncIterator[SseChunk]:
async for chunk in self._stream("GET", f"/chat/sessions/{session_id}/generation/stream"):
yield chunk
+77
View File
@@ -60,6 +60,32 @@ async def _run_chat_stream(
HaClient(settings.ha_api_base_url, linked.api_token),
ha_api_base=settings.ha_api_base_url,
)
elif chunk.event == "vision":
data = chunk.data if isinstance(chunk.data, dict) else {}
images = data.get("images")
if isinstance(images, list) and images:
for index, item in enumerate(images, start=1):
if not isinstance(item, dict):
continue
parsed = item.get("parsed")
model = item.get("model")
preview = ""
if isinstance(parsed, dict):
preview = str(parsed.get("description") or "")[:400]
lines = [f"Vision {index}/{len(images)} ({model or '?'}):", preview or "(нет описания)"]
if isinstance(parsed, dict) and parsed.get("fitness_hints"):
lines.append(f"fitness_hints: {parsed.get('fitness_hints')}")
await message.answer("\n".join(lines)[:4000])
else:
parsed = data.get("parsed")
model = data.get("model")
preview = ""
if isinstance(parsed, dict):
preview = str(parsed.get("description") or "")[:400]
lines = [f"Vision ({model or '?'}):", preview or "(нет описания)"]
if isinstance(parsed, dict) and parsed.get("fitness_hints"):
lines.append(f"fitness_hints: {parsed.get('fitness_hints')}")
await message.answer("\n".join(lines)[:4000])
elif chunk.event == "error":
err = str(chunk.data.get("message") or "Ошибка генерации")
await message.answer(err)
@@ -134,3 +160,54 @@ async def handle_chat_message(message: Message, settings: Settings, storage: Sto
except Exception as exc:
logger.exception("Chat stream failed for telegram_id=%s", message.from_user.id)
await message.answer(f"Ошибка при получении ответа: {exc}")
@router.message(F.photo, IsLinked())
async def handle_chat_photo(message: Message, settings: Settings, storage: Storage) -> None:
if not is_allowed(message, settings):
return
if not message.from_user or not message.photo:
return
linked = await storage.get_user(message.from_user.id)
if not linked:
return
lock = _user_lock(message.from_user.id)
if lock.locked():
await message.answer("Подожди, предыдущий ответ ещё генерируется.")
return
async with lock:
client = HaClient(settings.ha_api_base_url, linked.api_token)
caption = (message.caption or "").strip()
photo = message.photo[-1]
try:
file = await message.bot.get_file(photo.file_id)
if not file.file_path:
await message.answer("Не удалось получить файл фото.")
return
downloaded = await message.bot.download_file(file.file_path)
image_bytes = downloaded.read() if hasattr(downloaded, "read") else bytes(downloaded)
status = await client.generation_status(linked.session_id)
if status.get("active"):
stream = client.stream_generation(linked.session_id)
else:
stream = client.send_message_with_image_stream(
linked.session_id,
caption,
image_bytes,
filename="telegram.jpg",
)
except Exception as exc:
logger.exception("Failed to start chat photo for telegram_id=%s", message.from_user.id)
await message.answer(f"Ошибка связи с Home Assistant: {exc}")
return
try:
await _run_chat_stream(message, settings, storage, linked, _iter_stream(stream))
except Exception as exc:
logger.exception("Chat photo stream failed for telegram_id=%s", message.from_user.id)
await message.answer(f"Ошибка при получении ответа: {exc}")