A SaaS chatbot platform: customers sign up, create chatbot instances, upload a knowledge base, and embed a RAG-powered chatbot on their website with one <script> tag. Includes live-chat handoff to human operators, BANT/MEDDIC lead qualification, and a Razorpay billing system (INR, single rail).
| Module | Stack | Purpose |
|---|---|---|
api/ |
FastAPI · SQLAlchemy 2.0 · pgvector · LiteLLM · ARQ | REST + SSE + WebSocket; RAG pipeline; auth; ingestion; billing |
widget/ |
React 19 · Vite 7 · Tailwind v4 | Embeddable chat widget: a ~3KB loader IIFE (oyechats-widget.js) plus code-split ESM chunks it lazy-loads into a shadow root |
app/ |
React 19 · Vite 8 · React Router 7 · Recharts | Admin dashboard (bot config, KB, leads, billing, operator console) |
The marketing site lives in a separate sibling repo (oyechats-website/, Next.js 16) and is not part of this monorepo.
- Python 3.11 (pinned in
api/.python-version) - Node.js 20+ and npm
- PostgreSQL 16+ with the pgvector extension
- Redis 7+ (queue · cache · rate-limit; required in production, optional in dev)
- uv — Python dependency manager — install
- conda — optional, for local Python isolation only. Production runs Python under systemd directly; there is no conda env on the server.
Brings up Postgres + pgvector and the FastAPI backend in containers.
git clone <repo-url> && cd platform
cp api/.env.example api/.env # edit at minimum: OPENAI_API_KEY, GOOGLE_API_KEY
docker compose up --build| Service | URL |
|---|---|
| Backend API | http://localhost:8000 |
| Swagger UI | http://localhost:8000/docs |
| Postgres | localhost:5432 (db oyechats, user oyechats) |
conda is a local-development convenience. Production uses systemd directly.
# 1. Python env
conda create -n oye python=3.11 -y
conda activate oye
pip install uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Configure
cp api/.env.example api/.env
# Edit api/.env — see "Environment variables" below
# 3. Install backend deps
cd api
uv sync
# 4. Database
createdb oyechats # ensure pgvector extension is installed
uv run alembic upgrade head
# 5. Run dev server
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload# Chat widget (host-page-embeddable IIFE)
cd widget && npm install && npm run dev # http://localhost:5173
# Admin dashboard (SPA)
cd app && npm install && npm run dev # http://localhost:5174The Vite dev server on port 5173 cannot be embedded on external sites because of the React Fast Refresh preamble. To test embedding, run
npm run build && npx vite preview --port 4173inwidget/.
Create api/.env from api/.env.example. The full list with defaults lives in api/app/config.py; the most important ones:
| Variable | Description |
|---|---|
DB_URL |
Postgres connection string (postgresql://oyechats:oyechats@localhost:5432/oyechats) |
APP_ENV |
development, testing, or production |
OPENAI_API_KEY |
OpenAI key — primary chat completions (gpt-5.4-mini) and the moderation pre-check |
GOOGLE_API_KEY |
Google Gemini key — embeddings (gemini-embedding-001), the fallback chat model, and the gate / enrichment LLM |
| Variable | Description |
|---|---|
REDIS_URL |
Redis connection string (queue · cache · rate-limit). App fails fast on startup without it in production. |
CORS_ORIGINS |
Comma-separated allowlist (no wildcard with credentials) |
R2_KEY_ID, R2_APPLICATION_KEY, R2_BUCKET_NAME, R2_ENDPOINT |
Cloudflare R2 (S3-compatible) for file storage. Legacy B2_* env names are accepted as fallbacks. |
BREVO_API_KEY |
Transactional email |
RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET, RAZORPAY_WEBHOOK_SECRET |
Payment provider (INR) — the sole rail. BILLING_PROVIDER defaults to razorpay; there is no billing_service.py and no Stripe service module, only vestigial stripe_* columns on plans. |
SENTRY_DSN_BACKEND |
Error tracking |
| Variable | Default | Effect |
|---|---|---|
LLM_MODEL |
openai/gpt-5.4-mini |
Primary chat model (LiteLLM identifier) |
FALLBACK_MODEL |
gemini/gemini-2.5-flash |
Auto-fallback chain |
GEMINI_EMBED_MODEL |
gemini-embedding-001 |
Google embedding, Matryoshka-truncated to 768-dim (EMBED_DIMENSIONS) |
CHUNK_SIZE / CHUNK_OVERLAP |
1000 / 200 |
Document chunking |
CRAWL_PROVIDER_PRIMARY |
jina |
Which scrape backend page fetches try first; the other becomes the fallback |
MODERATION_ENABLED |
true |
OpenAI moderation pre-check |
CAG_LITE_THRESHOLD |
20 |
Skip retrieval for bots with ≤ N chunks |
RELEVANCE_GATE_ENABLED |
true |
CRAG-style relevance scoring — the control behind "answers only from your knowledge base". An empty value counts as unset (relevance_gate.py:63); see deploy-api.yml's ${VAR:-true}. |
RERANK_ENABLED |
false |
FlashRank cross-encoder rerank |
WORKER_ENABLED |
true |
If false, worker tasks fall back to in-process thread pool |
LANGFUSE_FORCE_DISABLE |
— | Escape hatch for low-memory hosts |
Full reference: docs/configuration.md and the system-design environments page.
# All commands assume conda env `oye` is active and cwd is api/
uv sync # install / sync deps
uv run uvicorn app.main:app --reload --port 8000 # dev server
uv run pytest # tests
uv run ruff check . # lint
uv run ruff format . # format
uv run alembic upgrade head # run migrations
uv run alembic revision --autogenerate -m "<message>" # new migrationcd widget && npm run lint && npm test && npm run build # widget
cd app && npm run lint && npx tsc --noEmit && npx vitest run && npm run build # adminapp/ is TypeScript, and npm run build does not typecheck — tsc --noEmit is a
separate, required gate. See CLAUDE.md.
docker compose up --build # full stack
docker compose up db -d # database only
docker compose down # stop (preserves volume)
docker compose down -v # stop + delete data volumeOnce the backend is running:
- Swagger UI — http://localhost:8000/docs
- ReDoc — http://localhost:8000/redoc
- Health —
/health(DB + Redis),/health/full(+ worker heartbeat),/health/live
Routers (each is mounted at the root, no /api prefix):
| Prefix | File | Purpose |
|---|---|---|
/auth/* |
api/app/api/auth_routes.py |
Register, login, OTP password reset |
/bots/* |
api/app/api/bot_routes.py |
Bot CRUD, public widget settings |
/chat/* |
api/app/api/chat_routes.py |
SSE chat stream, history, feedback |
/ws/* |
api/app/api/ws_routes.py |
WebSocket live-chat |
/documents/* |
api/app/api/document_routes.py |
Upload, crawl, list, delete |
/leads/* |
api/app/api/lead_routes.py |
Leads, BANT signals, qualification config |
/operators/* |
api/app/api/operator_routes.py |
Operator CRUD, handoff, assignment |
/canned-responses/* |
api/app/api/canned_response_routes.py |
Snippet CRUD |
/offline-messages/* |
api/app/api/offline_message_routes.py |
Offline form submissions |
/analytics/* |
api/app/api/analytics_routes.py |
Dashboard metrics |
/subscriptions/*, /credits/* |
api/app/api/subscription_routes.py |
Plans, invoices, top-ups |
/webhooks/* |
api/app/api/webhook_routes.py |
Customer webhook registrations |
/webhooks/billing/* |
api/app/api/webhook_billing_routes.py |
Inbound Razorpay webhooks |
/superadmin/* |
api/app/api/superadmin_routes.py + superadmin_routes_v2.py, superadmin_plan_routes.py, superadmin_promotion_routes.py, superadmin_ops_routes.py |
Super-admin only |
/client/* |
api/app/api/client_routes.py |
Client account settings |
/quotation/* |
api/app/api/quotation_routes.py |
Widget quotation flow |
/affiliates/* |
api/app/api/affiliate_routes.py |
Affiliate / referral program |
This table is a selection. api/app/api/ holds 32 route modules and main.py mounts 36 routers; read main.py for the complete wiring.
cd api
uv run pytest # all
uv run pytest tests/test_chat_security.py
uv run pytest -v # verbosecd widget && npm run build # → dist/oyechats-widget.js + dist/app/*<script src="https://cdn.oyechats.com/oyechats-widget.js" data-bot-key="bot-xxx"></script>The widget reads data-bot-key from its own <script> tag, mounts a <div id="oyechats-widget-root">, and lazy-loads its React bundle. See widget/README.md for the loader/chunk strategy.
platform/
├── api/ # FastAPI + ARQ + RAG
│ ├── app/
│ │ ├── main.py # entry · middleware · router wiring
│ │ ├── config.py # env-driven settings
│ │ ├── api/ # route modules (32 files, 36 mounted routers)
│ │ │ ├── auth.py # auth dependencies (get_current_*)
│ │ │ ├── auth_routes.py # register · login · OTP reset
│ │ │ ├── bot_routes.py # bot CRUD
│ │ │ ├── chat_routes.py # SSE chat stream
│ │ │ ├── ws_routes.py # WebSocket live-chat
│ │ │ ├── document_routes.py # upload + crawl
│ │ │ ├── lead_routes.py # leads + BANT
│ │ │ ├── operator_routes.py # live-chat staff
│ │ │ ├── subscription_routes.py # plans + credits + top-ups
│ │ │ ├── webhook_routes.py # customer webhook regs
│ │ │ ├── webhook_billing_routes.py # inbound Razorpay
│ │ │ └── …
│ │ ├── services/ # business logic
│ │ │ ├── rag_service.py # hybrid search + context assembly
│ │ │ ├── llm_service.py # LiteLLM wrapper (OpenAI → Gemini)
│ │ │ ├── live_chat_service.py # WebSocket ConnectionManager
│ │ │ ├── razorpay_service.py # Razorpay (sole payment rail)
│ │ │ ├── credit_service.py # FIFO credit ledger
│ │ │ ├── qualification_service.py # BANT / MEDDIC
│ │ │ ├── lead_service.py # tier transitions + decay
│ │ │ ├── webhook_service.py # outbound HMAC + retry
│ │ │ ├── email_service.py # Brevo
│ │ │ ├── crawl_orchestrator.py # crawl waves + streaming ingest
│ │ │ ├── jina_service.py # Jina Reader (primary crawl provider)
│ │ │ ├── spider_service.py # Spider.cloud (fallback)
│ │ │ ├── ws_backplane.py # Redis pub/sub for cross-process live chat
│ │ │ ├── intent_service.py # intent routing
│ │ │ ├── relevance_gate.py # CRAG-style gate
│ │ │ ├── reranker.py # FlashRank
│ │ │ └── r2_service.py # Cloudflare R2 (S3-compat)
│ │ ├── ingestion/ # RAG input pipeline
│ │ │ ├── pipeline.py # orchestrator
│ │ │ ├── extraction.py # pypdf · python-docx · text
│ │ │ ├── cleaner.py
│ │ │ ├── chunking.py # recursive splitter
│ │ │ ├── embedder.py # Gemini gemini-embedding-001 (768-dim)
│ │ │ └── enrichment.py # optional Gemini chunk-summary
│ │ ├── worker/ # ARQ tasks
│ │ ├── db/ # models · session · repository
│ │ ├── core/ # middleware · security · thread-pool
│ │ └── schemas/ # Pydantic v2
│ ├── alembic/ # migrations
│ ├── tests/
│ ├── systemd/ # production unit files
│ ├── nginx/ # production nginx config
│ └── scripts/ # backup.sh, seed scripts
├── widget/ # embeddable IIFE
├── app/ # admin dashboard SPA
├── docs/ # markdown + interactive system-design site
│ └── system-design/ # VitePress site (28+ pages, 44+ diagrams)
├── docker-compose.yml # local dev stack
├── CLAUDE.md # AI-assistant conventions
└── README.md # this file
| Layer | Technology |
|---|---|
| LLM (primary) | OpenAI gpt-5.4-mini via LiteLLM |
| LLM (fallback) | Google gemini-2.5-flash |
| Embeddings | Google gemini-embedding-001 (768-dim, L2-normalized) |
| Vector DB | PostgreSQL 16 + pgvector (hybrid search with TSVECTOR keyword) |
| Backend | FastAPI · SQLAlchemy 2.0 · Alembic · Pydantic v2 |
| Background queue | ARQ on Redis |
| Frontend | React 19 · Vite 7/8 · Tailwind v4 · React Router 7 |
| Web crawl | Jina Reader (primary) + Spider.cloud (fallback) — HTTP only, no local browser |
| File storage | Cloudflare R2 (S3-compatible) |
| Brevo | |
| Payments | Razorpay (INR) — single provider |
| Real-time | WebSocket (ws_routes.py) |
| Rate limiting | SlowAPI on Redis |
| Observability | Sentry · Langfuse (enabled whenever both keys are set; LANGFUSE_FORCE_DISABLE=true is the kill switch) |
| CDN | Cloudflare R2 + CDN — cdn.oyechats.com/oyechats-widget.js |
| Deploy | DigitalOcean droplet · systemd · Nginx · GitHub Actions (ci.yml, deploy-api.yml, deploy-widget.yml, deploy-app.yml) |
| Dependency mgmt | uv (Python) + npm (JavaScript) |
| Containerisation | Docker + Docker Compose (local dev only) |
CLAUDE.md— engineering conventions and AI-assistant guidedocs/— markdown reference (architecture, configuration, RAG pipeline, runbooks)docs/system-design/— VitePress site with C4 diagrams, critical-flow sequence diagrams, ER diagrams, and deployment topology. Run locally:cd docs/system-design && npm install && npm run dev
The git repo is digibranders/oye-chats-platform on GitHub. All work happens on the development branch; production is main and updates only via PR merge. See CLAUDE.md for the full workflow and pre-commit checks (ruff, pytest, npm run lint, npm run build).