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.
@@ -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
+ }