feat: unify card runtime and event-driven web ui
This commit is contained in:
parent
0edf8c3fef
commit
4dfb7ca3cc
105 changed files with 17382 additions and 8505 deletions
114
routes/messages.py
Normal file
114
routes/messages.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from app_dependencies import get_runtime
|
||||
from message_pipeline import (
|
||||
context_label_from_message_metadata,
|
||||
encode_sse_data,
|
||||
typed_message_from_api_notification,
|
||||
)
|
||||
from nanobot_api_client import stream_nanobot_agent_turn
|
||||
from route_helpers import read_json_request
|
||||
from session_store import normalize_session_chat_id
|
||||
from web_runtime import WebAppRuntime
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/message")
|
||||
async def post_message(
|
||||
request: Request,
|
||||
runtime: WebAppRuntime = Depends(get_runtime),
|
||||
) -> JSONResponse:
|
||||
try:
|
||||
payload = await read_json_request(request)
|
||||
except ValueError as exc:
|
||||
return JSONResponse({"error": str(exc)}, status_code=400)
|
||||
text = str(payload.get("text", "")).strip()
|
||||
metadata = payload.get("metadata", {})
|
||||
chat_id = normalize_session_chat_id(str(payload.get("chat_id", "web") or "web"))
|
||||
if not text:
|
||||
return JSONResponse({"error": "empty message"}, status_code=400)
|
||||
if not chat_id:
|
||||
return JSONResponse({"error": "invalid chat id"}, status_code=400)
|
||||
if not isinstance(metadata, dict):
|
||||
metadata = {}
|
||||
try:
|
||||
await runtime.gateway.send_user_message(text, metadata=metadata, chat_id=chat_id)
|
||||
except RuntimeError as exc:
|
||||
return JSONResponse({"error": str(exc)}, status_code=503)
|
||||
return JSONResponse({"status": "ok"})
|
||||
|
||||
|
||||
@router.post("/message/stream")
|
||||
async def stream_message(
|
||||
request: Request,
|
||||
runtime: WebAppRuntime = Depends(get_runtime),
|
||||
):
|
||||
try:
|
||||
payload = await read_json_request(request)
|
||||
except ValueError as exc:
|
||||
return JSONResponse({"error": str(exc)}, status_code=400)
|
||||
|
||||
text = str(payload.get("text", "")).strip()
|
||||
metadata = payload.get("metadata", {})
|
||||
chat_id = normalize_session_chat_id(str(payload.get("chat_id", "web") or "web"))
|
||||
if not text:
|
||||
return JSONResponse({"error": "empty message"}, status_code=400)
|
||||
if not chat_id:
|
||||
return JSONResponse({"error": "invalid chat id"}, status_code=400)
|
||||
if not isinstance(metadata, dict):
|
||||
metadata = {}
|
||||
context_label = context_label_from_message_metadata(metadata)
|
||||
|
||||
async def stream_turn():
|
||||
try:
|
||||
yield ": stream-open\n\n"
|
||||
yield encode_sse_data(
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": text,
|
||||
"is_progress": False,
|
||||
"is_tool_hint": False,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"context_label": context_label or None,
|
||||
}
|
||||
)
|
||||
|
||||
async for typed_event in stream_nanobot_agent_turn(
|
||||
text,
|
||||
chat_id=chat_id,
|
||||
metadata=metadata,
|
||||
timeout_seconds=60.0,
|
||||
notification_to_event=lambda obj: typed_message_from_api_notification(
|
||||
obj,
|
||||
default_chat_id=chat_id,
|
||||
),
|
||||
):
|
||||
if typed_event.get("type") == "card":
|
||||
await runtime.publish_cards_changed(
|
||||
typed_event.get("chat_id") if isinstance(typed_event, dict) else chat_id
|
||||
)
|
||||
elif typed_event.get("type") == "workbench":
|
||||
await runtime.publish_workbench_changed(
|
||||
typed_event.get("chat_id") if isinstance(typed_event, dict) else chat_id
|
||||
)
|
||||
yield encode_sse_data(typed_event)
|
||||
await runtime.publish_sessions_changed()
|
||||
except RuntimeError as exc:
|
||||
yield encode_sse_data({"type": "error", "error": str(exc)})
|
||||
|
||||
return StreamingResponse(
|
||||
stream_turn(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue