"""ReplyFlow FastAPI application entrypoint.""" from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from app.api import ( appointments, chat, conversations, dashboard, knowledge, whatsapp, ) from app.config import get_settings from app.db import init_db from app.schemas import HealthResponse from app.services.rag import get_knowledge_store, ingest_directory logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) logger = logging.getLogger("replyflow") @asynccontextmanager async def lifespan(_: FastAPI): """Initialize DB and knowledge base on startup.""" settings = get_settings() try: Path("data/db").mkdir(parents=True, exist_ok=True) Path(settings.chroma_path).mkdir(parents=True, exist_ok=True) Path(settings.knowledge_path).mkdir( parents=True, exist_ok=True ) await init_db() store = get_knowledge_store() if store.chunk_count == 0: kb_path = Path(settings.knowledge_path) count = await ingest_directory(kb_path) logger.info( "Seeded knowledge base with %s chunks", count ) else: logger.info( "Knowledge base already loaded (%s chunks)", store.chunk_count, ) except Exception as exc: # noqa: BLE001 # Never block Hugging Face from binding port 7860. logger.exception("Startup seed failed: %s", exc) yield settings = get_settings() app = FastAPI( title=settings.app_name, version="1.0.1", description=( "Multilingual customer chatbot for web and WhatsApp " "with RAG, lead qualification, and appointments." ), lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) app.include_router(chat.router) app.include_router(appointments.router) app.include_router(conversations.router) app.include_router(dashboard.router) app.include_router(knowledge.router) app.include_router(whatsapp.router) static_dir = Path(__file__).parent / "static" app.mount( "/static", StaticFiles(directory=str(static_dir)), name="static", ) @app.get("/api/health", response_model=HealthResponse) async def health() -> HealthResponse: """Health check and integration flags.""" store = get_knowledge_store() return HealthResponse( status="ok", app=settings.app_name, openai=settings.has_openai, calcom=settings.has_calcom, whatsapp=settings.has_whatsapp, knowledge_chunks=store.chunk_count, ) @app.get("/") async def home() -> FileResponse: """Landing page.""" return FileResponse(static_dir / "index.html") @app.get("/chat") async def chat_page() -> FileResponse: """Full-page chat demo.""" return FileResponse(static_dir / "chat.html") @app.get("/dashboard") async def dashboard_page() -> FileResponse: """Operator dashboard.""" return FileResponse(static_dir / "dashboard.html") @app.get("/widget.js") async def widget_script() -> FileResponse: """Embeddable chat widget script.""" return FileResponse( static_dir / "widget" / "replyflow-widget.js", media_type="application/javascript", )