feat: add Settings and Suite pages with comprehensive settings management and question suite builder
- Implemented SettingsPage for configuring default provider, temperature, and model paths. - Added SuitePage for managing a suite of questions with features to add, edit, duplicate, and delete questions. - Introduced TypeScript configuration files for app and node environments. - Set up Vite configuration for development server with API proxying to backend.
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
"""FastAPI application factory.
|
||||
|
||||
In production the same process serves the JSON API and the compiled React
|
||||
bundle from ``web/dist``. During frontend development Vite serves the UI on its
|
||||
own port and proxies ``/api`` back here, so CORS is opened for localhost only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.requests import Request
|
||||
|
||||
from . import __version__
|
||||
from .api import router
|
||||
from .core_bridge import ROOT_DIR, ensure_directories
|
||||
from .plugins import get_plugin_manager
|
||||
|
||||
WEB_DIST = ROOT_DIR / "web" / "dist"
|
||||
|
||||
DEV_ORIGINS = [
|
||||
"http://localhost:5173",
|
||||
"http://127.0.0.1:5173",
|
||||
]
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
ensure_directories()
|
||||
|
||||
app = FastAPI(
|
||||
title="LM-Gambit",
|
||||
description="Automated LLM diagnostic suite.",
|
||||
version=__version__,
|
||||
docs_url="/api/docs",
|
||||
openapi_url="/api/openapi.json",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=DEV_ORIGINS,
|
||||
allow_credentials=False,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(router)
|
||||
_mount_plugin_routes(app)
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health() -> dict:
|
||||
return {"status": "ok", "version": __version__, "ui_built": WEB_DIST.is_dir()}
|
||||
|
||||
_mount_frontend(app)
|
||||
return app
|
||||
|
||||
|
||||
def _mount_plugin_routes(app: FastAPI) -> None:
|
||||
"""Give each plugin defining register_routes its own /api/plugins/<slug>.
|
||||
|
||||
Routes are bound at startup, so a plugin that adds or changes endpoints
|
||||
needs a server restart — unlike its other hooks, which reload live.
|
||||
"""
|
||||
manager = get_plugin_manager()
|
||||
for plugin in manager.with_hook("register_routes"):
|
||||
plugin_router = APIRouter(
|
||||
prefix=f"/api/plugins/{plugin.slug}",
|
||||
tags=[f"plugin:{plugin.slug}"],
|
||||
)
|
||||
try:
|
||||
plugin.module.register_routes(plugin_router) # type: ignore[union-attr]
|
||||
except Exception as exc: # noqa: BLE001 - never let a plugin block startup
|
||||
print(
|
||||
f"[plugin:{plugin.slug}] register_routes() failed: {exc}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
if plugin_router.routes:
|
||||
app.include_router(plugin_router)
|
||||
|
||||
|
||||
def _mount_frontend(app: FastAPI) -> None:
|
||||
assets_dir = WEB_DIST / "assets"
|
||||
if assets_dir.is_dir():
|
||||
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
|
||||
|
||||
@app.get("/{full_path:path}", include_in_schema=False)
|
||||
async def spa(request: Request, full_path: str) -> object:
|
||||
if full_path.startswith("api/"):
|
||||
return JSONResponse({"detail": "Not found."}, status_code=404)
|
||||
|
||||
index_file = WEB_DIST / "index.html"
|
||||
if not index_file.is_file():
|
||||
return JSONResponse(
|
||||
{
|
||||
"detail": "Frontend not built.",
|
||||
"fix": "Run 'npm install && npm run build' inside web/, "
|
||||
"or start the Vite dev server with 'npm run dev'.",
|
||||
},
|
||||
status_code=503,
|
||||
)
|
||||
|
||||
# Serve real files (favicon, manifest, …) directly; everything else
|
||||
# falls through to index.html so client-side routing works on reload.
|
||||
if full_path:
|
||||
candidate = (WEB_DIST / full_path).resolve()
|
||||
if candidate.is_file() and WEB_DIST.resolve() in candidate.parents:
|
||||
return FileResponse(candidate)
|
||||
|
||||
return FileResponse(index_file)
|
||||
|
||||
|
||||
app = create_app()
|
||||
Reference in New Issue
Block a user