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",
    )