Spaces:
Sleeping
Sleeping
File size: 3,708 Bytes
8981bf6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | """FastAPI application: chat UI + ROI dashboard + JSON API.
Single-container web app. The agent and ROI report are built once at startup. Routes:
GET / chat interface (htmx)
POST /chat handle a customer message, returns an HTML fragment (htmx) or
JSON (when Accept: application/json)
GET /dashboard ROI dashboard
GET /api/roi ROI report as JSON
GET /healthz liveness probe
"""
from __future__ import annotations
from pathlib import Path
from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from .agent import SupportAgent
from .config import get_settings
from .metrics import compute_roi
_HERE = Path(__file__).resolve().parent
templates = Jinja2Templates(directory=str(_HERE / "templates"))
def create_app() -> FastAPI:
settings = get_settings()
app = FastAPI(title="SupportCopilot", version="0.1.0")
static_dir = _HERE / "static"
if static_dir.exists():
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
# Build the agent once; reuse across requests (it is stateless per call).
agent = SupportAgent(settings=settings)
app.state.agent = agent
app.state.provider_name = agent.provider.name
app.state.backend_name = agent.retriever.backend_name
# Precompute the ROI summary once for the homepage proof strip (cheap: it just
# replays the seeded tickets through the agent we already built).
app.state.roi_summary = compute_roi(agent=agent).as_summary()
@app.get("/healthz")
def healthz() -> dict:
return {
"status": "ok",
"provider": app.state.provider_name,
"retrieval": app.state.backend_name,
}
@app.get("/", response_class=HTMLResponse)
def index(request: Request) -> HTMLResponse:
return templates.TemplateResponse(
request,
"index.html",
{
"active": "chat",
"provider": app.state.provider_name,
"backend": app.state.backend_name,
"roi": app.state.roi_summary,
},
)
@app.post("/chat")
def chat(
request: Request,
message: str = Form(...),
order_id: str = Form(""),
email: str = Form(""),
):
resp = agent.handle(
message,
order_id=order_id.strip() or None,
email=email.strip() or None,
)
wants_json = "application/json" in request.headers.get("accept", "")
if wants_json:
return JSONResponse(resp.to_dict())
return templates.TemplateResponse(
request,
"_message.html",
{"message": message, "resp": resp},
)
@app.get("/dashboard", response_class=HTMLResponse)
def dashboard(request: Request) -> HTMLResponse:
report = compute_roi(agent=agent)
return templates.TemplateResponse(
request,
"dashboard.html",
{
"active": "dashboard",
"provider": app.state.provider_name,
"backend": app.state.backend_name,
"report": report,
},
)
@app.get("/api/roi")
def api_roi() -> JSONResponse:
report = compute_roi(agent=agent)
payload = report.as_summary()
payload["outcomes"] = [o.__dict__ for o in report.outcomes]
return JSONResponse(payload)
return app
# Module-level ASGI app for `uvicorn supportcopilot.app:app`.
app = create_app()
|