# Universal Chatbot Widget — Project Documentation

**Prepared by:** Alok Ranjan
**Document type:** Product & Engineering Specification
**Status:** Draft — Phase 1 planning

---

## 1. Executive Summary

The Universal Chatbot Widget is an embeddable chat solution that can be dropped into any website — WordPress, React, Next.js, plain HTML, or any other stack — via a single script tag. It progressively escalates a visitor's conversation through three tiers: a scripted rule engine for common queries, an LLM-powered AI mode for open-ended questions, and a live human handoff for cases that require a real agent. The system is aware of the host website's own content, so responses can be grounded in that site's actual pages rather than generic answers.

The goal of this document is to define the full scope of the project — from problem statement to final phase — so that development proceeds against a fixed architecture rather than being figured out ad hoc during implementation.

---

## 2. Problem Statement & Goals

**Problem:** Most small-to-mid websites cannot afford a dedicated support chatbot. Existing solutions are either too generic (no site-specific knowledge) or too expensive/complex to integrate (heavy SDKs, per-platform plugins).

**Goal:** Build a single, lightweight, framework-agnostic chatbot widget that:
- Installs with one script tag on any website
- Handles common questions instantly via scripted flows (no AI cost)
- Falls back to an LLM for anything outside the scripted flow
- Answers using the actual content of the host website (RAG)
- Lets a visitor escalate to a real human agent when needed
- Notifies the backend/agent in real time at each conversation-stage transition

**Non-goals for Phase 1:** Multi-language support, voice input, native mobile SDKs, a full enterprise helpdesk (ticketing, SLAs, multi-agent queueing) — these are explicitly deferred to later phases.

---

## 3. System Architecture

### 3.1 Core pillars

| Pillar | Responsibility |
|---|---|
| **Widget (client)** | Renders the chat UI on the host site, sends/receives messages, carries `siteId` and `sessionId`. Contains no business logic. |
| **Orchestrator / session layer** | The routing brain. Tracks each session's current mode (`rule` / `ai` / `human`) and directs each incoming message to the correct engine. |
| **Rule engine** | Matches incoming messages against a predefined JSON/DB-driven decision tree for instant, cost-free responses to common queries. |
| **AI (LLM) engine** | Handles messages that fall outside the rule engine's coverage, using an LLM API, optionally grounded with retrieved website context. |
| **RAG / context layer** | Crawls or ingests a website's content, chunks and embeds it into a vector store, and retrieves relevant chunks at query time to ground AI answers. |
| **Notification / handoff layer** | Fires real-time "ring" events to the agent dashboard when a visitor requests a human, and relays messages once an agent joins. |

### 3.2 Message flow

```
Widget → Backend API (orchestrator)
            |
      check session.mode
            |
   ┌────────┼─────────┐
  rule       ai        human
   |          |           |
 lookup    RAG retrieve  ring event
   |        + LLM call    → agent dashboard
   |          |           |
        Response → Widget
```

### 3.3 Active user tracking (admin visibility)

