@voxdiscover/voiceserver-react
v0.1.0
Published
React hooks and provider for Voxdiscover voice agents
Downloads
84
Maintainers
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-jsOr with npm/yarn:
npm install @voxdiscover/voiceserver-react @voxdiscover/voiceserver @daily-co/daily-js
yarn add @voxdiscover/voiceserver-react @voxdiscover/voiceserver @daily-co/daily-jsQuick 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
useVoiceAgenthook 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
useSyncExternalStorefor 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 devVisit 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:
- Transcripts will stop persisting but the app continues working
- Check browser console for "Failed to persist transcripts" warnings
- Clear sessionStorage to free up space:
sessionStorage.clear()
Connection Errors
If you experience connection errors:
- Verify your session token is valid (not expired)
- Check that your backend URL is correct
- Ensure your backend is running and accessible
- 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
