# Voicebot (Django APIs + MCube Telephony + LiveKit Agents)

This repo is a **single monorepo** with two top-level app folders:

- **`backend/`**: Django control-plane (`manage.py`, APIs, Swagger) + **voice runtime** under **`backend/agent_runtime/`** (LiveKit worker + MCube services)
- **`frontend/`**: Web UI (Vite + React). Hosted behind Nginx at **`https://app3.syntheon.in/`**

**Runtime versions (recommended)**

- **Python**: 3.10.12 (supported band: **3.10–3.12**)
- **Node**: v20.20.0 (**npm** 10.8.x)

**One Python venv (repo root)**

```bash
cd /path/to/livekit_voicebot
python -m venv .venv
source .venv/Scripts/activate  # Windows Git Bash
python -m pip install -U pip
python -m pip install -r requirements.txt
```

**Ports (internal vs exposed)**

- **Internal-only (keep closed on the internet)**:
  - **6379**: Redis (Django cache + MCube-related state)
  - **5672**: RabbitMQ (MCube pipeline queues)
  - **8000**: Django backend (served via Nginx at `/api`)
  - **8002**: MCube `webhook_server` (served via proxy at `/webhooks/*` and `/api/mcube/*`)
  - **9001**: MCube `ws_bridge` (served via proxy at `/ws/*` and `/bid/websocket/*`)
  - **8088**: MCube `proxy_server` (Nginx proxies to this internally)
  - **3000**: Frontend UI (Nginx proxies `/` here)
  - **4174**: Masterpanel UI (Nginx proxies `/master/` here)
- **Public**:
  - **80/443**: Nginx (serves `https://app3.syntheon.in`)

## High-level telephony flow (MCube)

1. You create/update a **Bot** in Django (`/api/bots/`).
2. You start an outbound call from Django (`POST /api/calls/`).
3. Django (or campaign workers) call the MCube outbound helper at **`https://app3.syntheon.in/api/mcube/outbound-call`** (public) or `http://127.0.0.1:8088/api/mcube/outbound-call` (same service, local-only).
4. MCube connects back to your public URLs (domain) and opens the websocket to `wss://app3.syntheon.in/bid/websocket/<call_id>` (or `/ws/<call_id>` depending on your MCube setup).
5. `ws_bridge` streams audio in/out, publishes transcripts to RabbitMQ, and consumes TTS chunks from RabbitMQ.

## LiveKit “web voice” flow (frontend + worker)

1. Start the **worker** (waits for LiveKit jobs)
2. Start the **frontend** and click **Start call**
3. The frontend requests a token from **`/api/token`** and includes **agent configuration** (instructions/first message/providers) via LiveKit **RoomConfiguration metadata**
4. The worker (`backend/agent_runtime/src/agent.py`) reads that metadata and speaks accordingly

### Run Django

```bash
cd c:\voicebot\livekit_voicebot\backend
python manage.py migrate
python manage.py runserver 0.0.0.0:8000
```

Swagger (hosted): `https://app3.syntheon.in/api/docs/`

Step-by-step **Django + MCube webhook** (env files, ports, PowerShell commands): see [`backend/LOCAL_DEV_STARTUP.md`](backend/LOCAL_DEV_STARTUP.md).

## Telephony (PSTN) quick test (curl)

This tests the **MCube outbound-call helper** directly (bypasses Django).

### Prerequisites

- **Redis** on `6379`
- **RabbitMQ** on `5672`
- **MCube services** running:
  - `webhook_server` on **8002**
  - `ws_bridge` on **9001**
  - `ai_worker` (RabbitMQ consumer)
  - `proxy_server` on **8088** (recommended; lets you tunnel **one** port)
- No tunnel required in production since MCube will call `https://app3.syntheon.in/...`.

Your `backend/.env` should have public URLs like:

- `MCUBE_PUBLIC_BASE_URL=https://app3.syntheon.in`
- `MCUBE_PUBLIC_WS_URL_BASE=wss://app3.syntheon.in`
- `MCUBE_WS_PATH_PREFIX=/bid/websocket` (or `/ws` depending on your MCube environment)

### Test request (PowerShell)

Important: **no spaces** in URLs. The webhook path is `/webhooks/mcube` and the websocket path is `/ws/<call_id>` when `MCUBE_WS_PATH_PREFIX=/ws`.

**Production / Postman / HTTPS — full path ends with `outbound-call` (not `outbound-cal`):**

```bash
curl --location 'https://app3.syntheon.in/api/mcube/outbound-call' \
  --header 'Content-Type: application/json' \
  --data '{"to":"7004670611"}'
```

Optional fields (when MCube needs callbacks / websocket):

```bash
curl --location 'https://app3.syntheon.in/api/mcube/outbound-call' \
  --header 'Content-Type: application/json' \
  --data '{
    "to": "7004670611",
    "agent_name": "default",
    "call_id": "test_cb_01",
    "callback_url": "https://app3.syntheon.in/webhooks/mcube",
    "refurl": "wss://app3.syntheon.in/bid/websocket/test_cb_01"
  }'
```

**Example (PowerShell, single line):**

```powershell
$body = @{ to = '8249241195'; agent_name = 'default'; callback_url = 'https://app3.syntheon.in/webhooks/mcube'; refurl = 'wss://app3.syntheon.in/bid/websocket/test_cb_01'; call_id = 'test_cb_01' } | ConvertTo-Json; Invoke-RestMethod -Method POST -Uri 'https://app3.syntheon.in/api/mcube/outbound-call' -ContentType 'application/json' -Body $body -TimeoutSec 60 | ConvertTo-Json -Depth 10
```

**Local-only** (machine running `proxy_server` on :8088): same path on `http://127.0.0.1:8088/api/mcube/outbound-call`.

