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,464 @@
|
|
|
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>VoiceSDK - Vanilla JavaScript Example</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
10
|
+
max-width: 1000px;
|
|
11
|
+
margin: 0 auto;
|
|
12
|
+
padding: 20px;
|
|
13
|
+
background: #F9FAFB;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.container {
|
|
17
|
+
background: white;
|
|
18
|
+
padding: 30px;
|
|
19
|
+
border-radius: 12px;
|
|
20
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
21
|
+
margin-bottom: 20px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
h1 {
|
|
25
|
+
color: #111827;
|
|
26
|
+
margin-top: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.status {
|
|
30
|
+
padding: 12px;
|
|
31
|
+
border-radius: 6px;
|
|
32
|
+
margin: 20px 0;
|
|
33
|
+
font-family: monospace;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.status.connected {
|
|
38
|
+
background: #D1FAE5;
|
|
39
|
+
color: #065F46;
|
|
40
|
+
border: 1px solid #10B981;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.status.disconnected {
|
|
44
|
+
background: #FEE2E2;
|
|
45
|
+
color: #991B1B;
|
|
46
|
+
border: 1px solid #EF4444;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.status.connecting {
|
|
50
|
+
background: #FEF3C7;
|
|
51
|
+
color: #92400E;
|
|
52
|
+
border: 1px solid #F59E0B;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.controls {
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 12px;
|
|
58
|
+
margin: 20px 0;
|
|
59
|
+
flex-wrap: wrap;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
button {
|
|
63
|
+
background: #4F46E5;
|
|
64
|
+
color: white;
|
|
65
|
+
border: none;
|
|
66
|
+
padding: 12px 24px;
|
|
67
|
+
border-radius: 6px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
font-size: 16px;
|
|
70
|
+
transition: background-color 0.2s;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
button:hover:not(:disabled) {
|
|
74
|
+
background: #4338CA;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
button:disabled {
|
|
78
|
+
background: #9CA3AF;
|
|
79
|
+
cursor: not-allowed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
button.recording {
|
|
83
|
+
background: #EF4444;
|
|
84
|
+
animation: pulse 1.5s infinite;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
button.recording:hover {
|
|
88
|
+
background: #DC2626;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keyframes pulse {
|
|
92
|
+
0%, 100% { transform: scale(1); }
|
|
93
|
+
50% { transform: scale(1.05); }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.messages {
|
|
97
|
+
border: 1px solid #E5E7EB;
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
height: 300px;
|
|
100
|
+
overflow-y: auto;
|
|
101
|
+
padding: 16px;
|
|
102
|
+
background: #F9FAFB;
|
|
103
|
+
margin: 20px 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.message {
|
|
107
|
+
margin-bottom: 12px;
|
|
108
|
+
padding: 8px 12px;
|
|
109
|
+
border-radius: 6px;
|
|
110
|
+
max-width: 80%;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.message.user {
|
|
114
|
+
background: #E5E7EB;
|
|
115
|
+
margin-left: auto;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.message.agent {
|
|
119
|
+
background: #F3F4F6;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.message.system {
|
|
123
|
+
background: #EFF6FF;
|
|
124
|
+
color: #1E40AF;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.message.error {
|
|
128
|
+
background: #FEE2E2;
|
|
129
|
+
color: #991B1B;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.indicators {
|
|
133
|
+
display: flex;
|
|
134
|
+
gap: 20px;
|
|
135
|
+
margin-top: 20px;
|
|
136
|
+
font-size: 14px;
|
|
137
|
+
color: #6B7280;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.indicator {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 8px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.indicator-dot {
|
|
147
|
+
width: 8px;
|
|
148
|
+
height: 8px;
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
background: #9CA3AF;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.indicator-dot.active {
|
|
154
|
+
background: #10B981;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.indicator-dot.recording {
|
|
158
|
+
background: #EF4444;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.code-block {
|
|
162
|
+
background: #1F2937;
|
|
163
|
+
color: #F9FAFB;
|
|
164
|
+
padding: 16px;
|
|
165
|
+
border-radius: 6px;
|
|
166
|
+
font-family: monospace;
|
|
167
|
+
font-size: 12px;
|
|
168
|
+
overflow-x: auto;
|
|
169
|
+
margin: 20px 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.voice-button-demo {
|
|
173
|
+
margin: 20px 0;
|
|
174
|
+
padding: 20px;
|
|
175
|
+
border: 2px dashed #D1D5DB;
|
|
176
|
+
border-radius: 8px;
|
|
177
|
+
text-align: center;
|
|
178
|
+
}
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<div class="container">
|
|
183
|
+
<h1>🎤 VoiceSDK - Vanilla JavaScript Example</h1>
|
|
184
|
+
|
|
185
|
+
<div class="info">
|
|
186
|
+
<p>This example demonstrates how to use the VoiceSDK with vanilla JavaScript.
|
|
187
|
+
The SDK provides real-time voice interaction with AI agents using WebSocket communication.</p>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Connection Status -->
|
|
191
|
+
<div id="status" class="status disconnected">
|
|
192
|
+
Status: Disconnected
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!-- Controls -->
|
|
196
|
+
<div class="controls">
|
|
197
|
+
<button id="connectBtn">Connect</button>
|
|
198
|
+
<button id="disconnectBtn" disabled>Disconnect</button>
|
|
199
|
+
<button id="recordBtn" disabled>Start Recording</button>
|
|
200
|
+
<button id="stopBtn" disabled>Stop Recording</button>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<!-- Voice Button Demo -->
|
|
204
|
+
<div class="voice-button-demo">
|
|
205
|
+
<h3>Voice Button Component Demo:</h3>
|
|
206
|
+
<p>This is a pre-built voice button component:</p>
|
|
207
|
+
<div id="voice-button-container"></div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<!-- Messages -->
|
|
211
|
+
<div class="messages" id="messages">
|
|
212
|
+
<div class="message system">
|
|
213
|
+
<strong>System:</strong> Ready to connect. Click "Connect" to start.
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<!-- Status Indicators -->
|
|
218
|
+
<div class="indicators">
|
|
219
|
+
<div class="indicator">
|
|
220
|
+
<div id="connectionDot" class="indicator-dot"></div>
|
|
221
|
+
<span>Connection</span>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="indicator">
|
|
224
|
+
<div id="recordingDot" class="indicator-dot"></div>
|
|
225
|
+
<span>Recording</span>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="indicator">
|
|
228
|
+
<div id="playingDot" class="indicator-dot"></div>
|
|
229
|
+
<span>Playing</span>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<!-- Code Example -->
|
|
234
|
+
<div class="code-block">
|
|
235
|
+
<div>// VoiceSDK Usage Example</div>
|
|
236
|
+
<div>const voiceSDK = new VoiceSDK({</div>
|
|
237
|
+
<div> websocketUrl: 'wss://speech.bidme.co.il/ws/conv',</div>
|
|
238
|
+
<div> agentId: 'your_agent_id',</div>
|
|
239
|
+
<div> appId: 'your_app_id'</div>
|
|
240
|
+
<div>});</div>
|
|
241
|
+
<div></div>
|
|
242
|
+
<div>voiceSDK.on('connected', () => console.log('Connected!'));</div>
|
|
243
|
+
<div>voiceSDK.on('recordingStarted', () => console.log('Recording...'));</div>
|
|
244
|
+
<div></div>
|
|
245
|
+
<div>await voiceSDK.connect();</div>
|
|
246
|
+
<div>await voiceSDK.startRecording();</div>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Load the SDK -->
|
|
251
|
+
<script src="../agent-widget.js"></script>
|
|
252
|
+
|
|
253
|
+
<script>
|
|
254
|
+
// VoiceSDK instance
|
|
255
|
+
let voiceSDK = null;
|
|
256
|
+
let isConnected = false;
|
|
257
|
+
let isRecording = false;
|
|
258
|
+
let isPlaying = false;
|
|
259
|
+
|
|
260
|
+
// DOM elements
|
|
261
|
+
const statusDiv = document.getElementById('status');
|
|
262
|
+
const messagesDiv = document.getElementById('messages');
|
|
263
|
+
const connectBtn = document.getElementById('connectBtn');
|
|
264
|
+
const disconnectBtn = document.getElementById('disconnectBtn');
|
|
265
|
+
const recordBtn = document.getElementById('recordBtn');
|
|
266
|
+
const stopBtn = document.getElementById('stopBtn');
|
|
267
|
+
const connectionDot = document.getElementById('connectionDot');
|
|
268
|
+
const recordingDot = document.getElementById('recordingDot');
|
|
269
|
+
const playingDot = document.getElementById('playingDot');
|
|
270
|
+
|
|
271
|
+
// Initialize VoiceSDK
|
|
272
|
+
function initializeVoiceSDK() {
|
|
273
|
+
voiceSDK = new TTPAgentSDK.VoiceSDK({
|
|
274
|
+
websocketUrl: 'wss://speech.bidme.co.il/ws/conv',
|
|
275
|
+
agentId: 'demo_agent_123',
|
|
276
|
+
appId: 'demo_app_456',
|
|
277
|
+
voice: 'default',
|
|
278
|
+
language: 'en',
|
|
279
|
+
autoReconnect: true
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Set up event handlers
|
|
283
|
+
voiceSDK.on('connected', () => {
|
|
284
|
+
isConnected = true;
|
|
285
|
+
updateStatus('Connected', 'connected');
|
|
286
|
+
updateIndicators();
|
|
287
|
+
updateButtons();
|
|
288
|
+
addMessage('system', 'Connected to voice agent successfully!');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
voiceSDK.on('disconnected', () => {
|
|
292
|
+
isConnected = false;
|
|
293
|
+
isRecording = false;
|
|
294
|
+
isPlaying = false;
|
|
295
|
+
updateStatus('Disconnected', 'disconnected');
|
|
296
|
+
updateIndicators();
|
|
297
|
+
updateButtons();
|
|
298
|
+
addMessage('system', 'Disconnected from voice agent');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
voiceSDK.on('recordingStarted', () => {
|
|
302
|
+
isRecording = true;
|
|
303
|
+
updateIndicators();
|
|
304
|
+
updateButtons();
|
|
305
|
+
addMessage('user', '🎤 Recording started...');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
voiceSDK.on('recordingStopped', () => {
|
|
309
|
+
isRecording = false;
|
|
310
|
+
updateIndicators();
|
|
311
|
+
updateButtons();
|
|
312
|
+
addMessage('user', '⏹️ Recording stopped');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
voiceSDK.on('playbackStarted', () => {
|
|
316
|
+
isPlaying = true;
|
|
317
|
+
updateIndicators();
|
|
318
|
+
addMessage('agent', '🔊 Agent is speaking...');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
voiceSDK.on('playbackStopped', () => {
|
|
322
|
+
isPlaying = false;
|
|
323
|
+
updateIndicators();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
voiceSDK.on('message', (message) => {
|
|
327
|
+
if (message.type === 'agent_response') {
|
|
328
|
+
addMessage('agent', message.agent_response);
|
|
329
|
+
} else if (message.type === 'user_transcript') {
|
|
330
|
+
addMessage('user', message.user_transcription);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
voiceSDK.on('error', (error) => {
|
|
335
|
+
console.error('VoiceSDK Error:', error);
|
|
336
|
+
addMessage('error', `Error: ${error.message}`);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Update status display
|
|
341
|
+
function updateStatus(message, type) {
|
|
342
|
+
statusDiv.textContent = `Status: ${message}`;
|
|
343
|
+
statusDiv.className = `status ${type}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Update status indicators
|
|
347
|
+
function updateIndicators() {
|
|
348
|
+
connectionDot.className = `indicator-dot ${isConnected ? 'active' : ''}`;
|
|
349
|
+
recordingDot.className = `indicator-dot ${isRecording ? 'recording' : ''}`;
|
|
350
|
+
playingDot.className = `indicator-dot ${isPlaying ? 'active' : ''}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Update button states
|
|
354
|
+
function updateButtons() {
|
|
355
|
+
connectBtn.disabled = isConnected;
|
|
356
|
+
disconnectBtn.disabled = !isConnected;
|
|
357
|
+
recordBtn.disabled = !isConnected || isRecording;
|
|
358
|
+
stopBtn.disabled = !isConnected || !isRecording;
|
|
359
|
+
|
|
360
|
+
if (isRecording) {
|
|
361
|
+
recordBtn.classList.add('recording');
|
|
362
|
+
} else {
|
|
363
|
+
recordBtn.classList.remove('recording');
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Add message to chat
|
|
368
|
+
function addMessage(type, text) {
|
|
369
|
+
const messageDiv = document.createElement('div');
|
|
370
|
+
messageDiv.className = `message ${type}`;
|
|
371
|
+
|
|
372
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
373
|
+
const typeLabel = type === 'user' ? '👤 You' :
|
|
374
|
+
type === 'agent' ? '🤖 Agent' :
|
|
375
|
+
type === 'error' ? '❌ Error' : 'ℹ️ System';
|
|
376
|
+
|
|
377
|
+
messageDiv.innerHTML = `
|
|
378
|
+
<div style="font-weight: bold; margin-bottom: 4px;">${typeLabel}</div>
|
|
379
|
+
<div>${text}</div>
|
|
380
|
+
<div style="font-size: 12px; color: #6B7280; margin-top: 4px;">${timestamp}</div>
|
|
381
|
+
`;
|
|
382
|
+
|
|
383
|
+
messagesDiv.appendChild(messageDiv);
|
|
384
|
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Event handlers
|
|
388
|
+
connectBtn.addEventListener('click', async () => {
|
|
389
|
+
try {
|
|
390
|
+
updateStatus('Connecting...', 'connecting');
|
|
391
|
+
await voiceSDK.connect();
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error('Connection failed:', error);
|
|
394
|
+
updateStatus('Connection failed', 'disconnected');
|
|
395
|
+
addMessage('error', `Connection failed: ${error.message}`);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
disconnectBtn.addEventListener('click', () => {
|
|
400
|
+
voiceSDK.disconnect();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
recordBtn.addEventListener('click', async () => {
|
|
404
|
+
try {
|
|
405
|
+
await voiceSDK.startRecording();
|
|
406
|
+
} catch (error) {
|
|
407
|
+
console.error('Recording start failed:', error);
|
|
408
|
+
addMessage('error', `Recording failed: ${error.message}`);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
stopBtn.addEventListener('click', async () => {
|
|
413
|
+
try {
|
|
414
|
+
await voiceSDK.stopRecording();
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.error('Recording stop failed:', error);
|
|
417
|
+
addMessage('error', `Stop recording failed: ${error.message}`);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Initialize Voice Button Component
|
|
422
|
+
function initializeVoiceButton() {
|
|
423
|
+
const container = document.getElementById('voice-button-container');
|
|
424
|
+
|
|
425
|
+
// Create a simple voice button using the VanillaVoiceButton
|
|
426
|
+
const voiceButton = new TTPAgentSDK.VanillaVoiceButton({
|
|
427
|
+
container: container,
|
|
428
|
+
websocketUrl: 'wss://speech.bidme.co.il/ws/conv',
|
|
429
|
+
agentId: 'demo_agent_123',
|
|
430
|
+
appId: 'demo_app_456',
|
|
431
|
+
onConnected: () => {
|
|
432
|
+
console.log('Voice Button connected');
|
|
433
|
+
addMessage('system', 'Voice Button connected');
|
|
434
|
+
},
|
|
435
|
+
onRecordingStarted: () => {
|
|
436
|
+
console.log('Voice Button recording started');
|
|
437
|
+
addMessage('system', 'Voice Button recording started');
|
|
438
|
+
},
|
|
439
|
+
onPlaybackStarted: () => {
|
|
440
|
+
console.log('Voice Button playback started');
|
|
441
|
+
addMessage('system', 'Voice Button playback started');
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Initialize everything when page loads
|
|
447
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
448
|
+
console.log('Initializing VoiceSDK example...');
|
|
449
|
+
|
|
450
|
+
// Check if SDK is loaded
|
|
451
|
+
if (typeof TTPAgentSDK === 'undefined') {
|
|
452
|
+
addMessage('error', 'VoiceSDK not loaded. Make sure the script is included.');
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
initializeVoiceSDK();
|
|
457
|
+
initializeVoiceButton();
|
|
458
|
+
|
|
459
|
+
addMessage('system', 'VoiceSDK example initialized. Click "Connect" to start.');
|
|
460
|
+
console.log('VoiceSDK example ready!');
|
|
461
|
+
});
|
|
462
|
+
</script>
|
|
463
|
+
</body>
|
|
464
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ttp-agent-sdk",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Comprehensive Voice Agent SDK for web integration with real-time audio, WebSocket communication, and React components",
|
|
5
|
+
"main": "dist/agent-widget.js",
|
|
6
|
+
"module": "src/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/",
|
|
10
|
+
"src/",
|
|
11
|
+
"examples/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"GETTING_STARTED.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "webpack --mode production",
|
|
17
|
+
"build:dev": "webpack --mode development",
|
|
18
|
+
"dev": "webpack serve --mode development",
|
|
19
|
+
"watch": "webpack --watch --mode development",
|
|
20
|
+
"start-backend": "node test-server.js",
|
|
21
|
+
"test": "npm run start-backend",
|
|
22
|
+
"clean": "rm -rf dist/",
|
|
23
|
+
"prebuild": "npm run clean"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"voice",
|
|
27
|
+
"agent",
|
|
28
|
+
"sdk",
|
|
29
|
+
"widget",
|
|
30
|
+
"websocket",
|
|
31
|
+
"audio",
|
|
32
|
+
"real-time",
|
|
33
|
+
"react",
|
|
34
|
+
"ai",
|
|
35
|
+
"conversation"
|
|
36
|
+
],
|
|
37
|
+
"author": "TTP Agent Team",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/yinon11/ttp-sdk-front.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/yinon11/ttp-sdk-front/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/yinon11/ttp-sdk-front#readme",
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=16.8.0",
|
|
49
|
+
"react-dom": ">=16.8.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@babel/core": "^7.23.0",
|
|
53
|
+
"@babel/preset-env": "^7.23.0",
|
|
54
|
+
"@babel/preset-react": "^7.22.0",
|
|
55
|
+
"babel-loader": "^9.1.0",
|
|
56
|
+
"copy-webpack-plugin": "^13.0.1",
|
|
57
|
+
"css-loader": "^6.8.0",
|
|
58
|
+
"style-loader": "^3.3.0",
|
|
59
|
+
"webpack": "^5.102.1",
|
|
60
|
+
"webpack-cli": "^6.0.1",
|
|
61
|
+
"webpack-dev-server": "^5.2.2"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer - Handles audio playback with queue system
|
|
3
|
+
*/
|
|
4
|
+
import EventEmitter from './EventEmitter.js';
|
|
5
|
+
|
|
6
|
+
export default class AudioPlayer extends EventEmitter {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
super();
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.audioContext = null;
|
|
11
|
+
this.audioQueue = [];
|
|
12
|
+
this.isPlaying = false;
|
|
13
|
+
this.isProcessingQueue = false;
|
|
14
|
+
this.currentSource = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Add audio data to playback queue
|
|
19
|
+
*/
|
|
20
|
+
playAudio(audioData) {
|
|
21
|
+
try {
|
|
22
|
+
const audioBlob = this.createAudioBlob(audioData);
|
|
23
|
+
this.audioQueue.push(audioBlob);
|
|
24
|
+
|
|
25
|
+
// Process queue if not already playing or processing
|
|
26
|
+
if (!this.isPlaying && !this.isProcessingQueue) {
|
|
27
|
+
setTimeout(() => this.processQueue(), 50);
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.emit('playbackError', error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create audio blob from ArrayBuffer
|
|
36
|
+
*/
|
|
37
|
+
createAudioBlob(arrayBuffer) {
|
|
38
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
39
|
+
|
|
40
|
+
// Detect audio format
|
|
41
|
+
if (uint8Array.length >= 4) {
|
|
42
|
+
// WAV header (RIFF)
|
|
43
|
+
if (uint8Array[0] === 0x52 && uint8Array[1] === 0x49 &&
|
|
44
|
+
uint8Array[2] === 0x46 && uint8Array[3] === 0x46) {
|
|
45
|
+
return new Blob([arrayBuffer], { type: 'audio/wav' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MP3 header
|
|
49
|
+
if (uint8Array[0] === 0xFF && (uint8Array[1] & 0xE0) === 0xE0) {
|
|
50
|
+
return new Blob([arrayBuffer], { type: 'audio/mpeg' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// OGG header
|
|
54
|
+
if (uint8Array[0] === 0x4F && uint8Array[1] === 0x67 &&
|
|
55
|
+
uint8Array[2] === 0x67 && uint8Array[3] === 0x53) {
|
|
56
|
+
return new Blob([arrayBuffer], { type: 'audio/ogg' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Default to WAV format
|
|
61
|
+
return new Blob([arrayBuffer], { type: 'audio/wav' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Process audio queue
|
|
66
|
+
*/
|
|
67
|
+
async processQueue() {
|
|
68
|
+
// Prevent multiple simultaneous queue processing
|
|
69
|
+
if (this.isProcessingQueue || this.isPlaying || this.audioQueue.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.isProcessingQueue = true;
|
|
74
|
+
|
|
75
|
+
const audioBlob = this.audioQueue.shift();
|
|
76
|
+
if (!audioBlob) {
|
|
77
|
+
this.isProcessingQueue = false;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
this.isPlaying = true;
|
|
83
|
+
this.emit('playbackStarted');
|
|
84
|
+
|
|
85
|
+
// Create AudioContext if not exists
|
|
86
|
+
if (!this.audioContext) {
|
|
87
|
+
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const audioContext = this.audioContext;
|
|
91
|
+
|
|
92
|
+
// Resume AudioContext if suspended
|
|
93
|
+
if (audioContext.state === 'suspended') {
|
|
94
|
+
await audioContext.resume();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Create audio source from blob
|
|
98
|
+
const arrayBuffer = await audioBlob.arrayBuffer();
|
|
99
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
100
|
+
|
|
101
|
+
const source = audioContext.createBufferSource();
|
|
102
|
+
source.buffer = audioBuffer;
|
|
103
|
+
source.connect(audioContext.destination);
|
|
104
|
+
|
|
105
|
+
this.currentSource = source;
|
|
106
|
+
|
|
107
|
+
// Handle audio end
|
|
108
|
+
source.onended = () => {
|
|
109
|
+
this.isPlaying = false;
|
|
110
|
+
this.isProcessingQueue = false;
|
|
111
|
+
this.currentSource = null;
|
|
112
|
+
this.emit('playbackStopped');
|
|
113
|
+
|
|
114
|
+
// Process next audio in queue if there are more items
|
|
115
|
+
if (this.audioQueue.length > 0) {
|
|
116
|
+
setTimeout(() => this.processQueue(), 100);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Start playback
|
|
121
|
+
source.start();
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.isPlaying = false;
|
|
125
|
+
this.isProcessingQueue = false;
|
|
126
|
+
this.currentSource = null;
|
|
127
|
+
this.emit('playbackError', error);
|
|
128
|
+
|
|
129
|
+
// Try to process next audio in queue if there are more items
|
|
130
|
+
if (this.audioQueue.length > 0) {
|
|
131
|
+
setTimeout(() => this.processQueue(), 100);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Stop current playback and clear queue
|
|
138
|
+
*/
|
|
139
|
+
stop() {
|
|
140
|
+
this.stopImmediate();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Stop current playback immediately and clear queue
|
|
145
|
+
*/
|
|
146
|
+
stopImmediate() {
|
|
147
|
+
if (this.currentSource) {
|
|
148
|
+
try {
|
|
149
|
+
this.currentSource.stop();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// Ignore errors when stopping
|
|
152
|
+
}
|
|
153
|
+
this.currentSource = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.isPlaying = false;
|
|
157
|
+
this.isProcessingQueue = false;
|
|
158
|
+
this.audioQueue = [];
|
|
159
|
+
this.emit('playbackStopped');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get playback status
|
|
164
|
+
*/
|
|
165
|
+
getStatus() {
|
|
166
|
+
return {
|
|
167
|
+
isPlaying: this.isPlaying,
|
|
168
|
+
isProcessingQueue: this.isProcessingQueue,
|
|
169
|
+
queueLength: this.audioQueue.length,
|
|
170
|
+
audioContextState: this.audioContext ? this.audioContext.state : 'closed'
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Cleanup resources
|
|
176
|
+
*/
|
|
177
|
+
destroy() {
|
|
178
|
+
this.stop();
|
|
179
|
+
|
|
180
|
+
if (this.audioContext && this.audioContext.state !== 'closed') {
|
|
181
|
+
this.audioContext.close();
|
|
182
|
+
this.audioContext = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|