ttp-agent-sdk 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GETTING_STARTED.md +429 -0
- package/README.md +303 -0
- package/dist/agent-widget.js +3 -0
- package/dist/agent-widget.js.LICENSE.txt +21 -0
- package/dist/agent-widget.js.map +1 -0
- package/dist/examples/react-example.html +455 -0
- package/dist/examples/react-example.jsx +307 -0
- package/dist/examples/test.html +235 -0
- package/dist/examples/vanilla-example.html +464 -0
- package/dist/index.html +224 -0
- package/examples/react-example.html +455 -0
- package/examples/react-example.jsx +307 -0
- package/examples/test.html +235 -0
- package/examples/vanilla-example.html +464 -0
- package/package.json +63 -0
- package/src/core/AudioPlayer.js +185 -0
- package/src/core/AudioRecorder.js +128 -0
- package/src/core/ConnectionManager.js +86 -0
- package/src/core/EventEmitter.js +53 -0
- package/src/core/VoiceSDK.js +390 -0
- package/src/core/WebSocketManager.js +218 -0
- package/src/core/WebSocketManagerV2.js +211 -0
- package/src/core/WebSocketSingleton.js +171 -0
- package/src/index.js +64 -0
- package/src/legacy/AgentSDK.js +462 -0
- package/src/react/VoiceButton.jsx +163 -0
- package/src/vanilla/VoiceButton.js +190 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy AgentSDK - Backward Compatibility Layer
|
|
3
|
+
*
|
|
4
|
+
* This maintains the original AgentSDK API while using the new VoiceSDK internally.
|
|
5
|
+
* This ensures existing integrations continue to work without changes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { VoiceSDK } from '../index.js';
|
|
9
|
+
|
|
10
|
+
export class AgentSDK {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.voiceSDK = null;
|
|
14
|
+
this.isConnected = false;
|
|
15
|
+
this.isListening = false;
|
|
16
|
+
|
|
17
|
+
// Legacy callback properties
|
|
18
|
+
this.onConnected = () => {};
|
|
19
|
+
this.onDisconnected = () => {};
|
|
20
|
+
this.onError = (error) => console.error('SDK Error:', error);
|
|
21
|
+
this.onTranscript = (text) => {};
|
|
22
|
+
this.onAgentSpeaking = (isStart) => {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async connect(signedUrl) {
|
|
26
|
+
try {
|
|
27
|
+
if (!signedUrl) {
|
|
28
|
+
throw new Error('signedUrl is required');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create VoiceSDK instance
|
|
32
|
+
this.voiceSDK = new VoiceSDK({
|
|
33
|
+
websocketUrl: signedUrl,
|
|
34
|
+
autoReconnect: false
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Set up event handlers to map to legacy callbacks
|
|
38
|
+
this.voiceSDK.on('connected', () => {
|
|
39
|
+
this.isConnected = true;
|
|
40
|
+
this.onConnected();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.voiceSDK.on('disconnected', () => {
|
|
44
|
+
this.isConnected = false;
|
|
45
|
+
this.onDisconnected();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.voiceSDK.on('error', (error) => {
|
|
49
|
+
this.onError(error);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
this.voiceSDK.on('message', (message) => {
|
|
53
|
+
this.handleWebSocketMessage(message);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.voiceSDK.on('recordingStarted', () => {
|
|
57
|
+
this.isListening = true;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.voiceSDK.on('recordingStopped', () => {
|
|
61
|
+
this.isListening = false;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.voiceSDK.on('playbackStarted', () => {
|
|
65
|
+
this.onAgentSpeaking(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.voiceSDK.on('playbackStopped', () => {
|
|
69
|
+
this.onAgentSpeaking(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Connect using VoiceSDK
|
|
73
|
+
await this.voiceSDK.connect();
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
this.onError(error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
handleWebSocketMessage(message) {
|
|
82
|
+
// Map new message format to legacy format
|
|
83
|
+
switch (message.type) {
|
|
84
|
+
case 'connected':
|
|
85
|
+
console.log('Session started successfully');
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 'user_transcript':
|
|
89
|
+
this.onTranscript(message.user_transcription || message.text);
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case 'agent_response':
|
|
93
|
+
// Handle agent text response
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 'barge_in':
|
|
97
|
+
// Handle barge-in
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case 'stop_playing':
|
|
101
|
+
// Handle stop playing
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case 'error':
|
|
105
|
+
this.onError(new Error(message.message));
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async startListening() {
|
|
111
|
+
if (this.voiceSDK) {
|
|
112
|
+
await this.voiceSDK.startRecording();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
stopListening() {
|
|
117
|
+
if (this.voiceSDK) {
|
|
118
|
+
this.voiceSDK.stopRecording();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
updateVariables(variables) {
|
|
123
|
+
if (this.voiceSDK && this.isConnected) {
|
|
124
|
+
// Send variables update message
|
|
125
|
+
this.voiceSDK.webSocketManager.sendMessage({
|
|
126
|
+
t: 'update_variables',
|
|
127
|
+
variables
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
disconnect() {
|
|
133
|
+
if (this.voiceSDK) {
|
|
134
|
+
this.voiceSDK.destroy();
|
|
135
|
+
this.voiceSDK = null;
|
|
136
|
+
}
|
|
137
|
+
this.isConnected = false;
|
|
138
|
+
this.isListening = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================
|
|
143
|
+
// WIDGET - Pre-built UI using the SDK
|
|
144
|
+
// ============================================
|
|
145
|
+
|
|
146
|
+
export class AgentWidget {
|
|
147
|
+
constructor(config) {
|
|
148
|
+
this.config = config;
|
|
149
|
+
this.sdk = new AgentSDK();
|
|
150
|
+
this.isOpen = false;
|
|
151
|
+
this.isActive = false;
|
|
152
|
+
|
|
153
|
+
this.position = config.position || 'bottom-right';
|
|
154
|
+
this.primaryColor = config.primaryColor || '#4F46E5';
|
|
155
|
+
|
|
156
|
+
this.setupEventHandlers();
|
|
157
|
+
this.createWidget();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setupEventHandlers() {
|
|
161
|
+
this.sdk.onConnected = () => {
|
|
162
|
+
this.updateStatus('connected');
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.sdk.onDisconnected = () => {
|
|
166
|
+
this.updateStatus('disconnected');
|
|
167
|
+
this.isActive = false;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
this.sdk.onError = (error) => {
|
|
171
|
+
this.showError(error.message);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
this.sdk.onTranscript = (text) => {
|
|
175
|
+
this.addMessage('user', text);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
this.sdk.onAgentSpeaking = (isStart) => {
|
|
179
|
+
if (isStart) {
|
|
180
|
+
this.showAgentThinking();
|
|
181
|
+
} else {
|
|
182
|
+
this.hideAgentThinking();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
createWidget() {
|
|
188
|
+
const widget = document.createElement('div');
|
|
189
|
+
widget.id = 'agent-widget';
|
|
190
|
+
widget.innerHTML = `
|
|
191
|
+
<style>
|
|
192
|
+
#agent-widget {
|
|
193
|
+
position: fixed;
|
|
194
|
+
${this.position.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'}
|
|
195
|
+
${this.position.includes('right') ? 'right: 20px;' : 'left: 20px;'}
|
|
196
|
+
z-index: 9999;
|
|
197
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#agent-button {
|
|
201
|
+
width: 60px;
|
|
202
|
+
height: 60px;
|
|
203
|
+
border-radius: 50%;
|
|
204
|
+
background: ${this.primaryColor};
|
|
205
|
+
border: none;
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
transition: transform 0.2s;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#agent-button:hover {
|
|
215
|
+
transform: scale(1.1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#agent-button svg {
|
|
219
|
+
width: 28px;
|
|
220
|
+
height: 28px;
|
|
221
|
+
fill: white;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#agent-panel {
|
|
225
|
+
display: none;
|
|
226
|
+
position: absolute;
|
|
227
|
+
bottom: 80px;
|
|
228
|
+
${this.position.includes('right') ? 'right: 0;' : 'left: 0;'}
|
|
229
|
+
width: 350px;
|
|
230
|
+
height: 500px;
|
|
231
|
+
background: white;
|
|
232
|
+
border-radius: 12px;
|
|
233
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
overflow: hidden;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#agent-panel.open {
|
|
239
|
+
display: flex;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#agent-header {
|
|
243
|
+
background: ${this.primaryColor};
|
|
244
|
+
color: white;
|
|
245
|
+
padding: 16px;
|
|
246
|
+
display: flex;
|
|
247
|
+
justify-content: space-between;
|
|
248
|
+
align-items: center;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
#agent-close {
|
|
252
|
+
background: none;
|
|
253
|
+
border: none;
|
|
254
|
+
color: white;
|
|
255
|
+
cursor: pointer;
|
|
256
|
+
font-size: 24px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#agent-messages {
|
|
260
|
+
flex: 1;
|
|
261
|
+
overflow-y: auto;
|
|
262
|
+
padding: 16px;
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-direction: column;
|
|
265
|
+
gap: 12px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.message {
|
|
269
|
+
padding: 12px;
|
|
270
|
+
border-radius: 8px;
|
|
271
|
+
max-width: 80%;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.message.user {
|
|
275
|
+
background: #E5E7EB;
|
|
276
|
+
align-self: flex-end;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.message.agent {
|
|
280
|
+
background: #F3F4F6;
|
|
281
|
+
align-self: flex-start;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#agent-controls {
|
|
285
|
+
padding: 16px;
|
|
286
|
+
border-top: 1px solid #E5E7EB;
|
|
287
|
+
display: flex;
|
|
288
|
+
justify-content: center;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#agent-mic-button {
|
|
292
|
+
width: 60px;
|
|
293
|
+
height: 60px;
|
|
294
|
+
border-radius: 50%;
|
|
295
|
+
border: none;
|
|
296
|
+
background: ${this.primaryColor};
|
|
297
|
+
cursor: pointer;
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
justify-content: center;
|
|
301
|
+
transition: all 0.2s;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#agent-mic-button.active {
|
|
305
|
+
background: #EF4444;
|
|
306
|
+
animation: pulse 1.5s infinite;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#agent-mic-button svg {
|
|
310
|
+
width: 28px;
|
|
311
|
+
height: 28px;
|
|
312
|
+
fill: white;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@keyframes pulse {
|
|
316
|
+
0%, 100% { transform: scale(1); }
|
|
317
|
+
50% { transform: scale(1.05); }
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.agent-thinking {
|
|
321
|
+
font-style: italic;
|
|
322
|
+
color: #6B7280;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.error-message {
|
|
326
|
+
background: #FEE2E2;
|
|
327
|
+
color: #991B1B;
|
|
328
|
+
padding: 12px;
|
|
329
|
+
border-radius: 8px;
|
|
330
|
+
margin: 8px;
|
|
331
|
+
}
|
|
332
|
+
</style>
|
|
333
|
+
|
|
334
|
+
<button id="agent-button">
|
|
335
|
+
<svg viewBox="0 0 24 24">
|
|
336
|
+
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
|
|
337
|
+
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
|
|
338
|
+
</svg>
|
|
339
|
+
</button>
|
|
340
|
+
|
|
341
|
+
<div id="agent-panel">
|
|
342
|
+
<div id="agent-header">
|
|
343
|
+
<h3 style="margin: 0;">Voice Assistant</h3>
|
|
344
|
+
<button id="agent-close">×</button>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<div id="agent-messages"></div>
|
|
348
|
+
|
|
349
|
+
<div id="agent-controls">
|
|
350
|
+
<button id="agent-mic-button">
|
|
351
|
+
<svg viewBox="0 0 24 24">
|
|
352
|
+
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
|
|
353
|
+
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
|
|
354
|
+
</svg>
|
|
355
|
+
</button>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
`;
|
|
359
|
+
|
|
360
|
+
document.body.appendChild(widget);
|
|
361
|
+
|
|
362
|
+
document.getElementById('agent-button').onclick = () => this.togglePanel();
|
|
363
|
+
document.getElementById('agent-close').onclick = () => this.togglePanel();
|
|
364
|
+
document.getElementById('agent-mic-button').onclick = () => this.toggleVoice();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
togglePanel() {
|
|
368
|
+
this.isOpen = !this.isOpen;
|
|
369
|
+
const panel = document.getElementById('agent-panel');
|
|
370
|
+
panel.classList.toggle('open');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async toggleVoice() {
|
|
374
|
+
if (!this.isActive) {
|
|
375
|
+
try {
|
|
376
|
+
const signedUrl = await this.getSignedUrl();
|
|
377
|
+
await this.sdk.connect(signedUrl);
|
|
378
|
+
await this.sdk.startListening();
|
|
379
|
+
this.isActive = true;
|
|
380
|
+
document.getElementById('agent-mic-button').classList.add('active');
|
|
381
|
+
this.addMessage('system', 'Listening...');
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('Failed to start:', error);
|
|
384
|
+
this.showError(error.message);
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
this.sdk.stopListening();
|
|
388
|
+
this.sdk.disconnect();
|
|
389
|
+
this.isActive = false;
|
|
390
|
+
document.getElementById('agent-mic-button').classList.remove('active');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async getSignedUrl() {
|
|
395
|
+
if (typeof this.config.getSessionUrl === 'string') {
|
|
396
|
+
const response = await fetch(this.config.getSessionUrl, {
|
|
397
|
+
method: 'POST',
|
|
398
|
+
headers: {
|
|
399
|
+
'Content-Type': 'application/json',
|
|
400
|
+
},
|
|
401
|
+
body: JSON.stringify({
|
|
402
|
+
agentId: this.config.agentId,
|
|
403
|
+
variables: this.config.variables || {}
|
|
404
|
+
})
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
throw new Error(`Failed to get session URL: ${response.statusText}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const data = await response.json();
|
|
412
|
+
return data.signedUrl || data.wsUrl || data.url;
|
|
413
|
+
}
|
|
414
|
+
else if (typeof this.config.getSessionUrl === 'function') {
|
|
415
|
+
const result = await this.config.getSessionUrl({
|
|
416
|
+
agentId: this.config.agentId,
|
|
417
|
+
variables: this.config.variables || {}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
return typeof result === 'string' ? result : (result.signedUrl || result.wsUrl || result.url);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
throw new Error('getSessionUrl is required (URL string or function)');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
addMessage(type, text) {
|
|
428
|
+
const messages = document.getElementById('agent-messages');
|
|
429
|
+
const message = document.createElement('div');
|
|
430
|
+
message.className = `message ${type}`;
|
|
431
|
+
message.textContent = text;
|
|
432
|
+
messages.appendChild(message);
|
|
433
|
+
messages.scrollTop = messages.scrollHeight;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
showAgentThinking() {
|
|
437
|
+
const messages = document.getElementById('agent-messages');
|
|
438
|
+
const thinking = document.createElement('div');
|
|
439
|
+
thinking.className = 'message agent agent-thinking';
|
|
440
|
+
thinking.id = 'thinking-indicator';
|
|
441
|
+
thinking.textContent = 'Agent is speaking...';
|
|
442
|
+
messages.appendChild(thinking);
|
|
443
|
+
messages.scrollTop = messages.scrollHeight;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
hideAgentThinking() {
|
|
447
|
+
const thinking = document.getElementById('thinking-indicator');
|
|
448
|
+
if (thinking) thinking.remove();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
showError(message) {
|
|
452
|
+
const messages = document.getElementById('agent-messages');
|
|
453
|
+
const error = document.createElement('div');
|
|
454
|
+
error.className = 'error-message';
|
|
455
|
+
error.textContent = message;
|
|
456
|
+
messages.appendChild(error);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
updateStatus(status) {
|
|
460
|
+
console.log('Widget status:', status);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoiceButton - React component for voice interaction
|
|
3
|
+
*/
|
|
4
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
5
|
+
import VoiceSDK from '../core/VoiceSDK.js';
|
|
6
|
+
|
|
7
|
+
const VoiceButton = ({
|
|
8
|
+
websocketUrl,
|
|
9
|
+
agentId, // Optional - for direct agent access (unsecured method)
|
|
10
|
+
voice = 'default',
|
|
11
|
+
language = 'en',
|
|
12
|
+
autoReconnect = true,
|
|
13
|
+
onConnected,
|
|
14
|
+
onDisconnected,
|
|
15
|
+
onRecordingStarted,
|
|
16
|
+
onRecordingStopped,
|
|
17
|
+
onPlaybackStarted,
|
|
18
|
+
onPlaybackStopped,
|
|
19
|
+
onError,
|
|
20
|
+
onMessage,
|
|
21
|
+
onBargeIn,
|
|
22
|
+
onStopPlaying,
|
|
23
|
+
className = '',
|
|
24
|
+
style = {},
|
|
25
|
+
children
|
|
26
|
+
}) => {
|
|
27
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
28
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
29
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
30
|
+
const [connectionStatus, setConnectionStatus] = useState('Disconnected');
|
|
31
|
+
|
|
32
|
+
const voiceSDKRef = useRef(null);
|
|
33
|
+
|
|
34
|
+
// Initialize VoiceSDK
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
console.log(`🎙️ VoiceButton: Creating VoiceSDK instance for ${websocketUrl}`);
|
|
37
|
+
|
|
38
|
+
// Clean up existing instance if any
|
|
39
|
+
if (voiceSDKRef.current) {
|
|
40
|
+
console.log(`🎙️ VoiceButton: Destroying existing VoiceSDK instance`);
|
|
41
|
+
voiceSDKRef.current.destroy();
|
|
42
|
+
voiceSDKRef.current = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const voiceSDK = new VoiceSDK({
|
|
46
|
+
websocketUrl,
|
|
47
|
+
agentId, // Pass through agentId if provided
|
|
48
|
+
voice,
|
|
49
|
+
language,
|
|
50
|
+
autoReconnect
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Setup event listeners
|
|
54
|
+
voiceSDK.on('connected', () => {
|
|
55
|
+
setIsConnected(true);
|
|
56
|
+
setConnectionStatus('Connected');
|
|
57
|
+
onConnected?.();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
voiceSDK.on('disconnected', () => {
|
|
61
|
+
setIsConnected(false);
|
|
62
|
+
setConnectionStatus('Disconnected');
|
|
63
|
+
onDisconnected?.();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
voiceSDK.on('recordingStarted', () => {
|
|
67
|
+
setIsRecording(true);
|
|
68
|
+
onRecordingStarted?.();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
voiceSDK.on('recordingStopped', () => {
|
|
72
|
+
setIsRecording(false);
|
|
73
|
+
onRecordingStopped?.();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
voiceSDK.on('playbackStarted', () => {
|
|
77
|
+
setIsPlaying(true);
|
|
78
|
+
onPlaybackStarted?.();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
voiceSDK.on('playbackStopped', () => {
|
|
82
|
+
setIsPlaying(false);
|
|
83
|
+
onPlaybackStopped?.();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
voiceSDK.on('error', (error) => {
|
|
87
|
+
onError?.(error);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
voiceSDK.on('message', (message) => {
|
|
91
|
+
onMessage?.(message);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
voiceSDK.on('bargeIn', (message) => {
|
|
95
|
+
onBargeIn?.(message);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
voiceSDK.on('stopPlaying', (message) => {
|
|
99
|
+
onStopPlaying?.(message);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
voiceSDKRef.current = voiceSDK;
|
|
103
|
+
|
|
104
|
+
// Auto-connect
|
|
105
|
+
voiceSDK.connect();
|
|
106
|
+
|
|
107
|
+
// Cleanup on unmount
|
|
108
|
+
return () => {
|
|
109
|
+
console.log(`🎙️ VoiceButton: Cleaning up VoiceSDK instance for ${websocketUrl}`);
|
|
110
|
+
if (voiceSDKRef.current) {
|
|
111
|
+
voiceSDKRef.current.destroy();
|
|
112
|
+
voiceSDKRef.current = null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [websocketUrl, agentId, voice, language]);
|
|
116
|
+
|
|
117
|
+
// Handle button click
|
|
118
|
+
const handleClick = async () => {
|
|
119
|
+
if (!voiceSDKRef.current) return;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await voiceSDKRef.current.toggleRecording();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Error toggling recording:', error);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Default button content
|
|
129
|
+
const defaultContent = (
|
|
130
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
131
|
+
<div style={{ fontSize: '20px' }}>
|
|
132
|
+
{isRecording ? '🔴' : '🎤'}
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
{isRecording ? 'Stop Listening' : 'Start Listening'}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<button
|
|
142
|
+
className={`voice-button ${isRecording ? 'recording' : ''} ${className}`}
|
|
143
|
+
style={{
|
|
144
|
+
padding: '12px 24px',
|
|
145
|
+
border: 'none',
|
|
146
|
+
borderRadius: '8px',
|
|
147
|
+
backgroundColor: isRecording ? '#dc3545' : '#007bff',
|
|
148
|
+
color: 'white',
|
|
149
|
+
cursor: 'pointer',
|
|
150
|
+
fontSize: '16px',
|
|
151
|
+
fontWeight: '500',
|
|
152
|
+
transition: 'all 0.2s ease',
|
|
153
|
+
...style
|
|
154
|
+
}}
|
|
155
|
+
onClick={handleClick}
|
|
156
|
+
disabled={!isConnected}
|
|
157
|
+
>
|
|
158
|
+
{children || defaultContent}
|
|
159
|
+
</button>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default VoiceButton;
|