File size: 3,612 Bytes
582520a a6e51ba 582520a a6e51ba 582520a a6e51ba 582520a a6e51ba 582520a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """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",
)
|