# Engineering Guide: Vapi Voice AI Agent Web Widget Deployment & Audio Reactive Interface

> **Puzzle me this #22 Capstone**: How to deploy a customized Vapi voice assistant to a modern web application (such as Bolt.new or Vite React), handle WebRTC audio streams and volume telemetry, and build responsive audio-reactive UI widgets.

---

## 1. Executive Architecture Summary

Deploying low-latency, conversational voice agents onto web properties requires bridging WebRTC streaming audio, Speech-to-Text (STT), Large Language Models (LLM), Text-to-Speech (TTS), and responsive browser UI state.

**The Clue & Solution**:
> *"Visit the VAPI docs on deploying your assistant to the web"*

The official Vapi documentation (`docs.vapi.ai`) outlines two complementary deployment strategies:
1. **Zero-Build Script Tag Embed (`@vapi-ai/html-script-tag`)**: A single script inclusion that mounts an iframe or custom button with customizable positioning, color themes, and exposed CSS hook classes.
2. **Programmatic Web SDK (`@vapi-ai/web`)**: A full WebRTC client library providing direct access to connection lifecycle hooks (`call-start`, `call-end`), speech detection (`speech-start`, `speech-end`), audio volume telemetry (`volume-level`), and bidirectional function-calling messages (`message`).

```
┌─────────────────────────────────────────────────────────────────────────┐
│                     1. CLIENT APPLICATION (Browser)                     │
│                                                                         │
│   User Action: Click "Talk with Support" Button                         │
│                                     │                                   │
│                                     ▼                                   │
│                 window.vapiSDK.run({ ... })  OR  vapi.start()           │
│                                     │                                   │
│                     Requests Microphone Permissions                     │
│                     Mounts Audio-Reactive Equalizer (|||||)             │
└─────────────────────────────────────┬───────────────────────────────────┘
                                      │
                                      ▼  (WebRTC Audio Stream + Signaling WS)
┌─────────────────────────────────────────────────────────────────────────┐
│                         2. VAPI CLOUD PIPELINE                          │
│                                                                         │
│   ┌──────────────────┐    ┌─────────────────┐    ┌──────────────────┐   │
│   │  Deepgram Nova-2 │───▶│  LLM Engine     │───▶│  Cartesia /      │   │
│   │  (STT: ~150ms)   │    │  (GPT-4o/Claude)│    │  ElevenLabs TTS  │   │
│   └──────────────────┘    └─────────────────┘    └──────────────────┘   │
│            ▲                                              │             │
│            │                                              │             │
│    User Audio Stream                              Assistant Audio       │
└─────────────────────────────────────┬───────────────────────────────────┘
                                      │
                                      ▼  (Telemetry Events)
┌─────────────────────────────────────────────────────────────────────────┐
│                   3. CLIENT EVENT BUS & UI UPDATES                      │
│                                                                         │
│  ┌────────────────────────┐  ┌────────────────────────┐  ┌────────────┐ │
│  │ 'speech-start' Event   │  │ 'volume-level' Meter   │  │ 'message'  │ │
│  │ Status: "Speaking..."  │  │ Modulates 5-Bar Waves  │  │ Transcripts│ │
│  │ Animates Teal Waves    │  │ (0.0 to 1.0 amplitude) │  │ Tool Calls │ │
│  └────────────────────────┘  └────────────────────────┘  └────────────┘ │
│                                                                         │
│        Call Controls: [ Mic Mute Toggle ]  [ ■ End Call ]               │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 2. The Clue Deconstructed: Vapi Web Deployment Paths

### 2.1 Option A: HTML Script Tag Embed (`@vapi-ai/html-script-tag`)

The simplest deployment method, suitable for static sites, CMS platforms, and rapid prototyping in Bolt.new:

```html
<!-- Place inside <head> or right before </body> in index.html -->
<script>
  (function (d, t) {
    var g = document.createElement(t),
      s = d.getElementsByTagName(t)[0];
    g.src = "https://cdn.jsdelivr.net/gh/VapiAI/html-script-tag@latest/dist/assets/index.js";
    g.defer = true;
    g.async = true;
    s.parentNode.insertBefore(g, s);

    g.onload = function () {
      const vapi = window.vapiSDK.run({
        apiKey: "YOUR_VAPI_PUBLIC_KEY", // Public key from Vapi Dashboard
        assistant: "YOUR_ASSISTANT_ID", // Assistant ID or inline config
        config: {
          position: "bottom-left",
          offset: "32px",
          width: "56px",
          height: "56px",
          idle: {
            color: "rgb(47, 230, 192)",
            type: "pill",
            title: "Talk with Support",
            subtitle: "Click to start",
            icon: "https://unpkg.com/lucide-static@0.321.0/icons/phone.svg"
          },
          loading: {
            color: "rgb(56, 189, 248)",
            type: "pill",
            title: "Connecting...",
            subtitle: "Please wait",
            icon: "https://unpkg.com/lucide-static@0.321.0/icons/loader-2.svg"
          },
          active: {
            color: "rgb(239, 68, 68)",
            type: "pill",
            title: "Assistant Speaking...",
            subtitle: "End the call",
            icon: "https://unpkg.com/lucide-static@0.321.0/icons/phone-off.svg"
          }
        }
      });
    };
  })(document, "script");
