Basic Voice Chat Component

Here’s a complete example of a React component that creates a voice conversation using the useConversation hook:
import React, { useState } from "react";
import { type SessionConfig } from "@outspeed/client";
import { useConversation } from "@outspeed/react";

const getEphemeralKeyFromServer = async (config: SessionConfig) => {
  const tokenResponse = await fetch("/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(config),
  });

  const data = await tokenResponse.json();
  if (!tokenResponse.ok) {
    throw new Error("Failed to get ephemeral key");
  }

  return data.client_secret.value;
};

const sessionConfig: SessionConfig = {
  model: "outspeed-v1",
  instructions: "You are a helpful but witty assistant named Alfred.",
  voice: "david", // see the voices page for all available voices
  turn_detection: {
    type: "semantic_vad",
  },
  first_message: "Hello, how can I assist you with Outspeed today?",
};

export default function VoiceChat() {
  const [sessionCreated, setSessionCreated] = useState(false);
  const [isConnecting, setIsConnecting] = useState(false);

  const conversation = useConversation({
    sessionConfig: sessionConfig,
  });

  const startSession = async () => {
    try {
      setIsConnecting(true);
      const ephemeralKey = await getEphemeralKeyFromServer(sessionConfig);
      await conversation.startSession(ephemeralKey);

      // Listen for session creation event
      conversation.on("session.created", (event) => {
        console.log("Session created:", event);
        setSessionCreated(true);
        setIsConnecting(false);
      });
    } catch (error) {
      console.error("Error starting session:", error);
      setIsConnecting(false);
    }
  };

  const endSession = async () => {
    try {
      await conversation.endSession();
      setSessionCreated(false);
    } catch (error) {
      console.error("Error ending session:", error);
    }
  };

  if (isConnecting) {
    return (
      <div className="voice-chat">
        <h2>Connecting...</h2>
        <p>Please wait while we establish the connection.</p>
      </div>
    );
  }

  if (sessionCreated) {
    return (
      <div className="voice-chat">
        <h2>🎙️ Voice Chat Active</h2>
        <p>You can now speak with the AI assistant!</p>
        <button onClick={endSession} className="end-button">
          End Session
        </button>
      </div>
    );
  }

  return (
    <div className="voice-chat">
      <h2>Voice AI Assistant</h2>
      <p>Click the button below to start a voice conversation.</p>
      <button onClick={startSession} className="start-button">
        Start Voice Chat
      </button>
    </div>
  );
}
Want to use a different voice? See all available voices you can choose from.

Connection Management

onDisconnect Callback

Use onDisconnect to handle cleanup when the conversation ends:
const conversation = useConversation({
  onDisconnect: () => {
    console.log("Disconnected! cleaning up...");
    setSessionCreated(false);
    // Add any cleanup logic here
  },
});
Important: Use onDisconnect for cleanup logic, not onError. The onError callback is called for runtime errors as well, so it’s not ideal for cleanup tasks.

onError Callback

Use onError specifically for handling errors:
const conversation = useConversation({
  onError: (err) => {
    console.error("Conversation error:", err);
    // Handle error display/logging only
  },
  onDisconnect: () => {
    console.log("Session ended");
    setSessionCreated(false);
    // Handle cleanup here instead
  },
});

Advanced Features

Event Handling

You can listen to various events during the conversation:
// Listen for speech detection
conversation.on("input_audio_buffer.speech_started", () => {
  console.log("User started speaking");
});

conversation.on("input_audio_buffer.speech_stopped", () => {
  console.log("User stopped speaking");
});

// Listen for AI responses
conversation.on("response.text.delta", (event) => {
  console.log("AI response text:", event.delta);
});

conversation.on("response.audio_transcript.delta", (event) => {
  console.log("AI speech transcript:", event.delta);
});

Text Input

You can also send text messages programmatically:
const sendTextMessage = () => {
  conversation.sendText("Tell me about the weather today");
};

return (
  <div>
    {/* ... other components */}
    <button onClick={sendTextMessage}>Send Text Message</button>
  </div>
);

Mute Control

Control the microphone state by passing micMuted prop to useConversation:
const [isMicMuted, setIsMicMuted] = useState(false);

const conversation = useConversation({
  micMuted: isMicMuted,
});

const toggleMute = () => {
  setIsMicMuted(!isMicMuted);
};

return (
  <div>
    {/* ... other components */}
    <button onClick={toggleMute}>{isMicMuted ? "🔇 Unmute" : "🎤 Mute"}</button>
  </div>
);

Volume Control

Control the AI’s voice volume by passing volume prop to useConversation (value between 0 and 1):
const [volume, setVolume] = useState(0.8);

const conversation = useConversation({
  volume: volume,
});

return (
  <div>
    {/* ... other components */}
    <label>
      Volume: {Math.round(volume * 100)}%
      <input
        type="range"
        min="0"
        max="1"
        step="0.1"
        value={volume}
        onChange={(e) => setVolume(parseFloat(e.target.value))}
      />
    </label>
  </div>
);

Complete Example with Error Handling

Here’s a more complete example showing proper error and disconnect handling:
const [error, setError] = useState<string | null>(null);
const [sessionCreated, setSessionCreated] = useState(false);

const conversation = useConversation({
  onError: (err) => {
    console.error("Conversation error:", err);
    setError(err.message);
    // Only handle error display here
  },
  onDisconnect: () => {
    console.log("Session ended");
    setSessionCreated(false);
    setError(null); // Clear any previous errors
    // Handle all cleanup logic here
  },
});

// Display error to user
if (error) {
  return (
    <div className="error-state">
      <h2>❌ Error</h2>
      <p>{error}</p>
      <button onClick={() => setError(null)}>Try Again</button>
    </div>
  );
}

Next Steps