2026-04-06 15:42:53 -04:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from contextlib import asynccontextmanager
|
2026-02-28 22:12:04 -05:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-04-06 15:42:53 -04:00
|
|
|
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
|
|
|
|
2026-04-06 15:42:53 -04: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"
|
2026-04-06 15:42:53 -04:00
|
|
|
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
|
|
|
)
|
2026-04-06 15:42:53 -04: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
|
|
|
)
|
2026-04-06 15:42:53 -04:00
|
|
|
app.state.runtime = runtime
|
2026-03-14 20:21:44 -04:00
|
|
|
try:
|
2026-04-06 15:42:53 -04:00
|
|
|
yield
|
2026-03-14 20:21:44 -04:00
|
|
|
finally:
|
2026-04-06 15:42:53 -04:00
|
|
|
await runtime.shutdown()
|
2026-03-12 09:25:15 -04:00
|
|
|
|
|
|
|
|
|
2026-04-06 15:42:53 -04:00
|
|
|
def create_app() -> FastAPI:
|
|
|
|
|
app = FastAPI(title="Nanobot SuperTonic Wisper Web", lifespan=app_lifespan)
|
2026-03-12 09:25:15 -04:00
|
|
|
|
2026-04-06 15:42:53 -04:00
|
|
|
app.add_middleware(
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
allow_origins=["*"],
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
allow_headers=["*"],
|
2026-03-06 22:51:19 -05:00
|
|
|
)
|
|
|
|
|
|
2026-04-06 15:42:53 -04: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
|
|
|
|
2026-04-06 15:42:53 -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
|
|
|
|
2026-04-06 15:42:53 -04:00
|
|
|
return app
|
2026-03-06 22:51:19 -05:00
|
|
|
|
|
|
|
|
|
2026-04-06 15:42:53 -04:00
|
|
|
app = create_app()
|