fix: package standalone app.server module and bump model-router-cli to 1.0.2

This commit is contained in:
Vardhman Gupta
2026-09-11 13:30:23 +05:30
parent 2e013188b1
commit e6d9640112
4 changed files with 136 additions and 5 deletions
+21 -4
View File
@@ -269,17 +269,34 @@ def analytics():
def ui( def ui(
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host address to bind the web server"), host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host address to bind the web server"),
port: int = typer.Option(8000, "--port", "-p", help="Port to run the Model Router Control Room on"), port: int = typer.Option(8000, "--port", "-p", help="Port to run the Model Router Control Room on"),
open_browser: bool = typer.Option(False, "--open-browser", help="Automatically open dashboard in default web browser"),
): ):
"""Launch the Model Router API Gateway & AI Traffic Control Room UI.""" """Launch the Model Router API Gateway & AI Traffic Control Room UI."""
import uvicorn import uvicorn
console.print(Panel.fit( import webbrowser
import threading
import time
url = f"http://{host}:{port}"
message = (
f"[bold cyan]Starting Model Router AI Traffic Control Room[/bold cyan]\n" f"[bold cyan]Starting Model Router AI Traffic Control Room[/bold cyan]\n"
f"Web Dashboard: [bold green]http://{host}:{port}[/bold green]\n" f"Web Dashboard: [bold green]{url}[/bold green]\n"
f"API Documentation: [bold green]http://{host}:{port}/docs[/bold green]", f"API Documentation: [bold green]{url}/docs[/bold green]"
)
console.print(Panel.fit(
message,
title="MODEL ROUTER UI LAUNCHER", title="MODEL ROUTER UI LAUNCHER",
border_style="cyan" border_style="cyan"
)) ))
uvicorn.run("main:app", host=host, port=port, reload=False, app_dir=os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
if open_browser:
def _open():
time.sleep(1.2)
webbrowser.open(url)
threading.Thread(target=_open, daemon=True).start()
from app.server import app as fastapi_app
uvicorn.run(fastapi_app, host=host, port=port, log_level="info")
if __name__ == "__main__": if __name__ == "__main__":
+111
View File
@@ -0,0 +1,111 @@
import asyncio
import json
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from sse_starlette.sse import EventSourceResponse
from sqlalchemy import select, desc
from app.config.settings import get_settings
from app.storage.database import init_db, AsyncSessionLocal
from app.storage.models import RequestRecord
from app.api.routes import router as api_router
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Initialize SQLite database and seed defaults
await init_db()
yield
app = FastAPI(
title="Model Router",
description="Intelligent LLM Request Routing Platform — AI Traffic Control Room",
version="1.0.0",
lifespan=lifespan,
)
# CORS configuration
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(api_router)
@app.get("/api/traffic/stream")
async def stream_live_traffic():
"""
SSE Live Stream endpoint for the Traffic Control Room visualization.
Pushes newly persisted requests to connected frontend clients.
"""
async def event_generator():
last_seen_id = None
while True:
try:
async with AsyncSessionLocal() as session:
res = await session.execute(
select(RequestRecord).order_by(desc(RequestRecord.timestamp)).limit(1)
)
latest = res.scalar_one_or_none()
if latest and latest.request_id != last_seen_id:
last_seen_id = latest.request_id
payload = {
"request_id": latest.request_id,
"timestamp": latest.timestamp.isoformat() if latest.timestamp else "",
"prompt": latest.prompt,
"task_type": latest.task_type,
"complexity": latest.complexity,
"selected_model": latest.selected_model,
"provider": latest.provider,
"status": latest.status,
"total_latency_ms": latest.total_latency_ms,
"estimated_cost": latest.estimated_cost,
}
yield {
"event": "traffic_event",
"data": json.dumps(payload),
}
except Exception:
pass
await asyncio.sleep(1.5)
return EventSourceResponse(event_generator())
# Serve Bundled Frontend (SPA) if static directory exists
STATIC_DIR = os.path.join(os.path.dirname(__file__), "static")
if os.path.exists(STATIC_DIR):
assets_dir = os.path.join(STATIC_DIR, "assets")
if os.path.exists(assets_dir):
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
if full_path.startswith("api/") or full_path.startswith("docs") or full_path.startswith("openapi.json"):
raise HTTPException(status_code=404, detail="Not Found")
target_file = os.path.join(STATIC_DIR, full_path)
if os.path.exists(target_file) and os.path.isfile(target_file):
return FileResponse(target_file)
return FileResponse(os.path.join(STATIC_DIR, "index.html"))
else:
@app.get("/")
async def root():
return {
"message": "Model Router AI Control Room Backend is active.",
"docs": "/docs",
"health": "/api/health",
}
+3
View File
@@ -4,6 +4,8 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sse_starlette.sse import EventSourceResponse from sse_starlette.sse import EventSourceResponse
import uvicorn
from app.server import app, settings
from app.config.settings import get_settings from app.config.settings import get_settings
from app.storage.database import init_db, AsyncSessionLocal from app.storage.database import init_db, AsyncSessionLocal
@@ -116,3 +118,4 @@ if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run("main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG) uvicorn.run("main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG)
uvicorn.run("app.server:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "model-router-cli" name = "model-router-cli"
version = "1.0.0" version = "1.0.2"
description = "Intelligent, explainable LLM request routing platform & AI Traffic Control Room" description = "Intelligent, explainable LLM request routing platform & AI Traffic Control Room"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"