Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

175 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MVP — Call Center Rep Portal & Compliance

Full-stack call center MVP: NestJS backend (API + WebSocket), Next.js frontend, Supabase (PostgreSQL + Auth), RingCentral dialer, AssemblyAI real-time STT, and live compliance (rules, violations, alerts).


Table of contents


Features

Area Description
Auth Email/password signup & login, Supabase (OTP, magic link, reset), Google OAuth, RingCentral OAuth
Orgs & roles Organizations, members, platform roles (rep / admin / super_admin), permission-based access (RBAC)
Rep Portal & dialer RingCentral Embeddable widget (RingOut), dialpad, call flow (ready → in progress → ended), disposition (outcome, notes)
Mic & sound Centralized permission request/check (see Mic and sound permissions); required for live transcription and call audio
Live transcript Real-time speech-to-text via AssemblyAI streaming STT (server) or browser Speech Recognition (fallback); stored per call
Compliance Org-level rules (Disclosure, Prohibited Language, etc.), real-time evaluation during calls, violation alerts, compliance score
AI Call Assistant Same audio → AssemblyAI streaming STT → transcript → optional RAG/LLM layer (app uses a stub; replace with your embedding + vector DB + LLM)
Calls & DB All calls stored with transcripts, compliance score, violations, and in-call compliance alert history
Compliance UI Rules CRUD, violations list & verification, TPI/DNC, simulate transcript to test rules
Training / onboarding Training modules, onboarding, skill development (dashboard routes)
Other Call center outbound, AI coaching, campaigns, QA, analytics, knowledge base, settings, admin

Installation

Prerequisites

  • Node.js 20+
  • npm (or yarn/pnpm)
  • Supabase account
  • RingCentral developer app (for dialer)
  • AssemblyAI API key (optional; for server-side live streaming transcription)

1. Clone and install dependencies

git clone <repo-url>
cd MVP

# Backend
cd backend && npm install && cd ..

# Frontend
cd frontend && npm install && cd ..

2. Database (Supabase)

  1. Create a project at supabase.com.
  2. In Project Settings → Database, copy the connection string (URI). Use the direct connection (port 5432) for migrations.
  3. Set DATABASE_URL in backend/.env to that URI (replace [YOUR-PASSWORD] with your DB password).
  4. Run migrations (see Database & migrations).

3. Backend environment

cp backend/.env.example backend/.env
# Edit backend/.env: DATABASE_URL, SUPABASE_*, SESSION_SECRET, RINGCENTRAL_*, ASSEMBLYAI_API_KEY (optional), etc.

4. Frontend environment

cp frontend/.env.example frontend/.env
# Set NEXT_PUBLIC_API_URL (e.g. http://localhost:3001) and NEXT_PUBLIC_WS_URL (e.g. ws://localhost:3001).
# For RingCentral dialer: NEXT_PUBLIC_RINGCENTRAL_CLIENT_ID, NEXT_PUBLIC_RINGCENTRAL_SERVER (optional).

5. Supabase Auth configuration

  • In Supabase Dashboard → Authentication → URL Configuration, add:
    • Site URL and Redirect URLs: http://localhost:3000, http://localhost:3000/auth/callback, http://localhost:3000/auth/reset-password.

Environment

