ttp-agent-sdk 2.1.8 → 2.1.10
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/dist/agent-widget.js +1 -1
- package/dist/agent-widget.js.map +1 -1
- package/dist/audio-processor.js +1 -0
- package/{examples/test.html → dist/examples/test-agent-app.html} +111 -45
- package/dist/examples/test-signed-link.html +453 -0
- package/{dist/examples/test.html → examples/test-agent-app.html} +111 -45
- package/examples/test-signed-link.html +453 -0
- package/package.json +1 -1
- package/dist/examples/enhanced-widget-examples.html +0 -471
- package/dist/examples/multi-platform-examples.html +0 -1119
- package/dist/examples/react-example.html +0 -774
- package/dist/examples/react-example.jsx +0 -307
- package/dist/examples/vanilla-example.html +0 -464
- package/examples/enhanced-widget-examples.html +0 -471
- package/examples/multi-platform-examples.html +0 -1119
- package/examples/react-example.html +0 -774
- package/examples/react-example.jsx +0 -307
- package/examples/vanilla-example.html +0 -464
|
@@ -1,774 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>React VoiceSDK Example - TTP Agent SDK</title>
|
|
7
|
-
<style>
|
|
8
|
-
* {
|
|
9
|
-
box-sizing: border-box;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
body {
|
|
13
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
14
|
-
max-width: 1200px;
|
|
15
|
-
margin: 0 auto;
|
|
16
|
-
padding: 20px;
|
|
17
|
-
background: #f8fafc;
|
|
18
|
-
color: #1e293b;
|
|
19
|
-
line-height: 1.6;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.container {
|
|
23
|
-
background: white;
|
|
24
|
-
padding: 40px;
|
|
25
|
-
border-radius: 16px;
|
|
26
|
-
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
|
27
|
-
margin-bottom: 30px;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
h1 {
|
|
31
|
-
color: #1e293b;
|
|
32
|
-
margin-top: 0;
|
|
33
|
-
font-size: 2.5rem;
|
|
34
|
-
font-weight: 700;
|
|
35
|
-
margin-bottom: 10px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.subtitle {
|
|
39
|
-
color: #64748b;
|
|
40
|
-
font-size: 1.2rem;
|
|
41
|
-
margin-bottom: 30px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.back-link {
|
|
45
|
-
display: inline-flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
background: #4f46e5;
|
|
48
|
-
color: white;
|
|
49
|
-
padding: 12px 24px;
|
|
50
|
-
text-decoration: none;
|
|
51
|
-
border-radius: 8px;
|
|
52
|
-
font-weight: 600;
|
|
53
|
-
margin-bottom: 30px;
|
|
54
|
-
transition: all 0.2s;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.back-link:hover {
|
|
58
|
-
background: #4338ca;
|
|
59
|
-
transform: translateY(-1px);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.info {
|
|
63
|
-
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
|
64
|
-
border-left: 4px solid #3b82f6;
|
|
65
|
-
padding: 20px;
|
|
66
|
-
margin: 30px 0;
|
|
67
|
-
border-radius: 8px;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.info strong {
|
|
71
|
-
color: #1e40af;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.section {
|
|
75
|
-
margin: 40px 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.section h2 {
|
|
79
|
-
color: #4f46e5;
|
|
80
|
-
font-size: 1.8rem;
|
|
81
|
-
margin-bottom: 20px;
|
|
82
|
-
border-bottom: 2px solid #e2e8f0;
|
|
83
|
-
padding-bottom: 10px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.code-container {
|
|
87
|
-
position: relative;
|
|
88
|
-
background: #1e293b;
|
|
89
|
-
border-radius: 12px;
|
|
90
|
-
overflow: hidden;
|
|
91
|
-
margin: 20px 0;
|
|
92
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.code-header {
|
|
96
|
-
background: #334155;
|
|
97
|
-
padding: 12px 20px;
|
|
98
|
-
display: flex;
|
|
99
|
-
justify-content: space-between;
|
|
100
|
-
align-items: center;
|
|
101
|
-
border-bottom: 1px solid #475569;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.code-title {
|
|
105
|
-
color: #e2e8f0;
|
|
106
|
-
font-weight: 600;
|
|
107
|
-
font-size: 14px;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.copy-button {
|
|
111
|
-
background: #4f46e5;
|
|
112
|
-
color: white;
|
|
113
|
-
border: none;
|
|
114
|
-
padding: 8px 16px;
|
|
115
|
-
border-radius: 6px;
|
|
116
|
-
font-size: 12px;
|
|
117
|
-
font-weight: 600;
|
|
118
|
-
cursor: pointer;
|
|
119
|
-
transition: all 0.2s;
|
|
120
|
-
display: flex;
|
|
121
|
-
align-items: center;
|
|
122
|
-
gap: 6px;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.copy-button:hover {
|
|
126
|
-
background: #4338ca;
|
|
127
|
-
transform: translateY(-1px);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.copy-button.copied {
|
|
131
|
-
background: #10b981;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.code-block {
|
|
135
|
-
background: #1e293b;
|
|
136
|
-
color: #e2e8f0;
|
|
137
|
-
padding: 30px;
|
|
138
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
139
|
-
font-size: 14px;
|
|
140
|
-
line-height: 1.6;
|
|
141
|
-
overflow-x: auto;
|
|
142
|
-
white-space: pre;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.note {
|
|
146
|
-
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
|
147
|
-
border-left: 4px solid #f59e0b;
|
|
148
|
-
padding: 20px;
|
|
149
|
-
margin: 30px 0;
|
|
150
|
-
border-radius: 8px;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.note strong {
|
|
154
|
-
color: #92400e;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.feature-grid {
|
|
158
|
-
display: grid;
|
|
159
|
-
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
160
|
-
gap: 20px;
|
|
161
|
-
margin: 30px 0;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.feature-card {
|
|
165
|
-
background: #f8fafc;
|
|
166
|
-
padding: 24px;
|
|
167
|
-
border-radius: 12px;
|
|
168
|
-
border: 1px solid #e2e8f0;
|
|
169
|
-
transition: all 0.2s;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.feature-card:hover {
|
|
173
|
-
transform: translateY(-2px);
|
|
174
|
-
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.feature-card h3 {
|
|
178
|
-
color: #4f46e5;
|
|
179
|
-
margin-top: 0;
|
|
180
|
-
font-size: 1.2rem;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.step {
|
|
184
|
-
background: #f1f5f9;
|
|
185
|
-
padding: 20px;
|
|
186
|
-
border-radius: 8px;
|
|
187
|
-
margin: 20px 0;
|
|
188
|
-
border-left: 4px solid #4f46e5;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.step-number {
|
|
192
|
-
background: #4f46e5;
|
|
193
|
-
color: white;
|
|
194
|
-
width: 24px;
|
|
195
|
-
height: 24px;
|
|
196
|
-
border-radius: 50%;
|
|
197
|
-
display: inline-flex;
|
|
198
|
-
align-items: center;
|
|
199
|
-
justify-content: center;
|
|
200
|
-
font-weight: bold;
|
|
201
|
-
margin-right: 12px;
|
|
202
|
-
}
|
|
203
|
-
</style>
|
|
204
|
-
</head>
|
|
205
|
-
<body>
|
|
206
|
-
<a href="../index.html" class="back-link">
|
|
207
|
-
← Back to SDK Home
|
|
208
|
-
</a>
|
|
209
|
-
|
|
210
|
-
<div class="container">
|
|
211
|
-
<h1>⚛️ React VoiceSDK Integration</h1>
|
|
212
|
-
<p class="subtitle">Clean, readable examples for integrating TTP Agent SDK with React</p>
|
|
213
|
-
|
|
214
|
-
<div class="info">
|
|
215
|
-
<strong>📋 Prerequisites:</strong> This guide assumes you have a React development environment set up.
|
|
216
|
-
Copy the code examples below and customize them for your application.
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
<div class="section">
|
|
220
|
-
<h2>🚀 Quick Start</h2>
|
|
221
|
-
|
|
222
|
-
<div class="step">
|
|
223
|
-
<span class="step-number">1</span>
|
|
224
|
-
<strong>Install the SDK:</strong>
|
|
225
|
-
<div class="code-container">
|
|
226
|
-
<div class="code-header">
|
|
227
|
-
<span class="code-title">Terminal</span>
|
|
228
|
-
<button class="copy-button" onclick="copyToClipboard('install-command')">
|
|
229
|
-
📋 Copy
|
|
230
|
-
</button>
|
|
231
|
-
</div>
|
|
232
|
-
<div class="code-block" id="install-command">npm install ttp-agent-sdk react react-dom</div>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<div class="step">
|
|
237
|
-
<span class="step-number">2</span>
|
|
238
|
-
<strong>Choose your integration method:</strong>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<div class="section">
|
|
243
|
-
<h2>🎯 Method 1: Basic VoiceSDK Integration</h2>
|
|
244
|
-
|
|
245
|
-
<div class="code-container">
|
|
246
|
-
<div class="code-header">
|
|
247
|
-
<span class="code-title">VoiceChat.jsx</span>
|
|
248
|
-
<button class="copy-button" onclick="copyToClipboard('basic-example')">
|
|
249
|
-
📋 Copy to LLM
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
<div class="code-block" id="basic-example">import React, { useState, useEffect, useRef } from 'react';
|
|
253
|
-
import { VoiceSDK } from 'ttp-agent-sdk';
|
|
254
|
-
|
|
255
|
-
function VoiceChat() {
|
|
256
|
-
const [isConnected, setIsConnected] = useState(false);
|
|
257
|
-
const [isRecording, setIsRecording] = useState(false);
|
|
258
|
-
const [messages, setMessages] = useState([]);
|
|
259
|
-
const voiceSDKRef = useRef(null);
|
|
260
|
-
|
|
261
|
-
useEffect(() => {
|
|
262
|
-
const voiceSDK = new VoiceSDK({
|
|
263
|
-
websocketUrl: 'wss://speech.talktopc.com/ws/conv',
|
|
264
|
-
agentId: 'your_agent_id',
|
|
265
|
-
appId: 'your_app_id'
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Event handlers
|
|
269
|
-
voiceSDK.on('connected', () => setIsConnected(true));
|
|
270
|
-
voiceSDK.on('disconnected', () => setIsConnected(false));
|
|
271
|
-
voiceSDK.on('recordingStarted', () => setIsRecording(true));
|
|
272
|
-
voiceSDK.on('recordingStopped', () => setIsRecording(false));
|
|
273
|
-
|
|
274
|
-
voiceSDK.on('message', (message) => {
|
|
275
|
-
if (message.type === 'agent_response') {
|
|
276
|
-
setMessages(prev => [...prev, {
|
|
277
|
-
type: 'agent',
|
|
278
|
-
text: message.agent_response
|
|
279
|
-
}]);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
voiceSDKRef.current = voiceSDK;
|
|
284
|
-
|
|
285
|
-
return () => voiceSDK.destroy();
|
|
286
|
-
}, []);
|
|
287
|
-
|
|
288
|
-
const handleConnect = async () => {
|
|
289
|
-
await voiceSDKRef.current.connect();
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const handleToggleRecording = async () => {
|
|
293
|
-
if (isRecording) {
|
|
294
|
-
await voiceSDKRef.current.stopRecording();
|
|
295
|
-
} else {
|
|
296
|
-
await voiceSDKRef.current.startRecording();
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
return (
|
|
301
|
-
<div>
|
|
302
|
-
<button onClick={handleConnect} disabled={isConnected}>
|
|
303
|
-
{isConnected ? 'Connected' : 'Connect'}
|
|
304
|
-
</button>
|
|
305
|
-
|
|
306
|
-
<button onClick={handleToggleRecording} disabled={!isConnected}>
|
|
307
|
-
{isRecording ? 'Stop Recording' : 'Start Recording'}
|
|
308
|
-
</button>
|
|
309
|
-
|
|
310
|
-
<div>
|
|
311
|
-
{messages.map((msg, i) => (
|
|
312
|
-
<div key={i}>
|
|
313
|
-
<strong>{msg.type}:</strong> {msg.text}
|
|
314
|
-
</div>
|
|
315
|
-
))}
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export default VoiceChat;</div>
|
|
322
|
-
</div>
|
|
323
|
-
</div>
|
|
324
|
-
|
|
325
|
-
<div class="section">
|
|
326
|
-
<h2>🎨 Method 2: Using VoiceButton Component</h2>
|
|
327
|
-
|
|
328
|
-
<div class="code-container">
|
|
329
|
-
<div class="code-header">
|
|
330
|
-
<span class="code-title">App.jsx</span>
|
|
331
|
-
<button class="copy-button" onclick="copyToClipboard('voicebutton-example')">
|
|
332
|
-
📋 Copy to LLM
|
|
333
|
-
</button>
|
|
334
|
-
</div>
|
|
335
|
-
<div class="code-block" id="voicebutton-example">import React from 'react';
|
|
336
|
-
import { VoiceButton } from 'ttp-agent-sdk';
|
|
337
|
-
|
|
338
|
-
function App() {
|
|
339
|
-
return (
|
|
340
|
-
<div>
|
|
341
|
-
<h1>My Voice App</h1>
|
|
342
|
-
|
|
343
|
-
<VoiceButton
|
|
344
|
-
websocketUrl="wss://speech.talktopc.com/ws/conv"
|
|
345
|
-
agentId="your_agent_id"
|
|
346
|
-
appId="your_app_id"
|
|
347
|
-
onConnected={() => console.log('Connected!')}
|
|
348
|
-
onRecordingStarted={() => console.log('Recording...')}
|
|
349
|
-
onPlaybackStarted={() => console.log('Playing audio...')}
|
|
350
|
-
/>
|
|
351
|
-
</div>
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export default App;</div>
|
|
356
|
-
</div>
|
|
357
|
-
</div>
|
|
358
|
-
|
|
359
|
-
<div class="section">
|
|
360
|
-
<h2>🔧 Method 3: Enhanced AgentWidget (Recommended)</h2>
|
|
361
|
-
|
|
362
|
-
<div class="code-container">
|
|
363
|
-
<div class="code-header">
|
|
364
|
-
<span class="code-title">VoiceWidget.jsx</span>
|
|
365
|
-
<button class="copy-button" onclick="copyToClipboard('enhanced-example')">
|
|
366
|
-
📋 Copy to LLM
|
|
367
|
-
</button>
|
|
368
|
-
</div>
|
|
369
|
-
<div class="code-block" id="enhanced-example">import React, { useEffect, useRef } from 'react';
|
|
370
|
-
import { EnhancedAgentWidget } from 'ttp-agent-sdk';
|
|
371
|
-
|
|
372
|
-
function VoiceWidget() {
|
|
373
|
-
const widgetRef = useRef(null);
|
|
374
|
-
|
|
375
|
-
useEffect(() => {
|
|
376
|
-
if (widgetRef.current) return;
|
|
377
|
-
|
|
378
|
-
widgetRef.current = new EnhancedAgentWidget({
|
|
379
|
-
agentId: 'your_agent_id',
|
|
380
|
-
|
|
381
|
-
// Get signed URL from your backend
|
|
382
|
-
getSessionUrl: async ({ agentId, variables }) => {
|
|
383
|
-
const response = await fetch('/api/get-voice-session', {
|
|
384
|
-
method: 'POST',
|
|
385
|
-
headers: {
|
|
386
|
-
'Content-Type': 'application/json',
|
|
387
|
-
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
388
|
-
},
|
|
389
|
-
body: JSON.stringify({ agentId, variables })
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const data = await response.json();
|
|
393
|
-
return data.signedUrl;
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
// Customize the widget
|
|
397
|
-
icon: {
|
|
398
|
-
type: 'custom',
|
|
399
|
-
customImage: '/your-logo.png',
|
|
400
|
-
size: 'large'
|
|
401
|
-
},
|
|
402
|
-
|
|
403
|
-
button: {
|
|
404
|
-
primaryColor: '#4f46e5',
|
|
405
|
-
hoverColor: '#4338ca',
|
|
406
|
-
size: 'large'
|
|
407
|
-
},
|
|
408
|
-
|
|
409
|
-
position: {
|
|
410
|
-
vertical: 'bottom',
|
|
411
|
-
horizontal: 'right',
|
|
412
|
-
offset: { x: 30, y: 30 }
|
|
413
|
-
},
|
|
414
|
-
|
|
415
|
-
variables: {
|
|
416
|
-
userId: localStorage.getItem('userId'),
|
|
417
|
-
page: 'homepage'
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
return () => {
|
|
422
|
-
if (widgetRef.current) {
|
|
423
|
-
widgetRef.current.destroy();
|
|
424
|
-
widgetRef.current = null;
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
}, []);
|
|
428
|
-
|
|
429
|
-
return null; // Widget renders itself
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export default VoiceWidget;</div>
|
|
433
|
-
</div>
|
|
434
|
-
</div>
|
|
435
|
-
|
|
436
|
-
<div class="section">
|
|
437
|
-
<h2>🏗️ Complete App Example</h2>
|
|
438
|
-
|
|
439
|
-
<div class="code-container">
|
|
440
|
-
<div class="code-header">
|
|
441
|
-
<span class="code-title">CompleteVoiceApp.jsx</span>
|
|
442
|
-
<button class="copy-button" onclick="copyToClipboard('complete-example')">
|
|
443
|
-
📋 Copy to LLM
|
|
444
|
-
</button>
|
|
445
|
-
</div>
|
|
446
|
-
<div class="code-block" id="complete-example">import React, { useState, useEffect, useRef } from 'react';
|
|
447
|
-
import { VoiceSDK } from 'ttp-agent-sdk';
|
|
448
|
-
|
|
449
|
-
function CompleteVoiceApp() {
|
|
450
|
-
const [isConnected, setIsConnected] = useState(false);
|
|
451
|
-
const [isRecording, setIsRecording] = useState(false);
|
|
452
|
-
const [isPlaying, setIsPlaying] = useState(false);
|
|
453
|
-
const [messages, setMessages] = useState([]);
|
|
454
|
-
const [connectionStatus, setConnectionStatus] = useState('Disconnected');
|
|
455
|
-
|
|
456
|
-
const voiceSDKRef = useRef(null);
|
|
457
|
-
|
|
458
|
-
useEffect(() => {
|
|
459
|
-
const voiceSDK = new VoiceSDK({
|
|
460
|
-
websocketUrl: 'wss://speech.talktopc.com/ws/conv',
|
|
461
|
-
agentId: 'your_agent_id',
|
|
462
|
-
appId: 'your_app_id',
|
|
463
|
-
voice: 'default',
|
|
464
|
-
language: 'en',
|
|
465
|
-
autoReconnect: true
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
// Comprehensive event handlers
|
|
469
|
-
voiceSDK.on('connected', () => {
|
|
470
|
-
setIsConnected(true);
|
|
471
|
-
setConnectionStatus('Connected');
|
|
472
|
-
addMessage('system', 'Connected to voice agent');
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
voiceSDK.on('disconnected', () => {
|
|
476
|
-
setIsConnected(false);
|
|
477
|
-
setIsRecording(false);
|
|
478
|
-
setIsPlaying(false);
|
|
479
|
-
setConnectionStatus('Disconnected');
|
|
480
|
-
addMessage('system', 'Disconnected from voice agent');
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
voiceSDK.on('recordingStarted', () => {
|
|
484
|
-
setIsRecording(true);
|
|
485
|
-
addMessage('user', '🎤 Recording...');
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
voiceSDK.on('recordingStopped', () => {
|
|
489
|
-
setIsRecording(false);
|
|
490
|
-
addMessage('user', '⏹️ Recording stopped');
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
voiceSDK.on('playbackStarted', () => {
|
|
494
|
-
setIsPlaying(true);
|
|
495
|
-
addMessage('agent', '🔊 Agent is speaking...');
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
voiceSDK.on('playbackStopped', () => {
|
|
499
|
-
setIsPlaying(false);
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
voiceSDK.on('message', (message) => {
|
|
503
|
-
if (message.type === 'agent_response') {
|
|
504
|
-
addMessage('agent', message.agent_response);
|
|
505
|
-
} else if (message.type === 'user_transcript') {
|
|
506
|
-
addMessage('user', message.user_transcription);
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
voiceSDK.on('error', (error) => {
|
|
511
|
-
console.error('VoiceSDK Error:', error);
|
|
512
|
-
addMessage('error', `Error: ${error.message}`);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
voiceSDKRef.current = voiceSDK;
|
|
516
|
-
|
|
517
|
-
return () => {
|
|
518
|
-
if (voiceSDKRef.current) {
|
|
519
|
-
voiceSDKRef.current.destroy();
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
}, []);
|
|
523
|
-
|
|
524
|
-
const addMessage = (type, text) => {
|
|
525
|
-
setMessages(prev => [...prev, {
|
|
526
|
-
type,
|
|
527
|
-
text,
|
|
528
|
-
timestamp: new Date()
|
|
529
|
-
}]);
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
const handleConnect = async () => {
|
|
533
|
-
if (voiceSDKRef.current) {
|
|
534
|
-
try {
|
|
535
|
-
setConnectionStatus('Connecting...');
|
|
536
|
-
await voiceSDKRef.current.connect();
|
|
537
|
-
} catch (error) {
|
|
538
|
-
console.error('Connection failed:', error);
|
|
539
|
-
setConnectionStatus('Connection failed');
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
const handleDisconnect = () => {
|
|
545
|
-
if (voiceSDKRef.current) {
|
|
546
|
-
voiceSDKRef.current.disconnect();
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
const handleToggleRecording = async () => {
|
|
551
|
-
if (voiceSDKRef.current) {
|
|
552
|
-
try {
|
|
553
|
-
if (isRecording) {
|
|
554
|
-
await voiceSDKRef.current.stopRecording();
|
|
555
|
-
} else {
|
|
556
|
-
await voiceSDKRef.current.startRecording();
|
|
557
|
-
}
|
|
558
|
-
} catch (error) {
|
|
559
|
-
console.error('Recording toggle failed:', error);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
return (
|
|
565
|
-
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
|
|
566
|
-
<h1>🎤 Voice Chat App</h1>
|
|
567
|
-
|
|
568
|
-
{/* Status Indicator */}
|
|
569
|
-
<div style={{
|
|
570
|
-
padding: '12px',
|
|
571
|
-
borderRadius: '8px',
|
|
572
|
-
marginBottom: '20px',
|
|
573
|
-
backgroundColor: isConnected ? '#d1fae5' : '#fee2e2',
|
|
574
|
-
color: isConnected ? '#065f46' : '#991b1b',
|
|
575
|
-
border: `1px solid ${isConnected ? '#10b981' : '#ef4444'}`
|
|
576
|
-
}}>
|
|
577
|
-
<strong>Status:</strong> {connectionStatus}
|
|
578
|
-
</div>
|
|
579
|
-
|
|
580
|
-
{/* Control Buttons */}
|
|
581
|
-
<div style={{ display: 'flex', gap: '12px', marginBottom: '20px' }}>
|
|
582
|
-
<button
|
|
583
|
-
onClick={handleConnect}
|
|
584
|
-
disabled={isConnected}
|
|
585
|
-
style={{
|
|
586
|
-
padding: '12px 24px',
|
|
587
|
-
backgroundColor: isConnected ? '#9ca3af' : '#4f46e5',
|
|
588
|
-
color: 'white',
|
|
589
|
-
border: 'none',
|
|
590
|
-
borderRadius: '6px',
|
|
591
|
-
cursor: isConnected ? 'not-allowed' : 'pointer'
|
|
592
|
-
}}
|
|
593
|
-
>
|
|
594
|
-
Connect
|
|
595
|
-
</button>
|
|
596
|
-
|
|
597
|
-
<button
|
|
598
|
-
onClick={handleDisconnect}
|
|
599
|
-
disabled={!isConnected}
|
|
600
|
-
style={{
|
|
601
|
-
padding: '12px 24px',
|
|
602
|
-
backgroundColor: !isConnected ? '#9ca3af' : '#ef4444',
|
|
603
|
-
color: 'white',
|
|
604
|
-
border: 'none',
|
|
605
|
-
borderRadius: '6px',
|
|
606
|
-
cursor: !isConnected ? 'not-allowed' : 'pointer'
|
|
607
|
-
}}
|
|
608
|
-
>
|
|
609
|
-
Disconnect
|
|
610
|
-
</button>
|
|
611
|
-
|
|
612
|
-
<button
|
|
613
|
-
onClick={handleToggleRecording}
|
|
614
|
-
disabled={!isConnected}
|
|
615
|
-
style={{
|
|
616
|
-
padding: '12px 24px',
|
|
617
|
-
backgroundColor: !isConnected ? '#9ca3af' : (isRecording ? '#ef4444' : '#10b981'),
|
|
618
|
-
color: 'white',
|
|
619
|
-
border: 'none',
|
|
620
|
-
borderRadius: '6px',
|
|
621
|
-
cursor: !isConnected ? 'not-allowed' : 'pointer'
|
|
622
|
-
}}
|
|
623
|
-
>
|
|
624
|
-
{isRecording ? 'Stop Recording' : 'Start Recording'}
|
|
625
|
-
</button>
|
|
626
|
-
</div>
|
|
627
|
-
|
|
628
|
-
{/* Messages Area */}
|
|
629
|
-
<div style={{
|
|
630
|
-
border: '1px solid #e5e7eb',
|
|
631
|
-
borderRadius: '8px',
|
|
632
|
-
height: '400px',
|
|
633
|
-
overflowY: 'auto',
|
|
634
|
-
padding: '16px',
|
|
635
|
-
backgroundColor: '#f9fafb'
|
|
636
|
-
}}>
|
|
637
|
-
<h3>Messages:</h3>
|
|
638
|
-
{messages.length === 0 ? (
|
|
639
|
-
<p style={{ color: '#6b7280', fontStyle: 'italic' }}>
|
|
640
|
-
No messages yet. Connect and start recording to begin the conversation.
|
|
641
|
-
</p>
|
|
642
|
-
) : (
|
|
643
|
-
messages.map((message, index) => (
|
|
644
|
-
<div
|
|
645
|
-
key={index}
|
|
646
|
-
style={{
|
|
647
|
-
marginBottom: '12px',
|
|
648
|
-
padding: '8px 12px',
|
|
649
|
-
borderRadius: '6px',
|
|
650
|
-
backgroundColor: message.type === 'user' ? '#e5e7eb' :
|
|
651
|
-
message.type === 'agent' ? '#f3f4f6' :
|
|
652
|
-
message.type === 'error' ? '#fee2e2' : '#eff6ff',
|
|
653
|
-
color: message.type === 'error' ? '#991b1b' : '#111827',
|
|
654
|
-
alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start',
|
|
655
|
-
maxWidth: '80%',
|
|
656
|
-
marginLeft: message.type === 'user' ? 'auto' : '0',
|
|
657
|
-
marginRight: message.type === 'user' ? '0' : 'auto'
|
|
658
|
-
}}
|
|
659
|
-
>
|
|
660
|
-
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
|
|
661
|
-
{message.type === 'user' ? '👤 You' :
|
|
662
|
-
message.type === 'agent' ? '🤖 Agent' :
|
|
663
|
-
message.type === 'error' ? '❌ Error' : 'ℹ️ System'}
|
|
664
|
-
</div>
|
|
665
|
-
<div>{message.text}</div>
|
|
666
|
-
<div style={{
|
|
667
|
-
fontSize: '12px',
|
|
668
|
-
color: '#6b7280',
|
|
669
|
-
marginTop: '4px'
|
|
670
|
-
}}>
|
|
671
|
-
{message.timestamp.toLocaleTimeString()}
|
|
672
|
-
</div>
|
|
673
|
-
</div>
|
|
674
|
-
))
|
|
675
|
-
)}
|
|
676
|
-
</div>
|
|
677
|
-
|
|
678
|
-
{/* Status Indicators */}
|
|
679
|
-
<div style={{
|
|
680
|
-
display: 'flex',
|
|
681
|
-
gap: '20px',
|
|
682
|
-
marginTop: '20px',
|
|
683
|
-
fontSize: '14px',
|
|
684
|
-
color: '#6b7280'
|
|
685
|
-
}}>
|
|
686
|
-
<div>
|
|
687
|
-
<span style={{
|
|
688
|
-
display: 'inline-block',
|
|
689
|
-
width: '8px',
|
|
690
|
-
height: '8px',
|
|
691
|
-
borderRadius: '50%',
|
|
692
|
-
backgroundColor: isConnected ? '#10b981' : '#ef4444',
|
|
693
|
-
marginRight: '8px'
|
|
694
|
-
}}></span>
|
|
695
|
-
Connection
|
|
696
|
-
</div>
|
|
697
|
-
<div>
|
|
698
|
-
<span style={{
|
|
699
|
-
display: 'inline-block',
|
|
700
|
-
width: '8px',
|
|
701
|
-
height: '8px',
|
|
702
|
-
borderRadius: '50%',
|
|
703
|
-
backgroundColor: isRecording ? '#ef4444' : '#9ca3af',
|
|
704
|
-
marginRight: '8px'
|
|
705
|
-
}}></span>
|
|
706
|
-
Recording
|
|
707
|
-
</div>
|
|
708
|
-
<div>
|
|
709
|
-
<span style={{
|
|
710
|
-
display: 'inline-block',
|
|
711
|
-
width: '8px',
|
|
712
|
-
height: '8px',
|
|
713
|
-
borderRadius: '50%',
|
|
714
|
-
backgroundColor: isPlaying ? '#10b981' : '#9ca3af',
|
|
715
|
-
marginRight: '8px'
|
|
716
|
-
}}></span>
|
|
717
|
-
Playing
|
|
718
|
-
</div>
|
|
719
|
-
</div>
|
|
720
|
-
</div>
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
export default CompleteVoiceApp;</div>
|
|
725
|
-
</div>
|
|
726
|
-
</div>
|
|
727
|
-
|
|
728
|
-
<div class="feature-grid">
|
|
729
|
-
<div class="feature-card">
|
|
730
|
-
<h3>🎯 Method 1: Basic VoiceSDK</h3>
|
|
731
|
-
<p>Direct integration with VoiceSDK class. Full control over events and state management.</p>
|
|
732
|
-
</div>
|
|
733
|
-
|
|
734
|
-
<div class="feature-card">
|
|
735
|
-
<h3>🎨 Method 2: VoiceButton</h3>
|
|
736
|
-
<p>Pre-built React component. Quick integration with minimal code.</p>
|
|
737
|
-
</div>
|
|
738
|
-
|
|
739
|
-
<div class="feature-card">
|
|
740
|
-
<h3>🔧 Method 3: Enhanced Widget</h3>
|
|
741
|
-
<p>Advanced customizable widget with signed link authentication. Production-ready.</p>
|
|
742
|
-
</div>
|
|
743
|
-
</div>
|
|
744
|
-
|
|
745
|
-
<div class="note">
|
|
746
|
-
<strong>💡 Pro Tip:</strong> For production applications, use Method 3 (Enhanced AgentWidget) with signed link authentication.
|
|
747
|
-
It provides the best security, customization options, and user experience.
|
|
748
|
-
</div>
|
|
749
|
-
</div>
|
|
750
|
-
|
|
751
|
-
<script>
|
|
752
|
-
function copyToClipboard(elementId) {
|
|
753
|
-
const element = document.getElementById(elementId);
|
|
754
|
-
const text = element.textContent;
|
|
755
|
-
|
|
756
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
757
|
-
const button = element.parentElement.querySelector('.copy-button');
|
|
758
|
-
const originalText = button.textContent;
|
|
759
|
-
|
|
760
|
-
button.textContent = '✅ Copied!';
|
|
761
|
-
button.classList.add('copied');
|
|
762
|
-
|
|
763
|
-
setTimeout(() => {
|
|
764
|
-
button.textContent = originalText;
|
|
765
|
-
button.classList.remove('copied');
|
|
766
|
-
}, 2000);
|
|
767
|
-
}).catch(err => {
|
|
768
|
-
console.error('Failed to copy: ', err);
|
|
769
|
-
alert('Failed to copy to clipboard');
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
</script>
|
|
773
|
-
</body>
|
|
774
|
-
</html>
|