Admins need real-time visibility into who is currently active on the chatbot for their site:
- **Logged-in visitors:** the host site passes identity to the widget at init time (`user: { id, name, email }`), which the widget forwards to the backend on session creation.
- **Anonymous visitors:** the backend captures the request IP address (via `X-Forwarded-For` when behind a proxy/load balancer) as the tracking identifier.
- **Real-time mechanism:** every widget session opens a WebSocket connection to the backend (using FastAPI's native WebSocket support, not a third-party Socket.io library — keeps the vanilla-JS widget lightweight since it needs no `socket.io-client` dependency). This applies to all sessions, not just human-handoff sessions as originally scoped in section 3.1 — presence is best expressed as "connection open = active, connection close = inactive", removing the need for a separate heartbeat/TTL mechanism.
- **Presence store:** for a single backend instance (Phase 1 scale), an in-memory dictionary (`sessionId → session info`, grouped by `siteId`) on the backend process is sufficient — no Redis or external store required. The admin dashboard connects over its own WebSocket and receives live push updates as sessions connect/disconnect.
- **Where this breaks:** once the backend needs to horizontally scale to multiple instances behind a load balancer, an in-memory dictionary is no longer shared across instances. At that point a shared store (e.g. Redis pub/sub) is needed to sync presence across instances — deferred to Phase 2, since Phase 1 traffic does not require it.

**Note on MongoDB for presence:** presence data is transient and should NOT be written to MongoDB on every connect/disconnect — this adds unnecessary latency, database load, and (on a paid tier) direct cost for no benefit. MongoDB is reserved for permanent records only (Tenant, Conversation, Message, RingEvent); presence lives in memory (or Redis, once needed).

**Privacy note:** IP addresses and any captured name/email are personal data (PII). If the product serves users in jurisdictions with data-privacy regulations (GDPR, India's DPDP Act, etc.), this requires a retention limit and a basic privacy policy before wider launch — flagged here so it isn't a late surprise.

### 3.4 Multi-tenancy model

Every website that installs the widget is a **tenant**, identified by a `siteId`. This is the single most important architectural decision in the system, and is enforced from day one:

- Every backend query is scoped by `siteId` — no exceptions.
- The widget script embeds `siteId`; the backend additionally validates the request's origin domain against the tenant's registered domain(s), so a stolen `siteId` alone cannot be used from another site.
- Per-tenant configuration (rule engine JSON, AI enabled/disabled, indexed context status, usage limits) lives in a single `Tenant` record.

---

## 4. Technology Stack

| Layer | Choice | Reasoning |
|---|---|---|
| Widget | Vanilla JavaScript, Shadow DOM | Must run inside any host site regardless of the site's own framework, without CSS/JS conflicts |
| Backend | FastAPI (Python) | Async-native, strong ecosystem for LLM/embedding integrations, fast to build REST APIs |
| Real-time layer | FastAPI native WebSocket | Built into FastAPI already — no extra dependency; native browser `WebSocket` API keeps the widget lightweight (avoids adding the `socket.io-client` library to a vanilla-JS widget). Drives live presence for every session (connect/disconnect) and carries live messages during human handoff |
| Primary database | MongoDB | Flexible schema for conversations/sessions, already familiar in current stack |
| Vector store | Qdrant / pgvector / Pinecone (final pick deferred to Phase 1 build) | Stores website content embeddings for RAG retrieval |
| LLM provider | OpenAI or Claude API | Swappable behind a single AI-engine interface |
| Agent dashboard | Separate lightweight React/Next.js app | Where human agents see ring events and reply to escalated chats |

---

## 5. Data Model

```
Tenant (Site)
  siteId, domain, name, plan, aiEnabled, contextIndexedAt, createdAt

Session
  sessionId, siteId, mode (rule | ai | human)
  isLoggedIn, userId, userName, userEmail   (present only if isLoggedIn)
  ipAddress                                  (present only if anonymous)
  createdAt, lastActiveAt

Conversation
  conversationId, sessionId, siteId, status (open | closed)

Message
  messageId, conversationId, sender (user | bot | agent), content, timestamp

Agent
  agentId, siteId(s), name, status (online | offline)

RingEvent
  eventId, siteId, sessionId, type (session_start | ai_mode_start | human_requested), timestamp

ContextChunk
  chunkId, siteId, sourceUrl, text, embeddingVector
```

---

## 6. API Contract (high level)

```
POST /api/v1/session/init        → create or resume a session
POST /api/v1/message              → single entry point; orchestrator routes internally
POST /api/v1/handoff/request      → switches session to human mode, fires ring event
GET  /api/v1/conversation/{id}    → fetch conversation history
GET  /api/v1/admin/active-sessions?siteId= → list currently active sessions (logged-in details or IP)
WS   /ws/agent                    → agent dashboard connects to receive ring events + live presence updates
WS   /ws/session/{id}             → opened for every session (drives presence); also carries live messages once in human mode
```

Design principle: the widget always talks to `/message` regardless of mode — it has no awareness of which engine is answering. All intelligence lives server-side in the orchestrator.

---

## 7. "Ring" / Notification Events

| Trigger | Event | Delivery |
|---|---|---|
| Widget loads / session starts | `session_start` | Logged only (no real-time push needed) |
| Session escalates to AI mode | `ai_mode_start` | Logged only |
| Visitor requests a human | `human_requested` | Real-time push to agent dashboard via WebSocket |

Only the human-requested event needs real-time delivery. This keeps the real-time infrastructure minimal in Phase 1 rather than pushing every state change over a socket.

---

## 8. Phase-wise Roadmap

### Phase 0 — Foundation (this document + environment setup)
- Finalize architecture (this document)
- Set up FastAPI project skeleton, MongoDB, repo structure
- Define Pydantic schemas for all data models above

### Phase 1 — MVP (core 4 pillars)

**Milestone 1: Widget shell + tenant registration**
- Vanilla JS embeddable widget with Shadow DOM
- Site registration API — generates `siteId` + install snippet
- Basic chat UI: open/close, send/receive message

**Milestone 2: Orchestrator + rule engine**
- Session init and mode-tracking logic
- JSON-driven rule engine for scripted flows (greetings, FAQs)
- `/message` endpoint routing to rule engine, with auto-escalation to AI when no rule matches
- Active-user tracking: identity capture for logged-in visitors, IP capture for anonymous ones, WebSocket-based presence (connect/disconnect), and an admin endpoint/live feed to list active sessions per site

**Milestone 3: AI engine + RAG**
- Website content ingestion (manual paste/upload for Phase 1, to keep scope realistic)
- Chunking + embedding + vector store integration
- Context retrieval wired into the LLM call

**Milestone 4: Human handoff**
- "Talk to agent" trigger → `human_requested` ring event
- Minimal agent-side interface (or a Slack/Teams webhook bridge, to avoid building a full helpdesk this early)
- WebSocket relay for live messages once an agent joins

### Phase 2 — Hardening & scale
- Rate limiting and abuse protection on public-facing APIs
- Per-tenant usage limits and billing hooks
- Auto-crawl (sitemap-based) content ingestion, replacing manual paste
- Proper agent dashboard: queueing, multiple agents per site, chat history search
- Analytics: conversation volume, escalation rate, common unanswered queries

### Phase 3 — Advanced features (post-MVP)
- Multi-language support
- Sentiment-based auto-escalation (frustrated user → auto-suggest human)
- Custom branding/theme per tenant
- Native integrations (Slack, Teams, WhatsApp as agent-side channels)
- A/B testing of rule-engine flows

---

## 9. Risks & Mitigations

| Risk | Mitigation |
|---|---|
| Multi-tenant data leakage | `siteId` scoping enforced at the query layer from day one, not retrofitted |
| Public API abuse | Origin + siteId double validation, rate limiting from Milestone 1 |
| LLM/embedding cost overrun | Per-tenant message limits in Phase 1; rule engine absorbs most traffic before AI is invoked |
| Real-time infra complexity | Sockets scoped only to the human-handoff path, not every event |
| Scope creep on human handoff | Phase 1 deliberately avoids building a full helpdesk — webhook/simple relay only |
| PII exposure (IP, name, email in active-user tracking) | Apply a retention limit (e.g. auto-delete session data after 30 days); add a basic privacy policy before wider launch |

---

## 10. Estimated Timeline (solo developer, part-time alongside other commitments)

| Milestone | Duration |
|---|---|
| Phase 0 (setup) | 3–4 days |
| Milestone 1 (widget + tenant) | Week 1–2 |
| Milestone 2 (orchestrator + rules) | Week 2–3 |
| Milestone 3 (AI + RAG) | Week 3–5 |
| Milestone 4 (human handoff) | Week 5–6 |
| **Phase 1 total** | **~6–8 weeks** |

---

## 11. Open Decisions (to be finalized before build starts)

- Vector store: Qdrant vs pgvector vs Pinecone
- LLM provider: OpenAI vs Claude, and cost-control strategy
- Human handoff Phase 1 depth: webhook bridge vs minimal custom dashboard
- Content ingestion Phase 1 method: manual paste vs basic crawler
