import asyncio import json from pathlib import Path from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from app.db import close_pool, fetch_all, init_pool from app.routes import ( retail, agents, analytics, documents, api, clients, dashboard, deals, herman, studio, marketing, monitor, products, reports, suppliers, voice, settings, browser, hermes, beurs, packaging, ops, ops_api, ) from app.routes.admin_api import admin_router, ai_router, herman_api, voice_api from app.routes.settings_api import settings_router from app.routes.agents_api import router as agents_api_router from app.routes.marketing_api import router as marketing_api_router from app.routes.projects_api import router as projects_api_router BASE_DIR = Path(__file__).resolve().parent.parent templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) app = FastAPI(title="Foodlinkk Command Center", version="2.5.0") app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static") for r in ( dashboard.router, beurs.router, agents.router, marketing.router, retail.router, clients.router, deals.router, products.router, suppliers.router, monitor.router, analytics.router, documents.router, reports.router, voice.router, settings.router, browser.router, herman.router, studio.router, hermes.router, packaging.router, ops.router, api.router, ops_api.router, admin_router, ai_router, herman_api, voice_api, settings_router, agents_api_router, marketing_api_router, projects_api_router, ): app.include_router(r) @app.on_event("startup") def on_startup() -> None: init_pool() @app.on_event("shutdown") def on_shutdown() -> None: close_pool() @app.websocket("/ws/agents") async def ws_agents(websocket: WebSocket) -> None: await websocket.accept() last_payload: str | None = None try: while True: try: rows = fetch_all( """SELECT id, agent_name, event_type, title, body, status, created_at FROM agent_events ORDER BY created_at DESC LIMIT 50""" ) for row in rows: if row.get("created_at") is not None: row["created_at"] = row["created_at"].isoformat() payload = json.dumps({"events": rows}) except Exception as exc: payload = json.dumps({"error": str(exc), "events": []}) if payload != last_payload: await websocket.send_text(payload) last_payload = payload await asyncio.sleep(3) except WebSocketDisconnect: return @app.websocket("/ws/feed") async def ws_feed(websocket: WebSocket) -> None: await websocket.accept() try: while True: snapshot = {"type": "heartbeat", "events": []} try: snapshot["events"] = fetch_all( "SELECT agent_name, event_type, title, status, created_at FROM agent_events ORDER BY created_at DESC LIMIT 15" ) for row in snapshot["events"]: if row.get("created_at"): row["created_at"] = row["created_at"].isoformat() except Exception as exc: snapshot["error"] = str(exc) await websocket.send_text(json.dumps(snapshot)) await asyncio.sleep(5) except WebSocketDisconnect: return