**Apache: Django HTML 404 on `https://…/api/mcube/outbound-call`**

That response means Apache sent the request to **Django** (`:8000`), not the MCube proxy (`:8088`). Apache matches **`ProxyPass` in order — the first matching rule wins**, so the broad `/api/` rule must **not** appear before the MCube route. Use the repo’s `apache-app3-syntheon.conf`, which wraps `/api/mcube/` in `<Location>` and lists it **before** `ProxyPass /api/`, then:

```bash
sudo cp /var/www/html/livekitdocker/apache-app3-syntheon.conf /etc/apache2/sites-available/app3-syntheon.conf
sudo apache2ctl configtest && sudo systemctl reload apache2
```

Sanity checks:

```bash
# MCube proxy must respond (may be 4xx JSON from the app, but not Django HTML):
curl -sS -D- -o /dev/null -X POST http://127.0.0.1:8088/api/mcube/outbound-call \
  -H 'Content-Type: application/json' -d '{"to":"7004670611"}' | head -5

# Through Apache HTTPS (expect JSON / non-HTML body from MCube stack):
curl -sS -D- -o /dev/null -X POST https://app3.syntheon.in/api/mcube/outbound-call \
  -H 'Content-Type: application/json' -d '{"to":"7004670611"}' | head -5
```

Let’s Encrypt: `sudo certbot certificates` should list `app3.syntheon.in`. The vhost uses `fullchain.pem` + `privkey.pem` under `/etc/letsencrypt/live/app3.syntheon.in/`. Certbot’s “certificate not yet due for renewal” only means you already have files there — no need to renew.

Expected payload shape:

```json
{
  "to": "8249241195",
  "call_id": "test_cb_01",
  "callback_url": "https://10a4-14-194-217-62.ngrok-free.app/webhooks/mcube",
  "agent_name": "default",
  "refurl": "wss://10a4-14-194-217-62.ngrok-free.app/ws/test_cb_01"
}
```

```powershell
$CALL_ID = "test_cb_01"
$NGROK_HOST = "https://<your-ngrok-host>"   # example: https://xxxx.ngrok-free.app
$NGROK_WSS  = "wss://<your-ngrok-host>"     # example: wss://xxxx.ngrok-free.app

$body = @{
  to = "8249241195"
  agent_name = "default"
  call_id = $CALL_ID
  callback_url = "$NGROK_HOST/webhooks/mcube"
  refurl = "$NGROK_WSS/ws/$CALL_ID"
} | ConvertTo-Json

# Run via the local proxy (forwards HTTP->8002 and WS->9001)
Set-Location "c:\voicebot\livekit_voicebot"
Invoke-RestMethod `
  -Method POST `
  -Uri "https://app3.syntheon.in/api/mcube/outbound-call" `
  -ContentType "application/json" `
  -Body $body
```

If successful, you should see a response like:

- `status: "succ"`
- `mcube_call_sid: "<some id>"`

### Run LiveKit worker

```bash
cd c:\voicebot\livekit_voicebot\backend\agent_runtime
python src/agent.py download-files  # first run / after dependency upgrades
python src/agent.py dev
```

Env: `backend/agent_runtime/.env.local` (see `backend/agent_runtime/.env.example`)

### Run web UI (Vite)

```bash
cd c:\voicebot\livekit_voicebot\frontend
npm install
npm run dev -- --port 3000 --host 0.0.0.0
```

Env: `frontend/.env.local` must include:

- `LIVEKIT_URL`
- `LIVEKIT_API_KEY`
- `LIVEKIT_API_SECRET`
- `AGENT_NAME` (example: `default`, must match `@server.rtc_session(agent_name=...)` in `backend/agent_runtime/src/agent.py`)

### Change agent behavior from the UI

On the welcome screen, use **Agent behavior** fields (instructions / first message / STT+TTS providers / voice id / model overrides). These are sent as JSON keys like `agent_instructions`, `agent_first_message`, etc., matching `backend/agent_runtime/src/agent.py`.

## Docker (single container) + Hosted domain

This repo is configured to run **everything from one `Dockerfile`** in a single container:

- Redis (**6379**)
- RabbitMQ (**5672**)
- Django backend (**8000**)
- MCube `webhook_server` (**8002**)
- MCube `ws_bridge` (**9001**)
- MCube `proxy_server` (**8088**)
- Web UI (**3000**)
- Masterpanel (**4174**)
- (Optional) ngrok dashboard (**4040**) — not started automatically

### Build

```bash
docker build -t livekit-voicebot-all .
```

### Run

Pass your env files at runtime (recommended). Example:

```bash
docker run --rm \
  -p 80:80 -p 443:443 \
  --env-file backend/.env.docker \
  --env-file backend/agent_runtime/.env.local \
  --env-file frontend/.env.local \
  livekit-voicebot-all
```

Notes:

- Put a reverse-proxy on the host and keep container ports bound to `127.0.0.1` only.
- **Apache**: use `apache-app3-syntheon.conf`
- **Nginx**: use `nginx-app3-syntheon.conf`
- If you do not want host Nginx, you can still publish the internal ports and use an external reverse proxy/load balancer.

Notes:

- **Do not bake secrets into the image**. Keep them in `.env` files and pass via `--env-file`.
- Django migrations run on container start (best-effort).

## Repo layout

```
backend/
  manage.py                 # Django
  apps/ ...
  agent_runtime/            # LiveKit + MCube python services
    src/agent.py
    src/mcube_integration/
frontend/                   # web UI (Vite + React)
requirements.txt            # single consolidated python requirements (repo root)
```

## References

- `requirements.txt` (main Django + LiveKit/MCube worker)
- `requirements-live-calls-legacy.txt` (optional; only if you run `backend/agent_runtime/live_calls/*` demos)
