0%
Vibe44 Logo

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().

ProviderSDKModelsEmbeddings
Gemini@google/genaigemini-3.5-flash (default), GEMINI_MODEL override✅ Built-in
OpenAIopenaigpt-5.4-mini (default), uses max_completion_tokens✅ Built-in
Anthropic@anthropic-ai/sdkclaude-sonnet-4-6 (default)❌ Requires fallback
DeepSeekopenaideepseek-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

PropertyValue
Default Modelgemini-embedding-2
Dimensions3072
FallbackAnthropic/DeepSeek → set EMBEDDING_PROVIDER=gemini or openai

Intent Classification

IntentAction
Knowledge queryRAG search → contextual response
General questionDirect AI response (no RAG)
Support requestRoute to ticket creation
Off-topicPolite 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 context

Knowledge Base Management

Super-Admin → Knowledge Base:

OperationAction
AddTitle + content → auto-embedding
EditContent change → re-embedding
DeleteEntry + vector removed
Bulk re-embedAll embeddings regenerated
SearchPreview 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 balance

Per-tier limits in PRICING_TIERS.credits.monthly:

  • 🔥 Free: 50/mo
  • ⭐ Starter: higher
  • 💎 Pro: unlimited (-1)

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

StepWhat Happens
1. Super-Admin triggersgenerateLegalDocAction(type, locale) in app/actions/super-admin/legal.ts
2. AI provider callgetAIProvider().generateStructuredOutput(), temperature: 0.3
3. Structured outputresponseMimeType: "application/json" + responseSchema → { title, content }
4. PersonalizationLegalDocContext injects companyName, companyUrl, supportEmail, jurisdiction from config.app
5. Version controlAuto-increments version per type + locale combination
6. PublishpublishLegalDocAction(docId) — auto-unpublishes prior version
7. ServeGET /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)

PropertyValue
ProvidersElevenLabs, Murf AI (Universal Voice Adapter)
Modelseleven_multilingual_v2 (ElevenLabs), Murf API
Languages29+ (ElevenLabs), 20+ (Murf)
VoicesConfigurable per-app
StreamingChunked audio
PlaybackWeb 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)

PropertyValue
ProviderElevenLabs
Modelscribe_v2 (default, scribe_v1 fallback)
InputBrowser microphone
AccessHTTPS required (localhost for dev)
Permissionnavigator.mediaDevices.getUserMedia()
DiarizationSpeaker identification (optional)
Event TaggingLaughter, music, applause detection (optional)

STT API Route

POST /api/support/voice/stt

ParameterTypeRequiredDescription
audioFileYesAudio file (mp3, wav, webm, ogg, flac, m4a)
languageCodestringNoISO 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

  1. User clicks mic icon in widget
  2. Browser requests mic permission (HTTPS)
  3. Audio recorded (MediaRecorder API)
  4. → /api/support/voice/stt (Scribe v2, multipart field audio)
  5. Transcribed text through AI pipeline
  6. getAIProvider().streamChat() generates response
  7. → /api/support/voice (ElevenLabs eleven_multilingual_v2 or Murf TTS)
  8. 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=murf

No AI provider API key → widget hidden. No API key for any voice providers → text-only mode.


️ Database

TablePurposeKey Columns
ai_conversationsSessionsuser_id (nullable), title, language
ai_messagesMessagesconversation_id, role, content, token_count
knowledge_baseRAG vectorstitle, 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.