Spaces:
Sleeping
Sleeping
| """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() | |
| def healthz() -> dict: | |
| return { | |
| "status": "ok", | |
| "provider": app.state.provider_name, | |
| "retrieval": app.state.backend_name, | |
| } | |
| 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, | |
| }, | |
| ) | |
| 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}, | |
| ) | |
| 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, | |
| }, | |
| ) | |
| 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() | |