Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,22 +135,33 @@ configure({ apiKey: 'YOUR_DEEPGRAM_API_KEY' });
| `useDeepgramTextIntelligence` | Text analysis (summaries, topics, intents, sentiment) |
| `useDeepgramManagement` | Typed wrapper around the Management REST API |

> 💡 **Pro tip**: All hooks now export a `state` object (and other reactive values) so you can easily track connection status, errors, and transcripts without maintaining your own state.

---

## Voice Agent (`useDeepgramVoiceAgent`)

`useDeepgramVoiceAgent` connects to `wss://agent.deepgram.com/v1/agent/converse`, captures microphone audio, and optionally auto-plays the agent's streamed responses. It wraps the full Voice Agent messaging surface so you can react to conversation updates, function calls, warnings, and raw PCM audio.

> 🔊 **Audio Handling**: This hook uses `AVAudioEngine` on iOS for hardware-accelerated echo cancellation, ensuring the agent doesn't hear itself speak. It also manages the audio session automatically.

### Quick start

```tsx
const {
connect,
disconnect,
state, // { connectionState, error, warning }
agentStatus, // { thinking, latency }
conversation, // Array<{ role, content }>
injectUserMessage,
sendFunctionCallResponse,
updatePrompt,
} = useDeepgramVoiceAgent({
trackState: true, // Enable reactive state tracking
trackConversation: true, // Enable conversation history tracking
trackAgentStatus: true, // Enable agent status tracking
autoPlayAudio: true, // Automatically play agent audio
defaultSettings: {
audio: {
input: { encoding: 'linear16', sample_rate: 24_000 },
Expand Down Expand Up @@ -207,6 +218,7 @@ const rePrompt = () => {

return (
<>
<Text>Status: {state.connectionState}</Text>
<Button title="Start agent" onPress={begin} />
<Button title="Ask" onPress={askQuestion} />
<Button title="Send tool output" onPress={provideTooling} />
Expand All @@ -227,6 +239,10 @@ return (
| `endpoint` | `string` | WebSocket endpoint used for the agent conversation (defaults to `wss://agent.deepgram.com/v1/agent/converse`). |
| `defaultSettings` | `DeepgramVoiceAgentSettings` | Base `Settings` payload sent on connect; merge per-call overrides via `connect(override)`. |
| `autoStartMicrophone` | `boolean` | Automatically requests mic access and starts streaming PCM when `true` (default). |
| `autoPlayAudio` | `boolean` | Automatically plays received audio using the native player (default: `true`). |
| `trackState` | `boolean` | Enable reactive state tracking (connection, errors, warnings) via the `state` return value (default: `false`). |
| `trackConversation` | `boolean` | Enable conversation history tracking via the `conversation` return value (default: `false`). |
| `trackAgentStatus` | `boolean` | Enable agent status tracking (thinking, latency) via the `agentStatus` return value (default: `false`). |
| `downsampleFactor` | `number` | Manually override the downsample ratio applied to captured audio (defaults to a heuristic based on the requested sample rate). |

