@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
22import Vapi from '@vapi-ai/web' ;
33
44const 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
67if ( ! 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
1617function 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 >
0 commit comments