Variable Where Purpose
DATABASE_URL Backend Supabase PostgreSQL (direct, port 5432)
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_JWT_SECRET Backend Supabase Auth & API
FRONTEND_URL Backend CORS and redirects (e.g. http://localhost:3000)
SESSION_SECRET Backend JWT signing
RINGCENTRAL_* Backend RingCentral OAuth (dialer)
ASSEMBLYAI_API_KEY Backend Live streaming STT (optional; fallback: browser Speech Recognition)
NEXT_PUBLIC_API_URL Frontend API base (e.g. http://localhost:3001)
NEXT_PUBLIC_WS_URL Frontend WebSocket (e.g. ws://localhost:3001)
NEXT_PUBLIC_RINGCENTRAL_CLIENT_ID, NEXT_PUBLIC_RINGCENTRAL_SERVER Frontend RingCentral Embeddable widget

Running the app

# Terminal 1 — Backend (API + WebSocket)
cd backend && npm run start:dev

# Terminal 2 — Frontend
cd frontend && npm run dev
  • Frontend: http://localhost:3000
  • API: http://localhost:3001
  • WebSocket: ws://localhost:3001/ws?token=<JWT>
  • DB check: GET http://localhost:3001/db-check
  • Integrations (AssemblyAI, etc.): GET http://localhost:3001/integrations/status

Checking AssemblyAI on localhost

Live transcript can use server-side STT (AssemblyAI) or browser Speech Recognition. To use AssemblyAI:

  1. Get an API key
    Sign up at AssemblyAI, create an API key, and add it to backend/.env:

    ASSEMBLYAI_API_KEY=your_key_here
  2. Verify the backend sees it
    With the backend running (npm run start:dev in backend/), open:

    http://localhost:3001/integrations/status
    

    You should see:

    {
      "assemblyai": {
        "configured": true,
        "hint": "Live transcript (STT) and AI assistant will use AssemblyAI streaming when you are on a call."
      }
    }

    If configured is false, the key is missing or invalid in .env; fix it and restart the backend.

  3. Test on a call
    Go to Rep Portal → Dialer, start a call, allow the microphone, and speak. Transcript should appear in the Live Transcript panel (and Compliance will update). If it still doesn’t:

    • Confirm the WebSocket is open (no errors in the browser console).
    • Confirm the mic is allowed and unmuted (or that the “muted but still capturing” path is working).
    • Without AssemblyAI, the app falls back to browser Speech Recognition (Chrome/Edge recommended).

Database & migrations

  • ORM: Prisma; schema in backend/prisma/schema.prisma.
  • Migrations: backend/prisma/migrations/.

Commands (from backend/):

npx prisma generate   # Regenerate client after schema change
npm run db:migrate      # Apply migrations (dev)
npm run db:migrate:deploy   # Apply in production
npm run db:studio       # Open Prisma Studio

If you previously applied manual SQL and now use Prisma, you may need to mark the initial migration as applied:
npx prisma migrate resolve --applied 20250211000000_init (adjust name to match).


Mic and sound permissions

The dialer needs microphone (for live transcription and optional “Include call audio”) and sound (to hear the call). The app uses a small permission layer so requests and errors are consistent.

  • Frontend: frontend/src/lib/mediaPermissions.ts
    • checkMicPermission() — current mic state (if Permissions API available).
    • requestMicPermission() — request mic from a user gesture; returns stream or a structured error (denied / not found / error) with a user-facing message.
    • requestSoundPermission() — unlock playback via AudioContext on user gesture; returns context to keep alive and a message.
  • Usage: RingCentral “Connect” triggers a mic request so the browser prompt appears before redirect. On the call page, “Allow mic for live transcription” and “Allow sound for call audio” use the same helpers and show the same error copy (e.g. address bar → Site settings → Microphone / Sound).

Live transcript & compliance & AI assistant

  • Live transcript
    • Server (AssemblyAI): When ASSEMBLYAI_API_KEY is set, client sends PCM audio over WebSocket; backend streams it to AssemblyAI’s real-time streaming STT API and broadcasts transcript events. AssemblyAI does speech-to-text only (no RAG).
    • Fallback: If the key is not set, the client uses browser Speech Recognition and sends simulate-transcript; backend runs the same compliance logic and broadcasts transcript + compliance.
  • Compliance
    • On each transcript update (from server STT or simulate), the backend evaluates org compliance rules (disclosure + prohibited language), updates satisfied/violated rules and score, and broadcasts compliance. Violations are shown in the UI in real time; they are persisted to the DB only when the user saves the call (PATCH /calls/:id with violations).
  • AI Call Assistant (RAG pipeline uses transcript)
    • The same audio is sent to a second AssemblyAI streaming STT session for the “AI Call Assistant” panel. The backend receives transcript text and runs a stub suggestion (keyword-based). AssemblyAI does not perform RAG; for a real voice RAG pipeline you would take this transcript → embedding + vector DB → LLM (e.g. LangChain) and send the result as the suggestion.

See backend/src/ws/ws.gateway.ts (file-level comment) for the full flow.


AssemblyAI in this project (STT, not RAG)

AssemblyAI provides speech-to-text (live streaming and batch). It does not do RAG, embeddings, or LLM; it only turns audio into text.

Feature AssemblyAI This project
Live transcription ✅ Yes (streaming WebSocket API) Used for live call transcript + compliance input
Batch / file transcription ✅ Yes (not wired in this repo) Could be added for recordings
RAG / LLM ❌ No Transcript is the input to your RAG/LLM pipeline

Architecture (RingCentral + NestJS + voice AI):

Call audio (mic / RingCentral / WebRTC)
        ↓
AssemblyAI streaming STT  ←── ASSEMBLYAI_API_KEY
        ↓
Live transcript text
        ↓
├──→ Live Transcript UI + Compliance rules
└──→ (Optional) RAG pipeline: transcript → embedding + vector DB → LLM → suggestion

In this codebase:

  • transcription/ — one AssemblyAI streaming session per call for live transcript (and compliance).
  • assemblyai/ — a second streaming session per call for the AI Call Assistant panel; transcript is then passed to a stub (keyword-based). Replace that stub with your own RAG/LLM (e.g. LangChain + vector store) that consumes the transcript.

Database: calls, transcripts, violations, alert history, assistant logs

  • Calls — calls: one row per call (user, org, RingCentral id, status, duration, compliance score, outcome, notes, recording URL).
  • Live transcriptions — transcripts: one row per segment (call, speaker rep/customer, text, timestamp). Saved when the call ends: client sends full transcript in POST /calls/:id/end (replace), or if the client sends no transcript the server keeps what it persisted from the live STT buffer in stopTranscription.
  • Compliance — compliance_score on the call is set in POST /calls/:id/end. Violations (violations) are created when the rep clicks Save Call (PATCH) with violation data. In-call alerts (compliance_alerts): one row per real-time violation alert during the call for auditing.
  • Assistant logs — call_assistant_logs: one row per transcript turn + suggestion (callId, transcript, suggestion). Transcript comes from AssemblyAI streaming STT; suggestion is from the app's stub (replace with your RAG/LLM that consumes the transcript).

Checking compliance (course / rules)

  • Compliance dashboard: /dashboard/compliance — overview, links to rules and violations.
  • Rules: /dashboard/compliance/rules — create/edit rules (category, severity, detection pattern), simulate transcript to test rules before going live.
  • Violations: /dashboard/compliance/violations — list violations, verify/filter.
  • TPI/DNC: /dashboard/compliance/tpi-dnc — TPI and DNC list management.
  • During a call: Real-time compliance score and checklist (satisfied/violated) and violation alerts; all driven by the same rules and stored as above.

Project structure

MVP/
├── backend/                 # NestJS API + WebSocket
│   ├── src/
│   │   ├── admin/           # Platform admin
│   │   ├── assemblyai/      # AssemblyAI streaming STT for AI assistant; transcript fed to stub (replace with RAG/LLM)
│   │   ├── auth/            # JWT, Google, Supabase, RingCentral
│   │   ├── calls/           # Call CRUD, end call, transcripts, recording
│   │   ├── compliance/      # Rules, violations, evaluation
│   │   ├── config/          # Env, Supabase, RingCentral
│   │   ├── transcription/   # Live STT (AssemblyAI streaming), persist transcript
│   │   ├── ws/              # WebSocket gateway (dialer, transcript, compliance)
│   │   └── ...
│   └── prisma/              # Schema + migrations
├── frontend/                 # Next.js App Router
│   └── src/
│       ├── app/dashboard/    # Dashboard, rep-portal/dialer, compliance, ...
│       ├── components/      # UI, sidebar, guards
│       ├── contexts/        # User, WebSocket
│       ├── hooks/            # useMicrophoneStream
│       └── lib/              # api, mediaPermissions, roles, websocket
└── README.md                 # This file
  • Backend README: backend/README.md — API table, WebSocket auth, RingCentral redirect notes.
  • Project guide: PROJECT_GUIDE.md — project structure, features list, running locally, and deployment to Vercel (frontend) and Render (backend).
  • Frontend: Next.js default; no separate feature README.

Live transcript → compliance → AI assistant (detail)

┌─────────────────────────────────────────────────────────────────────────────┐
│  Frontend (dialer)                                                           │
│  • Mic + optional tab audio → PCM → WebSocket "audio" (speaker: rep/customer)│
│  • Or: browser Speech Recognition → WebSocket "simulate-transcript"         │
└─────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│  Backend WebSocket (ws.gateway.ts)                                           │
│  • join-call: starts transcription (rep + customer) + AssemblyAI RAG        │
│  • audio → TranscriptionService + AssemblyAIStreamingService                 │
│  • simulate-transcript → compliance eval + broadcast transcript            │
└─────────────────────────────────────────────────────────────────────────────┘
           │                              │                              │
           ▼                              ▼                              ▼
┌──────────────────────┐    ┌──────────────────────┐    ┌──────────────────────┐
│  Live transcript     │    │  Compliance          │    │  AI assistant (RAG)    │
│  • AssemblyAI v3     │    │  • Rep + customer     │    │  • Same audio →       │
│  • Two sessions       │    │    text → rules       │    │    AssemblyAI stream  │
│    (rep, customer)    │    │  • Satisfied/violated│    │  • Final turns →      │
│  • Broadcast          │    │  • Score 0–100%       │    │    suggestion stub    │
│    type: "transcript" │    │  • Broadcast         │    │  • Broadcast          │
│                       │    │    type: "compliance" │    │    assistant-transcript│
│                       │    │  • Alerts to DB       │    │    assistant-suggestion│
└──────────────────────┘    └──────────────────────┘    └──────────────────────┘

Live transcript: AssemblyAI path (when USE_ASSEMBLYAI_LIVE_TRANSCRIPTION): frontend sends PCM with speaker: "rep" or "customer"; backend streams to AssemblyAI v3 (one session per speaker); gateway broadcasts { type: "transcript", speaker, text, timestamp, isFinal }. Browser STT path: frontend sends "simulate-transcript"; gateway broadcasts same shape and runs compliance.

Compliance: ComplianceRulesService.evaluateTranscript(orgId, repTranscript, customerTranscript); violations broadcast in real time; persisted on Save Call (PATCH /calls/:id). In-call alerts → compliance_alerts.

AI assistant: Same PCM → AssemblyAIStreamingService; on final turn, stub ragSuggestionFromTranscript; broadcast assistant-transcript / assistant-suggestion; persist to callAssistantLog.

WebSocket message types: transcript, compliance, assistant-transcript, assistant-suggestion (server→client); audio, simulate-transcript (client→server).

Files: Pipeline — ws/ws.gateway.ts, rep-portal/dialer/DemoDialerContent.tsx. STT — transcription/transcription.service.ts, hooks/useMicrophoneStream.ts. Compliance — compliance/compliance.service.ts, compliance-rules.service.ts. AI assistant — assemblyai/assemblyai-streaming.service.ts.


Call audio & live AI (RingOut, WebRTC, capture)

Why RingOut alone is not enough: With RingOut, your app never receives the audio → no live transcription or RAG. You must capture both sides and send to AssemblyAI.

How this app captures audio:

Source How we get it Sent as
Rep speech Browser microphone (getUserMedia) event: "audio", speaker: "rep"
Customer speech Optional: “Include call audio (share tab)” — share RingCentral tab with tab audio (getDisplayMedia). Must check ☑ Share tab audio. event: "audio", speaker: "customer"

Safari: Tab audio cannot be captured via getDisplayMedia({ audio: true }). Rep transcript works; customer transcript via tab share does not work in Safari. For rep + customer in Safari, use RingCentral Web Phone (WebRTC) or Call Media Streaming to get the remote track.

Backend pipeline: Mic PCM → WebSocket audio (rep) → TranscriptionService → AssemblyAI v3. Tab PCM (customer) → WebSocket audio (customer) → second session → same compliance/RAG path.

Summary:

Setup Rep transcript Customer transcript RAG assistant
RingOut, mic only ✅ ❌ ✅ (rep only)
RingOut + “Include call audio” (tab share) ✅ ✅ (Chrome/Edge) ✅ (both)
RingCentral WebRTC / Media Streaming ✅ ✅ (clean) ✅ (both)

Making changes

Development workflow

  1. Backend (NestJS)

    • Code: backend/src/.
    • After changing Prisma schema: cd backend && npx prisma generate.
    • New migrations: npx prisma migrate dev --name your_migration_name.
    • Run: npm run start:dev (hot reload).
  2. Frontend (Next.js)

    • Code: frontend/src/.
    • Run: npm run dev.
    • Env: copy frontend/.env.example to frontend/.env and set NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL, and RingCentral/other keys as needed.
  3. Environment

    • Backend: backend/.env (see Environment).
    • Never commit .env; use .env.example as a template and document any new variables in the README.
  4. Testing

    • Backend: cd backend && npm run test (if test script exists).
    • Frontend: cd frontend && npm run build to catch build errors; add E2E/unit as needed.
  5. Today’s goals / stats

    • Backend: GET /calls/stats/today returns { calls, connects, qualified, booked } for the current user (or org for admins).
    • Frontend: dashboard uses api.getTodayGoals() and refetches on focus.

Before committing

  • Run npx prisma generate if schema changed.
  • Run npm run build in both backend and frontend.
  • Ensure no secrets in code; use env vars only.

Deployment

Backend (NestJS)

  1. Build

    cd backend && npm ci && npm run build
  2. Database

    • Set DATABASE_URL to your production PostgreSQL (e.g. Supabase direct connection).
    • Run migrations: npm run db:migrate:deploy (or npx prisma migrate deploy).
    • Ensure Supabase (or your DB) allows connections from the deployment host.
  3. Environment

    • Configure all required env vars in the host (see Environment): DATABASE_URL, SUPABASE_*, SESSION_SECRET, FRONTEND_URL, RINGCENTRAL_*, ASSEMBLYAI_API_KEY (optional), API_BASE_URL for webhooks.
  4. Run

    node dist/main.js

    Or use a process manager (e.g. PM2, systemd) and reverse proxy (e.g. Nginx) for HTTPS.

    • RingCentral webhooks: Set your RingCentral app’s webhook URLs to https://your-api-domain/calls/webhook/ringcentral and .../calls/webhook/ringcentral/ringout so call events and RingOut status are received.
  5. Health

    • Expose a health/readiness endpoint if needed; GET /db-check and GET /integrations/status can be used for basic checks.

Frontend (Next.js)

  1. Build

    cd frontend && npm ci && npm run build
  2. Environment

    • Set build-time env vars (e.g. in CI or host):
      NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL (your production API and WebSocket URLs),
      NEXT_PUBLIC_RINGCENTRAL_CLIENT_ID, NEXT_PUBLIC_RINGCENTRAL_SERVER (if using dialer).
    • These are embedded at build time; redeploy after changing them.
  3. Run

    npm run start

    Or deploy the .next output to Vercel, Node host, or static export per Next.js docs.

    • Ensure CORS on the backend allows your frontend origin (FRONTEND_URL).
  4. Auth

    • In Supabase Dashboard, add your production site URL and redirect URLs (e.g. https://your-app.com, https://your-app.com/auth/callback, https://your-app.com/auth/reset-password).
    • RingCentral: set the production redirect URI in the RingCentral app to match your backend’s RINGCENTRAL_CALLBACK_URL.

Checklist

  • Backend: DATABASE_URL, migrations, all env vars, webhook URLs.
  • Frontend: NEXT_PUBLIC_* set for production, build succeeds.
  • Supabase Auth redirect URLs and RingCentral redirect URI updated for production.
  • HTTPS and correct CORS/security headers in production.

Quick checklist after install

  1. Supabase project created; DATABASE_URL and Supabase keys in backend/.env.
  2. Backend migrations applied: cd backend && npm run db:migrate.
  3. Supabase Auth redirect URLs include your frontend URL and /auth/callback, /auth/reset-password.
  4. RingCentral app created; redirect URI matches RINGCENTRAL_CALLBACK_URL; backend and (if used) frontend RingCentral env set.
  5. Frontend .env: NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL.
  6. Start backend and frontend; open Dashboard → Dialer; connect RingCentral; start a call and confirm mic/sound prompts and (if configured) live transcript and compliance.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages