From 02fe426c946fae07dc0ceed16faf016ee9e0fbae Mon Sep 17 00:00:00 2001 From: Ken Schaefer Date: Tue, 12 May 2026 20:30:00 -0500 Subject: [PATCH] Add Hearthkeeper preview endpoint --- app/agent/orchestrator.py | 32 ++++++++++++++++++++++++++++++++ app/main.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/app/agent/orchestrator.py b/app/agent/orchestrator.py index f884f59..8193d29 100644 --- a/app/agent/orchestrator.py +++ b/app/agent/orchestrator.py @@ -398,6 +398,38 @@ class AgentOrchestrator: "third_tick_after_interval": third_tick, } + async def preview_hearthkeeper_prompt(self, session_id: str) -> dict: + """Generate a Hearthkeeper prompt preview without sending or recording it.""" + session_info = self.active_sessions.get(session_id) + if not session_info: + return {"generated": False, "reason": "session_not_found"} + + recent_discussion_messages = [] + async for db_session in get_session(): + repo = Repository(db_session) + recent_discussion_messages = await repo.get_recent_human_messages( + session_id=session_id, + limit=5, + ) + + recent_discussion = [ + message.content for message in recent_discussion_messages[:5] + ] + agent_response = await self.hearthkeeper.generate_prompt( + theme=session_info.get("theme"), + dashboard=session_info.get("dashboard"), + recent_discussion=recent_discussion, + ) + + return { + "generated": True, + "session_id": session_id, + "agent_response": agent_response, + "theme": session_info.get("theme"), + "dashboard": session_info.get("dashboard"), + "recent_discussion": recent_discussion, + } + async def _count_response_actions(self, session_id: str, mode: str) -> int: """Count response actions for a mode in a session.""" async for db_session in get_session(): diff --git a/app/main.py b/app/main.py index 2749665..720a606 100644 --- a/app/main.py +++ b/app/main.py @@ -302,6 +302,38 @@ async def test_loop_inactivity( } +@app.post("/admin/hearthkeeper/preview", dependencies=[Depends(require_admin)]) +async def preview_hearthkeeper_prompt(session_id: str | None = Form(None)) -> dict: + """Generate a Hearthkeeper prompt preview without posting to Twitch.""" + if not orchestrator: + raise HTTPException(status_code=503, detail="Orchestrator not initialized") + + resolved_session_id = session_id or get_current_stream_session_id() + if not resolved_session_id: + raise HTTPException(status_code=404, detail="No active stream session found") + + result = await orchestrator.preview_hearthkeeper_prompt(resolved_session_id) + if result.get("reason") == "session_not_found": + raise HTTPException(status_code=404, detail="Active session not found") + + live_status = None + session_info = orchestrator.active_sessions.get(resolved_session_id) + if twitch_live_status and session_info: + live_status = ( + await twitch_live_status.get_status(session_info["channel_name"]) + ).to_dict() + + return { + "status": "preview_generated" if result.get("generated") else "failed", + "preview": result, + "would_post_now": await orchestrator.can_interact_with_chat( + session_info["channel_name"] + ) if session_info else False, + "live_status": live_status, + "timestamp": datetime.utcnow().isoformat(), + } + + def serialize_dashboard(dashboard) -> dict: """Serialize a dashboard database model into an API response.""" return Repository.serialize_dashboard(dashboard)