Wrapper application around upstream Vanna with: - Tenant-aware ChromaDB memory (per program/store) - ClickHouse RLS runner with introspection guards - PT-BR system prompt and chat translations - Custom Plotly chart generator (ranked bar, datetime coercion) - Embed bootstrap (theme pierce + i18n + markdown) shared by demo and React app - Event sink for chat turn observability
96 lines
3.1 KiB
Python
96 lines
3.1 KiB
Python
"""FastAPI server: oficial VannaFastAPIServer + estáticos do web component."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from fastapi.responses import FileResponse, HTMLResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from vanna.servers.fastapi import VannaFastAPIServer
|
|
|
|
import csv_cleanup
|
|
from agent import RequestContextUserResolver, build_agent
|
|
from chat_filter import FilteringChatHandler
|
|
from events_sink import EventSink
|
|
|
|
|
|
def _build_app():
|
|
agent = build_agent(user_resolver=RequestContextUserResolver())
|
|
cors_origins = [
|
|
o.strip()
|
|
for o in os.environ.get("VANNA_CORS_ORIGINS", "*").split(",")
|
|
if o.strip()
|
|
] or ["*"]
|
|
server = VannaFastAPIServer(
|
|
agent,
|
|
config={
|
|
"cors": {
|
|
"allow_origins": cors_origins,
|
|
"allow_methods": ["*"],
|
|
"allow_headers": ["*"],
|
|
"allow_credentials": True,
|
|
},
|
|
"api_base_url": "",
|
|
},
|
|
)
|
|
server.chat_handler = FilteringChatHandler(agent, event_sink=EventSink())
|
|
fastapi_app = server.create_app()
|
|
|
|
here = os.path.dirname(os.path.abspath(__file__))
|
|
dist = os.path.join(here, "vanna", "frontends", "webcomponent", "dist")
|
|
if os.path.isdir(dist):
|
|
fastapi_app.mount("/static", StaticFiles(directory=dist), name="static")
|
|
|
|
@fastapi_app.get("/vanna-theme.css")
|
|
async def vanna_theme():
|
|
path = os.path.join(here, "static", "vanna-theme.css")
|
|
return FileResponse(path, media_type="text/css")
|
|
|
|
@fastapi_app.get("/vanna-embed-bootstrap.js")
|
|
async def vanna_embed_bootstrap():
|
|
path = os.path.join(here, "static", "vanna-embed-bootstrap.js")
|
|
return FileResponse(path, media_type="application/javascript")
|
|
|
|
@fastapi_app.get("/clubpetro-logo.png")
|
|
async def clubpetro_logo():
|
|
path = os.path.join(here, "static", "clubpetro-logo.png")
|
|
return FileResponse(path, media_type="image/png")
|
|
|
|
@fastapi_app.get("/clubpetro-logo.svg")
|
|
async def clubpetro_logo_svg():
|
|
path = os.path.join(here, "static", "clubpetro-logo.svg")
|
|
return FileResponse(path, media_type="image/svg+xml")
|
|
|
|
@fastapi_app.get("/dashboard-bg.png")
|
|
async def dashboard_bg():
|
|
path = os.path.join(here, "static", "dashboard-bg.png")
|
|
return FileResponse(path, media_type="image/png")
|
|
|
|
@fastapi_app.get("/embed-demo.html", response_class=HTMLResponse)
|
|
async def embed_demo():
|
|
path = os.path.join(here, "static", "embed-demo.html")
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
html = f.read()
|
|
return (
|
|
html
|
|
.replace("__PROGRAM_ID__", os.environ.get("RLS_PROGRAM_ID", ""))
|
|
.replace("__STORE_ID__", os.environ.get("RLS_STORE_ID", ""))
|
|
.replace("__USER_ID__", os.environ.get("RLS_USER_ID", ""))
|
|
)
|
|
|
|
return fastapi_app
|
|
|
|
|
|
app = _build_app()
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def _csv_cleanup_startup() -> None:
|
|
await csv_cleanup.startup()
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def _csv_cleanup_shutdown() -> None:
|
|
await csv_cleanup.shutdown()
|