React voice UI · local fixture demos

Deepgram Voice Agent UI for React

Build a playback-aware Deepgram voice agent orb with the browser SDK or your existing React AgentProvider, with safe local simulations and explicit audio ownership.

Requires orb-ui@0.10.0 or later. SDK types, offline fixtures, and browser lifecycle tests have been checked. Live provider credential tests have not been run.

Connect Deepgram Voice Agent to an audio-reactive orb without adding a second microphone or speaker pipeline. Use createDeepgramAdapter for browser SDK resources, or createDeepgramReactAdapter inside an existing AgentProvider.

Try the local simulation

This example uses simulated events and synthetic volume levels. It requests no microphone access, creates no Deepgram session, and spends no API credits. Start, interrupt, stop, and retry to explore the state transitions before connecting your own application.

Install and choose an audio owner

bash
pnpm add orb-ui@0.10.0 @deepgram/agents@0.1.2
# For an existing React provider:
pnpm add @deepgram/react@0.2.0

The SDK versions were checked on October 6, 2026. They are application dependencies; orb-ui imports no Deepgram runtime. The examples below are optional live integrations using your application's backend and account. The public simulation above never calls a token endpoint.

ApplicationAdapterAudio ownership
Browser SDK in React or another frameworkcreateDeepgramAdapterAdapter manages the injected session, microphone, and player
Existing AgentProvidercreateDeepgramReactAdapterDeepgram provider manages audio; adapter observes its hooks

Browser SDK: complete React component

Your own authenticated backend must issue a short-lived Deepgram grant token for an authorized caller. Set Cache-Control: no-store, constrain the caller's access and usage, and keep the long-lived API key server-only. Never put it in a VITE_* or NEXT_PUBLIC_* variable. Do not publish an anonymous token service that pays for other visitors' calls.

tsx
import { useEffect, useMemo } from 'react'
import { AgentMicrophone, AgentPlayer, AgentSession } from '@deepgram/agents'
import { Orb } from 'orb-ui'
import { createDeepgramAdapter } from 'orb-ui/adapters'

export function DeepgramVoice({ agentId }: { agentId: string }) {
  const adapter = useMemo(
    () =>
      createDeepgramAdapter({
        createResources() {
          const session = new AgentSession({
            auth: {
              tokenFactory: async () => {
                const response = await fetch('/api/deepgram-token', {
                  method: 'POST',
                  credentials: 'same-origin',
                  cache: 'no-store',
                })
                if (!response.ok) throw new Error('Your application could not authorize this call.')
                const { access_token } = await response.json()
                if (typeof access_token !== 'string') throw new Error('Invalid token response.')
                return access_token
              },
            },
            agent: agentId,
            audio: {
              input: { encoding: 'linear16', sampleRate: 16_000 },
              output: { encoding: 'linear16', sampleRate: 24_000 },
            },
            reconnect: { enabled: true, maxAttempts: 3 },
          })
          const microphone = new AgentMicrophone((chunk) => session.sendAudio(chunk), {
            sampleRate: 16_000,
            echoCancellation: true,
            noiseSuppression: true,
          })
          const player = new AgentPlayer({ sampleRate: 24_000 })
          return { session, microphone, player }
        },
      }),
    [agentId],
  )

  useEffect(() => () => adapter.stop(), [adapter])

  return (
    <Orb
      adapter={adapter}
      theme="circle"
      aria-label="Start or stop your Deepgram voice assistant"
    />
  )
}

start() opens the microphone from the user gesture and connects the injected session. It resolves after both microphone capture and SettingsApplied. The matching 16 kHz input and 24 kHz linear16 output keep the microphone, session, and PCM player in agreement. Each retry creates fresh resources. stop() and the last unsubscribe remove listeners, release capture, discard queued playback, and disconnect; late permission results are released too.

Existing React provider: observe hooks

Keep your existing AgentProvider. This component reads useAgentContext() through a ref so state and meter callbacks stay current. Pass provider errors into error; the provider owns shutdown on its own unmount. An explicit component cleanup also stops an active call when the orb is removed while the provider remains mounted.

tsx
import { useEffect, useMemo, useRef, useState } from 'react'
import { AgentProvider, useAgentContext } from '@deepgram/react'
import { Orb } from 'orb-ui'
import { createDeepgramReactAdapter } from 'orb-ui/adapters'

function ProviderOrb({ error }: { error?: unknown }) {
  const context = useAgentContext()
  const latest = useRef({ ...context, error })
  latest.current = { ...context, error }
  const adapter = useMemo(
    () =>
      createDeepgramReactAdapter({
        getSnapshot: () => latest.current,
        start: () => latest.current.start(),
        stop: () => latest.current.stop(),
      }),
    [],
  )
  useEffect(() => () => adapter.stop?.(), [adapter])
  return <Orb adapter={adapter} theme="circle" aria-label="Start or stop Deepgram" />
}

export function ExistingDeepgramProvider({ agentId }: { agentId: string }) {
  const [error, setError] = useState<unknown>()
  const config = useMemo(
    () => ({
      agent: agentId,
      auth: {
        tokenFactory: async () => {
          setError(undefined)
          const response = await fetch('/api/deepgram-token', {
            method: 'POST',
            credentials: 'same-origin',
            cache: 'no-store',
          })
          if (!response.ok) throw new Error('Call authorization failed.')
          const { access_token } = await response.json()
          if (typeof access_token !== 'string') throw new Error('Invalid token response.')
          return access_token
        },
      },
    }),
    [agentId],
  )
  return (
    <AgentProvider config={config} autoStart={false} onError={setError} onSdkError={setError}>
      <ProviderOrb error={error} />
    </AgentProvider>
  )
}

This observer polls existing SDK meters; it never captures, queues, or disposes provider audio. Do not use the managed browser adapter for the same provider session.

State mapping and interruptions

SignalOrb state
Start, connection attempt, automatic reconnectconnecting, with cleared meters
Settings applied and microphone readylistening
Agent thinking or sending output before measured playbackthinking
Measured output from the SDK playerspeaking
Agent audio done while playback remains queuedspeaking until the queue drains
User starts speakingPlayer interrupted; listening, output meter cleared
Permission, agent, terminal connection, or startup failureerror
Stop or managed-adapter unmountidle

AgentStartedSpeaking indicates audio generation, so it cannot establish audible output by itself. The browser adapter starts speaking after a player sample and waits for its playback queue to drain. The React bridge combines a measured sample with Deepgram's playback-aware mode; muted output clears the speaking display. Input metering remains active during assistant speech for full-duplex interactions.

Verify in your application

Synthetic event tests and real SDK type fixtures cover these mappings. Credentialed Deepgram sessions were not run. Test HTTPS microphone permission, denied permission, browser audio unlock, disconnect/reconnect, barge-in, stop during authorization, and route unmount in your own application before shipping. The current SDK player does not expose an audio-context resume failure event: confirm audible output on your target browsers and expose an application retry control if autoplay is blocked. A queued buffer alone never makes this adapter show speaking.

For inline agent settings, Deepgram can restore conversation context on reconnect. An agent UUID cannot carry that same restored context; choose the form that fits your continuity requirements. The adapter observes the SDK's reconnect behavior rather than retrying credentials itself.

Sources: Browser SDK overview, JavaScript SDK, React hooks and provider.