nanobot-voice-interface/app.py

104 lines
3.1 KiB
Python
Raw Normal View History

from __future__ import annotations
from contextlib import asynccontextmanager
2026-02-28 22:12:04 -05:00
from pathlib import Path
from fastapi import FastAPI
2026-03-06 22:51:19 -05:00
from fastapi.middleware.cors import CORSMiddleware
2026-03-14 20:21:44 -04:00
from fastapi.responses import FileResponse, JSONResponse
2026-03-04 08:20:42 -05:00
from fastapi.staticfiles import StaticFiles
2026-02-28 22:12:04 -05:00
from card_store import CARD_TEMPLATES_DIR, NANOBOT_WORKSPACE
from routes import (
cards_router,
inbox_router,
messages_router,
rtc_router,
sessions_router,
templates_router,
tools_router,
workbench_router,
)
from session_store import list_web_sessions, normalize_session_chat_id
from web_runtime import create_web_runtime
2026-02-28 22:12:04 -05:00
BASE_DIR = Path(__file__).resolve().parent
2026-03-06 22:51:19 -05:00
DIST_DIR = BASE_DIR / "frontend" / "dist"
NANOBOT_REPO_DIR = BASE_DIR.parent / "nanobot"
NANOBOT_RUNTIME_WORKSPACE = (
(NANOBOT_WORKSPACE / "workspace")
if (NANOBOT_WORKSPACE / "workspace").exists()
else NANOBOT_WORKSPACE
2026-03-06 22:51:19 -05:00
)
INBOX_DIR = NANOBOT_RUNTIME_WORKSPACE / "inbox"
TOOL_JOB_TIMEOUT_SECONDS = 300.0
TOOL_JOB_RETENTION_SECONDS = 15 * 60
@asynccontextmanager
async def app_lifespan(app: FastAPI):
runtime = create_web_runtime(
repo_dir=NANOBOT_REPO_DIR,
inbox_dir=INBOX_DIR,
tool_job_timeout_seconds=TOOL_JOB_TIMEOUT_SECONDS,
tool_job_retention_seconds=TOOL_JOB_RETENTION_SECONDS,
list_sessions=list_web_sessions,
normalize_chat_id=normalize_session_chat_id,
2026-03-12 09:25:15 -04:00
)
app.state.runtime = runtime
2026-03-14 20:21:44 -04:00
try:
yield
2026-03-14 20:21:44 -04:00
finally:
await runtime.shutdown()
2026-03-12 09:25:15 -04:00
def create_app() -> FastAPI:
app = FastAPI(title="Nanobot SuperTonic Wisper Web", lifespan=app_lifespan)
2026-03-12 09:25:15 -04:00
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
2026-03-06 22:51:19 -05:00
)
@app.get("/health")
async def health() -> JSONResponse:
return JSONResponse({"status": "ok"})
app.include_router(tools_router)
app.include_router(sessions_router)
app.include_router(cards_router)
app.include_router(workbench_router)
app.include_router(inbox_router)
app.include_router(templates_router)
app.include_router(messages_router)
app.include_router(rtc_router)
if DIST_DIR.exists():
assets_dir = DIST_DIR / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
if CARD_TEMPLATES_DIR.exists():
app.mount(
"/card-templates",
StaticFiles(directory=str(CARD_TEMPLATES_DIR)),
name="card-templates",
)
2026-03-12 09:25:15 -04:00
@app.get("/{full_path:path}")
async def spa_fallback(full_path: str) -> FileResponse:
candidate = DIST_DIR / full_path
if candidate.is_file():
return FileResponse(str(candidate))
response = FileResponse(str(DIST_DIR / "index.html"))
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
return response
2026-03-06 22:51:19 -05:00
return app
2026-03-06 22:51:19 -05:00
app = create_app()