Add War Room voice meeting, fix chat overlay, and ship 5 cyan UI themes.
Voice Room lets humans talk with agents via Whisper/TTS; chat typing no longer blocked by a full-screen overlay, attachments/avatars are larger, and Settings exposes five vivid mid-dark cyan/blue themes for the whole cockpit.
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
"""Daily Herman War Room digest → Telegram (Aïssa + Mo)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from app.config import settings
|
||||
from app.db import execute, fetch_all, fetch_one
|
||||
from app.services import llm_router
|
||||
|
||||
log = logging.getLogger("cockpit.warroom_digest")
|
||||
|
||||
|
||||
def _recipients() -> list[tuple[str, str]]:
|
||||
"""(chat_id, display_name)"""
|
||||
return [
|
||||
(str(getattr(settings, "HERMES_CEO_CHAT_ID", None) or "8859782446"), "Aïssa"),
|
||||
(str(getattr(settings, "HERMES_CTO_CHAT_ID", None) or "789036463"), "Mo"),
|
||||
]
|
||||
|
||||
|
||||
def collect_warroom_day(*, hours: int = 24, limit: int = 120) -> list[dict[str, Any]]:
|
||||
hours = max(1, min(int(hours), 72))
|
||||
rows = fetch_all(
|
||||
"""
|
||||
SELECT m.id, m.sender_type, m.agent_key, m.username, m.content, m.created_at, r.kind, r.title
|
||||
FROM warroom_messages m
|
||||
JOIN warroom_rooms r ON r.id = m.room_id
|
||||
WHERE m.created_at >= NOW() - make_interval(hours => %s)
|
||||
AND r.kind = 'warroom'
|
||||
ORDER BY m.created_at ASC
|
||||
LIMIT %s
|
||||
""",
|
||||
(hours, limit),
|
||||
)
|
||||
out = []
|
||||
for r in rows or []:
|
||||
item = dict(r)
|
||||
if item.get("created_at") and hasattr(item["created_at"], "isoformat"):
|
||||
item["created_at"] = item["created_at"].isoformat()
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
def _transcript(messages: list[dict[str, Any]], *, max_chars: int = 6000) -> str:
|
||||
lines: list[str] = []
|
||||
for m in messages:
|
||||
st = m.get("sender_type")
|
||||
if st == "user":
|
||||
who = m.get("username") or "user"
|
||||
elif st == "agent":
|
||||
who = m.get("agent_key") or "agent"
|
||||
else:
|
||||
who = "system"
|
||||
content = (m.get("content") or "").strip().replace("\n", " ")
|
||||
if not content:
|
||||
continue
|
||||
lines.append(f"- {who}: {content[:280]}")
|
||||
text = "\n".join(lines)
|
||||
return text[-max_chars:]
|
||||
|
||||
|
||||
async def generate_digest_text(messages: list[dict[str, Any]]) -> str:
|
||||
day = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
if not messages:
|
||||
return (
|
||||
f"📋 War Room dagrapport {day}\n\n"
|
||||
"Geen activiteit in #war-room vandaag. Agents stonden klaar; geen overleg gelogd."
|
||||
)
|
||||
transcript = _transcript(messages)
|
||||
system = (
|
||||
"Je bent Herman, Co-CEO van Foodlinkk. Schrijf een beknopte Nederlandse "
|
||||
"Telegram-samenvatting van het War Room-overleg voor Aïssa (CEO) en Mo (CTO).\n"
|
||||
"Structuur:\n"
|
||||
f"1) Kop: 📋 War Room dagrapport {day}\n"
|
||||
"2) 3–6 bullets: wat agents bespraken (pipeline, deals, acties, blockers)\n"
|
||||
"3) Sectie 'Acties / open punten' met concrete next steps\n"
|
||||
"4) 1 zin afsluiting\n"
|
||||
"Max ~350 woorden. Geen @zelf-prefix. Geen 'agents staan uit'."
|
||||
)
|
||||
user = f"War Room transcript (nieuwste onderaan):\n{transcript}"
|
||||
reply, _meta = await llm_router.chat_messages(
|
||||
[{"role": "system", "content": system}, {"role": "user", "content": user}],
|
||||
timeout=90.0,
|
||||
user=str(getattr(settings, "HERMES_CEO_CHAT_ID", None) or "8859782446"),
|
||||
max_tokens=900,
|
||||
temperature=0.3,
|
||||
)
|
||||
text = (reply or "").strip()
|
||||
if not text.startswith("📋"):
|
||||
text = f"📋 War Room dagrapport {day}\n\n{text}"
|
||||
return text[:3500]
|
||||
|
||||
|
||||
async def _deliver_telegram(chat_id: str, name: str, digest: str) -> dict[str, Any]:
|
||||
"""Ask Hermes agent to deliver the digest into that Telegram chat."""
|
||||
system = (
|
||||
f"Je bent Herman. Bezorg het volgende War Room-dagrapport NU via Telegram "
|
||||
f"aan {name} (chat_id={chat_id}). Stuur de tekst zo letterlijk mogelijk. "
|
||||
"Bevestig kort of verzonden."
|
||||
)
|
||||
try:
|
||||
reply, meta = await llm_router.chat_messages(
|
||||
[
|
||||
{"role": "system", "content": system},
|
||||
{"role": "user", "content": digest},
|
||||
],
|
||||
timeout=120.0,
|
||||
user=str(chat_id),
|
||||
max_tokens=200,
|
||||
temperature=0.1,
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"chat_id": chat_id,
|
||||
"name": name,
|
||||
"confirm": (reply or "")[:240],
|
||||
"provider": (meta or {}).get("provider_type"),
|
||||
}
|
||||
except Exception as exc:
|
||||
log.exception("warroom digest deliver failed %s", chat_id)
|
||||
return {"ok": False, "chat_id": chat_id, "name": name, "error": str(exc)}
|
||||
|
||||
|
||||
def _persist(digest: str, message_count: int, deliveries: list[dict[str, Any]]) -> None:
|
||||
try:
|
||||
execute(
|
||||
"""
|
||||
INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel, metadata)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)
|
||||
""",
|
||||
(
|
||||
"herman",
|
||||
"warroom_digest",
|
||||
"warroom_daily_digest",
|
||||
"War Room dagrapport verstuurd",
|
||||
digest[:4000],
|
||||
"completed",
|
||||
"telegram",
|
||||
__import__("json").dumps(
|
||||
{
|
||||
"message_count": message_count,
|
||||
"deliveries": deliveries,
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
log.exception("persist digest event failed")
|
||||
|
||||
try:
|
||||
from app.services import agent_chat
|
||||
|
||||
agent_chat.post_system_line(
|
||||
"📤 Dagrapport verstuurd naar Aïssa + Mo via Telegram.\n\n" + digest[:1500],
|
||||
agent_key="herman",
|
||||
metadata={"kind": "warroom_digest", "deliveries": deliveries},
|
||||
)
|
||||
except Exception:
|
||||
log.exception("post digest to warroom failed")
|
||||
|
||||
|
||||
async def run_daily_digest(*, hours: int = 24, deliver: bool = True) -> dict[str, Any]:
|
||||
messages = collect_warroom_day(hours=hours)
|
||||
digest = await generate_digest_text(messages)
|
||||
deliveries: list[dict[str, Any]] = []
|
||||
if deliver:
|
||||
for chat_id, name in _recipients():
|
||||
deliveries.append(await _deliver_telegram(chat_id, name, digest))
|
||||
_persist(digest, len(messages), deliveries)
|
||||
return {
|
||||
"ok": True,
|
||||
"message_count": len(messages),
|
||||
"digest": digest,
|
||||
"deliveries": deliveries,
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
Reference in New Issue
Block a user