Skip to content

Commit 16a04ea

Browse files
committed
feat: add "reconnect" method
1 parent a37ef7a commit 16a04ea

File tree

5 files changed

+584
-8
lines changed

5 files changed

+584
-8
lines changed

api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18143,6 +18143,15 @@ export interface CreateWebCallDTO {
1814318143
workflow?: CreateWorkflowDTO;
1814418144
/** These are the overrides for the `workflow` or `workflowId`'s settings and template variables. */
1814518145
workflowOverrides?: WorkflowOverrides;
18146+
/**
18147+
* This determines whether the daily room will be deleted and all participants will be kicked once the user leaves the room.
18148+
* If set to `false`, the room will be kept alive even after the user leaves, allowing clients to reconnect to the same room.
18149+
* If set to `true`, the room will be deleted and reconnection will not be allowed.
18150+
*
18151+
* Defaults to `true`.
18152+
* @example true
18153+
*/
18154+
roomDeleteOnUserLeaveEnabled?: boolean;
1814618155
}
1814718156

1814818157
export interface UpdateCallDTO {

example/src/App.tsx

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
22
import Vapi from '@vapi-ai/web';
33

44
const VAPI_PUBLIC_KEY = import.meta.env.VITE_VAPI_PUBLIC_KEY;
5+
const VAPI_API_BASE_URL = import.meta.env.VITE_VAPI_API_BASE_URL;
56

67
if (!VAPI_PUBLIC_KEY) {
78
throw new Error('VITE_VAPI_PUBLIC_KEY is required. Please set it in your .env.local file.');
@@ -14,7 +15,7 @@ interface Message {
1415
}
1516

1617
function App() {
17-
const [vapi] = useState(() => new Vapi(VAPI_PUBLIC_KEY));
18+
const [vapi] = useState(() => new Vapi(VAPI_PUBLIC_KEY, VAPI_API_BASE_URL));
1819
const [connected, setConnected] = useState(false);
1920
const [assistantIsSpeaking, setAssistantIsSpeaking] = useState(false);
2021
const [volumeLevel, setVolumeLevel] = useState(0);
@@ -25,16 +26,29 @@ function App() {
2526
const [interruptionsEnabled, setInterruptionsEnabled] = useState(true);
2627
const [interruptAssistantEnabled, setInterruptAssistantEnabled] = useState(true);
2728
const [endCallAfterSay, setEndCallAfterSay] = useState(false);
29+
const [storedWebCall, setStoredWebCall] = useState<any>(null);
2830

2931
useEffect(() => {
32+
// Check for stored webCall on component mount
33+
const stored = localStorage.getItem('vapi-webcall');
34+
if (stored) {
35+
try {
36+
const parsedWebCall = JSON.parse(stored);
37+
setStoredWebCall(parsedWebCall);
38+
} catch (error) {
39+
console.error('Error parsing stored webCall:', error);
40+
localStorage.removeItem('vapi-webcall');
41+
}
42+
}
43+
3044
// Update current time every second
3145
const timer = setInterval(() => {
3246
setCurrentTime(new Date().toLocaleTimeString());
3347
}, 1000);
3448

3549
// Set up Vapi event listeners
3650
vapi.on('call-start', () => {
37-
console.log('Call started');
51+
console.log('Call started - call-start event fired');
3852
setConnected(true);
3953
addMessage('system', 'Call connected');
4054
});
@@ -44,7 +58,7 @@ function App() {
4458
setConnected(false);
4559
setAssistantIsSpeaking(false);
4660
setVolumeLevel(0);
47-
addMessage('system', 'Call ended');
61+
addMessage('system', 'Call ended - webCall data preserved for reconnection');
4862
});
4963

5064
vapi.on('speech-start', () => {
@@ -84,7 +98,7 @@ function App() {
8498
console.error('Vapi error:', error);
8599
addMessage('system', `Error: ${error.message || error}`);
86100
});
87-
101+
88102
return () => {
89103
clearInterval(timer);
90104
vapi.stop();
@@ -104,7 +118,7 @@ function App() {
104118
addMessage('system', 'Starting call...');
105119

106120
// Start call with assistant configuration
107-
await vapi.start({
121+
const webCall = await vapi.start({
108122
// Basic assistant configuration
109123
model: {
110124
provider: "openai",
@@ -137,8 +151,23 @@ function App() {
137151

138152
// Max call duration (in seconds) - 10 minutes
139153
maxDurationSeconds: 600
154+
}, undefined, undefined, undefined, undefined, {
155+
roomDeleteOnUserLeaveEnabled: false
140156
});
141157

158+
// Store webCall in localStorage if it was created successfully
159+
if (webCall) {
160+
const webCallToStore = {
161+
webCallUrl: (webCall as any).webCallUrl,
162+
id: webCall.id,
163+
artifactPlan: webCall.artifactPlan,
164+
assistant: webCall.assistant
165+
};
166+
localStorage.setItem('vapi-webcall', JSON.stringify(webCallToStore));
167+
setStoredWebCall(webCallToStore);
168+
addMessage('system', 'Call data stored for reconnection');
169+
}
170+
142171
} catch (error) {
143172
console.error('Error starting call:', error);
144173
addMessage('system', `Failed to start call: ${error}`);
@@ -149,6 +178,41 @@ function App() {
149178
vapi.stop();
150179
};
151180

181+
const reconnectCall = async () => {
182+
if (!storedWebCall) {
183+
addMessage('system', 'No stored call data found');
184+
return;
185+
}
186+
187+
try {
188+
addMessage('system', 'Reconnecting to previous call...');
189+
console.log('Attempting reconnect with data:', storedWebCall);
190+
await vapi.reconnect(storedWebCall);
191+
addMessage('system', 'Reconnect method completed successfully');
192+
193+
// Add a small delay to allow events to propagate
194+
setTimeout(() => {
195+
if (!connected) {
196+
addMessage('system', 'Warning: Reconnect completed but connected state not updated. This may indicate an issue with event handling.');
197+
}
198+
}, 1000);
199+
200+
} catch (error) {
201+
console.error('Error reconnecting:', error);
202+
addMessage('system', `Failed to reconnect: ${error}`);
203+
204+
// Clear invalid stored data
205+
localStorage.removeItem('vapi-webcall');
206+
setStoredWebCall(null);
207+
}
208+
};
209+
210+
const clearStoredCall = () => {
211+
localStorage.removeItem('vapi-webcall');
212+
setStoredWebCall(null);
213+
addMessage('system', 'Cleared stored call data');
214+
};
215+
152216
const toggleMute = () => {
153217
const newMutedState = !isMuted;
154218
vapi.setMuted(newMutedState);
@@ -216,7 +280,7 @@ function App() {
216280
borderRadius: '8px',
217281
marginBottom: '20px'
218282
}}>
219-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
283+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '10px' }}>
220284
<div>
221285
<strong>Status:</strong>
222286
<span style={{
@@ -225,10 +289,33 @@ function App() {
225289
}}>
226290
{connected ? 'Connected' : 'Disconnected'}
227291
</span>
292+
{storedWebCall && !connected && (
293+
<span style={{
294+
color: '#f59e0b',
295+
marginLeft: '8px',
296+
fontSize: '14px'
297+
}}>
298+
(Reconnect Available)
299+
</span>
300+
)}
228301
</div>
229302
<div>Current Time: {currentTime}</div>
230303
</div>
231304

305+
{storedWebCall && (
306+
<div style={{
307+
marginTop: '10px',
308+
padding: '8px 12px',
309+
backgroundColor: connected ? '#dcfce7' : '#fef3c7',
310+
borderRadius: '4px',
311+
fontSize: '14px',
312+
color: connected ? '#166534' : '#92400e'
313+
}}>
314+
<strong>Stored Call:</strong> ID {storedWebCall.id || 'Unknown'} -
315+
{connected ? ' Currently active' : ' Ready to reconnect'}
316+
</div>
317+
)}
318+
232319
{connected && (
233320
<div style={{ marginTop: '10px' }}>
234321
<div style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
@@ -261,7 +348,8 @@ function App() {
261348
display: 'flex',
262349
gap: '10px',
263350
justifyContent: 'center',
264-
marginBottom: '20px'
351+
marginBottom: '20px',
352+
flexWrap: 'wrap'
265353
}}>
266354
<button
267355
onClick={startCall}
@@ -278,6 +366,23 @@ function App() {
278366
>
279367
Start Call
280368
</button>
369+
370+
{storedWebCall && !connected && (
371+
<button
372+
onClick={reconnectCall}
373+
style={{
374+
padding: '12px 24px',
375+
backgroundColor: '#f59e0b',
376+
color: 'white',
377+
border: 'none',
378+
borderRadius: '6px',
379+
cursor: 'pointer',
380+
fontSize: '16px'
381+
}}
382+
>
383+
Reconnect to Stored Call
384+
</button>
385+
)}
281386

282387
<button
283388
onClick={stopCall}
@@ -326,6 +431,23 @@ function App() {
326431
>
327432
Send Context
328433
</button>
434+
435+
{storedWebCall && (
436+
<button
437+
onClick={clearStoredCall}
438+
style={{
439+
padding: '12px 24px',
440+
backgroundColor: '#6b7280',
441+
color: 'white',
442+
border: 'none',
443+
borderRadius: '6px',
444+
cursor: 'pointer',
445+
fontSize: '16px'
446+
}}
447+
>
448+
Clear Stored Call
449+
</button>
450+
)}
329451
</div>
330452

331453
{/* Manual Say Controls */}
@@ -539,6 +661,9 @@ function App() {
539661
<li>Use "Mute" to temporarily disable your microphone</li>
540662
<li>Say "goodbye" or "end call" to end the conversation</li>
541663
<li>Click "Stop Call" to manually end the call</li>
664+
<li><strong>Persistent Storage:</strong> Call data is automatically saved and persists even after calls end</li>
665+
<li><strong>Reconnection:</strong> Use "Reconnect to Stored Call" to rejoin your previous session anytime</li>
666+
<li>Use "Clear Stored Call" to permanently remove saved call data when no longer needed</li>
542667
</ul>
543668
</div>
544669
</div>

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"test:example": "npm run pack:local && cd example && npm install && npm run build",
1515
"pack:local": "./scripts/build-latest.sh",
1616
"clean-builds": "rm -f vapi-ai-web-*.tgz && echo '🧹 Cleaned up all build tarballs'",
17-
"dev:example": "npm run pack:local && cd example && npm install && npm run dev"
17+
"dev:example": "npm run pack:local && cd example && npm install && npm run dev",
18+
"re-build-local-example": "./scripts/re-build-local-example.sh"
1819
},
1920
"repository": {
2021
"type": "git",

scripts/re-build-local-example.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Clean up the example node_modules and package-lock.json
6+
rm -rf example/node_modules
7+
rm -f example/package-lock.json
8+
9+
# Clean up the local build clean-builds
10+
npm run clean-builds
11+
12+
# Re-pack local build
13+
npm run pack:local
14+
15+
# Re-install the dependencies
16+
cd example
17+
npm install

0 commit comments

Comments
 (0)