Repository navigation
feat: add realtime chat support with OpenAI and ElevenLabs adapters #300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
22d4737
Realtime chat basically working
jherr 71ad066
Realtime chat basically working
jherr 5ad2d2c
more updates
jherr f91425c
Merge remote-tracking branch 'origin/main' into realtime-chat
jherr 41a5e97
chore: update pnpm-lock.yaml and latest realtime chat changes
jherr aa48ba2
ci: apply automated fixes
autofix-ci[bot] 18ce05c
Merge remote-tracking branch 'origin/main' into realtime-chat
jherr 008a824
Merge remote-tracking branch 'origin/main' into realtime-chat
jherr e9fe67b
Merge remote-tracking branch 'origin/main' into realtime-chat
jherr a611ad4
Merge remote-tracking branch 'origin/main' into realtime-chat
jherr c7c3520
chore: merge origin/main into pr-300
jherr 8bc5a93
API improved
jherr dc7c45f
API further improved
jherr ddca0d6
API further improved
jherr 4025e87
ci: apply automated fixes
autofix-ci[bot] a5f9879
Updating the docs
jherr 2d8990e
text chat
jherr 26cd4bd
Merge branch 'realtime-chat' of github.com:TanStack/ai into realtime-…
jherr 62301f6
ci: apply automated fixes
autofix-ci[bot] 14fed75
Merge branch 'main' into realtime-chat
jherr 2ca3efc
Getting close to releasable
jherr d0233b5
updating changeset
jherr 1b4e0f1
ci: apply automated fixes
autofix-ci[bot] f4e62ac
Realtime chat basically working
jherr 929b4a8
Realtime chat basically working
jherr 882c2be
more updates
jherr 5298fa8
chore: update pnpm-lock.yaml and latest realtime chat changes
jherr dbb9cab
ci: apply automated fixes
autofix-ci[bot] 8dbdddf
API improved
jherr 44d0740
API further improved
jherr 0165d97
API further improved
jherr 1f5eaab
Updating the docs
jherr 98831f8
text chat
jherr 88a308f
ci: apply automated fixes
autofix-ci[bot] bd8f479
ci: apply automated fixes
autofix-ci[bot] 3580092
Getting close to releasable
jherr 5ff0f68
updating changeset
jherr 3d6ce16
Resolve merge conflicts in realtime adapters and lockfile
jherr 32844fb
Merge main into realtime-chat
jherr fb148cc
docs: add missing RealtimeStateChangeCallback type alias doc
jherr c7a57c6
ci: apply automated fixes
autofix-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,313 @@ | ||
| --- | ||
| title: Realtime Voice Chat Architecture | ||
| id: realtime-chat-architecture | ||
| --- | ||
|
|
||
| # Realtime Voice Chat Architecture | ||
|
|
||
| This document describes the architecture of TanStack AI's realtime voice-to-voice chat capability, which enables browser-based voice conversations with AI models. | ||
|
|
||
| ## Overview | ||
|
|
||
| The realtime chat system provides a vendor-neutral, type-safe abstraction for voice-to-voice AI interactions. It currently supports: | ||
|
|
||
| - **OpenAI Realtime API** - WebRTC-based connection with GPT-4o realtime models | ||
| - **ElevenLabs Conversational AI** - SDK-based connection for voice conversations | ||
|
|
||
| ## Architecture Layers | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ React Application │ | ||
| │ ┌─────────────────────────────────────────────────────────┐ │ | ||
| │ │ useRealtimeChat() │ │ | ||
| │ │ - Connection state (status, mode) │ │ | ||
| │ │ - Messages & transcripts │ │ | ||
| │ │ - Audio visualization (levels, waveforms) │ │ | ||
| │ │ - Control methods (connect, disconnect, interrupt) │ │ | ||
| │ └─────────────────────────────────────────────────────────┘ │ | ||
| └─────────────────────────────────────────────────────────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ @tanstack/ai-client │ | ||
| │ ┌─────────────────────────────────────────────────────────┐ │ | ||
| │ │ RealtimeClient │ │ | ||
| │ │ - Connection lifecycle management │ │ | ||
| │ │ - Token refresh scheduling │ │ | ||
| │ │ - Event subscription & dispatch │ │ | ||
| │ │ - Tool execution coordination │ │ | ||
| │ └─────────────────────────────────────────────────────────┘ │ | ||
| └─────────────────────────────────────────────────────────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ Provider Adapters │ | ||
| │ ┌──────────────────────┐ ┌──────────────────────┐ │ | ||
| │ │ openaiRealtime() │ │ elevenlabsRealtime() │ │ | ||
| │ │ - WebRTC connection │ │ - SDK wrapper │ │ | ||
| │ │ - Audio I/O │ │ - Signed URL auth │ │ | ||
| │ │ - Event mapping │ │ - Event mapping │ │ | ||
| │ └──────────────────────┘ └──────────────────────┘ │ | ||
| └─────────────────────────────────────────────────────────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ Server-Side │ | ||
| │ ┌─────────────────────────────────────────────────────────┐ │ | ||
| │ │ Token Generation Endpoint │ │ | ||
| │ │ - openaiRealtimeToken() - ephemeral client secrets │ │ | ||
| │ │ - elevenlabsRealtimeToken() - signed URLs │ │ | ||
| │ └─────────────────────────────────────────────────────────┘ │ | ||
| └─────────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Key Components | ||
|
|
||
| ### 1. Token Adapters (Server-Side) | ||
|
|
||
| Token adapters generate short-lived credentials for client-side connections. This keeps API keys secure on the server. | ||
|
|
||
| ```typescript | ||
| // Server-side token endpoint | ||
| import { realtimeToken } from '@tanstack/ai' | ||
| import { openaiRealtimeToken } from '@tanstack/ai-openai' | ||
|
|
||
| const token = await realtimeToken({ | ||
| adapter: openaiRealtimeToken({ | ||
| model: 'gpt-4o-realtime-preview', | ||
| voice: 'alloy', | ||
| instructions: 'You are a helpful assistant.', | ||
| turnDetection: { | ||
| type: 'server_vad', | ||
| threshold: 0.5, | ||
| silence_duration_ms: 500, | ||
| }, | ||
| }), | ||
| }) | ||
| ``` | ||
|
|
||
| **Token Structure:** | ||
| ```typescript | ||
| interface RealtimeToken { | ||
| provider: string // 'openai' | 'elevenlabs' | ||
| token: string // Ephemeral token or signed URL | ||
| expiresAt: number // Expiration timestamp (ms) | ||
| config: RealtimeSessionConfig // Session configuration | ||
| } | ||
| ``` | ||
|
|
||
| ### 2. Client Adapters (Browser-Side) | ||
|
|
||
| Client adapters handle the actual connection to provider APIs, managing: | ||
| - WebRTC or WebSocket connections | ||
| - Audio capture and playback | ||
| - Event translation to common format | ||
| - Audio visualization data | ||
|
|
||
| ```typescript | ||
| // Client-side adapter usage | ||
| import { openaiRealtime } from '@tanstack/ai-openai' | ||
|
|
||
| const adapter = openaiRealtime({ | ||
| connectionMode: 'webrtc', // default | ||
| }) | ||
| ``` | ||
|
|
||
| ### 3. RealtimeClient | ||
|
|
||
| The `RealtimeClient` class manages the connection lifecycle: | ||
|
|
||
| - **Connection Management**: Connect, disconnect, reconnect | ||
| - **Token Refresh**: Automatically refreshes tokens before expiry | ||
| - **Event Handling**: Subscribes to adapter events and dispatches to callbacks | ||
| - **State Management**: Tracks status, mode, messages, transcripts | ||
| - **Tool Execution**: Coordinates client-side tool calls | ||
|
|
||
| ### 4. useRealtimeChat Hook | ||
|
|
||
| The React hook provides a reactive interface: | ||
|
|
||
| ```typescript | ||
| const { | ||
| // Connection state | ||
| status, // 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'error' | ||
| error, | ||
| connect, | ||
| disconnect, | ||
|
|
||
| // Conversation state | ||
| mode, // 'idle' | 'listening' | 'thinking' | 'speaking' | ||
| messages, | ||
| pendingUserTranscript, | ||
| pendingAssistantTranscript, | ||
|
|
||
| // Voice control | ||
| startListening, | ||
| stopListening, | ||
| interrupt, | ||
|
|
||
| // Audio visualization | ||
| inputLevel, | ||
| outputLevel, | ||
| getInputTimeDomainData, | ||
| getOutputTimeDomainData, | ||
| } = useRealtimeChat({ | ||
| getToken: () => fetch('/api/realtime-token').then(r => r.json()), | ||
| adapter: openaiRealtime(), | ||
| }) | ||
| ``` | ||
|
|
||
| ## Connection Flow | ||
|
|
||
| ### OpenAI WebRTC Flow | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Browser | ||
| participant Server | ||
| participant OpenAI | ||
|
|
||
| Browser->>Server: POST /api/realtime-token | ||
| Server->>OpenAI: POST /v1/realtime/sessions | ||
| OpenAI-->>Server: { client_secret, expires_at } | ||
| Server-->>Browser: RealtimeToken | ||
|
|
||
| Browser->>Browser: getUserMedia() - request mic | ||
| Browser->>Browser: Create RTCPeerConnection | ||
| Browser->>Browser: Add audio track to PC | ||
| Browser->>Browser: createOffer() | ||
|
|
||
| Browser->>OpenAI: POST /v1/realtime?model=... | ||
| Note right of Browser: SDP offer + ephemeral token | ||
| OpenAI-->>Browser: SDP answer | ||
|
|
||
| Browser->>Browser: setRemoteDescription() | ||
| Note over Browser,OpenAI: WebRTC connection established | ||
|
|
||
| Browser->>OpenAI: Audio via WebRTC | ||
| OpenAI-->>Browser: Audio + events via WebRTC | ||
| ``` | ||
|
|
||
| ### ElevenLabs Flow | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Browser | ||
| participant Server | ||
| participant ElevenLabs | ||
|
|
||
| Browser->>Server: POST /api/realtime-token | ||
| Server->>ElevenLabs: POST /v1/convai/conversation/get_signed_url | ||
| ElevenLabs-->>Server: { signed_url } | ||
| Server-->>Browser: RealtimeToken | ||
|
|
||
| Browser->>ElevenLabs: Conversation.startSession(signedUrl) | ||
| Note over Browser,ElevenLabs: SDK handles WebSocket/WebRTC | ||
|
|
||
| Browser->>ElevenLabs: Audio via SDK | ||
| ElevenLabs-->>Browser: Audio + events via SDK | ||
| ``` | ||
|
|
||
| ## Audio Visualization | ||
|
|
||
| The system provides real-time audio visualization through the `AudioVisualization` interface: | ||
|
|
||
| ```typescript | ||
| interface AudioVisualization { | ||
| inputLevel: number // 0-1 normalized input volume (RMS) | ||
| outputLevel: number // 0-1 normalized output volume (RMS) | ||
| getInputFrequencyData(): Uint8Array // FFT frequency bins | ||
| getOutputFrequencyData(): Uint8Array | ||
| getInputTimeDomainData(): Uint8Array // Raw waveform samples | ||
| getOutputTimeDomainData(): Uint8Array | ||
| inputSampleRate: number | ||
| outputSampleRate: number | ||
| } | ||
| ``` | ||
|
|
||
| The OpenAI adapter uses Web Audio API `AnalyserNode` for visualization: | ||
| - `fftSize: 2048` for high-resolution analysis | ||
| - RMS (Root Mean Square) calculation for accurate volume levels | ||
| - Separate analysers for input (microphone) and output (AI voice) | ||
|
|
||
| ## Event System | ||
|
|
||
| Adapters emit standardized events: | ||
|
|
||
| | Event | Payload | Description | | ||
| |-------|---------|-------------| | ||
| | `status_change` | `{ status }` | Connection status changed | | ||
| | `mode_change` | `{ mode }` | Conversation mode changed | | ||
| | `transcript` | `{ role, transcript, isFinal }` | Speech-to-text update | | ||
| | `message_complete` | `{ message }` | Full message received | | ||
| | `tool_call` | `{ toolCallId, toolName, input }` | Tool invocation requested | | ||
| | `interrupted` | `{ messageId? }` | Response was interrupted | | ||
| | `error` | `{ error }` | Error occurred | | ||
|
|
||
| ## Current Status | ||
|
|
||
| ### Implemented Features | ||
|
|
||
| - [x] OpenAI Realtime API integration (WebRTC) | ||
| - [x] ElevenLabs Conversational AI integration | ||
| - [x] Token generation and refresh | ||
| - [x] Audio capture and playback | ||
| - [x] Real-time transcription display | ||
| - [x] Audio visualization (levels, waveforms) | ||
| - [x] Interrupt capability | ||
| - [x] React hook (`useRealtimeChat`) | ||
| - [x] Demo application at `/realtime` route | ||
|
|
||
| ### Known Limitations | ||
|
|
||
| - **Device Selection**: Currently uses system default audio devices. Custom device selection not yet implemented. | ||
| - **ElevenLabs SDK**: Using `@11labs/client@0.2.0` which has limited TypeScript support. | ||
| - **Push-to-Talk**: Manual VAD mode implemented but not exposed in demo UI. | ||
| - **Tool Calling**: Framework supports tools but demo doesn't showcase them. | ||
|
|
||
| ### Demo Application | ||
|
|
||
| The `examples/ts-react-chat` application includes a realtime voice chat demo at the `/realtime` route: | ||
|
|
||
| **Features:** | ||
| - Provider selection (OpenAI / ElevenLabs) | ||
| - Connection status indicator | ||
| - Conversation mode indicator (Listening/Thinking/Speaking) | ||
| - Message history with transcripts | ||
| - Audio level meters | ||
| - Waveform visualization (debug mode) | ||
| - Interrupt button during AI speech | ||
|
|
||
| **Required Environment Variables:** | ||
| ```bash | ||
| OPENAI_API_KEY=sk-... | ||
| ELEVENLABS_API_KEY=xi-... # Optional, for ElevenLabs | ||
| ELEVENLABS_AGENT_ID=... # Optional, for ElevenLabs | ||
| ``` | ||
|
|
||
| ## Files Reference | ||
|
|
||
| ### Core Types | ||
| - `packages/typescript/ai/src/realtime/types.ts` - Core type definitions | ||
| - `packages/typescript/ai-client/src/realtime-types.ts` - Client-side types | ||
|
|
||
| ### Token Generation (Server) | ||
| - `packages/typescript/ai/src/realtime/index.ts` - `realtimeToken()` function | ||
| - `packages/typescript/ai-openai/src/realtime/token.ts` - OpenAI token adapter | ||
| - `packages/typescript/ai-elevenlabs/src/realtime/token.ts` - ElevenLabs token adapter | ||
|
|
||
| ### Client Adapters | ||
| - `packages/typescript/ai-openai/src/realtime/adapter.ts` - OpenAI WebRTC adapter | ||
| - `packages/typescript/ai-elevenlabs/src/realtime/adapter.ts` - ElevenLabs SDK adapter | ||
|
|
||
| ### Client Library | ||
| - `packages/typescript/ai-client/src/realtime-client.ts` - RealtimeClient class | ||
|
|
||
| ### React Integration | ||
| - `packages/typescript/ai-react/src/use-realtime-chat.ts` - React hook | ||
| - `packages/typescript/ai-react/src/realtime-types.ts` - Hook types | ||
|
|
||
| ### Demo Application | ||
| - `examples/ts-react-chat/src/routes/realtime.tsx` - Demo UI component | ||
| - `examples/ts-react-chat/src/routes/api.realtime-token.ts` - Token API endpoint | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,11 @@ | ||
| # OpenAI API Key | ||
| # Get yours at: https://platform.openai.com/api-keys | ||
| OPENAI_API_KEY=sk-... | ||
| OPENAI_API_KEY=sk-... | ||
|
|
||
| # ElevenLabs API Key (for realtime voice) | ||
| # Get yours at: https://elevenlabs.io/app/settings/api-keys | ||
| ELEVENLABS_API_KEY=xi-... | ||
|
|
||
| # ElevenLabs Agent ID (for realtime voice) | ||
| # Create an agent at: https://elevenlabs.io/app/conversational-ai | ||
| ELEVENLABS_AGENT_ID=... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The event table is missing
audio_chunk.RealtimeEventincludesaudio_chunk, so this table no longer matches the public contract and will mislead adapter authors or consumers reading the docs.🤖 Prompt for AI Agents