#### Callbacks
Expand Down Expand Up @@ -268,6 +284,10 @@ return (
| `updatePrompt` | `(prompt: string) => boolean` | Replaces the active system prompt. |
| `sendMedia` | `(chunk: ArrayBuffer \| Uint8Array \| number[]) => boolean` | Streams additional PCM audio to the agent (e.g., pre-recorded buffers). |
| `isConnected` | `() => boolean` | Returns `true` when the socket is open. |
| `clearConversation` | `() => void` | Clears the internal conversation history. |
| `state` | `DeepgramVoiceAgentState` | Reactive state object (requires `trackState: true`). |
| `conversation` | `DeepgramVoiceAgentConversationMessage[]` | Reactive conversation history (requires `trackConversation: true`). |
| `agentStatus` | `DeepgramVoiceAgentStatus` | Reactive agent status (requires `trackAgentStatus: true`). |

#### Settings payload (`DeepgramVoiceAgentSettings`)

Expand Down Expand Up @@ -301,7 +321,14 @@ The speech hook streams microphone audio using WebSockets and can also transcrib
### Live streaming quick start

```tsx
const { startListening, stopListening } = useDeepgramSpeechToText({
const {
startListening,
stopListening,
state, // { status, error }
transcript, // "Hello world..."
} = useDeepgramSpeechToText({
trackState: true,
trackTranscript: true,
onTranscript: console.log,
live: {
apiVersion: 'v2',
Expand All @@ -311,6 +338,7 @@ const { startListening, stopListening } = useDeepgramSpeechToText({
},
});

<Text>Transcript: {transcript}</Text>
<Button
title="Start"
onPress={() => startListening({ keywords: ['Deepgram'] })}
Expand Down Expand Up @@ -355,6 +383,8 @@ const pickFile = async () => {
| `onTranscribeError` | `(error: unknown) => void` | Fired if prerecorded transcription fails. |
| `live` | `DeepgramLiveListenOptions` | Default options merged into every live stream. |
| `prerecorded` | `DeepgramPrerecordedOptions` | Default options merged into every file transcription. |
| `trackState` | `boolean` | Enable reactive state tracking via the `state` return value (default: `false`). |
| `trackTranscript` | `boolean` | Enable reactive transcript tracking via the `transcript` return value (default: `false`). |

#### Returned methods

Expand All @@ -363,6 +393,9 @@ const pickFile = async () => {
| `startListening` | `(options?: DeepgramLiveListenOptions) => Promise<void>` | Requests mic access, starts recording, and streams audio to Deepgram. |
| `stopListening` | `() => void` | Stops recording and closes the active WebSocket. |
| `transcribeFile` | `(file: DeepgramPrerecordedSource, options?: DeepgramPrerecordedOptions) => Promise<void>` | Uploads a file/URI/URL and resolves via the success/error callbacks. |
| `state` | `DeepgramSpeechToTextState` | Reactive state object (requires `trackState: true`). |
| `transcript` | `string` | Reactive final transcript (requires `trackTranscript: true`). |
| `interimTranscript`| `string` | Reactive interim transcript (requires `trackTranscript: true`). |

#### Live transcription options (`DeepgramLiveListenOptions`)

Expand Down Expand Up @@ -487,7 +520,10 @@ const {
clearStream,
closeStreamGracefully,
stopStreaming,
state, // { status, error }
} = useDeepgramTextToSpeech({
trackState: true,
autoPlayAudio: true, // Automatically play received audio
options: {
stream: {
model: 'aura-2-asteria-en',
Expand Down Expand Up @@ -525,6 +561,8 @@ closeStreamGracefully();
| `onStreamError` | `(error: unknown) => void` | Fired when the WebSocket errors. |
| `onStreamEnd` | `() => void` | Fired when the stream closes (gracefully or otherwise). |
| `options` | `UseDeepgramTextToSpeechOptions` | Default configuration merged into HTTP and streaming requests. |
| `autoPlayAudio` | `boolean` | Automatically plays received audio using the native player (default: `true`). |
| `trackState` | `boolean` | Enable reactive state tracking via the `state` return value (default: `false`). |

#### Returned methods

Expand All @@ -538,6 +576,7 @@ closeStreamGracefully();
| `clearStream` | `() => boolean` | Clears buffered text/audio without closing the socket. |
| `closeStreamGracefully` | `() => boolean` | Asks Deepgram to finish outstanding audio then close the stream. |
| `stopStreaming` | `() => void` | Force-closes the socket and releases resources. |
| `state` | `DeepgramTextToSpeechState` | Reactive state object (requires `trackState: true`). |

#### Configuration (`UseDeepgramTextToSpeechOptions`)

Expand Down Expand Up @@ -604,7 +643,8 @@ closeStreamGracefully();
Run summarisation, topic detection, intent detection, sentiment analysis, and more over plain text or URLs.

```tsx
const { analyze } = useDeepgramTextIntelligence({
const { analyze, state } = useDeepgramTextIntelligence({
trackState: true,
onAnalyzeSuccess: (result) => console.log(result.summary),
options: {
summarize: true,
Expand Down Expand Up @@ -632,6 +672,7 @@ await analyze({ text: 'Deepgram makes voice data useful.' });
| `language` | `DeepgramTextIntelligenceLanguage` | BCP-47 language hint (defaults to `'en'`). |
| `callback` | `string` | Webhook URL invoked after processing completes. |
| `callbackMethod` | `'POST' \| 'PUT' \| (string & {})` | HTTP method used for the callback. |
| `trackState` | `boolean` | Enable reactive state tracking via the `state` return value (default: `false`). |

---

Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- boost (1.84.0)
- Deepgram (1.0.5):
- Deepgram (2.0.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1799,7 +1799,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
Deepgram: 234b0f4e42380dc2129dbc9d10533eb4019b5df0
Deepgram: 43333e852b373355fe2bec14eeaa43ba1e72f9ab
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b
Expo: 1687edb10c76b0c0f135306d6ae245379f50ed54
Expand Down
120 changes: 48 additions & 72 deletions example/src/SpeechToText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,76 +11,48 @@ import * as DocumentPicker from 'expo-document-picker';
import { useDeepgramSpeechToText } from 'react-native-deepgram';

export default function SpeechToText() {
// live stream state
const [liveTranscript, setLiveTranscript] = useState('');
const [liveStatus, setLiveStatus] = useState<'idle' | 'listening' | 'error'>(
'idle'
);
const [liveError, setLiveError] = useState<string | null>(null);
const [liveInterimTranscript, setLiveInterimTranscript] = useState('');

// file transcription state
const [fileTranscript, setFileTranscript] = useState('');
const [fileStatus, setFileStatus] = useState<
'idle' | 'transcribing' | 'error'
>('idle');
const [fileError, setFileError] = useState<string | null>(null);

const { startListening, stopListening, transcribeFile } =
useDeepgramSpeechToText({
onBeforeStart: () => {
setLiveStatus('listening');
setLiveTranscript('');
setLiveInterimTranscript('');
setLiveError(null);
},
onStart: () => {
// WebSocket opened
},
onTranscript: (text, info) => {
if (info?.isFinal) {
setLiveTranscript((prev) => {
const next = prev ? `${prev} ${text}` : text;
return next.trim();
});
setLiveInterimTranscript('');
} else {
setLiveInterimTranscript(text);
}
},
onError: (err) => {
setLiveStatus('error');
setLiveError(err instanceof Error ? err.message : String(err));
setLiveInterimTranscript('');
},
onEnd: () => {
setLiveStatus('idle');
const {
startListening,
stopListening,
state: liveState,
} = useDeepgramSpeechToText({
trackState: true,
onBeforeStart: () => {
setLiveTranscript('');
setLiveInterimTranscript('');
},
onTranscript: (text, info) => {
if (info?.isFinal) {
setLiveTranscript((prev) => {
const next = prev ? `${prev} ${text}` : text;
return next.trim();
});
setLiveInterimTranscript('');
},
// file callbacks
onBeforeTranscribe: () => {
setFileStatus('transcribing');
setFileTranscript('');
setFileError(null);
},
onTranscribeSuccess: (text) => {
setFileStatus('idle');
setFileTranscript(text);
},
onTranscribeError: (err) => {
setFileStatus('error');
setFileError(err instanceof Error ? err.message : String(err));
},
live: {
model: 'nova-2',
interimResults: true,
punctuate: true,
},
prerecorded: {
punctuate: true,
summarize: 'v2',
},
});
} else {
setLiveInterimTranscript(text);
}
},
onEnd: () => setLiveInterimTranscript(''),
live: {
model: 'nova-2',
interimResults: true,
punctuate: true,
},
});

const { transcribeFile, state: fileState } = useDeepgramSpeechToText({
trackState: true,
onBeforeTranscribe: () => setFileTranscript(''),
onTranscribeSuccess: setFileTranscript,
prerecorded: {
punctuate: true,
summarize: 'v2',
},
});

const pickAndTranscribe = async () => {
try {
Expand Down Expand Up @@ -118,18 +90,20 @@ export default function SpeechToText() {
<Button
title="Start Listening"
onPress={() => startListening({ keywords: ['Deepgram'] })}
disabled={liveStatus === 'listening'}
disabled={liveState?.status === 'listening'}
/>
<Button
title="Stop Listening"
onPress={stopListening}
disabled={liveStatus !== 'listening'}
disabled={liveState?.status !== 'listening'}
/>
</View>
{liveStatus === 'listening' && (
{liveState?.status === 'listening' && (
<Text style={styles.status}>Listening…</Text>
)}
{liveError && <Text style={styles.error}>Error: {liveError}</Text>}
{liveState?.error && (
<Text style={styles.error}>Error: {liveState.error.message}</Text>
)}
<ScrollView style={styles.outputContainer}>
<Text style={styles.transcript}>
🎤 Live: {combinedLiveTranscript || 'No live transcript yet.'}
Expand All @@ -141,13 +115,15 @@ export default function SpeechToText() {
<Button
title="Pick & Transcribe File"
onPress={pickAndTranscribe}
disabled={fileStatus === 'transcribing'}
disabled={fileState?.status === 'transcribing'}
/>
</View>
{fileStatus === 'transcribing' && (
{fileState?.status === 'transcribing' && (
<Text style={styles.status}>Transcribing file…</Text>
)}
{fileError && <Text style={styles.error}>File error: {fileError}</Text>}
{fileState?.error && (
<Text style={styles.error}>File error: {fileState.error.message}</Text>
)}
<ScrollView style={styles.outputContainer}>
<Text style={styles.transcript}>
📄 File: {fileTranscript || 'No file transcript yet.'}
Expand Down
28 changes: 10 additions & 18 deletions example/src/TextIntelligence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ import { useDeepgramTextIntelligence } from 'react-native-deepgram';
export default function TextIntelligence() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);

const { analyze } = useDeepgramTextIntelligence({
const { analyze, state } = useDeepgramTextIntelligence({
trackState: true,
options: {
summarize: true,
topics: true,
Expand All @@ -27,19 +26,8 @@ export default function TextIntelligence() {
sentiment: true,
language: 'en-US',
},
onBeforeAnalyze: () => {
setLoading(true);
setError(null);
setResult(null);
},
onAnalyzeSuccess: (res) => {
setLoading(false);
setResult(res);
},
onAnalyzeError: (err) => {
setLoading(false);
setError(err.message);
},
onBeforeAnalyze: () => setResult(null),
onAnalyzeSuccess: setResult,
});

const handleAnalyze = () => {
Expand All @@ -58,8 +46,12 @@ export default function TextIntelligence() {

<Button title="Analyze Text" onPress={handleAnalyze} />

{loading && <Text style={styles.status}>Analyzing...</Text>}
{error && <Text style={styles.error}>Error: {error}</Text>}
{state?.status === 'analyzing' && (
<Text style={styles.status}>Analyzing...</Text>
)}
{state?.error && (
<Text style={styles.error}>Error: {state.error.message}</Text>
)}

{result && (
<ScrollView style={styles.outputContainer}>
Expand Down
Loading
Loading