npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@voxdiscover/voiceserver-react

v0.1.0

Published

React hooks and provider for Voxdiscover voice agents

Downloads

84

Readme

@voxdiscover/voiceserver-react

React adapter for Voice_server voice agents. Provides hooks, context provider, and automatic state management for building real-time voice AI applications.

Installation

pnpm add @voxdiscover/voiceserver-react @voxdiscover/voiceserver @daily-co/daily-js

Or with npm/yarn:

npm install @voxdiscover/voiceserver-react @voxdiscover/voiceserver @daily-co/daily-js
yarn add @voxdiscover/voiceserver-react @voxdiscover/voiceserver @daily-co/daily-js

Quick Start

import { VoiceAgentProvider, useVoiceAgent } from '@voxdiscover/voiceserver-react';

function App() {
  return (
    <VoiceAgentProvider token="your-session-token">
      <VoiceChat />
    </VoiceAgentProvider>
  );
}

function VoiceChat() {
  const { connect, disconnect, isConnected, transcripts } = useVoiceAgent();

  return (
    <div>
      <button onClick={connect} disabled={isConnected}>
        Start Call
      </button>
      <button onClick={disconnect} disabled={!isConnected}>
        End Call
      </button>
      <div>
        {transcripts.map((t, i) => (
          <p key={i}>{t.speaker}: {t.text}</p>
        ))}
      </div>
    </div>
  );
}

Features

  • React Hooks: Single comprehensive useVoiceAgent hook with all functionality
  • Automatic State Management: Connection state, transcripts, and errors synced with React
  • TypeScript Support: Full type definitions included
  • SSR-Safe: Works with Next.js, Remix, and other SSR frameworks
  • Transcript Persistence: Automatically saves transcripts to sessionStorage
  • Retry Logic: Built-in exponential backoff with countdown UI feedback
  • Concurrent-Safe: Uses useSyncExternalStore for React 18+ concurrent features
  • Memory Leak Protection: Automatic cleanup on unmount

Usage

Provider Setup

Wrap your app (or part of it) with VoiceAgentProvider:

import { VoiceAgentProvider } from '@voxdiscover/voiceserver-react';

function App() {
  return (
    <VoiceAgentProvider
      token="your-session-token"
      baseUrl="https://voiceserver.voxdiscover.com" // Optional, defaults to https://voiceserver.voxdiscover.com
      reconnection={{
        enabled: true,
        maxAttempts: 5,
        initialDelay: 1000,
      }}
      onConnect={() => console.log('Connected!')}
      onDisconnect={() => console.log('Disconnected')}
      onError={(error) => console.error('Error:', error)}
    >
      <YourApp />
    </VoiceAgentProvider>
  );
}

useVoiceAgent Hook

Access all voice agent functionality from the hook:

import { useVoiceAgent } from '@voxdiscover/voiceserver-react';

function VoiceChat() {
  const {
    // Raw agent instance for advanced use cases
    agent,

    // Connection state
    callState, // 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'failed'
    isConnected,
    isConnecting,
    isReconnecting,

    // Transcripts and errors
    transcripts, // Array of TranscriptData
    error, // Current error, auto-cleared on success

    // Retry state
    retryState, // { isRetrying, attempt, maxAttempts, nextRetryIn }

    // Actions
    connect,
    disconnect,
    mute,
    unmute,
    retryConnect,
  } = useVoiceAgent();

  return (
    <div>
      {/* Your UI */}
    </div>
  );
}

Connection Management

function ConnectionControls() {
  const { connect, disconnect, isConnected, isConnecting } = useVoiceAgent();

  return (
    <div>
      <button
        onClick={connect}
        disabled={isConnected || isConnecting}
      >
        {isConnecting ? 'Connecting...' : 'Start Call'}
      </button>
      <button
        onClick={disconnect}
        disabled={!isConnected}
      >
        End Call
      </button>
    </div>
  );
}

Audio Controls

function AudioControls() {
  const { mute, unmute, isConnected } = useVoiceAgent();
  const [isMuted, setIsMuted] = useState(false);

  const toggleMute = () => {
    if (isMuted) {
      unmute();
      setIsMuted(false);
    } else {
      mute();
      setIsMuted(true);
    }
  };

  return (
    <button onClick={toggleMute} disabled={!isConnected}>
      {isMuted ? 'Unmute' : 'Mute'}
    </button>
  );
}

Displaying Transcripts

