ttp-agent-sdk 2.1.0 → 2.1.2

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,1119 @@
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>TTP Agent SDK - Multi-Platform Examples</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
+ .tabs {
87
+ display: flex;
88
+ background: #f1f5f9;
89
+ border-radius: 8px 8px 0 0;
90
+ overflow: hidden;
91
+ margin-bottom: 0;
92
+ }
93
+
94
+ .tab {
95
+ flex: 1;
96
+ padding: 12px 20px;
97
+ background: #f1f5f9;
98
+ border: none;
99
+ cursor: pointer;
100
+ font-weight: 600;
101
+ color: #64748b;
102
+ transition: all 0.2s;
103
+ border-bottom: 3px solid transparent;
104
+ }
105
+
106
+ .tab.active {
107
+ background: white;
108
+ color: #4f46e5;
109
+ border-bottom-color: #4f46e5;
110
+ }
111
+
112
+ .tab:hover:not(.active) {
113
+ background: #e2e8f0;
114
+ color: #475569;
115
+ }
116
+
117
+ .tab-content {
118
+ display: none;
119
+ }
120
+
121
+ .tab-content.active {
122
+ display: block;
123
+ }
124
+
125
+ .code-container {
126
+ position: relative;
127
+ background: #1e293b;
128
+ border-radius: 0 0 12px 12px;
129
+ overflow: hidden;
130
+ margin: 0 0 20px 0;
131
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
132
+ }
133
+
134
+ .code-header {
135
+ background: #334155;
136
+ padding: 12px 20px;
137
+ display: flex;
138
+ justify-content: space-between;
139
+ align-items: center;
140
+ border-bottom: 1px solid #475569;
141
+ }
142
+
143
+ .code-title {
144
+ color: #e2e8f0;
145
+ font-weight: 600;
146
+ font-size: 14px;
147
+ }
148
+
149
+ .copy-button {
150
+ background: #4f46e5;
151
+ color: white;
152
+ border: none;
153
+ padding: 8px 16px;
154
+ border-radius: 6px;
155
+ font-size: 12px;
156
+ font-weight: 600;
157
+ cursor: pointer;
158
+ transition: all 0.2s;
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 6px;
162
+ }
163
+
164
+ .copy-button:hover {
165
+ background: #4338ca;
166
+ transform: translateY(-1px);
167
+ }
168
+
169
+ .copy-button.copied {
170
+ background: #10b981;
171
+ }
172
+
173
+ .code-block {
174
+ background: #1e293b;
175
+ color: #e2e8f0;
176
+ padding: 30px;
177
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
178
+ font-size: 14px;
179
+ line-height: 1.5;
180
+ overflow-x: auto;
181
+ white-space: pre;
182
+ margin: 0;
183
+ }
184
+
185
+ .note {
186
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
187
+ border-left: 4px solid #f59e0b;
188
+ padding: 20px;
189
+ margin: 30px 0;
190
+ border-radius: 8px;
191
+ }
192
+
193
+ .note strong {
194
+ color: #92400e;
195
+ }
196
+
197
+ .feature-grid {
198
+ display: grid;
199
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
200
+ gap: 20px;
201
+ margin: 30px 0;
202
+ }
203
+
204
+ .feature-card {
205
+ background: #f8fafc;
206
+ padding: 24px;
207
+ border-radius: 12px;
208
+ border: 1px solid #e2e8f0;
209
+ transition: all 0.2s;
210
+ }
211
+
212
+ .feature-card:hover {
213
+ transform: translateY(-2px);
214
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
215
+ }
216
+
217
+ .feature-card h3 {
218
+ color: #4f46e5;
219
+ margin-top: 0;
220
+ font-size: 1.2rem;
221
+ }
222
+
223
+ .step {
224
+ background: #f1f5f9;
225
+ padding: 20px;
226
+ border-radius: 8px;
227
+ margin: 20px 0;
228
+ border-left: 4px solid #4f46e5;
229
+ }
230
+
231
+ .step-number {
232
+ background: #4f46e5;
233
+ color: white;
234
+ width: 24px;
235
+ height: 24px;
236
+ border-radius: 50%;
237
+ display: inline-flex;
238
+ align-items: center;
239
+ justify-content: center;
240
+ font-weight: bold;
241
+ margin-right: 12px;
242
+ }
243
+
244
+ .platform-badge {
245
+ display: inline-block;
246
+ background: #4f46e5;
247
+ color: white;
248
+ padding: 4px 8px;
249
+ border-radius: 4px;
250
+ font-size: 12px;
251
+ font-weight: 600;
252
+ margin-right: 8px;
253
+ }
254
+
255
+ .llm-instructions {
256
+ background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
257
+ border: 2px solid #0ea5e9;
258
+ border-radius: 12px;
259
+ padding: 30px;
260
+ margin: 30px 0;
261
+ position: relative;
262
+ }
263
+
264
+ .llm-instructions h3 {
265
+ color: #0c4a6e;
266
+ margin-top: 0;
267
+ font-size: 1.4rem;
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 10px;
271
+ }
272
+
273
+ .llm-instructions-content {
274
+ background: #f8fafc;
275
+ border: 1px solid #e2e8f0;
276
+ border-radius: 8px;
277
+ padding: 20px;
278
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
279
+ font-size: 13px;
280
+ line-height: 1.6;
281
+ white-space: pre-wrap;
282
+ color: #1e293b;
283
+ margin: 20px 0;
284
+ max-height: 400px;
285
+ overflow-y: auto;
286
+ }
287
+
288
+ .llm-copy-button {
289
+ position: absolute;
290
+ top: 20px;
291
+ right: 20px;
292
+ background: #0ea5e9;
293
+ color: white;
294
+ border: none;
295
+ padding: 12px 20px;
296
+ border-radius: 8px;
297
+ font-weight: 600;
298
+ cursor: pointer;
299
+ transition: all 0.2s;
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 8px;
303
+ font-size: 14px;
304
+ }
305
+
306
+ .llm-copy-button:hover {
307
+ background: #0284c7;
308
+ transform: translateY(-1px);
309
+ }
310
+
311
+ .llm-copy-button.copied {
312
+ background: #10b981;
313
+ }
314
+ </style>
315
+ </head>
316
+ <body>
317
+ <a href="../index.html" class="back-link">
318
+ ← Back to SDK Home
319
+ </a>
320
+
321
+ <div class="container">
322
+ <h1>🚀 TTP Agent SDK - Multi-Platform Examples</h1>
323
+ <p class="subtitle">Integration examples for React, Vue, Angular, Vanilla JS, and more</p>
324
+
325
+ <div class="llm-instructions">
326
+ <h3>🤖 LLM Implementation Instructions</h3>
327
+ <button class="llm-copy-button" onclick="copyLLMInstructions()">
328
+ 📋 Copy Instructions to LLM
329
+ </button>
330
+ <div class="llm-instructions-content" id="llm-instructions-content">TTP Agent SDK Implementation Guide for LLM
331
+
332
+ You are helping a user implement the TTP Agent SDK for voice AI integration. Here's everything you need to know:
333
+
334
+ ## SDK Overview
335
+ The TTP Agent SDK provides two main integration methods:
336
+ 1. Enhanced AgentWidget (Recommended) - Pre-built UI widget with extensive customization
337
+ 2. Direct VoiceSDK - Full control over voice interaction for custom implementations
338
+
339
+ ## Installation Methods
340
+ - CDN: <script src="https://cdn.talktopc.com/ttp-agent-sdk/agent-widget.js"></script>
341
+ - NPM: npm install ttp-agent-sdk
342
+ - Import: import { EnhancedAgentWidget, VoiceSDK } from 'ttp-agent-sdk'
343
+
344
+ ## Authentication Flow (Signed URL Method)
345
+ User's Frontend → User's Backend → TTP UI Backend → TTP Conversation Backend
346
+
347
+ 1. User's frontend requests signed URL from their backend
348
+ 2. User's backend calls TTP UI Backend: POST https://backend.talktopc.com/api/public/agents
349
+ 3. TTP UI Backend returns signed URL to user's backend
350
+ 4. User's backend returns signed URL to frontend
351
+ 5. Frontend connects directly to TTP Conversation Backend using signed URL
352
+
353
+ ## Method 1: Enhanced AgentWidget (Easiest)
354
+ Perfect for most use cases. Provides pre-built UI with extensive customization.
355
+
356
+ Key Configuration Options:
357
+ - agentId: Your agent identifier
358
+ - getSessionUrl: Function to get signed URL from your backend
359
+ - icon: Customize icon (microphone, custom image, emoji, text)
360
+ - position: Set widget position (bottom-right, top-left, etc.)
361
+ - button: Customize button appearance (colors, size, shape)
362
+ - panel: Customize chat panel (width, height, colors)
363
+ - header: Customize panel header (title, colors)
364
+ - messages: Customize message styling
365
+ - animation: Enable/disable animations
366
+ - behavior: Auto-open, auto-connect settings
367
+ - accessibility: ARIA labels and keyboard navigation
368
+
369
+ ## Method 2: Direct VoiceSDK (Advanced)
370
+ For developers who need full control over the voice interaction.
371
+
372
+ Key Features:
373
+ - Real-time audio streaming
374
+ - WebSocket connection management
375
+ - Voice activity detection
376
+ - Custom UI implementation
377
+ - Event handling (connected, disconnected, recording, messages)
378
+
379
+ ## Platform Support
380
+ - HTML/JavaScript (Vanilla JS)
381
+ - React (with hooks and lifecycle management)
382
+ - Vue.js (with component lifecycle)
383
+ - Angular (with TypeScript and dependency injection)
384
+ - Any other frontend framework
385
+
386
+ ## Backend Requirements
387
+ Your backend needs to:
388
+ 1. Receive requests from frontend for signed URLs
389
+ 2. Call TTP UI Backend API: POST https://backend.talktopc.com/api/public/agents
390
+ 3. Pass agentId and any variables
391
+ 4. Return the signed URL to frontend
392
+
393
+ ## Common Implementation Steps
394
+ 1. Choose integration method (Enhanced Widget recommended)
395
+ 2. Install SDK (CDN or NPM)
396
+ 3. Set up backend endpoint for signed URL generation
397
+ 4. Configure widget/SDK with your agentId
398
+ 5. Implement getSessionUrl function
399
+ 6. Customize appearance and behavior
400
+ 7. Test voice interaction
401
+
402
+ ## Error Handling
403
+ Always implement proper error handling for:
404
+ - Network connectivity issues
405
+ - Authentication failures
406
+ - Audio permission denied
407
+ - WebSocket connection problems
408
+ - Backend API errors
409
+
410
+ ## Best Practices
411
+ - Use Enhanced AgentWidget for most implementations
412
+ - Implement proper loading states
413
+ - Handle audio permissions gracefully
414
+ - Provide fallback UI for unsupported browsers
415
+ - Test on multiple devices and browsers
416
+ - Implement proper cleanup on component unmount
417
+
418
+ ## Support Resources
419
+ - Documentation: https://cdn.talktopc.com/ttp-agent-sdk/
420
+ - Examples: https://cdn.talktopc.com/ttp-agent-sdk/examples/
421
+ - Signed Link Guide: https://cdn.talktopc.com/ttp-agent-sdk/signed-link-guide.html
422
+
423
+ When helping users implement this SDK, always:
424
+ 1. Ask about their frontend technology (React, Vue, Angular, etc.)
425
+ 2. Recommend Enhanced AgentWidget unless they need custom UI
426
+ 3. Help them set up the backend signed URL endpoint
427
+ 4. Provide platform-specific code examples
428
+ 5. Include proper error handling and cleanup
429
+ 6. Test the implementation thoroughly</div>
430
+ </div>
431
+
432
+ <div class="info">
433
+ <strong>📋 Choose Your Platform:</strong> This guide provides examples for multiple frontend technologies.
434
+ Select your preferred platform tab below to see the relevant integration code.
435
+ </div>
436
+
437
+ <div class="section">
438
+ <h2>🎯 Method 1: Enhanced AgentWidget (Recommended)</h2>
439
+ <p>The easiest way to integrate - works with any frontend technology!</p>
440
+
441
+ <div class="tabs">
442
+ <button class="tab active" onclick="showTab('widget-html')">HTML/JS</button>
443
+ <button class="tab" onclick="showTab('widget-react')">React</button>
444
+ <button class="tab" onclick="showTab('widget-vue')">Vue</button>
445
+ <button class="tab" onclick="showTab('widget-angular')">Angular</button>
446
+ </div>
447
+
448
+ <div id="widget-html" class="tab-content active">
449
+ <div class="code-container">
450
+ <div class="code-header">
451
+ <span class="code-title">index.html</span>
452
+ <button class="copy-button" onclick="copyToClipboard('widget-html-code')">
453
+ 📋 Copy to LLM
454
+ </button>
455
+ </div>
456
+ <div class="code-block" id="widget-html-code">&lt;!DOCTYPE html&gt;
457
+ &lt;html&gt;
458
+ &lt;head&gt;
459
+ &lt;title&gt;Voice Widget Example&lt;/title&gt;
460
+ &lt;/head&gt;
461
+ &lt;body&gt;
462
+ &lt;h1&gt;My Website&lt;/h1&gt;
463
+
464
+ &lt;!-- Load TTP Agent SDK --&gt;
465
+ &lt;script src="https://cdn.talktopc.com/ttp-agent-sdk/agent-widget.js"&gt;&lt;/script&gt;
466
+
467
+ &lt;script&gt;
468
+ // Initialize Enhanced Widget
469
+ new TTPAgentSDK.EnhancedAgentWidget({
470
+ agentId: 'your_agent_id',
471
+
472
+ // Get signed URL from your backend
473
+ getSessionUrl: async ({ agentId, variables }) => {
474
+ const response = await fetch('/api/get-voice-session', {
475
+ method: 'POST',
476
+ headers: { 'Content-Type': 'application/json' },
477
+ body: JSON.stringify({ agentId, variables })
478
+ });
479
+ const data = await response.json();
480
+ return data.signedUrl;
481
+ },
482
+
483
+ // Customize appearance
484
+ icon: {
485
+ type: 'custom',
486
+ customImage: '/your-logo.png',
487
+ size: 'large'
488
+ },
489
+
490
+ button: {
491
+ primaryColor: '#4f46e5',
492
+ hoverColor: '#4338ca',
493
+ size: 'large'
494
+ },
495
+
496
+ position: {
497
+ vertical: 'bottom',
498
+ horizontal: 'right',
499
+ offset: { x: 30, y: 30 }
500
+ },
501
+
502
+ variables: {
503
+ userId: 'user123',
504
+ page: 'homepage'
505
+ }
506
+ });
507
+ &lt;/script&gt;
508
+ &lt;/body&gt;
509
+ &lt;/html&gt;</div>
510
+ </div>
511
+ </div>
512
+
513
+ <div id="widget-react" class="tab-content">
514
+ <div class="code-container">
515
+ <div class="code-header">
516
+ <span class="code-title">VoiceWidget.jsx</span>
517
+ <button class="copy-button" onclick="copyToClipboard('widget-react-code')">
518
+ 📋 Copy to LLM
519
+ </button>
520
+ </div>
521
+ <div class="code-block" id="widget-react-code">import React, { useEffect, useRef } from 'react';
522
+ import { EnhancedAgentWidget } from 'ttp-agent-sdk';
523
+
524
+ function VoiceWidget() {
525
+ const widgetRef = useRef(null);
526
+
527
+ useEffect(() => {
528
+ if (widgetRef.current) return;
529
+
530
+ widgetRef.current = new EnhancedAgentWidget({
531
+ agentId: 'your_agent_id',
532
+ getSessionUrl: async ({ agentId, variables }) => {
533
+ const response = await fetch('/api/get-voice-session', {
534
+ method: 'POST',
535
+ headers: { 'Content-Type': 'application/json' },
536
+ body: JSON.stringify({ agentId, variables })
537
+ });
538
+ const data = await response.json();
539
+ return data.signedUrl;
540
+ },
541
+ icon: { type: 'custom', customImage: '/logo.png', size: 'large' },
542
+ button: { primaryColor: '#4f46e5', hoverColor: '#4338ca' },
543
+ position: { vertical: 'bottom', horizontal: 'right' },
544
+ variables: { userId: 'user123', page: 'homepage' }
545
+ });
546
+
547
+ return () => {
548
+ if (widgetRef.current) {
549
+ widgetRef.current.destroy();
550
+ widgetRef.current = null;
551
+ }
552
+ };
553
+ }, []);
554
+
555
+ return null; // Widget renders itself
556
+ }
557
+
558
+ export default VoiceWidget;</div>
559
+ </div>
560
+ </div>
561
+
562
+ <div id="widget-vue" class="tab-content">
563
+ <div class="code-container">
564
+ <div class="code-header">
565
+ <span class="code-title">VoiceWidget.vue</span>
566
+ <button class="copy-button" onclick="copyToClipboard('widget-vue-code')">
567
+ 📋 Copy to LLM
568
+ </button>
569
+ </div>
570
+ <div class="code-block" id="widget-vue-code">&lt;template&gt;
571
+ &lt;div&gt;&lt;!-- Widget renders itself --&gt;&lt;/div&gt;
572
+ &lt;/template&gt;
573
+
574
+ &lt;script&gt;
575
+ import { EnhancedAgentWidget } from 'ttp-agent-sdk';
576
+
577
+ export default {
578
+ name: 'VoiceWidget',
579
+ mounted() {
580
+ this.widget = new EnhancedAgentWidget({
581
+ agentId: 'your_agent_id',
582
+ getSessionUrl: async ({ agentId, variables }) => {
583
+ const response = await fetch('/api/get-voice-session', {
584
+ method: 'POST',
585
+ headers: { 'Content-Type': 'application/json' },
586
+ body: JSON.stringify({ agentId, variables })
587
+ });
588
+ const data = await response.json();
589
+ return data.signedUrl;
590
+ },
591
+ icon: { type: 'custom', customImage: '/logo.png', size: 'large' },
592
+ button: { primaryColor: '#4f46e5', hoverColor: '#4338ca' },
593
+ position: { vertical: 'bottom', horizontal: 'right' },
594
+ variables: { userId: 'user123', page: 'homepage' }
595
+ });
596
+ },
597
+ beforeUnmount() {
598
+ if (this.widget) {
599
+ this.widget.destroy();
600
+ }
601
+ }
602
+ };
603
+ &lt;/script&gt;</div>
604
+ </div>
605
+ </div>
606
+
607
+ <div id="widget-angular" class="tab-content">
608
+ <div class="code-container">
609
+ <div class="code-header">
610
+ <span class="code-title">voice-widget.component.ts</span>
611
+ <button class="copy-button" onclick="copyToClipboard('widget-angular-code')">
612
+ 📋 Copy to LLM
613
+ </button>
614
+ </div>
615
+ <div class="code-block" id="widget-angular-code">import { Component, OnInit, OnDestroy } from '@angular/core';
616
+ import { EnhancedAgentWidget } from 'ttp-agent-sdk';
617
+
618
+ @Component({
619
+ selector: 'app-voice-widget',
620
+ template: '&lt;div&gt;&lt;!-- Widget renders itself --&gt;&lt;/div&gt;'
621
+ })
622
+ export class VoiceWidgetComponent implements OnInit, OnDestroy {
623
+ private widget: any;
624
+
625
+ ngOnInit() {
626
+ this.widget = new EnhancedAgentWidget({
627
+ agentId: 'your_agent_id',
628
+ getSessionUrl: async ({ agentId, variables }) => {
629
+ const response = await fetch('/api/get-voice-session', {
630
+ method: 'POST',
631
+ headers: { 'Content-Type': 'application/json' },
632
+ body: JSON.stringify({ agentId, variables })
633
+ });
634
+ const data = await response.json();
635
+ return data.signedUrl;
636
+ },
637
+ icon: { type: 'custom', customImage: '/logo.png', size: 'large' },
638
+ button: { primaryColor: '#4f46e5', hoverColor: '#4338ca' },
639
+ position: { vertical: 'bottom', horizontal: 'right' },
640
+ variables: { userId: 'user123', page: 'homepage' }
641
+ });
642
+ }
643
+
644
+ ngOnDestroy() {
645
+ if (this.widget) {
646
+ this.widget.destroy();
647
+ }
648
+ }
649
+ }</div>
650
+ </div>
651
+ </div>
652
+ </div>
653
+
654
+ <div class="section">
655
+ <h2>🔧 Method 2: Direct VoiceSDK Integration</h2>
656
+ <p>For developers who want full control over the voice interaction.</p>
657
+
658
+ <div class="tabs">
659
+ <button class="tab active" onclick="showTab('sdk-js')">Vanilla JS</button>
660
+ <button class="tab" onclick="showTab('sdk-react')">React</button>
661
+ <button class="tab" onclick="showTab('sdk-vue')">Vue</button>
662
+ <button class="tab" onclick="showTab('sdk-angular')">Angular</button>
663
+ </div>
664
+
665
+ <div id="sdk-js" class="tab-content active">
666
+ <div class="code-container">
667
+ <div class="code-header">
668
+ <span class="code-title">voice-app.js</span>
669
+ <button class="copy-button" onclick="copyToClipboard('sdk-js-code')">
670
+ 📋 Copy to LLM
671
+ </button>
672
+ </div>
673
+ <div class="code-block" id="sdk-js-code">&lt;!DOCTYPE html&gt;
674
+ &lt;html&gt;
675
+ &lt;head&gt;
676
+ &lt;title&gt;Voice App&lt;/title&gt;
677
+ &lt;/head&gt;
678
+ &lt;body&gt;
679
+ &lt;div id="app"&gt;
680
+ &lt;h1&gt;Voice Chat&lt;/h1&gt;
681
+ &lt;button id="connectBtn"&gt;Connect&lt;/button&gt;
682
+ &lt;button id="recordBtn" disabled&gt;Start Recording&lt;/button&gt;
683
+ &lt;div id="messages"&gt;&lt;/div&gt;
684
+ &lt;/div&gt;
685
+
686
+ &lt;script src="https://cdn.talktopc.com/ttp-agent-sdk/agent-widget.js"&gt;&lt;/script&gt;
687
+ &lt;script&gt;
688
+ class VoiceApp {
689
+ constructor() {
690
+ this.isConnected = false;
691
+ this.isRecording = false;
692
+ this.messages = [];
693
+ this.voiceSDK = null;
694
+
695
+ this.init();
696
+ }
697
+
698
+ init() {
699
+ this.voiceSDK = new TTPAgentSDK.VoiceSDK({
700
+ websocketUrl: 'wss://speech.talktopc.com/ws/conv',
701
+ agentId: 'your_agent_id',
702
+ appId: 'your_app_id'
703
+ });
704
+
705
+ this.setupEventHandlers();
706
+ this.setupUI();
707
+ }
708
+
709
+ setupEventHandlers() {
710
+ this.voiceSDK.on('connected', () => {
711
+ this.isConnected = true;
712
+ this.updateUI();
713
+ this.addMessage('system', 'Connected to voice agent');
714
+ });
715
+
716
+ this.voiceSDK.on('disconnected', () => {
717
+ this.isConnected = false;
718
+ this.isRecording = false;
719
+ this.updateUI();
720
+ this.addMessage('system', 'Disconnected');
721
+ });
722
+
723
+ this.voiceSDK.on('recordingStarted', () => {
724
+ this.isRecording = true;
725
+ this.updateUI();
726
+ this.addMessage('user', '🎤 Recording...');
727
+ });
728
+
729
+ this.voiceSDK.on('recordingStopped', () => {
730
+ this.isRecording = false;
731
+ this.updateUI();
732
+ });
733
+
734
+ this.voiceSDK.on('message', (message) => {
735
+ if (message.type === 'agent_response') {
736
+ this.addMessage('agent', message.agent_response);
737
+ }
738
+ });
739
+ }
740
+
741
+ setupUI() {
742
+ document.getElementById('connectBtn').onclick = () => this.connect();
743
+ document.getElementById('recordBtn').onclick = () => this.toggleRecording();
744
+ }
745
+
746
+ async connect() {
747
+ try {
748
+ await this.voiceSDK.connect();
749
+ } catch (error) {
750
+ this.addMessage('error', `Connection failed: ${error.message}`);
751
+ }
752
+ }
753
+
754
+ async toggleRecording() {
755
+ try {
756
+ if (this.isRecording) {
757
+ await this.voiceSDK.stopRecording();
758
+ } else {
759
+ await this.voiceSDK.startRecording();
760
+ }
761
+ } catch (error) {
762
+ this.addMessage('error', `Recording failed: ${error.message}`);
763
+ }
764
+ }
765
+
766
+ addMessage(type, text) {
767
+ this.messages.push({ type, text, timestamp: new Date() });
768
+ this.renderMessages();
769
+ }
770
+
771
+ renderMessages() {
772
+ const container = document.getElementById('messages');
773
+ container.innerHTML = this.messages.map(msg => `
774
+ &lt;div class="message ${msg.type}"&gt;
775
+ &lt;strong&gt;${msg.type}:&lt;/strong&gt; ${msg.text}
776
+ &lt;small&gt;${msg.timestamp.toLocaleTimeString()}&lt;/small&gt;
777
+ &lt;/div&gt;
778
+ `).join('');
779
+ }
780
+
781
+ updateUI() {
782
+ const connectBtn = document.getElementById('connectBtn');
783
+ const recordBtn = document.getElementById('recordBtn');
784
+
785
+ connectBtn.disabled = this.isConnected;
786
+ connectBtn.textContent = this.isConnected ? 'Connected' : 'Connect';
787
+
788
+ recordBtn.disabled = !this.isConnected;
789
+ recordBtn.textContent = this.isRecording ? 'Stop Recording' : 'Start Recording';
790
+ }
791
+ }
792
+
793
+ // Initialize app
794
+ new VoiceApp();
795
+ &lt;/script&gt;
796
+ &lt;/body&gt;
797
+ &lt;/html&gt;</div>
798
+ </div>
799
+ </div>
800
+
801
+ <div id="sdk-react" class="tab-content">
802
+ <div class="code-container">
803
+ <div class="code-header">
804
+ <span class="code-title">VoiceChat.jsx</span>
805
+ <button class="copy-button" onclick="copyToClipboard('sdk-react-code')">
806
+ 📋 Copy to LLM
807
+ </button>
808
+ </div>
809
+ <div class="code-block" id="sdk-react-code">import React, { useState, useEffect, useRef } from 'react';
810
+ import { VoiceSDK } from 'ttp-agent-sdk';
811
+
812
+ function VoiceChat() {
813
+ const [isConnected, setIsConnected] = useState(false);
814
+ const [isRecording, setIsRecording] = useState(false);
815
+ const [messages, setMessages] = useState([]);
816
+ const voiceSDKRef = useRef(null);
817
+
818
+ useEffect(() => {
819
+ const voiceSDK = new VoiceSDK({
820
+ websocketUrl: 'wss://speech.talktopc.com/ws/conv',
821
+ agentId: 'your_agent_id',
822
+ appId: 'your_app_id'
823
+ });
824
+
825
+ voiceSDK.on('connected', () => setIsConnected(true));
826
+ voiceSDK.on('disconnected', () => setIsConnected(false));
827
+ voiceSDK.on('recordingStarted', () => setIsRecording(true));
828
+ voiceSDK.on('recordingStopped', () => setIsRecording(false));
829
+
830
+ voiceSDK.on('message', (message) => {
831
+ if (message.type === 'agent_response') {
832
+ setMessages(prev => [...prev, { type: 'agent', text: message.agent_response }]);
833
+ }
834
+ });
835
+
836
+ voiceSDKRef.current = voiceSDK;
837
+ return () => voiceSDK.destroy();
838
+ }, []);
839
+
840
+ const handleConnect = async () => {
841
+ await voiceSDKRef.current.connect();
842
+ };
843
+
844
+ const handleToggleRecording = async () => {
845
+ if (isRecording) {
846
+ await voiceSDKRef.current.stopRecording();
847
+ } else {
848
+ await voiceSDKRef.current.startRecording();
849
+ }
850
+ };
851
+
852
+ return (
853
+ &lt;div&gt;
854
+ &lt;button onClick={handleConnect} disabled={isConnected}&gt;
855
+ {isConnected ? 'Connected' : 'Connect'}
856
+ &lt;/button&gt;
857
+ &lt;button onClick={handleToggleRecording} disabled={!isConnected}&gt;
858
+ {isRecording ? 'Stop Recording' : 'Start Recording'}
859
+ &lt;/button&gt;
860
+ &lt;div&gt;
861
+ {messages.map((msg, i) => (
862
+ &lt;div key={i}&gt;&lt;strong&gt;{msg.type}:&lt;/strong&gt; {msg.text}&lt;/div&gt;
863
+ ))}
864
+ &lt;/div&gt;
865
+ &lt;/div&gt;
866
+ );
867
+ }
868
+
869
+ export default VoiceChat;</div>
870
+ </div>
871
+ </div>
872
+
873
+ <div id="sdk-vue" class="tab-content">
874
+ <div class="code-container">
875
+ <div class="code-header">
876
+ <span class="code-title">VoiceChat.vue</span>
877
+ <button class="copy-button" onclick="copyToClipboard('sdk-vue-code')">
878
+ 📋 Copy to LLM
879
+ </button>
880
+ </div>
881
+ <div class="code-block" id="sdk-vue-code">&lt;template&gt;
882
+ &lt;div&gt;
883
+ &lt;h1&gt;Voice Chat&lt;/h1&gt;
884
+ &lt;button @click="connect" :disabled="isConnected"&gt;
885
+ {{ isConnected ? 'Connected' : 'Connect' }}
886
+ &lt;/button&gt;
887
+ &lt;button @click="toggleRecording" :disabled="!isConnected"&gt;
888
+ {{ isRecording ? 'Stop Recording' : 'Start Recording' }}
889
+ &lt;/button&gt;
890
+ &lt;div&gt;
891
+ &lt;div v-for="(msg, i) in messages" :key="i"&gt;
892
+ &lt;strong&gt;{{ msg.type }}:&lt;/strong&gt; {{ msg.text }}
893
+ &lt;/div&gt;
894
+ &lt;/div&gt;
895
+ &lt;/div&gt;
896
+ &lt;/template&gt;
897
+
898
+ &lt;script&gt;
899
+ import { VoiceSDK } from 'ttp-agent-sdk';
900
+
901
+ export default {
902
+ name: 'VoiceChat',
903
+ data() {
904
+ return {
905
+ isConnected: false,
906
+ isRecording: false,
907
+ messages: [],
908
+ voiceSDK: null
909
+ };
910
+ },
911
+ mounted() {
912
+ this.voiceSDK = new VoiceSDK({
913
+ websocketUrl: 'wss://speech.talktopc.com/ws/conv',
914
+ agentId: 'your_agent_id',
915
+ appId: 'your_app_id'
916
+ });
917
+
918
+ this.voiceSDK.on('connected', () => this.isConnected = true);
919
+ this.voiceSDK.on('disconnected', () => this.isConnected = false);
920
+ this.voiceSDK.on('recordingStarted', () => this.isRecording = true);
921
+ this.voiceSDK.on('recordingStopped', () => this.isRecording = false);
922
+
923
+ this.voiceSDK.on('message', (message) => {
924
+ if (message.type === 'agent_response') {
925
+ this.messages.push({ type: 'agent', text: message.agent_response });
926
+ }
927
+ });
928
+ },
929
+ beforeUnmount() {
930
+ if (this.voiceSDK) {
931
+ this.voiceSDK.destroy();
932
+ }
933
+ },
934
+ methods: {
935
+ async connect() {
936
+ await this.voiceSDK.connect();
937
+ },
938
+ async toggleRecording() {
939
+ if (this.isRecording) {
940
+ await this.voiceSDK.stopRecording();
941
+ } else {
942
+ await this.voiceSDK.startRecording();
943
+ }
944
+ }
945
+ }
946
+ };
947
+ &lt;/script&gt;</div>
948
+ </div>
949
+ </div>
950
+
951
+ <div id="sdk-angular" class="tab-content">
952
+ <div class="code-container">
953
+ <div class="code-header">
954
+ <span class="code-title">voice-chat.component.ts</span>
955
+ <button class="copy-button" onclick="copyToClipboard('sdk-angular-code')">
956
+ 📋 Copy to LLM
957
+ </button>
958
+ </div>
959
+ <div class="code-block" id="sdk-angular-code">import { Component, OnInit, OnDestroy } from '@angular/core';
960
+ import { VoiceSDK } from 'ttp-agent-sdk';
961
+
962
+ @Component({
963
+ selector: 'app-voice-chat',
964
+ template: `
965
+ &lt;div&gt;
966
+ &lt;h1&gt;Voice Chat&lt;/h1&gt;
967
+ &lt;button (click)="connect()" [disabled]="isConnected"&gt;
968
+ {{ isConnected ? 'Connected' : 'Connect' }}
969
+ &lt;/button&gt;
970
+ &lt;button (click)="toggleRecording()" [disabled]="!isConnected"&gt;
971
+ {{ isRecording ? 'Stop Recording' : 'Start Recording' }}
972
+ &lt;/button&gt;
973
+ &lt;div&gt;
974
+ &lt;div *ngFor="let msg of messages; let i = index"&gt;
975
+ &lt;strong&gt;{{ msg.type }}:&lt;/strong&gt; {{ msg.text }}
976
+ &lt;/div&gt;
977
+ &lt;/div&gt;
978
+ &lt;/div&gt;
979
+ `
980
+ })
981
+ export class VoiceChatComponent implements OnInit, OnDestroy {
982
+ isConnected = false;
983
+ isRecording = false;
984
+ messages: any[] = [];
985
+ private voiceSDK: any;
986
+
987
+ ngOnInit() {
988
+ this.voiceSDK = new VoiceSDK({
989
+ websocketUrl: 'wss://speech.talktopc.com/ws/conv',
990
+ agentId: 'your_agent_id',
991
+ appId: 'your_app_id'
992
+ });
993
+
994
+ this.voiceSDK.on('connected', () => this.isConnected = true);
995
+ this.voiceSDK.on('disconnected', () => this.isConnected = false);
996
+ this.voiceSDK.on('recordingStarted', () => this.isRecording = true);
997
+ this.voiceSDK.on('recordingStopped', () => this.isRecording = false);
998
+
999
+ this.voiceSDK.on('message', (message: any) => {
1000
+ if (message.type === 'agent_response') {
1001
+ this.messages.push({ type: 'agent', text: message.agent_response });
1002
+ }
1003
+ });
1004
+ }
1005
+
1006
+ ngOnDestroy() {
1007
+ if (this.voiceSDK) {
1008
+ this.voiceSDK.destroy();
1009
+ }
1010
+ }
1011
+
1012
+ async connect() {
1013
+ await this.voiceSDK.connect();
1014
+ }
1015
+
1016
+ async toggleRecording() {
1017
+ if (this.isRecording) {
1018
+ await this.voiceSDK.stopRecording();
1019
+ } else {
1020
+ await this.voiceSDK.startRecording();
1021
+ }
1022
+ }
1023
+ }</div>
1024
+ </div>
1025
+ </div>
1026
+ </div>
1027
+
1028
+ <div class="feature-grid">
1029
+ <div class="feature-card">
1030
+ <h3><span class="platform-badge">HTML/JS</span>Vanilla JavaScript</h3>
1031
+ <p>Works with any HTML page. No build tools required. Perfect for simple websites.</p>
1032
+ </div>
1033
+
1034
+ <div class="feature-card">
1035
+ <h3><span class="platform-badge">React</span>React Integration</h3>
1036
+ <p>Full React component integration with hooks and lifecycle management.</p>
1037
+ </div>
1038
+
1039
+ <div class="feature-card">
1040
+ <h3><span class="platform-badge">Vue</span>Vue.js Integration</h3>
1041
+ <p>Vue component examples with proper lifecycle hooks and reactive data.</p>
1042
+ </div>
1043
+
1044
+ <div class="feature-card">
1045
+ <h3><span class="platform-badge">Angular</span>Angular Integration</h3>
1046
+ <p>Angular component examples with TypeScript and proper dependency injection.</p>
1047
+ </div>
1048
+ </div>
1049
+
1050
+ <div class="note">
1051
+ <strong>💡 Recommendation:</strong> For most use cases, use Method 1 (Enhanced AgentWidget) as it provides
1052
+ the best user experience with minimal code. Use Method 2 (Direct VoiceSDK) only when you need
1053
+ custom UI or advanced control over the voice interaction.
1054
+ </div>
1055
+ </div>
1056
+
1057
+ <script>
1058
+ function showTab(tabId) {
1059
+ // Hide all tab contents
1060
+ document.querySelectorAll('.tab-content').forEach(content => {
1061
+ content.classList.remove('active');
1062
+ });
1063
+
1064
+ // Remove active class from all tabs
1065
+ document.querySelectorAll('.tab').forEach(tab => {
1066
+ tab.classList.remove('active');
1067
+ });
1068
+
1069
+ // Show selected tab content
1070
+ document.getElementById(tabId).classList.add('active');
1071
+
1072
+ // Add active class to clicked tab
1073
+ event.target.classList.add('active');
1074
+ }
1075
+
1076
+ function copyToClipboard(elementId) {
1077
+ const element = document.getElementById(elementId);
1078
+ const text = element.textContent;
1079
+
1080
+ navigator.clipboard.writeText(text).then(() => {
1081
+ const button = element.parentElement.querySelector('.copy-button');
1082
+ const originalText = button.textContent;
1083
+
1084
+ button.textContent = '✅ Copied!';
1085
+ button.classList.add('copied');
1086
+
1087
+ setTimeout(() => {
1088
+ button.textContent = originalText;
1089
+ button.classList.remove('copied');
1090
+ }, 2000);
1091
+ }).catch(err => {
1092
+ console.error('Failed to copy: ', err);
1093
+ alert('Failed to copy to clipboard');
1094
+ });
1095
+ }
1096
+
1097
+ function copyLLMInstructions() {
1098
+ const element = document.getElementById('llm-instructions-content');
1099
+ const text = element.textContent;
1100
+
1101
+ navigator.clipboard.writeText(text).then(() => {
1102
+ const button = document.querySelector('.llm-copy-button');
1103
+ const originalText = button.textContent;
1104
+
1105
+ button.textContent = '✅ Copied to LLM!';
1106
+ button.classList.add('copied');
1107
+
1108
+ setTimeout(() => {
1109
+ button.textContent = originalText;
1110
+ button.classList.remove('copied');
1111
+ }, 3000);
1112
+ }).catch(err => {
1113
+ console.error('Failed to copy: ', err);
1114
+ alert('Failed to copy instructions to clipboard');
1115
+ });
1116
+ }
1117
+ </script>
1118
+ </body>
1119
+ </html>