72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
"""Chat detail (shell) page.
|
|
|
|
Renders ``/chats/<id>``: the title (host bot's name), a timeline placeholder,
|
|
the user-input form, and the drawer toggle. Turn handling lives in T19; this
|
|
module only sets up the structural shell.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from chat.state.entities import get_bot
|
|
from chat.state.world import get_chat
|
|
from chat.web.bots import get_conn
|
|
from chat.web.render import render_prose
|
|
from chat.web.turns import _read_recent_dialogue
|
|
|
|
TEMPLATES = Jinja2Templates(
|
|
directory=str(Path(__file__).resolve().parent.parent / "templates")
|
|
)
|
|
# Register the prose renderer as a Jinja filter so the chat-detail
|
|
# template can use ``{{ turn.text|render_prose|safe }}`` (Task 33).
|
|
# The renderer escapes user content internally; ``|safe`` is required
|
|
# because the output contains intentional ``<p>``/``<em>``/etc. tags.
|
|
TEMPLATES.env.filters["render_prose"] = render_prose
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/chats/{chat_id}", response_class=HTMLResponse)
|
|
async def chat_detail(chat_id: str, request: Request, conn=Depends(get_conn)):
|
|
chat = get_chat(conn, chat_id)
|
|
if chat is None:
|
|
raise HTTPException(status_code=404, detail=f"chat not found: {chat_id}")
|
|
|
|
host_bot = get_bot(conn, chat["host_bot_id"])
|
|
if host_bot is None:
|
|
# Defensive: chat row references a bot that doesn't exist. Treat as 404
|
|
# rather than crashing the template render.
|
|
raise HTTPException(
|
|
status_code=404, detail=f"host bot not found: {chat['host_bot_id']}"
|
|
)
|
|
|
|
# T19: render the timeline from event_log. We pull both user_turn and
|
|
# assistant_turn events for this chat, in chronological order. Each row
|
|
# is shaped ``{"speaker": ..., "text": ...}`` and the template
|
|
# discriminates roles via the speaker id (the literal "you" vs. a bot id).
|
|
raw_turns = _read_recent_dialogue(conn, chat_id, limit=200)
|
|
turns: list[dict] = []
|
|
for t in raw_turns:
|
|
if t["speaker"] == "you":
|
|
turns.append({"role": "you", "speaker": "you", "text": t["text"]})
|
|
else:
|
|
bot = get_bot(conn, t["speaker"])
|
|
label = bot["name"] if bot else t["speaker"]
|
|
turns.append({"role": "bot", "speaker": label, "text": t["text"]})
|
|
|
|
return TEMPLATES.TemplateResponse(
|
|
request,
|
|
"chat.html",
|
|
{
|
|
"chat": chat,
|
|
"host_bot": host_bot,
|
|
"turns": turns,
|
|
"active_nav": "chats",
|
|
},
|
|
)
|