function TranscriptDisplay() {
  const { transcripts } = useVoiceAgent();
  const transcriptEndRef = useRef<HTMLDivElement>(null);

  // Auto-scroll to bottom
  useEffect(() => {
    transcriptEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [transcripts]);

  return (
    <div className="transcript-list">
      {transcripts.map((transcript, index) => (
        <div key={index} className={transcript.speaker}>
          <strong>{transcript.speaker}:</strong> {transcript.text}
        </div>
      ))}
      <div ref={transcriptEndRef} />
    </div>
  );
}

Error Handling

function ErrorDisplay() {
  const { error, retryState, retryConnect } = useVoiceAgent();

  if (!error && !retryState.isRetrying) return null;

  return (
    <div className="error-banner">
      {error && (
        <div className="error-message">
          <strong>Error:</strong> {error.message}
        </div>
      )}
      {retryState.isRetrying && (
        <div className="retry-info">
          Retrying... attempt {retryState.attempt}/{retryState.maxAttempts}
          <br />
          Next retry in {Math.ceil(retryState.nextRetryIn / 1000)}s
          <button onClick={retryConnect}>Retry Now</button>
        </div>
      )}
    </div>
  );
}

Advanced: Direct Agent Access

For advanced use cases, access the raw VoiceAgent instance:

function AdvancedControls() {
  const { agent } = useVoiceAgent();

  const handleCustomEvent = () => {
    agent.on('custom:event', (data) => {
      console.log('Custom event:', data);
    });
  };

  return <button onClick={handleCustomEvent}>Setup Custom Listener</button>;
}

API Reference

VoiceAgentProvider Props

interface VoiceAgentProviderProps {
  children: ReactNode;
  token: string; // Session token from backend
  baseUrl?: string; // Backend URL, defaults to https://voiceserver.voxdiscover.com
  reconnection?: {
    enabled?: boolean;
    maxAttempts?: number;
    initialDelay?: number;
  };
  onConnect?: () => void;
  onDisconnect?: () => void;
  onError?: (error: Error) => void;
}

useVoiceAgent Return Type

interface UseVoiceAgentReturn {
  agent: VoiceAgent; // Raw agent instance
  callState: ConnectionState; // Current connection state
  transcripts: TranscriptData[]; // All transcripts
  error: Error | null; // Current error (auto-cleared)
  isConnected: boolean; // Derived from callState
  isConnecting: boolean; // Derived from callState
  isReconnecting: boolean; // Derived from callState
  retryState: RetryState; // Retry attempt info
  connect: () => Promise<void>; // Connect to session
  disconnect: () => Promise<void>; // Disconnect from session
  mute: () => void; // Mute microphone
  unmute: () => void; // Unmute microphone
  retryConnect: () => Promise<void>; // Manual retry
}

Types

// Connection states
type ConnectionState =
  | 'disconnected'
  | 'connecting'
  | 'connected'
  | 'reconnecting'
  | 'failed';

// Transcript data
interface TranscriptData {
  speaker: 'user' | 'agent';
  text: string;
  timestamp: number;
  isFinal: boolean;
}

// Retry state
interface RetryState {
  isRetrying: boolean;
  attempt: number;
  maxAttempts: number;
  nextRetryIn: number; // milliseconds
}

Examples

See the examples/basic-app directory for a complete working example demonstrating:

  • Connection lifecycle management
  • Audio controls (mute/unmute)
  • Real-time transcript display with auto-scroll
  • Error handling with visual feedback
  • Retry state display with countdown
  • Connection status indicators

To run the example:

cd examples/basic-app
cp .env.example .env
# Edit .env and add your session token
pnpm dev

Visit http://localhost:3001 to see the example app.

Troubleshooting

"useVoiceAgent must be used within VoiceAgentProvider"

Make sure your component using useVoiceAgent is wrapped with <VoiceAgentProvider>:

// ❌ Wrong
function App() {
  const { connect } = useVoiceAgent(); // Error!
  return <div>...</div>;
}

// ✅ Correct
function App() {
  return (
    <VoiceAgentProvider token={token}>
      <VoiceChat />
    </VoiceAgentProvider>
  );
}

function VoiceChat() {
  const { connect } = useVoiceAgent(); // Works!
  return <div>...</div>;
}

SSR Hydration Errors

The React adapter is SSR-safe by default. Storage operations use typeof window guards to prevent server-side errors.

If you still experience issues with Next.js, you can force client-side rendering:

import dynamic from 'next/dynamic';

const VoiceChat = dynamic(() => import('./VoiceChat'), { ssr: false });

Storage Quota Exceeded

Transcript persistence silently handles storage quota errors. If you hit quota limits:

  1. Transcripts will stop persisting but the app continues working
  2. Check browser console for "Failed to persist transcripts" warnings
  3. Clear sessionStorage to free up space: sessionStorage.clear()

Connection Errors

If you experience connection errors:

  1. Verify your session token is valid (not expired)
  2. Check that your backend URL is correct
  3. Ensure your backend is running and accessible
  4. Check browser console for detailed error messages

TypeScript

This package is written in TypeScript and includes full type definitions. All types are exported:

import type {
  VoiceAgentProviderProps,
  UseVoiceAgentReturn,
  RetryState,
  ConnectionState,
  TranscriptData,
  VoiceAgentConfig,
} from '@voxdiscover/voiceserver-react';

License

MIT