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()