ttp-agent-sdk 2.2.9 → 2.2.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.
@@ -1,1209 +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, maximum-scale=1.0, user-scalable=no">
6
- <title>Voice Agent Widget - Agent ID + App ID Test</title>
7
- <style>
8
- body {
9
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10
- max-width: 800px;
11
- margin: 40px auto;
12
- padding: 20px;
13
- background: #F9FAFB;
14
- }
15
-
16
- @media (max-width: 768px) {
17
- body {
18
- margin: 0;
19
- padding: 10px;
20
- }
21
- }
22
-
23
- .container {
24
- background: white;
25
- padding: 30px;
26
- border-radius: 12px;
27
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
28
- }
29
-
30
- @media (max-width: 768px) {
31
- .container {
32
- padding: 16px;
33
- border-radius: 8px;
34
- }
35
- }
36
-
37
- h1 {
38
- color: #111827;
39
- margin-top: 0;
40
- }
41
-
42
- .info {
43
- background: #EFF6FF;
44
- border-left: 4px solid #3B82F6;
45
- padding: 16px;
46
- margin: 20px 0;
47
- border-radius: 4px;
48
- }
49
-
50
- .config-info {
51
- background: #F0FDF4;
52
- border-left: 4px solid #10B981;
53
- padding: 16px;
54
- margin: 20px 0;
55
- border-radius: 4px;
56
- }
57
-
58
- .status {
59
- margin: 20px 0;
60
- padding: 12px;
61
- background: #F3F4F6;
62
- border-radius: 6px;
63
- font-family: monospace;
64
- font-size: 14px;
65
- }
66
-
67
- .status.success {
68
- background: #D1FAE5;
69
- color: #065F46;
70
- }
71
-
72
- .status.error {
73
- background: #FEE2E2;
74
- color: #991B1B;
75
- }
76
-
77
- button {
78
- background: #4F46E5;
79
- color: white;
80
- border: none;
81
- padding: 12px 24px;
82
- border-radius: 6px;
83
- cursor: pointer;
84
- font-size: 16px;
85
- margin: 10px 10px 10px 0;
86
- }
87
-
88
- button:hover {
89
- background: #4338CA;
90
- }
91
-
92
- .code-block {
93
- background: #1F2937;
94
- color: #F9FAFB;
95
- padding: 16px;
96
- border-radius: 6px;
97
- font-family: monospace;
98
- font-size: 12px;
99
- overflow-x: auto;
100
- margin: 20px 0;
101
- white-space: pre;
102
- -webkit-overflow-scrolling: touch;
103
- }
104
-
105
- @media (max-width: 768px) {
106
- .code-block {
107
- font-size: 11px;
108
- padding: 12px;
109
- margin: 16px 0;
110
- }
111
- }
112
-
113
- .code-block pre {
114
- margin: 0;
115
- font-family: inherit;
116
- }
117
-
118
- .code-block code {
119
- font-family: inherit;
120
- white-space: pre;
121
- }
122
-
123
- .tabs {
124
- display: flex;
125
- gap: 10px;
126
- margin-bottom: 15px;
127
- border-bottom: 2px solid #E5E7EB;
128
- }
129
-
130
- .tab {
131
- background: none;
132
- border: none;
133
- padding: 12px 24px;
134
- cursor: pointer;
135
- font-size: 14px;
136
- font-weight: 600;
137
- color: #6B7280;
138
- border-bottom: 3px solid transparent;
139
- transition: all 0.2s;
140
- margin-bottom: -2px;
141
- }
142
-
143
- .tab:hover {
144
- color: #4F46E5;
145
- }
146
-
147
- .tab.active {
148
- color: #4F46E5;
149
- border-bottom-color: #4F46E5;
150
- }
151
-
152
- .tab-content {
153
- display: none;
154
- }
155
-
156
- .tab-content.active {
157
- display: block;
158
- }
159
-
160
- .controls-panel {
161
- background: white;
162
- border: 2px solid #E5E7EB;
163
- border-radius: 8px;
164
- padding: 20px;
165
- margin: 20px 0;
166
- }
167
-
168
- .control-row {
169
- display: grid;
170
- grid-template-columns: 150px 150px 1fr auto;
171
- gap: 12px;
172
- align-items: center;
173
- margin-bottom: 12px;
174
- padding: 12px;
175
- background: #F9FAFB;
176
- border-radius: 6px;
177
- }
178
-
179
- @media (max-width: 768px) {
180
- .control-row {
181
- grid-template-columns: 1fr;
182
- gap: 8px;
183
- padding: 12px;
184
- }
185
-
186
- .control-row > * {
187
- width: 100%;
188
- }
189
-
190
- .control-row > div:last-child {
191
- display: flex;
192
- gap: 8px;
193
- margin-top: 8px;
194
- }
195
-
196
- .control-row button {
197
- flex: 1;
198
- padding: 10px;
199
- }
200
- }
201
-
202
- .control-row label {
203
- font-weight: 600;
204
- color: #374151;
205
- font-size: 13px;
206
- }
207
-
208
- .control-row select,
209
- .control-row input[type="text"],
210
- .control-row input[type="color"] {
211
- padding: 8px 12px;
212
- border: 1px solid #D1D5DB;
213
- border-radius: 4px;
214
- font-size: 14px;
215
- }
216
-
217
- .control-row input[type="color"] {
218
- width: 60px;
219
- height: 40px;
220
- cursor: pointer;
221
- }
222
-
223
- .control-row button {
224
- padding: 8px 16px;
225
- background: #3B82F6;
226
- color: white;
227
- border: none;
228
- border-radius: 4px;
229
- cursor: pointer;
230
- font-size: 13px;
231
- margin: 0;
232
- }
233
-
234
- .control-row button:hover {
235
- background: #2563EB;
236
- }
237
-
238
- .control-row button.remove {
239
- background: #EF4444;
240
- }
241
-
242
- .control-row button.remove:hover {
243
- background: #DC2626;
244
- }
245
-
246
- @media (max-width: 768px) {
247
- body {
248
- font-size: 14px;
249
- }
250
-
251
- h1 {
252
- font-size: 24px;
253
- margin-top: 0;
254
- margin-bottom: 16px;
255
- }
256
-
257
- h2 {
258
- font-size: 20px;
259
- margin-top: 24px;
260
- margin-bottom: 12px;
261
- }
262
-
263
- .info, .config-info {
264
- padding: 12px;
265
- margin: 16px 0;
266
- font-size: 14px;
267
- }
268
-
269
- .info ul, .config-info ul {
270
- margin: 8px 0 0 16px;
271
- padding-left: 16px;
272
- line-height: 1.6;
273
- }
274
-
275
- .info ol {
276
- margin: 8px 0 0 16px;
277
- padding-left: 16px;
278
- line-height: 1.6;
279
- }
280
-
281
- .status {
282
- padding: 10px;
283
- font-size: 12px;
284
- margin: 16px 0;
285
- }
286
-
287
- button {
288
- padding: 12px 20px;
289
- font-size: 14px;
290
- margin: 8px 8px 8px 0;
291
- touch-action: manipulation;
292
- min-height: 44px;
293
- }
294
-
295
- .tabs {
296
- flex-wrap: wrap;
297
- gap: 8px;
298
- }
299
-
300
- .tab {
301
- padding: 10px 16px;
302
- font-size: 13px;
303
- min-height: 44px;
304
- }
305
-
306
- .controls-panel {
307
- padding: 16px;
308
- margin: 16px 0;
309
- }
310
-
311
- .controls-panel button {
312
- width: 100%;
313
- margin: 8px 0;
314
- }
315
-
316
- .control-row {
317
- padding: 12px;
318
- }
319
-
320
- .control-row select,
321
- .control-row input[type="text"],
322
- .control-row input[type="color"] {
323
- font-size: 16px; /* Prevents iOS zoom on focus */
324
- min-height: 44px;
325
- }
326
-
327
- .control-row input[type="color"] {
328
- min-width: 50px;
329
- min-height: 50px;
330
- width: 50px;
331
- height: 50px;
332
- }
333
-
334
- #log {
335
- font-size: 11px;
336
- max-height: 200px;
337
- padding: 12px;
338
- }
339
- }
340
-
341
- @media (max-width: 480px) {
342
- button {
343
- width: 100%;
344
- margin: 8px 0;
345
- }
346
-
347
- .tabs {
348
- flex-direction: column;
349
- }
350
-
351
- .tab {
352
- width: 100%;
353
- text-align: left;
354
- }
355
- }
356
- </style>
357
- </head>
358
- <body>
359
- <div class="container">
360
- <h1>🎤 Voice Agent Widget - Agent ID + App ID Test</h1>
361
-
362
- <div class="config-info">
363
- <strong>📝 Configuration:</strong>
364
- <ul style="margin: 10px 0 0 20px; line-height: 1.8;">
365
- <li><strong>Agent ID:</strong> agent_87c4a55a1</li>
366
- <li><strong>App ID:</strong> app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC</li>
367
- <li><strong>Method:</strong> Direct connection (no signed link)</li>
368
- </ul>
369
- </div>
370
-
371
- <div class="info">
372
- <strong>📝 Testing Instructions:</strong>
373
- <ol style="margin: 10px 0 0 20px; line-height: 1.8;">
374
- <li>Look for the floating voice button in the bottom-right corner</li>
375
- <li>Click the button to open the widget</li>
376
- <li>Click the voice button to start (will request microphone permission)</li>
377
- <li>Widget will connect directly using agent ID + app ID</li>
378
- <li>Check the status updates below</li>
379
- <li>Try the interactive customization designer below to customize colors, sizes, text, and more in real-time!</li>
380
- </ol>
381
- </div>
382
-
383
- <h2>Widget Status:</h2>
384
- <div id="status" class="status">
385
- Waiting for widget to load...
386
- </div>
387
-
388
- <h2>Console Log:</h2>
389
- <div id="log" style="background: #1F2937; color: #F9FAFB; padding: 16px; border-radius: 6px; font-family: monospace; font-size: 12px; max-height: 300px; overflow-y: auto;">
390
- Console messages will appear here...
391
- </div>
392
-
393
- <h2>Implementation Code:</h2>
394
- <div class="tabs">
395
- <button class="tab active" onclick="switchTab('simple')">Simple</button>
396
- <button class="tab" onclick="switchTab('advanced')">All Options</button>
397
- </div>
398
-
399
- <div id="simple-tab" class="tab-content active">
400
- <div class="code-block"><pre><code>const widget = new TTPAgentSDK.AgentWidget({
401
- agentId: 'agent_87c4a55a1',
402
- appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC',
403
-
404
- primaryColor: '#10B981',
405
- position: 'bottom-right',
406
-
407
- // Icon configuration
408
- icon: {
409
- type: 'custom',
410
- customImage: 'https://talktopc.com/logo192.png',
411
- size: 'medium'
412
- },
413
-
414
- // Floating button (main button) colors - WHITE
415
- button: {
416
- backgroundColor: '#FFFFFF', // White floating button
417
- hoverColor: '#E5E7EB' // Gray on hover
418
- },
419
-
420
- // Header (top of panel) colors - LIGHT PURPLE
421
- header: {
422
- title: 'TTP Support',
423
- backgroundColor: '#A78BFA', // Light purple header
424
- textColor: '#FFFFFF'
425
- },
426
-
427
- // Mic button (inside panel) colors - GRAY
428
- panel: {
429
- micButtonColor: '#E5E7EB', // Gray mic button
430
- micButtonActiveColor: '#EF4444' // Red when active
431
- }
432
- });</code></pre></div>
433
- </div>
434
-
435
- <h2>🎨 Interactive Customization</h2>
436
- <div class="controls-panel">
437
- <div style="margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid #E5E7EB;">
438
- <strong style="color: #374151;">Customize widget colors and properties in real-time:</strong>
439
- </div>
440
-
441
- <div id="control-rows">
442
- <!-- Control rows will be added dynamically -->
443
- </div>
444
-
445
- <button id="add-control-btn" style="margin-top: 12px; padding: 10px 20px; background: #10B981;">
446
- + Add Customization
447
- </button>
448
-
449
- <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #E5E7EB;">
450
- <button id="reset-defaults-btn" style="background: #6B7280;">
451
- Reset to Defaults
452
- </button>
453
- </div>
454
- </div>
455
-
456
- <h2>Implementation Code:</h2>
457
- <div class="tabs">
458
- <button class="tab active" onclick="switchTab('simple')">Simple</button>
459
- <button class="tab" onclick="switchTab('advanced')">All Options</button>
460
- </div>
461
-
462
- <div id="advanced-tab" class="tab-content">
463
- <div class="code-block"><pre><code>const widget = new TTPAgentSDK.AgentWidget({
464
- // Required
465
- agentId: 'agent_87c4a55a1',
466
- appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC',
467
-
468
- // Optional: getSessionUrl - only needed for signed links or custom URL logic
469
- // getSessionUrl: async ({ agentId, appId, variables }) => {
470
- // const response = await fetch('https://your-backend.com/api/signed-url', {
471
- // method: 'POST',
472
- // headers: { 'Content-Type': 'application/json' },
473
- // body: JSON.stringify({ agentId, appId, variables })
474
- // });
475
- // const data = await response.json();
476
- // return data.signedUrl;
477
- // },
478
-
479
- // Simple styling (backward compatible)
480
- primaryColor: '#10B981',
481
- position: 'bottom-right', // or use advanced position object below
482
-
483
- // Icon Configuration
484
- icon: {
485
- type: 'custom', // 'microphone', 'custom', 'emoji', 'text'
486
- customImage: 'https://talktopc.com/logo192.png', // For type: 'custom'
487
- size: 'medium' // 'small', 'medium', 'large', 'xl'
488
- },
489
-
490
- // Button Configuration - Floating Button (Main Button) - WHITE
491
- button: {
492
- size: 'medium', // 'small', 'medium', 'large', 'xl'
493
- shape: 'circle', // 'circle', 'square', 'rounded'
494
- backgroundColor: '#FFFFFF', // Floating button background color (WHITE)
495
- hoverColor: '#E5E7EB', // Floating button hover color (gray)
496
- shadow: true,
497
- shadowColor: 'rgba(0,0,0,0.15)'
498
- },
499
-
500
- // Header Configuration - Top of Panel - LIGHT PURPLE
501
- header: {
502
- title: 'TTP Support', // Custom title for the widget header
503
- showTitle: true, // Show/hide title
504
- backgroundColor: '#A78BFA', // Header background color (LIGHT PURPLE)
505
- textColor: '#FFFFFF', // Header text color
506
- showCloseButton: true
507
- },
508
-
509
- // Panel Configuration - Includes Mic Button Colors - GRAY
510
- panel: {
511
- width: 350,
512
- height: 500,
513
- borderRadius: 12,
514
- backgroundColor: 'rgba(255,255,255,0.95)', // Panel background
515
- backdropFilter: null,
516
- border: '1px solid rgba(0,0,0,0.1)',
517
- micButtonColor: '#E5E7EB', // Mic button (inside panel) background color (GRAY)
518
- micButtonActiveColor: '#EF4444' // Mic button color when active/recording (RED)
519
- },
520
- // icon: {
521
- // type: 'microphone',
522
- // emoji: '🎤', // For type: 'emoji'
523
- // text: 'AI', // For type: 'text'
524
- // size: 'medium'
525
- // },
526
-
527
- // Advanced Positioning
528
- // position: {
529
- // vertical: 'bottom', // 'top', 'bottom', 'center'
530
- // horizontal: 'right', // 'left', 'right', 'center'
531
- // offset: { x: 20, y: 20 } // Custom offset in pixels
532
- // },
533
-
534
- // Button Configuration
535
- // button: {
536
- // size: 'medium', // 'small', 'medium', 'large', 'xl'
537
- // shape: 'circle', // 'circle', 'square', 'rounded'
538
- // primaryColor: '#10B981',
539
- // hoverColor: '#059669',
540
- // activeColor: '#EF4444',
541
- // shadow: true,
542
- // shadowColor: 'rgba(0,0,0,0.15)'
543
- // },
544
-
545
- // Panel Configuration
546
- // panel: {
547
- // width: 350,
548
- // height: 500,
549
- // borderRadius: 12,
550
- // backgroundColor: 'rgba(255,255,255,0.95)',
551
- // backdropFilter: 'blur(10px)', // Glass morphism effect
552
- // border: '1px solid rgba(0,0,0,0.1)'
553
- // },
554
-
555
- // Header Configuration
556
- // header: {
557
- // title: 'Voice Assistant',
558
- // showTitle: true,
559
- // backgroundColor: null, // Uses button primaryColor if null
560
- // textColor: '#FFFFFF',
561
- // showCloseButton: true
562
- // },
563
-
564
- // Messages Configuration
565
- // messages: {
566
- // userBackgroundColor: '#E5E7EB',
567
- // agentBackgroundColor: '#F3F4F6',
568
- // systemBackgroundColor: '#DCFCE7',
569
- // errorBackgroundColor: '#FEE2E2',
570
- // textColor: '#1F2937',
571
- // fontSize: '14px',
572
- // borderRadius: 8
573
- // },
574
-
575
- // Animation Configuration
576
- // animation: {
577
- // enableHover: true,
578
- // enablePulse: true,
579
- // enableSlide: true,
580
- // duration: 0.3
581
- // },
582
-
583
- // Behavior Configuration
584
- // behavior: {
585
- // startOpen: false, // Start with panel open on page load
586
- // hidden: false, // Hide the floating button when true
587
- // autoOpen: false, // Legacy: kept for backward-compatibility
588
- // autoConnect: false, // Automatically connect on load
589
- // showWelcomeMessage: true,
590
- // welcomeMessage: 'Hello! How can I help you today?'
591
- // },
592
-
593
- // Accessibility Configuration
594
- // accessibility: {
595
- // ariaLabel: 'Voice Assistant',
596
- // ariaDescription: 'Click to open voice assistant',
597
- // keyboardNavigation: true // ESC key to close
598
- // },
599
-
600
- // Custom CSS
601
- // customStyles: `
602
- // #agent-widget {
603
- // /* Your custom CSS here */
604
- // }
605
- // `,
606
-
607
- // Variables for your agent/backend
608
- variables: {
609
- testMode: true,
610
- userName: 'Test User',
611
- page: 'test-agent-app.html'
612
- }
613
- });</code></pre></div>
614
- </div>
615
-
616
- <script>
617
- function switchTab(tabName) {
618
- // Hide all tab contents
619
- document.querySelectorAll('.tab-content').forEach(content => {
620
- content.classList.remove('active');
621
- });
622
-
623
- // Remove active class from all tabs
624
- document.querySelectorAll('.tab').forEach(tab => {
625
- tab.classList.remove('active');
626
- });
627
-
628
- // Show selected tab content
629
- document.getElementById(tabName + '-tab').classList.add('active');
630
-
631
- // Add active class to clicked tab
632
- event.target.classList.add('active');
633
- }
634
- </script>
635
-
636
- <h2>Manual Tests:</h2>
637
- <button onclick="testMicButton()">Test Mic Button Click</button>
638
- <button onclick="testToggleVoice()">Test ToggleVoice Method</button>
639
- <button onclick="checkWidgetState()">Check Widget State</button>
640
- <button onclick="testMicrophone()">Test Microphone Access</button>
641
- </div>
642
-
643
- <!-- Load the widget -->
644
- <script src="../agent-widget.js" onload="console.log('SDK script loaded successfully')" onerror="console.error('Failed to load SDK script')"></script>
645
-
646
- <script>
647
- // Override console.log to show in page
648
- const originalLog = console.log;
649
- const originalError = console.error;
650
- const logDiv = document.getElementById('log');
651
-
652
- function addLog(message, type = 'log') {
653
- const time = new Date().toLocaleTimeString();
654
- const color = type === 'error' ? '#EF4444' : '#10B981';
655
- logDiv.innerHTML += `<div style="color: ${color};">[${time}] ${message}</div>`;
656
- logDiv.scrollTop = logDiv.scrollHeight;
657
- }
658
-
659
- console.log = (...args) => {
660
- originalLog(...args);
661
- addLog(args.join(' '));
662
- };
663
-
664
- console.error = (...args) => {
665
- originalError(...args);
666
- addLog(args.join(' '), 'error');
667
- };
668
-
669
- // Update status display
670
- function updateStatus(message, type = 'info') {
671
- const statusDiv = document.getElementById('status');
672
- statusDiv.textContent = message;
673
- statusDiv.className = 'status ' + type;
674
- }
675
-
676
- // ============================================
677
- // CUSTOMIZATION CONTROLS - Declare early
678
- // ============================================
679
- // Initialize variable first - MUST be declared before any functions that use it
680
- let controlRowCount = 0;
681
-
682
- const componentOptions = {
683
- 'button': {
684
- label: 'Floating Button',
685
- properties: {
686
- 'backgroundColor': { type: 'color', label: 'Background Color' },
687
- 'hoverColor': { type: 'color', label: 'Hover Color' },
688
- 'size': { type: 'select', label: 'Size', options: ['small', 'medium', 'large', 'xl'] },
689
- 'shape': { type: 'select', label: 'Shape', options: ['circle', 'square', 'rounded'] }
690
- }
691
- },
692
- 'header': {
693
- label: 'Header',
694
- properties: {
695
- 'backgroundColor': { type: 'color', label: 'Background Color' },
696
- 'textColor': { type: 'color', label: 'Text Color' },
697
- 'title': { type: 'text', label: 'Title' }
698
- }
699
- },
700
- 'panel': {
701
- label: 'Mic Button',
702
- properties: {
703
- 'micButtonColor': { type: 'color', label: 'Background Color' },
704
- 'micButtonActiveColor': { type: 'color', label: 'Active Color (Recording)' }
705
- }
706
- },
707
- 'micButtonHint': {
708
- label: 'Mic Button Hint',
709
- properties: {
710
- 'micButtonHint.text': { type: 'text', label: 'Hint Text (empty to hide)' },
711
- 'micButtonHint.color': { type: 'color', label: 'Text Color' },
712
- 'micButtonHint.fontSize': { type: 'text', label: 'Font Size (e.g., 12px)' }
713
- }
714
- },
715
- 'direction': {
716
- label: 'Text Direction',
717
- properties: {
718
- 'direction': { type: 'select', label: 'Direction', options: ['ltr', 'rtl'] }
719
- }
720
- }
721
- };
722
-
723
- function initializeCustomizationControls() {
724
- // Add one initial row
725
- addControlRow();
726
-
727
- // Set up button event listeners
728
- const addBtn = document.getElementById('add-control-btn');
729
- const resetBtn = document.getElementById('reset-defaults-btn');
730
- if (addBtn) {
731
- addBtn.addEventListener('click', addControlRow);
732
- }
733
- if (resetBtn) {
734
- resetBtn.addEventListener('click', resetToDefaults);
735
- }
736
- }
737
-
738
- function addControlRow() {
739
- controlRowCount++;
740
- const controlRows = document.getElementById('control-rows');
741
- const row = document.createElement('div');
742
- row.className = 'control-row';
743
- row.id = `control-row-${controlRowCount}`;
744
-
745
- // Component selector
746
- const componentSelect = document.createElement('select');
747
- componentSelect.id = `component-${controlRowCount}`;
748
- componentSelect.innerHTML = '<option value="">Select Component</option>';
749
- Object.keys(componentOptions).forEach(key => {
750
- const option = document.createElement('option');
751
- option.value = key;
752
- option.textContent = componentOptions[key].label;
753
- componentSelect.appendChild(option);
754
- });
755
- componentSelect.onchange = () => updatePropertySelect(controlRowCount);
756
-
757
- // Property selector (will be populated based on component)
758
- const propertySelect = document.createElement('select');
759
- propertySelect.id = `property-${controlRowCount}`;
760
- propertySelect.innerHTML = '<option value="">Select Property</option>';
761
-
762
- // Value input (dynamically changes based on property type)
763
- const valueContainer = document.createElement('div');
764
- valueContainer.id = `value-container-${controlRowCount}`;
765
- valueContainer.style.display = 'none';
766
-
767
- // Action buttons
768
- const actionContainer = document.createElement('div');
769
- actionContainer.style.display = 'flex';
770
- actionContainer.style.gap = '8px';
771
-
772
- const applyBtn = document.createElement('button');
773
- applyBtn.textContent = 'Apply';
774
- applyBtn.onclick = () => applyChange(controlRowCount);
775
-
776
- const removeBtn = document.createElement('button');
777
- removeBtn.textContent = 'Remove';
778
- removeBtn.className = 'remove';
779
- removeBtn.onclick = () => removeControlRow(controlRowCount);
780
-
781
- actionContainer.appendChild(applyBtn);
782
- actionContainer.appendChild(removeBtn);
783
-
784
- row.appendChild(componentSelect);
785
- row.appendChild(propertySelect);
786
- row.appendChild(valueContainer);
787
- row.appendChild(actionContainer);
788
-
789
- controlRows.appendChild(row);
790
- }
791
-
792
- function updatePropertySelect(rowId) {
793
- const componentSelect = document.getElementById(`component-${rowId}`);
794
- const propertySelect = document.getElementById(`property-${rowId}`);
795
- const valueContainer = document.getElementById(`value-container-${rowId}`);
796
-
797
- const component = componentSelect.value;
798
- if (!component) {
799
- propertySelect.innerHTML = '<option value="">Select Property</option>';
800
- valueContainer.style.display = 'none';
801
- valueContainer.innerHTML = '';
802
- return;
803
- }
804
-
805
- // Populate properties
806
- propertySelect.innerHTML = '<option value="">Select Property</option>';
807
- const properties = componentOptions[component].properties;
808
- Object.keys(properties).forEach(key => {
809
- const option = document.createElement('option');
810
- option.value = key;
811
- option.textContent = properties[key].label;
812
- propertySelect.appendChild(option);
813
- });
814
-
815
- propertySelect.onchange = () => updateValueInput(rowId);
816
- valueContainer.style.display = 'none';
817
- valueContainer.innerHTML = '';
818
- }
819
-
820
- function updateValueInput(rowId) {
821
- const componentSelect = document.getElementById(`component-${rowId}`);
822
- const propertySelect = document.getElementById(`property-${rowId}`);
823
- const valueContainer = document.getElementById(`value-container-${rowId}`);
824
-
825
- const component = componentSelect.value;
826
- const property = propertySelect.value;
827
-
828
- if (!component || !property) {
829
- valueContainer.style.display = 'none';
830
- valueContainer.innerHTML = '';
831
- return;
832
- }
833
-
834
- const propConfig = componentOptions[component].properties[property];
835
- valueContainer.innerHTML = '';
836
-
837
- if (propConfig.type === 'color') {
838
- const input = document.createElement('input');
839
- input.type = 'color';
840
- input.id = `value-${rowId}`;
841
- // Set current value from widget
842
- const currentValue = getCurrentValue(component, property);
843
- if (currentValue) input.value = rgbToHex(currentValue) || currentValue;
844
- valueContainer.appendChild(input);
845
- } else if (propConfig.type === 'select') {
846
- const select = document.createElement('select');
847
- select.id = `value-${rowId}`;
848
- propConfig.options.forEach(opt => {
849
- const option = document.createElement('option');
850
- option.value = opt;
851
- option.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);
852
- select.appendChild(option);
853
- });
854
- // Set current value
855
- const currentValue = getCurrentValue(component, property);
856
- if (currentValue !== null && currentValue !== undefined) {
857
- select.value = currentValue.toString();
858
- }
859
- valueContainer.appendChild(select);
860
- } else if (propConfig.type === 'text') {
861
- const input = document.createElement('input');
862
- input.type = 'text';
863
- input.id = `value-${rowId}`;
864
- input.placeholder = 'Enter value';
865
- // Set current value
866
- const currentValue = getCurrentValue(component, property);
867
- if (currentValue !== null && currentValue !== undefined) {
868
- input.value = currentValue;
869
- }
870
- valueContainer.appendChild(input);
871
- }
872
-
873
- valueContainer.style.display = 'block';
874
- }
875
-
876
- function getCurrentValue(component, property) {
877
- if (!window.testWidget) return null;
878
- const config = window.testWidget.config;
879
-
880
- if (component === 'button' && config.button) {
881
- return config.button[property];
882
- } else if (component === 'header' && config.header) {
883
- return config.header[property];
884
- } else if (component === 'panel' && config.panel) {
885
- return config.panel[property];
886
- } else if (component === 'micButtonHint' && config.panel?.micButtonHint) {
887
- // Handle nested micButtonHint properties
888
- const hintKey = property.split('.')[1]; // e.g., 'text', 'color', 'fontSize'
889
- const value = config.panel.micButtonHint[hintKey];
890
- return value;
891
- } else if (component === 'direction') {
892
- return config.direction;
893
- }
894
- return null;
895
- }
896
-
897
- function applyChange(rowId) {
898
- const componentSelect = document.getElementById(`component-${rowId}`);
899
- const propertySelect = document.getElementById(`property-${rowId}`);
900
- const valueInput = document.getElementById(`value-${rowId}`);
901
-
902
- const component = componentSelect.value;
903
- const property = propertySelect.value;
904
- let value = valueInput.value;
905
-
906
- if (!component || !property) {
907
- alert('Please select component and property');
908
- return;
909
- }
910
-
911
- // Allow empty value for text fields (to hide hint)
912
- if (value === undefined || value === null) {
913
- value = '';
914
- }
915
-
916
- if (!window.testWidget) {
917
- alert('Widget not initialized');
918
- return;
919
- }
920
-
921
- // Handle boolean values (only for non-empty values)
922
- if (value && value !== '') {
923
- if (value === 'true') value = true;
924
- if (value === 'false') value = false;
925
- }
926
-
927
- // Prepare update config
928
- const updateConfig = {};
929
- if (component === 'button') {
930
- updateConfig.button = { [property]: value };
931
- } else if (component === 'header') {
932
- updateConfig.header = { [property]: value };
933
- } else if (component === 'panel') {
934
- updateConfig.panel = { [property]: value };
935
- } else if (component === 'micButtonHint') {
936
- // Handle nested micButtonHint properties
937
- const hintKey = property.split('.')[1]; // e.g., 'text', 'color', 'fontSize', 'show'
938
- if (!updateConfig.panel) updateConfig.panel = {};
939
- if (!updateConfig.panel.micButtonHint) updateConfig.panel.micButtonHint = {};
940
- updateConfig.panel.micButtonHint[hintKey] = value;
941
- } else if (component === 'direction') {
942
- updateConfig.direction = value;
943
- }
944
-
945
- // Apply update
946
- console.log(`Applying change: ${component}.${property} = ${value}`);
947
- window.testWidget.updateConfig(updateConfig);
948
-
949
- // Update the displayed code blocks
950
- updateCodeDisplay();
951
- }
952
-
953
- function removeControlRow(rowId) {
954
- const row = document.getElementById(`control-row-${rowId}`);
955
- if (row) row.remove();
956
- }
957
-
958
- function resetToDefaults() {
959
- if (!window.testWidget || !window.defaultConfig) {
960
- alert('Widget or defaults not available');
961
- return;
962
- }
963
-
964
- if (confirm('Reset widget to default configuration?')) {
965
- window.testWidget.updateConfig(window.defaultConfig);
966
- updateCodeDisplay();
967
-
968
- // Clear all control rows
969
- const controlRows = document.getElementById('control-rows');
970
- controlRows.innerHTML = '';
971
- controlRowCount = 0;
972
- addControlRow();
973
- }
974
- }
975
-
976
- function updateCodeDisplay() {
977
- if (!window.testWidget) return;
978
- const config = window.testWidget.config;
979
-
980
- // Helper to safely get position value
981
- function getPositionValue() {
982
- if (!config.position) return 'bottom-right';
983
- if (typeof config.position === 'string') return config.position;
984
- // If it's an object, extract the string representation
985
- return `${config.position.vertical || 'bottom'}-${config.position.horizontal || 'right'}`;
986
- }
987
-
988
- // Update simple tab
989
- const simpleCode = document.querySelector('#simple-tab code');
990
- if (simpleCode) {
991
- const position = getPositionValue();
992
- simpleCode.textContent = `const widget = new TTPAgentSDK.AgentWidget({
993
- agentId: 'agent_87c4a55a1',
994
- appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC',
995
-
996
- primaryColor: '${config.primaryColor || '#10B981'}',
997
- position: '${position}',
998
-
999
- icon: {
1000
- type: '${config.icon?.type || 'custom'}',
1001
- customImage: '${config.icon?.customImage || 'https://talktopc.com/logo192.png'}',
1002
- size: '${config.icon?.size || 'medium'}'
1003
- },
1004
-
1005
- button: {
1006
- backgroundColor: '${config.button?.backgroundColor || '#FFFFFF'}',
1007
- hoverColor: '${config.button?.hoverColor || '#E5E7EB'}'
1008
- },
1009
-
1010
- header: {
1011
- title: '${(config.header?.title || 'TTP Support').replace(/'/g, "\\'")}',
1012
- backgroundColor: '${config.header?.backgroundColor || '#A78BFA'}',
1013
- textColor: '${config.header?.textColor || '#FFFFFF'}'
1014
- },
1015
-
1016
- panel: {
1017
- micButtonColor: '${config.panel?.micButtonColor || '#E5E7EB'}',
1018
- micButtonActiveColor: '${config.panel?.micButtonActiveColor || '#EF4444'}'
1019
- }
1020
- });`;
1021
- }
1022
- }
1023
-
1024
- function rgbToHex(rgb) {
1025
- if (!rgb) return null;
1026
- if (rgb.startsWith('#')) return rgb;
1027
- // Try to parse rgb/rgba format
1028
- const match = rgb.match(/\d+/g);
1029
- if (match && match.length >= 3) {
1030
- return '#' + match.slice(0, 3).map(x => {
1031
- const hex = parseInt(x).toString(16);
1032
- return hex.length === 1 ? '0' + hex : hex;
1033
- }).join('');
1034
- }
1035
- return null;
1036
- }
1037
-
1038
- // Initialize the widget
1039
- try {
1040
- console.log('Initializing widget with agent ID + app ID...');
1041
- console.log('TTPAgentSDK available:', typeof TTPAgentSDK);
1042
- console.log('TTPAgentSDK.AgentWidget available:', typeof TTPAgentSDK?.AgentWidget);
1043
-
1044
- if (typeof TTPAgentSDK === 'undefined') {
1045
- throw new Error('TTPAgentSDK is not defined. Check if the script loaded correctly.');
1046
- }
1047
-
1048
- if (typeof TTPAgentSDK.AgentWidget === 'undefined') {
1049
- throw new Error('TTPAgentSDK.AgentWidget is not defined. Check the SDK build.');
1050
- }
1051
-
1052
- // Create a new AgentWidget instance with agent ID + app ID
1053
- // getSessionUrl is now OPTIONAL - widget will auto-construct URL from agentId/appId
1054
- const widget = new TTPAgentSDK.AgentWidget({
1055
- agentId: 'agent_87c4a55a1',
1056
- appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC',
1057
-
1058
- // getSessionUrl is OPTIONAL - omitted here, so widget auto-constructs the WebSocket URL
1059
- // If you need signed links or custom URL logic, you can provide it:
1060
- // getSessionUrl: async ({ agentId, appId, variables }) => { ... }
1061
-
1062
- // Optional: Pass variables for your agent/backend
1063
- variables: {
1064
- testMode: true,
1065
- userName: 'Test User',
1066
- page: 'test-agent-app.html',
1067
- connectionType: 'direct'
1068
- },
1069
-
1070
- // Customize appearance (using simple config - backward compatible)
1071
- primaryColor: '#10B981',
1072
- position: 'bottom-right',
1073
-
1074
- // Set TTP icon
1075
- icon: {
1076
- type: 'custom',
1077
- customImage: 'https://talktopc.com/logo192.png',
1078
- size: 'medium'
1079
- },
1080
-
1081
- // Button colors - Floating button (main button) - WHITE
1082
- button: {
1083
- backgroundColor: '#FFFFFF', // White background for floating button
1084
- hoverColor: '#E5E7EB' // Gray on hover
1085
- },
1086
-
1087
- // Header colors - Top of panel - LIGHT PURPLE
1088
- header: {
1089
- title: 'TTP Support',
1090
- backgroundColor: '#A78BFA', // Light purple header background
1091
- textColor: '#FFFFFF' // White text
1092
- },
1093
-
1094
- // Panel colors - Mic button inside panel - GRAY
1095
- panel: {
1096
- micButtonColor: '#E5E7EB', // Gray mic button
1097
- micButtonActiveColor: '#EF4444' // Red when active/recording
1098
- },
1099
-
1100
- // Behavior: new options
1101
- behavior: {
1102
- startOpen: true, // Start with panel open on page load
1103
- hidden: false, // If true, hides the floating button
1104
- autoConnect: false,
1105
- showWelcomeMessage: true,
1106
- welcomeMessage: 'Hello! How can I help you today?'
1107
- },
1108
-
1109
- // Enhanced features are now available but using defaults
1110
- // You can optionally add:
1111
- // button: { size: 'large', shape: 'circle' },
1112
- // panel: { width: 400, height: 600 },
1113
- // header: { title: 'My Voice Assistant' },
1114
- // animation: { enableHover: true, enablePulse: true }
1115
- });
1116
-
1117
- // Store widget reference for testing (like the working implementation)
1118
- window.testWidget = widget;
1119
-
1120
- // Store default config for reset functionality (matches initial widget config)
1121
- window.defaultConfig = JSON.parse(JSON.stringify({
1122
- primaryColor: '#10B981',
1123
- position: 'bottom-right',
1124
- direction: 'ltr',
1125
- icon: {
1126
- type: 'custom',
1127
- customImage: 'https://talktopc.com/logo192.png',
1128
- size: 'medium'
1129
- },
1130
- button: {
1131
- backgroundColor: '#FFFFFF',
1132
- hoverColor: '#E5E7EB',
1133
- size: 'medium',
1134
- shape: 'circle'
1135
- },
1136
- header: {
1137
- title: 'TTP Support',
1138
- backgroundColor: '#A78BFA',
1139
- textColor: '#FFFFFF'
1140
- },
1141
- panel: {
1142
- micButtonColor: '#E5E7EB',
1143
- micButtonActiveColor: '#EF4444',
1144
- micButtonHint: {
1145
- text: 'Click the button to start voice conversation',
1146
- color: '#6B7280',
1147
- fontSize: '12px'
1148
- }
1149
- }
1150
- }));
1151
-
1152
- // Initialize customization controls
1153
- initializeCustomizationControls();
1154
-
1155
- console.log('Widget initialized successfully with agent ID + app ID!');
1156
- console.log('Widget instance:', widget);
1157
- console.log('Test methods available at window.testWidget');
1158
- updateStatus('Widget loaded and ready ✓', 'success');
1159
-
1160
- } catch (error) {
1161
- console.error('Failed to initialize widget:', error);
1162
- updateStatus('Widget failed to load ✗', 'error');
1163
- }
1164
-
1165
- // Test functions (matching the working implementation)
1166
- function testMicButton() {
1167
- console.log('🎤 Testing mic button manually...');
1168
- const micButton = document.getElementById('agent-mic-button');
1169
- if (micButton) {
1170
- console.log('🎤 Found mic button, clicking...');
1171
- micButton.click();
1172
- } else {
1173
- console.log('🎤 Mic button not found');
1174
- }
1175
- }
1176
-
1177
- function testToggleVoice() {
1178
- console.log('🎤 Testing toggleVoice method...');
1179
- if (window.testWidget && window.testWidget.toggleVoice) {
1180
- window.testWidget.toggleVoice();
1181
- } else {
1182
- console.log('🎤 toggleVoice method not available');
1183
- }
1184
- }
1185
-
1186
- function checkWidgetState() {
1187
- console.log('🎤 Widget state:', {
1188
- isActive: window.testWidget?.isActive,
1189
- isOpen: window.testWidget?.isOpen,
1190
- sdk: window.testWidget?.sdk
1191
- });
1192
- }
1193
-
1194
- async function testMicrophone() {
1195
- console.log('Testing microphone access...');
1196
- try {
1197
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1198
- console.log('Microphone access granted ✓');
1199
- stream.getTracks().forEach(track => track.stop());
1200
- alert('Microphone access OK!');
1201
- } catch (error) {
1202
- console.error('Microphone access denied:', error);
1203
- alert('Microphone access DENIED! Please allow microphone in browser settings.');
1204
- }
1205
- }
1206
-
1207
- </script>
1208
- </body>
1209
- </html>