</script>
```

#### CSS Customization Classes
The script tag injects classes that can be targeted for brand overrides:
- `.vapi-btn`: Base container button.
- `.vapi-btn-is-idle`: Resting disconnected state.
- `.vapi-btn-is-loading`: WebRTC negotiation / connecting state.
- `.vapi-btn-is-active`: Call established and streaming.
- `.vapi-btn-is-speaking`: Assistant voice synthesis actively outputting audio.
- `.vapi-btn-pill` / `.vapi-btn-round`: Widget geometry variants.

---

### 2.2 Option B: Modern Web SDK (`@vapi-ai/web`)

For full custom UI widgets—such as the floating card widget seen in the challenge submission video—developers use the `@vapi-ai/web` SDK.

```bash
npm install @vapi-ai/web
```

#### Reusable React Hook (`useVapi.ts`):
```typescript
import { useState, useEffect, useRef } from 'react';
import Vapi from '@vapi-ai/web';

export type CallStatus = 'idle' | 'loading' | 'active' | 'speaking' | 'muted';

export function useVapi(publicKey: string, assistantId: string) {
  const vapiRef = useRef<Vapi | null>(null);
  const [status, setStatus] = useState<CallStatus>('idle');
  const [volume, setVolume] = useState<number>(0);
  const [messages, setMessages] = useState<Array<{ role: string; text: string }>>([]);

  useEffect(() => {
    const vapi = new Vapi(publicKey);
    vapiRef.current = vapi;

    vapi.on('call-start', () => setStatus('active'));
    vapi.on('call-end', () => {
      setStatus('idle');
      setVolume(0);
    });
    vapi.on('speech-start', () => setStatus('speaking'));
    vapi.on('speech-end', () => setStatus('active'));
    vapi.on('volume-level', (lvl: number) => setVolume(lvl));
    vapi.on('message', (msg: any) => {
      if (msg.type === 'transcript' && msg.transcriptType === 'final') {
        setMessages((prev) => [...prev, { role: msg.role, text: msg.transcript }]);
      }
    });

    return () => {
      vapi.stop();
    };
  }, [publicKey]);

  const startCall = async () => {
    setStatus('loading');
    await vapiRef.current?.start(assistantId);
  };

  const endCall = () => {
    vapiRef.current?.stop();
  };

  const toggleMute = (muted: boolean) => {
    vapiRef.current?.setMuted(muted);
    if (muted) setStatus('muted');
    else setStatus('active');
  };

  return { status, volume, messages, startCall, endCall, toggleMute };
}
```

---

## 3. Engineering the Audio-Reactive Equalizer

As shown in the challenge proof video, when the assistant speaks, the widget displays an animated 5-bar teal equalizer (`|||||`) centered in the card canvas.

### Equalizer Math & Web Audio Pipeline
1. **Amplitude Mapping**: Convert Vapi's normalized volume telemetry (`0.0` to `1.0`) into discrete bar height percentages.
2. **Gaussian Stagger**: To create organic audio wave shapes, apply central bell-curve weight multipliers across the 5 bars (`[0.4, 0.75, 1.0, 0.75, 0.4]`).
3. **Smooth Damping**: Use linear interpolation (`lerp`) to prevent abrupt jumps between audio frames:
   $$\text{height}_i = \text{lerp}(\text{currentHeight}_i, \text{targetHeight}_i, 0.25)$$

```javascript
function updateEqualizer(volume, barsElements) {
  const weights = [0.45, 0.75, 1.0, 0.8, 0.5];
  barsElements.forEach((bar, idx) => {
    // Base resting height 12px, peak height 72px
    const minHeight = 12;
    const maxHeight = 72;
    const targetHeight = minHeight + (maxHeight - minHeight) * volume * weights[idx];
    bar.style.height = `${Math.max(minHeight, Math.round(targetHeight))}px`;
  });
}
```

---

## 4. Bolt.new Integration Playbook

When building or deploying on Bolt.new:
1. **Public vs Private Keys**: Always use the Vapi **Public API Key** in frontend browser client code. Never expose your Private Organization API Key in client bundles.
2. **Microphone Permissions Handling**: Ensure clean UX when the browser prompts for `navigator.mediaDevices.getUserMedia`. Display a friendly helper modal if permission is blocked.
3. **AudioContext Autoplay Policies**: Browsers require a direct user interaction (e.g. clicking the "Start Call" button) before activating an `AudioContext`. Do not trigger `.start()` on page load without a user gesture.

---

## 5. Automated Verification Checklist

| # | Test Verification Item | Expected Behavior |
|---|------------------------|-------------------|
| 1 | Vapi Web SDK / Script Tag | Script loads asynchronously without blocking page render |
| 2 | Microphone Permissions | Triggers browser media stream prompt on user click |
| 3 | WebRTC Audio Connection | Handshake completes under 1200ms; status moves to `active` |
| 4 | Equalizer Reactivity | Bar heights modulate proportionally to audio amplitude |
| 5 | Speech State Transition | Switches cleanly between `Speaking...` and `Listening...` |
| 6 | Microphone Mute Toggle | Halts outgoing audio without terminating WebRTC connection |
| 7 | End Call Button | Disconnects peer connection, resets UI to idle state |
| 8 | Mobile Viewport Safety | Widget adapts seamlessly without overflowing screen edges |
