AI & Voice
Universal AI Provider Adapter with 4 providers (Gemini, OpenAI, Anthropic, DeepSeek), RAG pipeline, intent classification, AI legal document generation, Universal Voice Adapter (ElevenLabs, Murf AI), and embedded chat widget.
️ Architecture
User input (text or voice)
↓
STT (ElevenLabs Scribe v2, if voice)
↓
Language detection + intent classification (getAIProvider())
↓
Embedding (getEmbeddingProvider() → gemini-embedding-2, 3072 dims)
↓
pgvector cosine similarity → knowledge_base
↓
Top-K context injected into system prompt
↓
getAIProvider().streamChat() responds (streaming SSE)
↓
TTS (getVoiceProvider() → ElevenLabs eleven_multilingual_v2 or Murf AI, if voice mode)
↓
Response → chat widget (Web Audio API)AI Chat
Universal AI Provider Adapter
The AI system uses a factory pattern — getAIProvider() reads the AI_PROVIDER env var and returns the configured provider. All providers implement the same AIProvider interface: streamChat(), classifyIntent(), generateStructuredOutput().
| Provider | SDK | Models | Embeddings |
|---|---|---|---|
| Gemini | @google/genai | gemini-3.5-flash (default), GEMINI_MODEL override | ✅ Built-in |
| OpenAI | openai | gpt-5.4-mini (default), uses max_completion_tokens | ✅ Built-in |
| Anthropic | @anthropic-ai/sdk | claude-sonnet-4-6 (default) | ❌ Requires fallback |
| DeepSeek | openai | deepseek-v4-flash (default), uses max_tokens | ❌ Requires fallback |
DeepSeek reuses the OpenAI SDK with
baseURL: "https://api.deepseek.com"— zero additional dependencies.
Client
// lib/ai/index.ts — Universal AI Provider Factory
import { getAIProvider, getEmbeddingProvider, isAIEnabled } from "@/lib/ai";
const ai = getAIProvider(); // Gemini | OpenAI | Anthropic | DeepSeek
const embedder = getEmbeddingProvider(); // Gemini | OpenAI (fallback for Anthropic/DeepSeek)Singleton per provider — created once, reused. isAIEnabled() gates on the configured AI_PROVIDER key and returns false for unknown provider names.
Embedding Config
| Property | Value |
|---|---|
| Default Model | gemini-embedding-2 |
| Dimensions | 3072 |
| Fallback | Anthropic/DeepSeek → set EMBEDDING_PROVIDER=gemini or openai |
Intent Classification
| Intent | Action |
|---|---|
| Knowledge query | RAG search → contextual response |
| General question | Direct AI response (no RAG) |
| Support request | Route to ticket creation |
| Off-topic | Polite redirect |
RAG Pipeline
"How do I add a payment provider?"
↓
gemini-embedding-2 → 3072-dim vector
↓
search_knowledge_base(embedding, threshold=0.7, count=5)
↓
Top 5 matches injected into system prompt
↓
getAIProvider().streamChat() responds grounded in contextKnowledge Base Management
Super-Admin → Knowledge Base:
| Operation | Action |
|---|---|
| Add | Title + content → auto-embedding |
| Edit | Content change → re-embedding |
| Delete | Entry + vector removed |
| Bulk re-embed | All embeddings regenerated |
| Search | Preview cosine similarity results |
Guest Access
No auth required. Conversations created lazily (DB record on first message, not widget open). Guest = user_id = NULL.
Credit System
Message sent → check balance > 0
↓
deduct_credits(userId, cost) — atomic FOR UPDATE lock
↓
Process → respond → return updated balancePer-tier limits in PRICING_TIERS.credits.monthly:
- 🔥 Free: 50/mo
- ⭐ Starter: higher
- 💎 Pro: unlimited (
-1)
️ AI Legal Document Generation
The Universal AI Provider generates versioned legal documents (Privacy Policy, Terms of Service, Cookie Policy, Refund Policy) via lib/legal-generator/index.ts.
How It Works
| Step | What Happens |
|---|---|
| 1. Super-Admin triggers | generateLegalDocAction(type, locale) in app/actions/super-admin/legal.ts |
| 2. AI provider call | getAIProvider().generateStructuredOutput(), temperature: 0.3 |
| 3. Structured output | responseMimeType: "application/json" + responseSchema → { title, content } |
| 4. Personalization | LegalDocContext injects companyName, companyUrl, supportEmail, jurisdiction from config.app |
| 5. Version control | Auto-increments version per type + locale combination |
| 6. Publish | publishLegalDocAction(docId) — auto-unpublishes prior version |
| 7. Serve | GET /api/legal?type=privacy_policy&locale=en — public, rate-limited, CDN-cached (s-maxage=3600) |
Supported document types are privacy_policy, terms_of_service,
cookie_policy, and refund_policy. The database contract is
type + locale + version; this preserves historical versions while preventing
duplicate version rows for the same document and locale.
Key Design Decisions
- Structured JSON output (not freeform text) — ensures deterministic, machine-readable results
- Low temperature (0.3) — precision over creativity for legal content
- Append-only versioning — every generation creates a new row; no destructive updates
- Atomic publish —
db.transaction()wraps read-unpublish-insert to prevent TOCTOU races
⚠️ AI-generated legal docs are a starting point. Have a lawyer review before going live.
️ Voice
TTS (Text-to-Speech)
| Property | Value |
|---|---|
| Providers | ElevenLabs, Murf AI (Universal Voice Adapter) |
| Models | eleven_multilingual_v2 (ElevenLabs), Murf API |
| Languages | 29+ (ElevenLabs), 20+ (Murf) |
| Voices | Configurable per-app |
| Streaming | Chunked audio |
| Playback | Web Audio API |
Murf responses return an audioFile URL that is fetched server-side. The Murf provider sanitizes that URL with sanitizeUrl() from lib/security/ssrf.ts before fetching, blocking private IPs, cloud metadata endpoints, non-HTTP(S) schemes, and numeric IP obfuscation in provider-returned media paths.
STT (Speech-to-Text)
| Property | Value |
|---|---|
| Provider | ElevenLabs |
| Model | scribe_v2 (default, scribe_v1 fallback) |
| Input | Browser microphone |
| Access | HTTPS required (localhost for dev) |
| Permission | navigator.mediaDevices.getUserMedia() |
| Diarization | Speaker identification (optional) |
| Event Tagging | Laughter, music, applause detection (optional) |
STT API Route
POST /api/support/voice/stt
| Parameter | Type | Required | Description |
|---|---|---|---|
audio | File | Yes | Audio file (mp3, wav, webm, ogg, flac, m4a) |
languageCode | string | No | ISO language code |
POST /api/support/transcribe remains as the legacy transcription route and
accepts multipart field file. Both transcription routes enforce a 10MB max
file size, validate the File object before reading it, and rate-limit paid STT
usage. No auth is required for guest support access. Invalid TTS/STT payload
details are logged server-side only; public JSON responses return generic
validation errors instead of Zod flatten() output.
Voice Flow
- User clicks mic icon in widget
- Browser requests mic permission (HTTPS)
- Audio recorded (MediaRecorder API)
- →
/api/support/voice/stt(Scribe v2, multipart fieldaudio) - Transcribed text through AI pipeline
getAIProvider().streamChat()generates response- →
/api/support/voice(ElevenLabseleven_multilingual_v2or Murf TTS) - Audio streamed back via Web Audio API
Chat Widget
components/support/chat-widget.tsx:
- Floating bottom-right toggle
- Voice input + playback
- Real-time streaming
- Lazy conversation creation
- Guest mode (no auth)
- Auto-language detection
- Content + event ID dedup
- Feature-flagged:
isAiChatEnabled()
Config
# AI Provider (required — set one)
AI_PROVIDER=gemini # "gemini" | "openai" | "anthropic" | "deepseek"
GEMINI_API_KEY=... # Required if AI_PROVIDER=gemini
OPENAI_API_KEY=... # Required if AI_PROVIDER=openai
ANTHROPIC_API_KEY=... # Required if AI_PROVIDER=anthropic
DEEPSEEK_API_KEY=... # Required if AI_PROVIDER=deepseek
# Embedding Provider (optional — defaults to AI_PROVIDER if supported)
EMBEDDING_PROVIDER=gemini # "gemini" | "openai" — required for Anthropic/DeepSeek
# Voice (optional)
VOICE_PROVIDER=elevenlabs # "elevenlabs" | "murf"
ELEVENLABS_API_KEY=... # Required if VOICE_PROVIDER=elevenlabs
MURFAI_API_KEY=... # Required if VOICE_PROVIDER=murfNo AI provider API key → widget hidden. No API key for any voice providers → text-only mode.
️ Database
| Table | Purpose | Key Columns |
|---|---|---|
ai_conversations | Sessions | user_id (nullable), title, language |
ai_messages | Messages | conversation_id, role, content, token_count |
knowledge_base | RAG vectors | title, content, embedding (3072), category |
pgvector
Enable in Supabase → Database → Extensions. Cosine similarity index created by db:push.
Copyright © 2026 Vibe44. Built with ❤️ by Scale with Alap.