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
132
routes/sessions.py
Normal file
132
routes/sessions.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from app_dependencies import get_runtime
|
||||
from message_pipeline import encode_sse_data
|
||||
from route_helpers import read_json_request
|
||||
from session_store import (
|
||||
create_session,
|
||||
delete_session,
|
||||
is_web_session_chat_id,
|
||||
list_web_sessions,
|
||||
load_session_payload,
|
||||
normalize_session_chat_id,
|
||||
rename_session,
|
||||
)
|
||||
from web_runtime import WebAppRuntime
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/events")
|
||||
async def stream_ui_events(request: Request, runtime: WebAppRuntime = Depends(get_runtime)):
|
||||
chat_id = normalize_session_chat_id(str(request.query_params.get("chat_id", "web") or "web"))
|
||||
if not chat_id:
|
||||
return JSONResponse({"error": "invalid chat id"}, status_code=400)
|
||||
|
||||
subscription_id, queue = await runtime.event_bus.subscribe(chat_id)
|
||||
|
||||
async def stream_events():
|
||||
try:
|
||||
yield ": stream-open\n\n"
|
||||
yield encode_sse_data({"type": "events.ready", "chat_id": chat_id})
|
||||
while True:
|
||||
if await request.is_disconnected():
|
||||
break
|
||||
try:
|
||||
payload = await asyncio.wait_for(queue.get(), timeout=20.0)
|
||||
except asyncio.TimeoutError:
|
||||
yield ": keepalive\n\n"
|
||||
continue
|
||||
yield encode_sse_data(payload)
|
||||
finally:
|
||||
await runtime.event_bus.unsubscribe(subscription_id)
|
||||
|
||||
return StreamingResponse(
|
||||
stream_events(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/sessions")
|
||||
async def get_sessions() -> JSONResponse:
|
||||
return JSONResponse({"sessions": list_web_sessions()})
|
||||
|
||||
|
||||
@router.get("/sessions/{chat_id}")
|
||||
async def get_session(chat_id: str) -> JSONResponse:
|
||||
session_chat_id = normalize_session_chat_id(chat_id)
|
||||
if not session_chat_id or not is_web_session_chat_id(session_chat_id):
|
||||
return JSONResponse({"error": "invalid session id"}, status_code=400)
|
||||
|
||||
payload = load_session_payload(session_chat_id)
|
||||
if payload is None:
|
||||
return JSONResponse({"error": "session not found"}, status_code=404)
|
||||
|
||||
return JSONResponse(payload)
|
||||
|
||||
|
||||
@router.post("/sessions")
|
||||
async def create_session_route(
|
||||
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)
|
||||
|
||||
title = str(payload.get("title", "")).strip()
|
||||
chat_id = f"web-{uuid.uuid4().hex[:8]}"
|
||||
summary = create_session(chat_id, title=title)
|
||||
await runtime.publish_sessions_changed()
|
||||
return JSONResponse({"session": summary}, status_code=201)
|
||||
|
||||
|
||||
@router.patch("/sessions/{chat_id}")
|
||||
async def rename_session_route(
|
||||
chat_id: str,
|
||||
request: Request,
|
||||
runtime: WebAppRuntime = Depends(get_runtime),
|
||||
) -> JSONResponse:
|
||||
session_chat_id = normalize_session_chat_id(chat_id)
|
||||
if not session_chat_id or not is_web_session_chat_id(session_chat_id):
|
||||
return JSONResponse({"error": "invalid session id"}, status_code=400)
|
||||
|
||||
try:
|
||||
payload = await read_json_request(request)
|
||||
except ValueError as exc:
|
||||
return JSONResponse({"error": str(exc)}, status_code=400)
|
||||
|
||||
title = str(payload.get("title", "")).strip()
|
||||
summary = rename_session(session_chat_id, title)
|
||||
if summary is None:
|
||||
return JSONResponse({"error": "session not found"}, status_code=404)
|
||||
await runtime.publish_sessions_changed()
|
||||
return JSONResponse({"session": summary})
|
||||
|
||||
|
||||
@router.delete("/sessions/{chat_id}")
|
||||
async def delete_session_route(
|
||||
chat_id: str,
|
||||
runtime: WebAppRuntime = Depends(get_runtime),
|
||||
) -> JSONResponse:
|
||||
session_chat_id = normalize_session_chat_id(chat_id)
|
||||
if not session_chat_id or not is_web_session_chat_id(session_chat_id):
|
||||
return JSONResponse({"error": "invalid session id"}, status_code=400)
|
||||
|
||||
if not delete_session(session_chat_id, runtime.delete_cards_for_chat):
|
||||
return JSONResponse({"error": "session not found"}, status_code=404)
|
||||
await runtime.publish_sessions_changed()
|
||||
await runtime.publish_cards_changed()
|
||||
return JSONResponse({"status": "ok", "chat_id": session_chat_id})
|
||||
Loading…
Add table
Add a link
Reference in a new issue