ttp-agent-sdk 2.34.0 → 2.34.4

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.
@@ -325,8 +325,7 @@
325
325
 
326
326
  .mock-widget {
327
327
  position: absolute;
328
- bottom: 60px;
329
- right: 60px;
328
+ /* Position is set dynamically via updateMockWidgetPosition() */
330
329
  z-index: 1000;
331
330
  /* Ensure widget doesn't get clipped */
332
331
  min-width: 360px;
@@ -402,10 +401,7 @@
402
401
  min-width: 450px;
403
402
  }
404
403
 
405
- .mock-widget {
406
- bottom: 20px;
407
- right: 20px;
408
- }
404
+ /* Position is set dynamically via updateMockWidgetPosition() */
409
405
  }
410
406
 
411
407
  @media (max-width: 1200px) {
@@ -423,7 +419,15 @@
423
419
  }
424
420
 
425
421
  /* Mock widget styles - will be dynamically generated */
422
+ .mock-button-container {
423
+ position: relative;
424
+ display: inline-flex;
425
+ align-items: center;
426
+ justify-content: center;
427
+ }
428
+
426
429
  .mock-widget-button {
430
+ position: relative;
427
431
  width: 60px;
428
432
  height: 60px;
429
433
  border-radius: 50%;
@@ -436,17 +440,150 @@
436
440
  justify-content: center;
437
441
  font-size: 24px;
438
442
  transition: all 0.3s;
443
+ margin: 0;
444
+ flex-shrink: 0;
439
445
  }
440
446
 
441
447
  .mock-widget-button:hover {
442
448
  transform: scale(1.1);
443
449
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
444
450
  }
451
+
452
+ /* Prompt bubble styles */
453
+ .mock-prompt-bubble {
454
+ position: absolute;
455
+ z-index: 10002;
456
+ pointer-events: none;
457
+ white-space: nowrap;
458
+ }
459
+
460
+ .mock-prompt-bubble.top {
461
+ bottom: calc(100% + 18px);
462
+ left: 50%;
463
+ transform: translateX(-50%);
464
+ }
465
+
466
+ .mock-prompt-bubble.left {
467
+ right: calc(100% + 12px);
468
+ top: 50%;
469
+ transform: translateY(-50%);
470
+ }
471
+
472
+ .mock-prompt-bubble.right {
473
+ left: calc(100% + 12px);
474
+ top: 50%;
475
+ transform: translateY(-50%);
476
+ }
477
+
478
+ .mock-prompt-bubble-content {
479
+ position: relative;
480
+ padding: 8px 16px;
481
+ border-radius: 20px;
482
+ font-weight: 500;
483
+ font-size: 14px;
484
+ box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
485
+ overflow: hidden;
486
+ }
487
+
488
+ .mock-prompt-bubble-shimmer {
489
+ position: absolute;
490
+ inset: 0;
491
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent);
492
+ animation: mock-shimmer 2s infinite;
493
+ }
494
+
495
+ @keyframes mock-shimmer {
496
+ 0% { transform: translateX(-100%); }
497
+ 100% { transform: translateX(200%); }
498
+ }
499
+
500
+ @keyframes mock-bounce {
501
+ 0%, 100% { transform: translateX(-50%) translateY(0); }
502
+ 50% { transform: translateX(-50%) translateY(-10px); }
503
+ }
504
+
505
+ @keyframes mock-float {
506
+ 0%, 100% { transform: translateX(-50%) translateY(0); }
507
+ 50% { transform: translateX(-50%) translateY(-8px); }
508
+ }
509
+
510
+ @keyframes mock-pulse-ring {
511
+ 0% { transform: scale(1); opacity: 0.4; }
512
+ 100% { transform: scale(1.8); opacity: 0; }
513
+ }
514
+
515
+ .mock-prompt-bubble.animation-bounce {
516
+ animation: mock-bounce 1s ease-in-out infinite;
517
+ }
518
+
519
+ .mock-prompt-bubble.animation-pulse {
520
+ animation: pulse 2s ease-in-out infinite;
521
+ }
522
+
523
+ .mock-prompt-bubble.animation-float {
524
+ animation: mock-float 2s ease-in-out infinite;
525
+ }
526
+
527
+ /* Ensure top-positioned bubbles maintain horizontal centering during animations */
528
+ .mock-prompt-bubble.top.animation-bounce,
529
+ .mock-prompt-bubble.top.animation-float,
530
+ .mock-prompt-bubble.top.animation-pulse {
531
+ /* Animation keyframes already include translateX(-50%) for centering */
532
+ }
533
+
534
+ .mock-prompt-bubble-arrow {
535
+ position: absolute;
536
+ width: 0;
537
+ height: 0;
538
+ border: 8px solid transparent;
539
+ }
540
+
541
+ .mock-prompt-bubble.top .mock-prompt-bubble-arrow {
542
+ top: 100%;
543
+ left: 50%;
544
+ transform: translateX(-50%);
545
+ border-top-color: var(--mock-prompt-bg-color, #7c3aed);
546
+ border-bottom: none;
547
+ margin-left: 0;
548
+ }
549
+
550
+ .mock-prompt-bubble.left .mock-prompt-bubble-arrow {
551
+ left: 100%;
552
+ top: 50%;
553
+ transform: translateY(-50%);
554
+ border-left-color: var(--mock-prompt-bg-color, #7c3aed);
555
+ border-right: none;
556
+ }
557
+
558
+ .mock-prompt-bubble.right .mock-prompt-bubble-arrow {
559
+ right: 100%;
560
+ top: 50%;
561
+ transform: translateY(-50%);
562
+ border-right-color: var(--mock-prompt-bg-color, #7c3aed);
563
+ border-left: none;
564
+ }
565
+
566
+ .mock-pulse-rings {
567
+ position: absolute;
568
+ inset: 0;
569
+ border-radius: 50%;
570
+ pointer-events: none;
571
+ }
572
+
573
+ .mock-pulse-ring {
574
+ position: absolute;
575
+ inset: 0;
576
+ border-radius: 50%;
577
+ animation: mock-pulse-ring 2s ease-out infinite;
578
+ }
579
+
580
+ .mock-pulse-ring:nth-child(2) {
581
+ animation-delay: 0.5s;
582
+ }
445
583
 
446
584
  .mock-widget-panel {
447
- position: absolute;
448
- bottom: 20px;
449
- right: 0;
585
+ position: relative;
586
+ /* Position is controlled by parent .mock-widget */
450
587
  width: 360px;
451
588
  height: 550px;
452
589
  background: white;
@@ -494,80 +631,116 @@
494
631
  .mock-panel-content {
495
632
  flex: 1;
496
633
  overflow-y: auto;
497
- padding: 20px;
634
+ padding: 0; /* Remove padding so landing screen fills entire area */
498
635
  }
499
636
 
500
637
  .mock-landing-screen {
501
638
  display: flex;
502
639
  flex-direction: column;
503
640
  align-items: center;
504
- justify-content: center;
505
- height: 100%;
506
- padding: 40px 20px;
641
+ justify-content: flex-start;
642
+ flex: 1;
643
+ padding: 32px 24px;
507
644
  text-align: center;
645
+ overflow-y: auto;
646
+ min-height: 0;
647
+ width: 100%;
648
+ /* Background will be set inline via style attribute to match widget config */
508
649
  }
509
650
 
510
651
  .mock-landing-logo {
511
- font-size: 64px;
512
- margin-bottom: 16px;
652
+ width: 88px;
653
+ height: 88px;
654
+ border-radius: 22px;
655
+ margin-bottom: 20px;
513
656
  display: flex;
514
657
  align-items: center;
515
658
  justify-content: center;
659
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
660
+ box-shadow: 0 8px 28px rgba(102, 126, 234, 0.35);
661
+ border: none; /* No border by default - will be overridden by inline styles */
516
662
  }
517
663
 
518
664
  .mock-landing-logo img {
519
665
  display: block;
666
+ border: none !important;
667
+ outline: none !important;
520
668
  }
521
669
 
522
670
  .mock-landing-title {
523
- font-size: 24px;
524
- font-weight: 700;
525
- color: #1e293b;
526
- margin-bottom: 8px;
671
+ font-size: 20px;
672
+ font-weight: 600;
673
+ color: #1e1b4b;
674
+ margin-bottom: 6px;
675
+ line-height: 1.3;
527
676
  }
528
677
 
529
678
  .mock-landing-subtitle {
530
679
  font-size: 14px;
531
680
  color: #64748b;
532
- margin-bottom: 32px;
681
+ margin-bottom: 28px;
533
682
  }
534
683
 
535
684
  .mock-mode-cards {
536
685
  display: flex;
537
- gap: 16px;
686
+ gap: 12px;
538
687
  width: 100%;
688
+ justify-content: center;
539
689
  }
540
690
 
541
691
  .mock-mode-card {
542
692
  flex: 1;
693
+ max-width: 160px;
543
694
  background: white;
544
- border: 2px solid #e5e7eb;
545
- border-radius: 12px;
546
- padding: 24px;
695
+ border: 1px solid rgba(0, 0, 0, 0.06);
696
+ border-radius: 18px;
697
+ padding: 20px 16px;
547
698
  cursor: pointer;
548
- transition: all 0.2s;
699
+ display: flex;
700
+ flex-direction: column;
701
+ align-items: center;
702
+ gap: 12px;
703
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
704
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
549
705
  }
550
706
 
551
707
  .mock-mode-card:hover {
552
- border-color: #667eea;
553
- transform: translateY(-2px);
708
+ transform: translateY(-4px);
709
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.2);
710
+ border-color: rgba(124, 58, 237, 0.3);
554
711
  }
555
712
 
556
713
  .mock-mode-icon {
557
- font-size: 32px;
558
- margin-bottom: 12px;
714
+ width: 56px;
715
+ height: 56px;
716
+ display: flex;
717
+ align-items: center;
718
+ justify-content: center;
719
+ border-radius: 16px;
720
+ background: #7C3AED;
721
+ color: #fff;
722
+ box-shadow: 0 4px 14px rgba(124, 58, 237, 0.35);
723
+ }
724
+
725
+ .mock-mode-icon svg {
726
+ stroke: white;
727
+ fill: none;
559
728
  }
560
729
 
561
730
  .mock-mode-title {
562
- font-size: 16px;
731
+ font-size: 14px;
563
732
  font-weight: 600;
564
- color: #1e293b;
565
- margin-bottom: 4px;
733
+ color: #1e1b4b;
734
+ text-align: center;
566
735
  }
567
736
 
568
737
  .mock-mode-desc {
569
- font-size: 12px;
570
- color: #64748b;
738
+ display: none; /* Hide description to match real widget */
739
+ }
740
+
741
+ @keyframes pulse {
742
+ 0%, 100% { transform: scale(1); opacity: 1; }
743
+ 50% { transform: scale(1.15); opacity: 0.8; }
571
744
  }
572
745
 
573
746
  .mock-text-interface {
@@ -586,21 +759,57 @@
586
759
  }
587
760
 
588
761
  .mock-message {
762
+ display: flex;
763
+ gap: 8px;
764
+ align-items: center;
589
765
  max-width: 75%;
766
+ padding: 4px 0;
767
+ }
768
+
769
+ .mock-message.user {
770
+ align-self: flex-end;
771
+ flex-direction: row-reverse; /* Avatar on right for LTR */
772
+ }
773
+
774
+ .mock-message.agent {
775
+ align-self: flex-start;
776
+ flex-direction: row; /* Avatar on left for LTR */
777
+ }
778
+
779
+ /* RTL support - reverse flex direction */
780
+ .mock-text-interface[style*="direction: rtl"] .mock-message.user {
781
+ flex-direction: row; /* Avatar on left for RTL */
782
+ }
783
+
784
+ .mock-text-interface[style*="direction: rtl"] .mock-message.agent {
785
+ flex-direction: row-reverse; /* Avatar on right for RTL */
786
+ }
787
+
788
+ .mock-message-avatar {
789
+ width: 20px;
790
+ height: 20px;
791
+ display: flex;
792
+ align-items: center;
793
+ justify-content: center;
794
+ flex-shrink: 0;
795
+ font-size: 18px;
796
+ line-height: 1;
797
+ background: transparent;
798
+ }
799
+
800
+ .mock-message-bubble {
590
801
  padding: 12px 16px;
591
802
  border-radius: 16px;
592
803
  font-size: 14px;
593
804
  line-height: 1.5;
594
805
  }
595
806
 
596
- .mock-message.user {
597
- align-self: flex-end;
807
+ .mock-message.user .mock-message-bubble {
598
808
  background: #E5E7EB;
599
809
  color: #1F2937;
600
810
  }
601
811
 
602
- .mock-message.agent {
603
- align-self: flex-start;
812
+ .mock-message.agent .mock-message-bubble {
604
813
  background: #F3F4F6;
605
814
  color: #1F2937;
606
815
  }
@@ -1074,11 +1283,15 @@
1074
1283
  <div class="preview-area" id="previewArea">
1075
1284
  <!-- Container to ensure proper scrolling - must be taller than preview area -->
1076
1285
  <div style="position: relative; min-height: 800px; width: 100%; padding-bottom: 200px;">
1077
- <div class="mock-widget" id="mockWidget" data-element-type="position" style="position: absolute; bottom: 60px; right: 60px;">
1286
+ <div class="mock-widget" id="mockWidget" data-element-type="position">
1078
1287
  <div class="mock-widget-panel" id="mockPanel">
1079
1288
  <!-- Panel content will be dynamically generated -->
1080
1289
  </div>
1081
- <button class="mock-widget-button" id="mockButton">🎤</button>
1290
+ <div class="mock-button-container" id="mockButtonContainer">
1291
+ <div class="mock-prompt-bubble" id="mockPromptBubble" style="display: none;"></div>
1292
+ <div class="mock-pulse-rings" id="mockPulseRings" style="display: none;"></div>
1293
+ <button class="mock-widget-button" id="mockButton">🎤</button>
1294
+ </div>
1082
1295
  </div>
1083
1296
  </div>
1084
1297
  </div>
@@ -1156,14 +1369,21 @@
1156
1369
  },
1157
1370
  header: {
1158
1371
  title: 'Chat Assistant',
1159
- backgroundColor: '#7C3AED',
1372
+ backgroundColor: '#7C3AED', // Default purple
1160
1373
  textColor: '#FFFFFF',
1161
- showCloseButton: true
1374
+ showCloseButton: true,
1375
+ onlineIndicatorText: 'Online',
1376
+ onlineIndicatorColor: '#FFFFFF',
1377
+ onlineIndicatorDotColor: '#10b981'
1162
1378
  },
1163
1379
  messages: {
1164
1380
  userBackgroundColor: '#E5E7EB',
1165
1381
  agentBackgroundColor: '#F3F4F6',
1166
- textColor: '#1F2937',
1382
+ textColor: '#1F2937', // Fallback for backward compatibility
1383
+ userTextColor: '#1F2937',
1384
+ agentTextColor: '#1F2937',
1385
+ userAvatarIcon: '👤',
1386
+ agentAvatarIcon: '🤖',
1167
1387
  fontSize: '14px',
1168
1388
  borderRadius: 16
1169
1389
  },
@@ -1177,6 +1397,8 @@
1177
1397
  voice: {
1178
1398
  micButtonColor: '#7C3AED',
1179
1399
  micButtonActiveColor: '#EF4444',
1400
+ speakerButtonColor: '#FFFFFF',
1401
+ endCallButtonColor: '#ef4444',
1180
1402
  avatarBackgroundColor: '#667eea',
1181
1403
  avatarType: 'icon', // 'icon' or 'image'
1182
1404
  avatarIcon: '🤖',
@@ -1186,29 +1408,48 @@
1186
1408
  startCallButtonTextColor: '#FFFFFF',
1187
1409
  statusTitleColor: '#1e293b',
1188
1410
  statusSubtitleColor: '#64748b',
1411
+ statusDotColor: '#10b981',
1412
+ statusText: 'Listening...',
1413
+ liveTranscriptTextColor: '#64748b',
1414
+ liveTranscriptFontSize: '14px',
1415
+ liveIndicatorDotColor: '#10b981',
1416
+ liveIndicatorTextColor: '#10b981',
1189
1417
  waveformType: 'waveform', // 'waveform', 'icon', or 'image'
1190
1418
  waveformIcon: '🎤',
1191
1419
  waveformImageUrl: ''
1192
1420
  },
1193
1421
  landing: {
1422
+ backgroundColor: 'linear-gradient(180deg, #ffffff 0%, rgba(168, 85, 247, 0.03) 100%)', // Default gradient matching real widget
1194
1423
  logo: '🤖',
1195
1424
  logoType: 'icon', // 'icon' or 'image'
1196
1425
  logoIcon: '🤖',
1197
1426
  logoImageUrl: '',
1427
+ logoBackgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
1428
+ logoBackgroundEnabled: true,
1198
1429
  title: 'Welcome to AI Assistant',
1199
1430
  subtitle: 'Choose how you\'d like to interact',
1200
1431
  voiceCardTitle: 'Voice Call',
1201
- voiceCardDesc: 'Start a voice conversation',
1202
1432
  textCardTitle: 'Text Chat',
1203
- textCardDesc: 'Chat via text messages',
1204
1433
  titleColor: '#1e293b',
1205
1434
  subtitleColor: '#64748b',
1206
1435
  modeCardBackgroundColor: '#FFFFFF'
1207
1436
  },
1208
1437
  position: {
1209
1438
  vertical: 'bottom',
1210
- horizontal: 'right',
1439
+ horizontal: 'left',
1211
1440
  offset: { x: 20, y: 20 }
1441
+ },
1442
+ promptAnimation: {
1443
+ enabled: false,
1444
+ text: 'Try me!',
1445
+ backgroundColor: 'linear-gradient(135deg, #7c3aed, #4f46e5)',
1446
+ textColor: '#ffffff',
1447
+ animationType: 'bounce',
1448
+ showShimmer: true,
1449
+ showPulseRings: true,
1450
+ hideAfterClick: true,
1451
+ hideAfterSeconds: null,
1452
+ position: 'top'
1212
1453
  }
1213
1454
  };
1214
1455
 
@@ -1217,13 +1458,17 @@
1217
1458
  let panelOpen = true; // Start with panel open to show defaults
1218
1459
  let clickTimeout = null; // Track single vs double click
1219
1460
  let historyExpanded = false; // Track conversation history state
1461
+ let isInitializing = true; // Track if we're in the initial load phase
1462
+ let lastClickTime = 0; // Track clicks for double-click detection
1463
+ let lastClickElement = null;
1464
+ let lastClickType = null;
1220
1465
 
1221
1466
  // Initialize mock widget
1222
1467
  function initMockWidget() {
1223
1468
  const mockButton = document.getElementById('mockButton');
1224
1469
  const mockPanel = document.getElementById('mockPanel');
1225
1470
 
1226
- // Apply button styles
1471
+ // Apply button styles (this will also update prompt bubble)
1227
1472
  applyButtonStyles(mockButton);
1228
1473
 
1229
1474
  // Open panel by default to show defaults
@@ -1236,13 +1481,28 @@
1236
1481
  if (panelOpen) {
1237
1482
  renderPanelContent();
1238
1483
  }
1484
+ // Update prompt bubble visibility based on panel state
1485
+ updatePromptBubble(mockButton);
1486
+ // Sync actual widget state with mock panel
1487
+ syncWidgetWithMockPanel();
1239
1488
  });
1240
1489
 
1241
1490
  // Render initial panel content with defaults
1242
1491
  renderPanelContent();
1243
1492
 
1493
+ // Update prompt bubble visibility (should be hidden since panel starts open)
1494
+ updatePromptBubble(mockButton);
1495
+
1244
1496
  // Show default customization controls
1245
1497
  showCustomizationControls('default');
1498
+
1499
+ // Initialize config code to show only appId and agentId
1500
+ updateConfigCode();
1501
+
1502
+ // Apply initial position after a brief delay to ensure DOM is ready
1503
+ setTimeout(() => {
1504
+ updateMockWidgetPosition();
1505
+ }, 0);
1246
1506
  }
1247
1507
 
1248
1508
  function applyButtonStyles(button) {
@@ -1297,6 +1557,9 @@
1297
1557
  // Make button highlightable
1298
1558
  button.dataset.elementType = 'button';
1299
1559
 
1560
+ // Update prompt bubble if enabled
1561
+ updatePromptBubble(button);
1562
+
1300
1563
  // Remove old event listeners by replacing the button
1301
1564
  const oldButton = button;
1302
1565
  const newButton = oldButton.cloneNode(true);
@@ -1306,15 +1569,147 @@
1306
1569
  newButton.addEventListener('click', (e) => {
1307
1570
  e.preventDefault();
1308
1571
  e.stopPropagation();
1309
- handleElementClick('button', newButton, e);
1572
+
1573
+ // For button, single click should toggle panel immediately
1574
+ // Double click will still select for editing (without double-toggling)
1575
+ const currentTime = Date.now();
1576
+ const timeSinceLastClick = currentTime - lastClickTime;
1577
+
1578
+ // Check if this is a double click (within 400ms and same element)
1579
+ if (timeSinceLastClick < 400 && lastClickElement === newButton && lastClickType === 'button') {
1580
+ // Double click - just select for editing (don't toggle panel again)
1581
+ lastClickTime = 0;
1582
+ lastClickElement = null;
1583
+ lastClickType = null;
1584
+ selectElement('button', newButton, e);
1585
+ } else {
1586
+ // Single click - toggle panel immediately and select button
1587
+ lastClickTime = currentTime;
1588
+ lastClickElement = newButton;
1589
+ lastClickType = 'button';
1590
+
1591
+ // Toggle panel
1592
+ panelOpen = !panelOpen;
1593
+ const panel = document.getElementById('mockPanel');
1594
+ panel.classList.toggle('open');
1595
+ if (panelOpen) {
1596
+ renderPanelContent();
1597
+ }
1598
+
1599
+ // Update prompt bubble visibility based on panel state
1600
+ updatePromptBubble(newButton);
1601
+ // Sync actual widget state with mock panel
1602
+ syncWidgetWithMockPanel();
1603
+
1604
+ // Select the button for editing (so user can customize it)
1605
+ selectElement('button', newButton, e);
1606
+ }
1310
1607
  });
1311
1608
  }
1312
1609
 
1610
+ function updatePromptBubble(button) {
1611
+ const promptConfig = widgetConfig.promptAnimation || {};
1612
+ const promptBubble = document.getElementById('mockPromptBubble');
1613
+ const pulseRings = document.getElementById('mockPulseRings');
1614
+
1615
+ if (!promptBubble || !pulseRings) return;
1616
+
1617
+ // Show/hide prompt bubble - hide when mock panel is open, show when closed
1618
+ if (promptConfig.enabled === true && !panelOpen) {
1619
+ promptBubble.style.display = 'block';
1620
+
1621
+ // Extract solid color from gradient for arrow
1622
+ let arrowColor = '#7c3aed';
1623
+ if (promptConfig.backgroundColor && promptConfig.backgroundColor.includes('gradient')) {
1624
+ const match = promptConfig.backgroundColor.match(/#[0-9a-fA-F]{6}/);
1625
+ if (match) arrowColor = match[0];
1626
+ } else if (promptConfig.backgroundColor) {
1627
+ arrowColor = promptConfig.backgroundColor;
1628
+ }
1629
+
1630
+ // Set CSS variable for arrow color
1631
+ promptBubble.style.setProperty('--mock-prompt-bg-color', arrowColor);
1632
+
1633
+ // Build prompt bubble HTML
1634
+ // Convert 'pulse' to 'bounce' if pulse is selected (pulse option removed)
1635
+ const animationType = promptConfig.animationType === 'pulse' ? 'bounce' : promptConfig.animationType;
1636
+ const animationClass = animationType === 'none' ? '' : `animation-${animationType}`;
1637
+ const shimmerHTML = promptConfig.showShimmer !== false ? '<div class="mock-prompt-bubble-shimmer"></div>' : '';
1638
+ const position = promptConfig.position || 'top';
1639
+
1640
+ promptBubble.className = `mock-prompt-bubble ${position} ${animationClass}`;
1641
+ promptBubble.innerHTML = `
1642
+ <div class="mock-prompt-bubble-content" style="background: ${promptConfig.backgroundColor || 'linear-gradient(135deg, #7c3aed, #4f46e5)'}; color: ${promptConfig.textColor || '#ffffff'};">
1643
+ ${shimmerHTML}
1644
+ <span style="position: relative; z-index: 1;">${promptConfig.text || 'Try me!'}</span>
1645
+ </div>
1646
+ <div class="mock-prompt-bubble-arrow"></div>
1647
+ `;
1648
+ } else {
1649
+ promptBubble.style.display = 'none';
1650
+ }
1651
+
1652
+ // Show/hide pulse rings - hide when mock panel is open, show when closed
1653
+ if (promptConfig.enabled === true && promptConfig.showPulseRings !== false && !panelOpen) {
1654
+ pulseRings.style.display = 'block';
1655
+
1656
+ // Extract solid color from gradient for rings
1657
+ let ringColor = '#7c3aed';
1658
+ if (promptConfig.backgroundColor && promptConfig.backgroundColor.includes('gradient')) {
1659
+ const match = promptConfig.backgroundColor.match(/#[0-9a-fA-F]{6}/);
1660
+ if (match) ringColor = match[0];
1661
+ } else if (promptConfig.backgroundColor) {
1662
+ ringColor = promptConfig.backgroundColor;
1663
+ }
1664
+
1665
+ pulseRings.innerHTML = `
1666
+ <div class="mock-pulse-ring" style="background-color: ${ringColor}33;"></div>
1667
+ <div class="mock-pulse-ring" style="background-color: ${ringColor}1a;"></div>
1668
+ `;
1669
+ } else {
1670
+ pulseRings.style.display = 'none';
1671
+ }
1672
+ }
1673
+
1313
1674
  function getSizeValue(size) {
1314
1675
  const sizes = { small: 48, medium: 60, large: 72, xl: 84 };
1315
1676
  return sizes[size] || 60;
1316
1677
  }
1317
1678
 
1679
+ function updateMockWidgetPosition() {
1680
+ const mockWidget = document.getElementById('mockWidget');
1681
+ if (!mockWidget) {
1682
+ console.warn('Mock widget not found, retrying...');
1683
+ setTimeout(updateMockWidgetPosition, 100);
1684
+ return;
1685
+ }
1686
+
1687
+ const pos = widgetConfig.position;
1688
+ const offset = pos.offset || { x: 20, y: 20 };
1689
+
1690
+ // Clear any existing positioning
1691
+ mockWidget.style.top = '';
1692
+ mockWidget.style.bottom = '';
1693
+ mockWidget.style.left = '';
1694
+ mockWidget.style.right = '';
1695
+
1696
+ // Set position
1697
+ mockWidget.style.position = 'absolute';
1698
+
1699
+ // Vertical positioning - use config value
1700
+ if (pos.vertical === 'top') {
1701
+ mockWidget.style.setProperty('top', `${offset.y}px`, 'important');
1702
+ mockWidget.style.setProperty('bottom', 'auto', 'important');
1703
+ } else if (pos.vertical === 'bottom') {
1704
+ mockWidget.style.setProperty('bottom', `${offset.y}px`, 'important');
1705
+ mockWidget.style.setProperty('top', 'auto', 'important');
1706
+ }
1707
+
1708
+ // Horizontal positioning - ALWAYS keep mock widget on the right side
1709
+ mockWidget.style.setProperty('right', `${offset.x}px`, 'important');
1710
+ mockWidget.style.setProperty('left', 'auto', 'important');
1711
+ }
1712
+
1318
1713
  function renderPanelContent() {
1319
1714
  const mockPanel = document.getElementById('mockPanel');
1320
1715
 
@@ -1322,8 +1717,12 @@
1322
1717
  mockPanel.style.width = widgetConfig.panel.width + 'px';
1323
1718
  mockPanel.style.height = widgetConfig.panel.height + 'px';
1324
1719
  mockPanel.style.borderRadius = widgetConfig.panel.borderRadius + 'px';
1325
- mockPanel.style.backgroundColor = widgetConfig.panel.backgroundColor;
1720
+ // Panel background should match landing background to avoid double-frame effect
1721
+ const landingBg = widgetConfig.landing.backgroundColor || 'linear-gradient(180deg, #ffffff 0%, rgba(168, 85, 247, 0.03) 100%)';
1722
+ mockPanel.style.backgroundColor = landingBg;
1326
1723
  mockPanel.style.border = widgetConfig.panel.border;
1724
+ // Apply direction (LTR/RTL)
1725
+ mockPanel.style.direction = widgetConfig.direction || 'ltr';
1327
1726
 
1328
1727
  // Render based on current view
1329
1728
  if (currentView === 'landing') {
@@ -1340,36 +1739,67 @@
1340
1739
 
1341
1740
  function renderLandingScreen(panel) {
1342
1741
  const config = widgetConfig.landing;
1742
+ const iconBgColor = config.modeCardIconBackgroundColor || '#7C3AED';
1743
+ // Determine logo background - use logoBackgroundColor if enabled, otherwise transparent
1744
+ let logoBg = 'transparent';
1745
+ if (config.logoType === 'image' && config.logoBackgroundEnabled !== false) {
1746
+ logoBg = config.logoBackgroundColor || 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
1747
+ } else if (config.logoType === 'icon') {
1748
+ // For icon type, use the default avatar background
1749
+ logoBg = config.avatarBackground || 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
1750
+ }
1751
+
1343
1752
  panel.innerHTML = `
1344
- <div class="mock-panel-header" style="background: ${widgetConfig.header.backgroundColor}; color: ${widgetConfig.header.textColor};" data-element-type="header">
1345
- <span>${widgetConfig.header.title}</span>
1753
+ <div class="mock-panel-header" style="background: ${widgetConfig.header.backgroundColor}; color: ${widgetConfig.header.textColor}; direction: ${widgetConfig.direction || 'ltr'};" data-element-type="header">
1754
+ <div style="display: flex; align-items: center; gap: 8px;">
1755
+ <span>${widgetConfig.header.title}</span>
1756
+ <div style="display: flex; align-items: center; gap: 6px; margin-left: 8px;" data-element-type="onlineIndicator">
1757
+ <div style="width: 6px; height: 6px; background: ${widgetConfig.header.onlineIndicatorDotColor || '#10b981'}; border-radius: 50%; animation: pulse 2s ease-in-out infinite;"></div>
1758
+ <span style="font-size: 12px; opacity: 0.9; color: ${widgetConfig.header.onlineIndicatorColor || widgetConfig.header.textColor};">${widgetConfig.header.onlineIndicatorText || 'Online'}</span>
1759
+ </div>
1760
+ </div>
1346
1761
  ${widgetConfig.header.showCloseButton ? '<button class="mock-panel-close" data-element-type="closeButton">×</button>' : ''}
1347
1762
  </div>
1348
- <div class="mock-panel-content">
1349
- <div class="mock-landing-screen">
1350
- <div class="mock-landing-logo" data-element-type="landingLogo">
1763
+ <div class="mock-panel-content" style="direction: ${widgetConfig.direction || 'ltr'};">
1764
+ <div class="mock-landing-screen" style="background: ${config.backgroundColor || 'linear-gradient(180deg, #ffffff 0%, rgba(168, 85, 247, 0.03) 100%)'}; height: 100%;" data-element-type="landingBackground">
1765
+ <div class="mock-landing-logo" style="background: ${logoBg}; ${logoBg !== 'transparent' ? 'box-shadow: 0 8px 28px rgba(102, 126, 234, 0.35); border: none !important;' : 'box-shadow: none !important; border: none !important; outline: none !important;'}" data-element-type="landingLogo">
1351
1766
  ${config.logoType === 'image' && config.logoImageUrl ? `
1352
- <img src="${config.logoImageUrl}" alt="Logo" style="max-width: 64px; max-height: 64px; object-fit: contain;">
1767
+ <img src="${config.logoImageUrl}" alt="Logo" style="max-width: 44px; max-height: 44px; object-fit: contain;">
1353
1768
  ` : `
1354
- <span>${config.logoIcon || config.logo || '🤖'}</span>
1769
+ <span style="font-size: 44px; line-height: 1;">${config.logoIcon || config.logo || '🤖'}</span>
1355
1770
  `}
1356
1771
  </div>
1357
- <div class="mock-landing-title" style="color: ${config.titleColor};" data-element-type="landingTitle">${config.title || 'Welcome to AI Assistant'}</div>
1358
- <div class="mock-landing-subtitle" style="color: ${config.subtitleColor};" data-element-type="landingSubtitle">${config.subtitle || 'Choose how you\'d like to interact'}</div>
1772
+ <div class="mock-landing-title" style="color: ${config.titleColor || '#1e1b4b'};" data-element-type="landingTitle">${config.title || 'Welcome to AI Assistant'}</div>
1773
+ <div class="mock-landing-subtitle" style="color: ${config.subtitleColor || '#64748b'};" data-element-type="landingSubtitle">${config.subtitle || 'Choose how you\'d like to interact'}</div>
1359
1774
  <div class="mock-mode-cards">
1360
- <div class="mock-mode-card" style="background: ${config.modeCardBackgroundColor};" data-element-type="modeCard" data-mode="voice">
1361
- <div class="mock-mode-icon">🎤</div>
1362
- <div class="mock-mode-title">${config.voiceCardTitle || 'Voice Call'}</div>
1363
- <div class="mock-mode-desc">${config.voiceCardDesc || 'Start a voice conversation'}</div>
1775
+ <div class="mock-mode-card" style="background: ${config.modeCardBackgroundColor || '#ffffff'}; border: 1px solid ${config.modeCardBorderColor || 'rgba(0, 0, 0, 0.06)'};" data-element-type="modeCard" data-mode="voice">
1776
+ <div class="mock-mode-icon" style="background: ${iconBgColor}; color: #fff; box-shadow: 0 4px 14px rgba(124, 58, 237, 0.35);">
1777
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 26px; height: 26px;">
1778
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
1779
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
1780
+ <line x1="12" y1="19" x2="12" y2="23"/>
1781
+ <line x1="8" y1="23" x2="16" y2="23"/>
1782
+ </svg>
1783
+ </div>
1784
+ <div class="mock-mode-title" style="color: ${config.modeCardTitleColor || '#1e1b4b'};">${config.voiceCardTitle || 'Voice Call'}</div>
1364
1785
  </div>
1365
- <div class="mock-mode-card" style="background: ${config.modeCardBackgroundColor};" data-element-type="modeCard" data-mode="text">
1366
- <div class="mock-mode-icon">💬</div>
1367
- <div class="mock-mode-title">${config.textCardTitle || 'Text Chat'}</div>
1368
- <div class="mock-mode-desc">${config.textCardDesc || 'Chat via text messages'}</div>
1786
+ <div class="mock-mode-card" style="background: ${config.modeCardBackgroundColor || '#ffffff'}; border: 1px solid ${config.modeCardBorderColor || 'rgba(0, 0, 0, 0.06)'};" data-element-type="modeCard" data-mode="text">
1787
+ <div class="mock-mode-icon" style="background: ${iconBgColor}; color: #fff; box-shadow: 0 4px 14px rgba(124, 58, 237, 0.35);">
1788
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 26px; height: 26px;">
1789
+ <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
1790
+ </svg>
1791
+ </div>
1792
+ <div class="mock-mode-title" style="color: ${config.modeCardTitleColor || '#1e1b4b'};">${config.textCardTitle || 'Text Chat'}</div>
1369
1793
  </div>
1370
1794
  </div>
1371
1795
  </div>
1372
1796
  </div>
1797
+ <div class="mock-footer" style="padding: 10px 16px; text-align: center; border-top: 1px solid rgba(0,0,0,0.06); font-size: 11px; color: #64748b; background: #fff; flex-shrink: 0;">
1798
+ <span style="display: inline-flex; align-items: center; gap: 4px;">
1799
+ <span>⚡</span>
1800
+ <span>Powered by TalkToPC</span>
1801
+ </span>
1802
+ </div>
1373
1803
  `;
1374
1804
 
1375
1805
  // Make elements selectable
@@ -1379,20 +1809,29 @@
1379
1809
  function renderTextInterface(panel) {
1380
1810
  const config = widgetConfig.text;
1381
1811
  panel.innerHTML = `
1382
- <div class="mock-panel-header" style="background: ${widgetConfig.header.backgroundColor}; color: ${widgetConfig.header.textColor};" data-element-type="header">
1812
+ <div class="mock-panel-header" style="background: ${widgetConfig.header.backgroundColor}; color: ${widgetConfig.header.textColor}; direction: ${widgetConfig.direction || 'ltr'};" data-element-type="header">
1383
1813
  <span>${widgetConfig.header.title}</span>
1384
1814
  ${widgetConfig.header.showCloseButton ? '<button class="mock-panel-close" data-element-type="closeButton" onclick="switchView(\'landing\')">×</button>' : ''}
1385
1815
  </div>
1386
- <div class="mock-text-interface">
1816
+ <div class="mock-text-interface" style="direction: ${widgetConfig.direction || 'ltr'};">
1387
1817
  <div class="mock-messages">
1388
- <div class="mock-message user" style="background: ${widgetConfig.messages.userBackgroundColor}; color: ${widgetConfig.messages.textColor}; border-radius: ${widgetConfig.messages.borderRadius}px; font-size: ${widgetConfig.messages.fontSize};" data-element-type="userMessage">
1389
- Hello! How can I help you?
1818
+ <div class="mock-message user" data-element-type="userMessage">
1819
+ <div class="mock-message-avatar">${widgetConfig.messages.userAvatarIcon || '👤'}</div>
1820
+ <div class="mock-message-bubble" style="background: ${widgetConfig.messages.userBackgroundColor}; color: ${widgetConfig.messages.userTextColor || widgetConfig.messages.textColor || '#1F2937'}; border-radius: ${widgetConfig.messages.borderRadius}px; font-size: ${widgetConfig.messages.fontSize};">
1821
+ Hello! How can I help you?
1822
+ </div>
1390
1823
  </div>
1391
- <div class="mock-message agent" style="background: ${widgetConfig.messages.agentBackgroundColor}; color: ${widgetConfig.messages.textColor}; border-radius: ${widgetConfig.messages.borderRadius}px; font-size: ${widgetConfig.messages.fontSize};" data-element-type="agentMessage">
1392
- Hi! I'm here to assist you. What would you like to know?
1824
+ <div class="mock-message agent" data-element-type="agentMessage">
1825
+ <div class="mock-message-avatar">${widgetConfig.messages.agentAvatarIcon || '🤖'}</div>
1826
+ <div class="mock-message-bubble" style="background: ${widgetConfig.messages.agentBackgroundColor}; color: ${widgetConfig.messages.agentTextColor || widgetConfig.messages.textColor || '#1F2937'}; border-radius: ${widgetConfig.messages.borderRadius}px; font-size: ${widgetConfig.messages.fontSize};">
1827
+ Hi! I'm here to assist you. What would you like to know?
1828
+ </div>
1393
1829
  </div>
1394
- <div class="mock-message user" style="background: ${widgetConfig.messages.userBackgroundColor}; color: ${widgetConfig.messages.textColor}; border-radius: ${widgetConfig.messages.borderRadius}px; font-size: ${widgetConfig.messages.fontSize};" data-element-type="userMessage">
1395
- Can you tell me about your features?
1830
+ <div class="mock-message user" data-element-type="userMessage">
1831
+ <div class="mock-message-avatar">${widgetConfig.messages.userAvatarIcon || '👤'}</div>
1832
+ <div class="mock-message-bubble" style="background: ${widgetConfig.messages.userBackgroundColor}; color: ${widgetConfig.messages.userTextColor || widgetConfig.messages.textColor || '#1F2937'}; border-radius: ${widgetConfig.messages.borderRadius}px; font-size: ${widgetConfig.messages.fontSize};">
1833
+ Can you tell me about your features?
1834
+ </div>
1396
1835
  </div>
1397
1836
  </div>
1398
1837
  <div class="mock-input-area">
@@ -1410,12 +1849,12 @@
1410
1849
  const config = widgetConfig.voice;
1411
1850
  // Show active call state matching real widget structure
1412
1851
  panel.innerHTML = `
1413
- <div class="mock-panel-header" style="background: ${widgetConfig.header.backgroundColor}; color: ${widgetConfig.header.textColor};" data-element-type="header">
1852
+ <div class="mock-panel-header" style="background: ${widgetConfig.header.backgroundColor}; color: ${widgetConfig.header.textColor}; direction: ${widgetConfig.direction || 'ltr'};" data-element-type="header">
1414
1853
  <div style="display: flex; align-items: center; gap: 8px;">
1415
1854
  <span>${widgetConfig.header.title}</span>
1416
- <div style="display: flex; align-items: center; gap: 6px; margin-left: 8px;">
1417
- <div style="width: 6px; height: 6px; background: #10b981; border-radius: 50%;"></div>
1418
- <span style="font-size: 12px; opacity: 0.9;">Online</span>
1855
+ <div style="display: flex; align-items: center; gap: 6px; margin-left: 8px;" data-element-type="onlineIndicator">
1856
+ <div style="width: 6px; height: 6px; background: ${widgetConfig.header.onlineIndicatorDotColor || '#10b981'}; border-radius: 50%;"></div>
1857
+ <span style="font-size: 12px; opacity: 0.9; color: ${widgetConfig.header.onlineIndicatorColor || widgetConfig.header.textColor};">${widgetConfig.header.onlineIndicatorText || 'Online'}</span>
1419
1858
  </div>
1420
1859
  </div>
1421
1860
  <div style="display: flex; align-items: center; gap: 8px;">
@@ -1423,7 +1862,7 @@
1423
1862
  ${widgetConfig.header.showCloseButton ? '<button class="mock-panel-close" data-element-type="closeButton">×</button>' : ''}
1424
1863
  </div>
1425
1864
  </div>
1426
- <div class="mock-voice-interface">
1865
+ <div class="mock-voice-interface" style="direction: ${widgetConfig.direction || 'ltr'};">
1427
1866
  <!-- Voice Section -->
1428
1867
  <!-- Multi-row layout when history is collapsed -->
1429
1868
  <div class="mock-voice-section" id="mockVoiceSectionExpanded">
@@ -1475,19 +1914,19 @@
1475
1914
  <span>Listening...</span>
1476
1915
  </div>
1477
1916
  <div class="mock-voice-controls">
1478
- <button class="mock-control-btn secondary" data-element-type="micButton" title="Mute">
1917
+ <button class="mock-control-btn secondary" data-element-type="micButton" title="Mute" style="background: ${config.micButtonColor || '#FFFFFF'};">
1479
1918
  <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
1480
1919
  <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
1481
1920
  <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
1482
1921
  <line x1="12" y1="19" x2="12" y2="23"/>
1483
1922
  </svg>
1484
1923
  </button>
1485
- <button class="mock-control-btn danger" data-element-type="endCallButton" title="End Call">
1924
+ <button class="mock-control-btn danger" data-element-type="endCallButton" title="End Call" style="background: ${config.endCallButtonColor || '#ef4444'};">
1486
1925
  <svg fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
1487
1926
  <path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z" transform="rotate(135 12 12)"/>
1488
1927
  </svg>
1489
1928
  </button>
1490
- <button class="mock-control-btn secondary" data-element-type="speakerButton" title="Speaker">
1929
+ <button class="mock-control-btn secondary" data-element-type="speakerButton" title="Speaker" style="background: ${config.speakerButtonColor || '#FFFFFF'};">
1491
1930
  <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
1492
1931
  <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
1493
1932
  <path d="M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"/>
@@ -1547,7 +1986,7 @@
1547
1986
  <span>Listening...</span>
1548
1987
  </div>
1549
1988
  <div class="mock-compact-controls">
1550
- <button class="mock-control-btn secondary" data-element-type="micButton" title="Mute">
1989
+ <button class="mock-control-btn secondary" data-element-type="micButton" title="Mute" style="background: ${config.micButtonColor || '#FFFFFF'};">
1551
1990
  <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
1552
1991
  <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
1553
1992
  <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
@@ -1583,11 +2022,11 @@
1583
2022
 
1584
2023
  <!-- Collapsed: Live transcript only (no bubbles) -->
1585
2024
  <div class="mock-live-transcript-collapsed" id="collapsedTranscript" data-element-type="liveTranscript">
1586
- <div class="mock-live-indicator" data-element-type="liveIndicator">
1587
- <div class="mock-live-dot"></div>
2025
+ <div class="mock-live-indicator" data-element-type="liveIndicator" style="color: ${config.liveIndicatorTextColor || '#10b981'};">
2026
+ <div class="mock-live-dot" style="background: ${config.liveIndicatorDotColor || '#10b981'};"></div>
1588
2027
  <span>LIVE</span>
1589
2028
  </div>
1590
- <div class="mock-live-text-collapsed" data-element-type="liveTranscriptText" style="color: #64748b; font-size: 14px; line-height: 1.6; margin-top: 8px;">
2029
+ <div class="mock-live-text-collapsed" data-element-type="liveTranscriptText" style="color: ${config.liveTranscriptTextColor || '#64748b'}; font-size: ${config.liveTranscriptFontSize || '14px'}; line-height: 1.6; margin-top: 8px;">
1591
2030
  Hello, I'm Sasha from Bridgewise, How can I help you today?
1592
2031
  </div>
1593
2032
  </div>
@@ -1671,36 +2110,73 @@
1671
2110
  });
1672
2111
 
1673
2112
  elements.forEach(el => {
1674
- // Skip if this element is inside another element with data-element-type (child elements)
2113
+ // Check if this element is inside another element with data-element-type (child elements)
1675
2114
  const parentWithType = el.parentElement.closest('[data-element-type]');
1676
- if (parentWithType && parentWithType !== panel) {
1677
- // This is a child element, make sure it stops propagation
1678
- const newEl = el.cloneNode(true);
1679
- if (el.id) newEl.id = el.id;
1680
- el.parentNode.replaceChild(newEl, el);
1681
-
1682
- newEl.addEventListener('click', (e) => {
2115
+ const isChild = parentWithType && parentWithType !== panel;
2116
+
2117
+ if (isChild) {
2118
+ // This is a child element (e.g., modeCard inside landingBackground)
2119
+ // Attach listener directly to the element
2120
+ el.addEventListener('click', (e) => {
2121
+ // Find the actual element with data-element-type (in case click is on child like icon/title)
2122
+ let targetElement = e.target;
2123
+ let foundElement = null;
2124
+
2125
+ // Walk up the DOM tree to find the element with data-element-type
2126
+ while (targetElement && targetElement !== panel && targetElement !== document.body) {
2127
+ if (targetElement.hasAttribute && targetElement.hasAttribute('data-element-type')) {
2128
+ foundElement = targetElement;
2129
+ break;
2130
+ }
2131
+ targetElement = targetElement.parentElement;
2132
+ }
2133
+
2134
+ // Use found element or fallback to the element we're attaching to
2135
+ const elementToUse = foundElement || el;
2136
+ const elementType = elementToUse.getAttribute('data-element-type');
2137
+
2138
+ console.log('✅ Child element clicked:', elementType, elementToUse.getAttribute('data-mode'), 'clicked on:', e.target.tagName, e.target.className);
2139
+
2140
+ // Don't stop propagation immediately - let handleElementClick process it
2141
+ handleElementClick(elementType, elementToUse, e);
2142
+
2143
+ // Only prevent default and stop propagation after handling
1683
2144
  e.preventDefault();
1684
2145
  e.stopPropagation();
1685
- handleElementClick(newEl.dataset.elementType, newEl, e);
1686
- });
2146
+ }, true); // Use capture phase - runs before bubbling phase, so it runs BEFORE parent handlers
1687
2147
  } else {
1688
- // This is a top-level element
1689
- const newEl = el.cloneNode(true);
1690
- if (el.id) newEl.id = el.id;
1691
- el.parentNode.replaceChild(newEl, el);
1692
-
1693
- newEl.addEventListener('click', (e) => {
1694
- // Check if click was on a child element with data-element-type
1695
- const clickedElement = e.target.closest('[data-element-type]');
1696
- if (clickedElement && clickedElement !== newEl) {
1697
- // Click was on a child element, don't handle it here
2148
+ // This is a top-level element (e.g., landingBackground)
2149
+ el.addEventListener('click', (e) => {
2150
+ // IMPORTANT: Check if click is inside ANY child element with data-element-type
2151
+ // Walk up from the target to see if we hit a child element with data-element-type
2152
+ let current = e.target;
2153
+ while (current && current !== el && current !== panel && current !== document.body) {
2154
+ // Check if this element has data-element-type and is a child of el
2155
+ if (current.hasAttribute && current.hasAttribute('data-element-type')) {
2156
+ if (current !== el && el.contains(current)) {
2157
+ // Found a child element with data-element-type - let it handle the click
2158
+ console.log('⏭️ Parent ignoring click - child element found:', current.dataset.elementType);
2159
+ return; // Don't handle - let the child handle it
2160
+ }
2161
+ }
2162
+ current = current.parentElement;
2163
+ }
2164
+
2165
+ // Also check: if the target itself is inside a child element with data-element-type
2166
+ const closestChild = Array.from(el.querySelectorAll('[data-element-type]')).find(child => {
2167
+ return child !== el && child.contains(e.target);
2168
+ });
2169
+ if (closestChild) {
2170
+ console.log('⏭️ Parent ignoring click - target is inside child:', closestChild.dataset.elementType);
1698
2171
  return;
1699
2172
  }
2173
+
2174
+ // Only handle clicks directly on this element (not on any child elements)
2175
+ console.log('✅ Parent element clicked directly:', el.dataset.elementType, 'target:', e.target);
1700
2176
  e.preventDefault();
1701
2177
  e.stopPropagation();
1702
- handleElementClick(newEl.dataset.elementType, newEl, e);
1703
- });
2178
+ handleElementClick(el.dataset.elementType, el, e);
2179
+ }, false); // Use bubbling phase so child handlers (capture phase) run first
1704
2180
  }
1705
2181
  });
1706
2182
  }
@@ -1734,18 +2210,21 @@
1734
2210
  showCustomizationControls(elementType);
1735
2211
  }
1736
2212
 
1737
- let lastClickTime = 0;
1738
- let lastClickElement = null;
1739
- let lastClickType = null;
1740
-
1741
2213
  function handleElementClick(elementType, element, event) {
2214
+ // If event was prevented/stopped, don't process
2215
+ if (event && event.defaultPrevented) {
2216
+ return;
2217
+ }
2218
+
1742
2219
  const currentTime = Date.now();
1743
2220
  const timeSinceLastClick = currentTime - lastClickTime;
1744
2221
 
1745
2222
  // Check if this is a double click (within 400ms and same element type)
2223
+ // Compare by element reference, not just type, to ensure it's the same element
1746
2224
  if (timeSinceLastClick < 400 && lastClickElement && elementType === lastClickType &&
1747
- lastClickElement.dataset.elementType === element.dataset.elementType) {
2225
+ lastClickElement === element) {
1748
2226
  // Double click detected - perform normal interaction
2227
+ console.log('🖱️ Double click detected on:', elementType);
1749
2228
  lastClickTime = 0;
1750
2229
  lastClickElement = null;
1751
2230
  lastClickType = null;
@@ -1754,6 +2233,7 @@
1754
2233
  }
1755
2234
 
1756
2235
  // Single click - wait to see if there's a second click
2236
+ console.log('🖱️ Single click detected on:', elementType, 'waiting for potential double click...');
1757
2237
  lastClickTime = currentTime;
1758
2238
  lastClickElement = element;
1759
2239
  lastClickType = elementType;
@@ -1761,6 +2241,7 @@
1761
2241
  setTimeout(() => {
1762
2242
  // If no second click happened, treat as single click
1763
2243
  if (lastClickTime === currentTime && lastClickElement === element) {
2244
+ console.log('🖱️ Single click confirmed on:', elementType);
1764
2245
  lastClickTime = 0;
1765
2246
  lastClickElement = null;
1766
2247
  lastClickType = null;
@@ -1788,6 +2269,13 @@
1788
2269
  if (panelOpen) {
1789
2270
  renderPanelContent();
1790
2271
  }
2272
+ // Update prompt bubble visibility based on panel state
2273
+ const mockButton = document.getElementById('mockButton');
2274
+ if (mockButton) {
2275
+ updatePromptBubble(mockButton);
2276
+ }
2277
+ // Sync actual widget state with mock panel
2278
+ syncWidgetWithMockPanel();
1791
2279
  } else if (elementType === 'conversationToggle') {
1792
2280
  // Toggle conversation history
1793
2281
  toggleConversationHistory();
@@ -1893,6 +2381,49 @@
1893
2381
  <input type="color" id="btnHoverColor" value="${widgetConfig.button.hoverColor}">
1894
2382
  </div>
1895
2383
  </div>
2384
+ <div class="customization-group">
2385
+ <h3>"Try Me" Prompt Animation</h3>
2386
+ <div class="control-item">
2387
+ <label>
2388
+ <input type="checkbox" id="promptEnabled" ${widgetConfig.promptAnimation?.enabled === true ? 'checked' : ''}>
2389
+ Enable Prompt Animation
2390
+ </label>
2391
+ </div>
2392
+ <div class="control-item" id="promptControls" style="display: ${widgetConfig.promptAnimation?.enabled === true ? 'block' : 'none'};">
2393
+ <label>Prompt Text</label>
2394
+ <input type="text" id="promptText" value="${widgetConfig.promptAnimation?.text || 'Try me!'}" placeholder="Try me!">
2395
+ </div>
2396
+ <div class="control-item" id="promptBgColorControl" style="display: ${widgetConfig.promptAnimation?.enabled === true ? 'block' : 'none'};">
2397
+ <label>Background Color/Gradient</label>
2398
+ <input type="text" id="promptBgColor" value="${widgetConfig.promptAnimation?.backgroundColor || 'linear-gradient(135deg, #7c3aed, #4f46e5)'}" placeholder="linear-gradient(135deg, #7c3aed, #4f46e5)">
2399
+ <p style="color: #6b7280; font-size: 11px; margin-top: 4px;">Supports solid colors (e.g., #7c3aed) or gradients</p>
2400
+ </div>
2401
+ <div class="control-item" id="promptTextColorControl" style="display: ${widgetConfig.promptAnimation?.enabled === true ? 'block' : 'none'};">
2402
+ <label>Text Color</label>
2403
+ <input type="color" id="promptTextColor" value="${widgetConfig.promptAnimation?.textColor || '#ffffff'}">
2404
+ </div>
2405
+ <div class="control-item" id="promptAnimationTypeControl" style="display: ${widgetConfig.promptAnimation?.enabled === true ? 'block' : 'none'};">
2406
+ <label>Animation Type</label>
2407
+ <select id="promptAnimationType">
2408
+ <option value="bounce" ${widgetConfig.promptAnimation?.animationType === 'bounce' ? 'selected' : ''}>Bounce</option>
2409
+ <option value="float" ${widgetConfig.promptAnimation?.animationType === 'float' ? 'selected' : ''}>Float</option>
2410
+ <option value="none" ${widgetConfig.promptAnimation?.animationType === 'none' ? 'selected' : ''}>None</option>
2411
+ </select>
2412
+ </div>
2413
+ <div class="control-item" id="promptHideAfterSecondsControl" style="display: ${widgetConfig.promptAnimation?.enabled === true ? 'block' : 'none'};">
2414
+ <label>Auto-Hide After (seconds)</label>
2415
+ <input type="number" id="promptHideAfterSeconds" value="${widgetConfig.promptAnimation?.hideAfterSeconds || ''}" placeholder="Leave empty for never" min="0" step="1">
2416
+ <p style="color: #6b7280; font-size: 11px; margin-top: 4px;">Leave empty to never auto-hide</p>
2417
+ </div>
2418
+ <div class="control-item" id="promptPositionControl" style="display: ${widgetConfig.promptAnimation?.enabled === true ? 'block' : 'none'};">
2419
+ <label>Position</label>
2420
+ <select id="promptPosition">
2421
+ <option value="top" ${widgetConfig.promptAnimation?.position === 'top' ? 'selected' : ''}>Top</option>
2422
+ <option value="left" ${widgetConfig.promptAnimation?.position === 'left' ? 'selected' : ''}>Left</option>
2423
+ <option value="right" ${widgetConfig.promptAnimation?.position === 'right' ? 'selected' : ''}>Right</option>
2424
+ </select>
2425
+ </div>
2426
+ </div>
1896
2427
  <div class="customization-group">
1897
2428
  <h3>Icon</h3>
1898
2429
  <div class="control-item">
@@ -1919,6 +2450,7 @@
1919
2450
  </div>
1920
2451
  `;
1921
2452
  break;
2453
+ case 'onlineIndicator':
1922
2454
  case 'header':
1923
2455
  controlsHTML = `
1924
2456
  <div class="customization-group">
@@ -1941,6 +2473,21 @@
1941
2473
  Show Close Button
1942
2474
  </label>
1943
2475
  </div>
2476
+ <div class="control-item" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #374151;">
2477
+ <label style="font-weight: 600; color: #f9fafb; margin-bottom: 8px; display: block;">Online Indicator</label>
2478
+ <div class="control-item">
2479
+ <label>Indicator Text</label>
2480
+ <input type="text" id="onlineIndicatorText" value="${widgetConfig.header.onlineIndicatorText || 'Online'}" placeholder="Online">
2481
+ </div>
2482
+ <div class="control-item">
2483
+ <label>Indicator Text Color</label>
2484
+ <input type="color" id="onlineIndicatorColor" value="${widgetConfig.header.onlineIndicatorColor || widgetConfig.header.textColor}">
2485
+ </div>
2486
+ <div class="control-item">
2487
+ <label>Indicator Dot Color</label>
2488
+ <input type="color" id="onlineIndicatorDotColor" value="${widgetConfig.header.onlineIndicatorDotColor || '#10b981'}">
2489
+ </div>
2490
+ </div>
1944
2491
  </div>
1945
2492
  `;
1946
2493
  break;
@@ -1960,10 +2507,6 @@
1960
2507
  <label>Border Radius (px)</label>
1961
2508
  <input type="number" id="panelRadius" value="${widgetConfig.panel.borderRadius}">
1962
2509
  </div>
1963
- <div class="control-item">
1964
- <label>Background Color</label>
1965
- <input type="color" id="panelBgColor" value="${widgetConfig.panel.backgroundColor}">
1966
- </div>
1967
2510
  </div>
1968
2511
  <div class="customization-group">
1969
2512
  <h3>Widget Position</h3>
@@ -2071,8 +2614,25 @@
2071
2614
  <input type="color" id="msgAgentBg" value="${widgetConfig.messages.agentBackgroundColor}">
2072
2615
  </div>
2073
2616
  <div class="control-item">
2074
- <label>Text Color</label>
2075
- <input type="color" id="msgTextColor" value="${widgetConfig.messages.textColor}">
2617
+ <label>User Message Text Color</label>
2618
+ <input type="color" id="msgUserTextColor" value="${widgetConfig.messages.userTextColor || widgetConfig.messages.textColor || '#1F2937'}">
2619
+ </div>
2620
+ <div class="control-item">
2621
+ <label>Agent Message Text Color</label>
2622
+ <input type="color" id="msgAgentTextColor" value="${widgetConfig.messages.agentTextColor || widgetConfig.messages.textColor || '#1F2937'}">
2623
+ </div>
2624
+ <div class="control-item" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #374151;">
2625
+ <label style="font-weight: 600; color: #f9fafb; margin-bottom: 8px; display: block;">Message Avatars</label>
2626
+ <div class="control-item">
2627
+ <label>User Avatar Icon</label>
2628
+ <input type="text" id="msgUserAvatarIcon" value="${widgetConfig.messages.userAvatarIcon || '👤'}" placeholder="👤">
2629
+ <p style="color: #6b7280; font-size: 11px; margin-top: 4px;">Emoji or text to display next to user messages</p>
2630
+ </div>
2631
+ <div class="control-item">
2632
+ <label>Agent Avatar Icon</label>
2633
+ <input type="text" id="msgAgentAvatarIcon" value="${widgetConfig.messages.agentAvatarIcon || '🤖'}" placeholder="🤖">
2634
+ <p style="color: #6b7280; font-size: 11px; margin-top: 4px;">Emoji or text to display next to agent messages</p>
2635
+ </div>
2076
2636
  </div>
2077
2637
  <div class="control-item">
2078
2638
  <label>Font Size</label>
@@ -2145,10 +2705,6 @@
2145
2705
  <label>Background Color</label>
2146
2706
  <input type="color" id="micBtnColor" value="${widgetConfig.voice.micButtonColor}">
2147
2707
  </div>
2148
- <div class="control-item">
2149
- <label>Active/Muted Color</label>
2150
- <input type="color" id="micBtnActive" value="${widgetConfig.voice.micButtonActiveColor}">
2151
- </div>
2152
2708
  </div>
2153
2709
  `;
2154
2710
  break;
@@ -2158,7 +2714,7 @@
2158
2714
  <h3>End Call Button</h3>
2159
2715
  <div class="control-item">
2160
2716
  <label>Background Color</label>
2161
- <input type="color" id="endCallBtnColor" value="#ef4444">
2717
+ <input type="color" id="endCallBtnColor" value="${widgetConfig.voice.endCallButtonColor || '#ef4444'}">
2162
2718
  </div>
2163
2719
  </div>
2164
2720
  `;
@@ -2169,7 +2725,7 @@
2169
2725
  <h3>Speaker Button</h3>
2170
2726
  <div class="control-item">
2171
2727
  <label>Background Color</label>
2172
- <input type="color" id="speakerBtnColor" value="#FFFFFF">
2728
+ <input type="color" id="speakerBtnColor" value="${widgetConfig.voice.speakerButtonColor || '#FFFFFF'}">
2173
2729
  </div>
2174
2730
  </div>
2175
2731
  `;
@@ -2222,15 +2778,15 @@
2222
2778
  <h3>Status Text</h3>
2223
2779
  <div class="control-item">
2224
2780
  <label>Status Text</label>
2225
- <input type="text" id="statusText" value="Listening..." placeholder="Listening...">
2781
+ <input type="text" id="statusText" value="${widgetConfig.voice.statusText || 'Listening...'}" placeholder="Listening...">
2226
2782
  </div>
2227
2783
  <div class="control-item">
2228
2784
  <label>Text Color</label>
2229
- <input type="color" id="statusTitleColor" value="${widgetConfig.voice.statusTitleColor}">
2785
+ <input type="color" id="statusTitleColor" value="${widgetConfig.voice.statusTitleColor || '#1e293b'}">
2230
2786
  </div>
2231
2787
  <div class="control-item">
2232
2788
  <label>Status Dot Color</label>
2233
- <input type="color" id="statusDotColor" value="#10b981">
2789
+ <input type="color" id="statusDotColor" value="${widgetConfig.voice.statusDotColor || '#10b981'}">
2234
2790
  </div>
2235
2791
  </div>
2236
2792
  `;
@@ -2242,11 +2798,11 @@
2242
2798
  <h3>Live Transcript (Collapsed View)</h3>
2243
2799
  <div class="control-item">
2244
2800
  <label>Transcript Text Color</label>
2245
- <input type="color" id="liveTranscriptColor" value="#64748b">
2801
+ <input type="color" id="liveTranscriptColor" value="${widgetConfig.voice.liveTranscriptTextColor || '#64748b'}">
2246
2802
  </div>
2247
2803
  <div class="control-item">
2248
2804
  <label>Font Size</label>
2249
- <input type="text" id="liveTranscriptFontSize" value="14px" placeholder="14px">
2805
+ <input type="text" id="liveTranscriptFontSize" value="${widgetConfig.voice.liveTranscriptFontSize || '14px'}" placeholder="14px">
2250
2806
  </div>
2251
2807
  <p style="color: #6b7280; font-size: 12px; margin-top: 8px;">
2252
2808
  This is the live transcript shown when history is collapsed. It displays only the current spoken text (max 2 lines).
@@ -2326,11 +2882,11 @@
2326
2882
  <h3>Live Indicator</h3>
2327
2883
  <div class="control-item">
2328
2884
  <label>Live Dot Color</label>
2329
- <input type="color" id="liveDotColor" value="#10b981">
2885
+ <input type="color" id="liveDotColor" value="${widgetConfig.voice.liveIndicatorDotColor || '#10b981'}">
2330
2886
  </div>
2331
2887
  <div class="control-item">
2332
2888
  <label>Live Text Color</label>
2333
- <input type="color" id="liveTextColor" value="#10b981">
2889
+ <input type="color" id="liveTextColor" value="${widgetConfig.voice.liveIndicatorTextColor || '#10b981'}">
2334
2890
  </div>
2335
2891
  </div>
2336
2892
  `;
@@ -2354,7 +2910,23 @@
2354
2910
  <label>Image URL</label>
2355
2911
  <input type="text" id="landingLogoImageUrl" value="${widgetConfig.landing.logoImageUrl || ''}" placeholder="https://example.com/logo.png">
2356
2912
  </div>
2357
- </div>
2913
+ <div class="control-item" id="logoBackgroundControl" style="display: ${widgetConfig.landing.logoType === 'image' ? 'block' : 'none'}; margin-top: 16px; padding-top: 16px; border-top: 1px solid #374151;">
2914
+ <label style="font-weight: 600; color: #f9fafb; margin-bottom: 8px; display: block;">Background</label>
2915
+ <div class="control-item">
2916
+ <label>
2917
+ <input type="checkbox" id="logoBackgroundEnabled" ${widgetConfig.landing.logoBackgroundEnabled !== false ? 'checked' : ''}>
2918
+ Enable Background
2919
+ </label>
2920
+ </div>
2921
+ <div class="control-item" id="logoBackgroundColorControl" style="display: ${widgetConfig.landing.logoBackgroundEnabled !== false ? 'block' : 'none'};">
2922
+ <label>Background Color</label>
2923
+ <input type="color" id="logoBackgroundColor" value="${widgetConfig.landing.logoBackgroundColor && widgetConfig.landing.logoBackgroundColor.startsWith('#') ? widgetConfig.landing.logoBackgroundColor : '#667eea'}">
2924
+ <p style="color: #6b7280; font-size: 11px; margin-top: 4px;">
2925
+ Background color for the logo container. Uncheck "Enable Background" to make it transparent.
2926
+ </p>
2927
+ </div>
2928
+ </div>
2929
+ </div>
2358
2930
  `;
2359
2931
  break;
2360
2932
  case 'landingTitle':
@@ -2387,6 +2959,30 @@
2387
2959
  </div>
2388
2960
  `;
2389
2961
  break;
2962
+ case 'landingBackground':
2963
+ // Extract color from gradient or use default
2964
+ const currentBg = widgetConfig.landing.backgroundColor || 'linear-gradient(180deg, #ffffff 0%, rgba(168, 85, 247, 0.03) 100%)';
2965
+ let defaultColor = '#ffffff';
2966
+ if (currentBg.startsWith('#')) {
2967
+ defaultColor = currentBg;
2968
+ } else if (currentBg.includes('#')) {
2969
+ // Extract the first hex color from gradient (usually the main color)
2970
+ const hexMatch = currentBg.match(/#[0-9a-fA-F]{6}/);
2971
+ if (hexMatch) defaultColor = hexMatch[0];
2972
+ }
2973
+ controlsHTML = `
2974
+ <div class="customization-group">
2975
+ <h3>Landing Screen - Background</h3>
2976
+ <div class="control-item">
2977
+ <label>Background Color</label>
2978
+ <input type="color" id="landingBgColor" value="${defaultColor}">
2979
+ <p style="color: #6b7280; font-size: 11px; margin-top: 4px;">
2980
+ Pick a solid color for the background. Default uses a subtle gradient.
2981
+ </p>
2982
+ </div>
2983
+ </div>
2984
+ `;
2985
+ break;
2390
2986
  case 'modeCard':
2391
2987
  controlsHTML = `
2392
2988
  <div class="customization-group">
@@ -2395,18 +2991,10 @@
2395
2991
  <label>Voice Card Title</label>
2396
2992
  <input type="text" id="voiceCardTitle" value="Voice Call" placeholder="Voice Call">
2397
2993
  </div>
2398
- <div class="control-item">
2399
- <label>Voice Card Description</label>
2400
- <input type="text" id="voiceCardDesc" value="Start a voice conversation" placeholder="Start a voice conversation">
2401
- </div>
2402
2994
  <div class="control-item">
2403
2995
  <label>Text Card Title</label>
2404
2996
  <input type="text" id="textCardTitle" value="Text Chat" placeholder="Text Chat">
2405
2997
  </div>
2406
- <div class="control-item">
2407
- <label>Text Card Description</label>
2408
- <input type="text" id="textCardDesc" value="Chat via text messages" placeholder="Chat via text messages">
2409
- </div>
2410
2998
  <div class="control-item">
2411
2999
  <label>Background Color</label>
2412
3000
  <input type="color" id="modeCardBg" value="${widgetConfig.landing.modeCardBackgroundColor}">
@@ -2485,6 +3073,65 @@
2485
3073
  applyButtonStyles(document.getElementById('mockButton'));
2486
3074
  updateConfigCode();
2487
3075
  });
3076
+
3077
+ // Prompt Animation controls
3078
+ document.getElementById('promptEnabled')?.addEventListener('change', (e) => {
3079
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3080
+ widgetConfig.promptAnimation.enabled = e.target.checked;
3081
+ const promptControls = document.getElementById('promptControls');
3082
+ const allControls = ['promptControls', 'promptBgColorControl', 'promptTextColorControl', 'promptAnimationTypeControl', 'promptHideAfterSecondsControl', 'promptPositionControl'];
3083
+ allControls.forEach(id => {
3084
+ const el = document.getElementById(id);
3085
+ if (el) el.style.display = e.target.checked ? 'block' : 'none';
3086
+ });
3087
+ applyButtonStyles(document.getElementById('mockButton'));
3088
+ updateConfigCode();
3089
+ });
3090
+
3091
+ document.getElementById('promptText')?.addEventListener('input', (e) => {
3092
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3093
+ widgetConfig.promptAnimation.text = e.target.value;
3094
+ applyButtonStyles(document.getElementById('mockButton'));
3095
+ updateConfigCode();
3096
+ });
3097
+
3098
+ document.getElementById('promptBgColor')?.addEventListener('input', (e) => {
3099
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3100
+ widgetConfig.promptAnimation.backgroundColor = e.target.value;
3101
+ applyButtonStyles(document.getElementById('mockButton'));
3102
+ updateConfigCode();
3103
+ });
3104
+
3105
+ document.getElementById('promptTextColor')?.addEventListener('input', (e) => {
3106
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3107
+ widgetConfig.promptAnimation.textColor = e.target.value;
3108
+ applyButtonStyles(document.getElementById('mockButton'));
3109
+ updateConfigCode();
3110
+ });
3111
+
3112
+ document.getElementById('promptAnimationType')?.addEventListener('change', (e) => {
3113
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3114
+ // If pulse was previously selected, default to bounce
3115
+ const value = e.target.value === 'pulse' ? 'bounce' : e.target.value;
3116
+ widgetConfig.promptAnimation.animationType = value;
3117
+ applyButtonStyles(document.getElementById('mockButton'));
3118
+ updateConfigCode();
3119
+ });
3120
+
3121
+
3122
+ document.getElementById('promptHideAfterSeconds')?.addEventListener('input', (e) => {
3123
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3124
+ const value = e.target.value.trim();
3125
+ widgetConfig.promptAnimation.hideAfterSeconds = value === '' ? null : parseInt(value) || null;
3126
+ updateConfigCode();
3127
+ });
3128
+
3129
+ document.getElementById('promptPosition')?.addEventListener('change', (e) => {
3130
+ if (!widgetConfig.promptAnimation) widgetConfig.promptAnimation = {};
3131
+ widgetConfig.promptAnimation.position = e.target.value;
3132
+ applyButtonStyles(document.getElementById('mockButton'));
3133
+ updateConfigCode();
3134
+ });
2488
3135
  document.getElementById('iconCustomImage')?.addEventListener('input', (e) => {
2489
3136
  widgetConfig.icon.customImage = e.target.value;
2490
3137
  applyButtonStyles(document.getElementById('mockButton'));
@@ -2502,15 +3149,17 @@
2502
3149
  });
2503
3150
  }
2504
3151
 
2505
- // Header controls
2506
- if (elementType === 'header') {
3152
+ // Header controls (also applies to onlineIndicator)
3153
+ if (elementType === 'header' || elementType === 'onlineIndicator') {
2507
3154
  document.getElementById('headerTitle')?.addEventListener('input', (e) => {
2508
3155
  widgetConfig.header.title = e.target.value;
2509
3156
  renderPanelContent();
2510
3157
  updateConfigCode();
2511
3158
  });
2512
3159
  document.getElementById('headerBgColor')?.addEventListener('input', (e) => {
2513
- widgetConfig.header.backgroundColor = e.target.value;
3160
+ const newColor = e.target.value;
3161
+ console.log('🎨 Header color changed to:', newColor);
3162
+ widgetConfig.header.backgroundColor = newColor;
2514
3163
  renderPanelContent();
2515
3164
  updateConfigCode();
2516
3165
  });
@@ -2524,6 +3173,21 @@
2524
3173
  renderPanelContent();
2525
3174
  updateConfigCode();
2526
3175
  });
3176
+ document.getElementById('onlineIndicatorText')?.addEventListener('input', (e) => {
3177
+ widgetConfig.header.onlineIndicatorText = e.target.value;
3178
+ renderPanelContent();
3179
+ updateConfigCode();
3180
+ });
3181
+ document.getElementById('onlineIndicatorColor')?.addEventListener('input', (e) => {
3182
+ widgetConfig.header.onlineIndicatorColor = e.target.value;
3183
+ renderPanelContent();
3184
+ updateConfigCode();
3185
+ });
3186
+ document.getElementById('onlineIndicatorDotColor')?.addEventListener('input', (e) => {
3187
+ widgetConfig.header.onlineIndicatorDotColor = e.target.value;
3188
+ renderPanelContent();
3189
+ updateConfigCode();
3190
+ });
2527
3191
  }
2528
3192
 
2529
3193
  // Panel controls
@@ -2537,31 +3201,31 @@
2537
3201
  updateConfigCode();
2538
3202
  });
2539
3203
  });
2540
- document.getElementById('panelBgColor')?.addEventListener('input', (e) => {
2541
- widgetConfig.panel.backgroundColor = e.target.value;
2542
- renderPanelContent();
2543
- updateConfigCode();
2544
- });
2545
3204
  // Position controls (included in panel settings)
2546
3205
  document.getElementById('positionVertical')?.addEventListener('change', (e) => {
2547
3206
  widgetConfig.position.vertical = e.target.value;
3207
+ updateMockWidgetPosition();
2548
3208
  updateConfigCode();
2549
3209
  });
2550
3210
  document.getElementById('positionHorizontal')?.addEventListener('change', (e) => {
2551
3211
  widgetConfig.position.horizontal = e.target.value;
3212
+ // Don't update mock widget position - it always stays on the right
2552
3213
  updateConfigCode();
2553
3214
  });
2554
3215
  document.getElementById('positionOffsetX')?.addEventListener('input', (e) => {
2555
3216
  widgetConfig.position.offset.x = parseInt(e.target.value) || 0;
3217
+ updateMockWidgetPosition();
2556
3218
  updateConfigCode();
2557
3219
  });
2558
3220
  document.getElementById('positionOffsetY')?.addEventListener('input', (e) => {
2559
3221
  widgetConfig.position.offset.y = parseInt(e.target.value) || 0;
3222
+ updateMockWidgetPosition();
2560
3223
  updateConfigCode();
2561
3224
  });
2562
3225
  // Direction controls (included in panel settings)
2563
3226
  document.getElementById('direction')?.addEventListener('change', (e) => {
2564
3227
  widgetConfig.direction = e.target.value;
3228
+ renderPanelContent(); // Re-render mock panel with new direction
2565
3229
  updateConfigCode();
2566
3230
  });
2567
3231
  }
@@ -2570,18 +3234,22 @@
2570
3234
  if (elementType === 'position') {
2571
3235
  document.getElementById('positionVertical')?.addEventListener('change', (e) => {
2572
3236
  widgetConfig.position.vertical = e.target.value;
3237
+ updateMockWidgetPosition();
2573
3238
  updateConfigCode();
2574
3239
  });
2575
3240
  document.getElementById('positionHorizontal')?.addEventListener('change', (e) => {
2576
3241
  widgetConfig.position.horizontal = e.target.value;
3242
+ // Don't update mock widget position - it always stays on the right
2577
3243
  updateConfigCode();
2578
3244
  });
2579
3245
  document.getElementById('positionOffsetX')?.addEventListener('input', (e) => {
2580
3246
  widgetConfig.position.offset.x = parseInt(e.target.value) || 0;
3247
+ updateMockWidgetPosition();
2581
3248
  updateConfigCode();
2582
3249
  });
2583
3250
  document.getElementById('positionOffsetY')?.addEventListener('input', (e) => {
2584
3251
  widgetConfig.position.offset.y = parseInt(e.target.value) || 0;
3252
+ updateMockWidgetPosition();
2585
3253
  updateConfigCode();
2586
3254
  });
2587
3255
  }
@@ -2590,6 +3258,7 @@
2590
3258
  if (elementType === 'direction') {
2591
3259
  document.getElementById('direction')?.addEventListener('change', (e) => {
2592
3260
  widgetConfig.direction = e.target.value;
3261
+ renderPanelContent(); // Re-render mock panel with new direction
2593
3262
  updateConfigCode();
2594
3263
  });
2595
3264
  }
@@ -2606,8 +3275,23 @@
2606
3275
  renderPanelContent();
2607
3276
  updateConfigCode();
2608
3277
  });
2609
- document.getElementById('msgTextColor')?.addEventListener('input', (e) => {
2610
- widgetConfig.messages.textColor = e.target.value;
3278
+ document.getElementById('msgUserTextColor')?.addEventListener('input', (e) => {
3279
+ widgetConfig.messages.userTextColor = e.target.value;
3280
+ renderPanelContent();
3281
+ updateConfigCode();
3282
+ });
3283
+ document.getElementById('msgAgentTextColor')?.addEventListener('input', (e) => {
3284
+ widgetConfig.messages.agentTextColor = e.target.value;
3285
+ renderPanelContent();
3286
+ updateConfigCode();
3287
+ });
3288
+ document.getElementById('msgUserAvatarIcon')?.addEventListener('input', (e) => {
3289
+ widgetConfig.messages.userAvatarIcon = e.target.value;
3290
+ renderPanelContent();
3291
+ updateConfigCode();
3292
+ });
3293
+ document.getElementById('msgAgentAvatarIcon')?.addEventListener('input', (e) => {
3294
+ widgetConfig.messages.agentAvatarIcon = e.target.value;
2611
3295
  renderPanelContent();
2612
3296
  updateConfigCode();
2613
3297
  });
@@ -2677,10 +3361,6 @@
2677
3361
  renderPanelContent();
2678
3362
  updateConfigCode();
2679
3363
  });
2680
- document.getElementById('micBtnActive')?.addEventListener('input', (e) => {
2681
- widgetConfig.voice.micButtonActiveColor = e.target.value;
2682
- updateConfigCode();
2683
- });
2684
3364
  }
2685
3365
 
2686
3366
  // Voice avatar controls (for voice interface)
@@ -2727,6 +3407,7 @@
2727
3407
  // Status text controls
2728
3408
  if (elementType === 'statusTitle' || elementType === 'statusSubtitle') {
2729
3409
  document.getElementById('statusText')?.addEventListener('input', (e) => {
3410
+ widgetConfig.voice.statusText = e.target.value;
2730
3411
  // Update status text in both regular and compact views
2731
3412
  const statusEls = document.querySelectorAll('.mock-voice-status span, .mock-compact-status span');
2732
3413
  statusEls.forEach(el => el.textContent = e.target.value);
@@ -2739,6 +3420,7 @@
2739
3420
  updateConfigCode();
2740
3421
  });
2741
3422
  document.getElementById('statusDotColor')?.addEventListener('input', (e) => {
3423
+ widgetConfig.voice.statusDotColor = e.target.value;
2742
3424
  const dots = document.querySelectorAll('.mock-status-dot');
2743
3425
  dots.forEach(dot => dot.style.background = e.target.value);
2744
3426
  updateConfigCode();
@@ -2753,14 +3435,18 @@
2753
3435
  // Live indicator controls
2754
3436
  if (elementType === 'liveIndicator') {
2755
3437
  document.getElementById('liveDotColor')?.addEventListener('input', (e) => {
3438
+ widgetConfig.voice.liveIndicatorDotColor = e.target.value;
2756
3439
  const dots = document.querySelectorAll('.mock-live-dot');
2757
3440
  dots.forEach(dot => dot.style.background = e.target.value);
2758
- updateConfigCode();
3441
+ renderPanelContent();
3442
+ updateConfigCode(); // This will call updateActualWidget() with debounce
2759
3443
  });
2760
3444
  document.getElementById('liveTextColor')?.addEventListener('input', (e) => {
3445
+ widgetConfig.voice.liveIndicatorTextColor = e.target.value;
2761
3446
  const indicators = document.querySelectorAll('.mock-live-indicator');
2762
3447
  indicators.forEach(ind => ind.style.color = e.target.value);
2763
- updateConfigCode();
3448
+ renderPanelContent();
3449
+ updateConfigCode(); // This will call updateActualWidget() with debounce
2764
3450
  });
2765
3451
  }
2766
3452
 
@@ -2781,8 +3467,10 @@
2781
3467
  // End call button controls
2782
3468
  if (elementType === 'endCallButton') {
2783
3469
  document.getElementById('endCallBtnColor')?.addEventListener('input', (e) => {
3470
+ widgetConfig.voice.endCallButtonColor = e.target.value;
2784
3471
  const buttons = document.querySelectorAll('[data-element-type="endCallButton"]');
2785
3472
  buttons.forEach(btn => btn.style.background = e.target.value);
3473
+ renderPanelContent();
2786
3474
  updateConfigCode();
2787
3475
  });
2788
3476
  }
@@ -2790,8 +3478,10 @@
2790
3478
  // Speaker button controls
2791
3479
  if (elementType === 'speakerButton') {
2792
3480
  document.getElementById('speakerBtnColor')?.addEventListener('input', (e) => {
3481
+ widgetConfig.voice.speakerButtonColor = e.target.value;
2793
3482
  const buttons = document.querySelectorAll('[data-element-type="speakerButton"]');
2794
3483
  buttons.forEach(btn => btn.style.background = e.target.value);
3484
+ renderPanelContent();
2795
3485
  updateConfigCode();
2796
3486
  });
2797
3487
  }
@@ -2831,14 +3521,18 @@
2831
3521
  // Live transcript controls
2832
3522
  if (elementType === 'liveTranscript' || elementType === 'liveTranscriptText') {
2833
3523
  document.getElementById('liveTranscriptColor')?.addEventListener('input', (e) => {
3524
+ widgetConfig.voice.liveTranscriptTextColor = e.target.value;
2834
3525
  const text = document.querySelector('.mock-live-text-collapsed');
2835
3526
  if (text) text.style.color = e.target.value;
2836
- updateConfigCode();
3527
+ renderPanelContent();
3528
+ updateConfigCode(); // This will call updateActualWidget() with debounce
2837
3529
  });
2838
3530
  document.getElementById('liveTranscriptFontSize')?.addEventListener('input', (e) => {
3531
+ widgetConfig.voice.liveTranscriptFontSize = e.target.value;
2839
3532
  const text = document.querySelector('.mock-live-text-collapsed');
2840
3533
  if (text) text.style.fontSize = e.target.value;
2841
- updateConfigCode();
3534
+ renderPanelContent();
3535
+ updateConfigCode(); // This will call updateActualWidget() with debounce
2842
3536
  });
2843
3537
  }
2844
3538
 
@@ -2863,8 +3557,10 @@
2863
3557
  // Show/hide relevant controls
2864
3558
  const iconControl = document.getElementById('logoIconControl');
2865
3559
  const imageControl = document.getElementById('logoImageControl');
3560
+ const backgroundControl = document.getElementById('logoBackgroundControl');
2866
3561
  if (iconControl) iconControl.style.display = e.target.value === 'icon' ? 'block' : 'none';
2867
3562
  if (imageControl) imageControl.style.display = e.target.value === 'image' ? 'block' : 'none';
3563
+ if (backgroundControl) backgroundControl.style.display = e.target.value === 'image' ? 'block' : 'none';
2868
3564
  renderPanelContent();
2869
3565
  updateConfigCode();
2870
3566
  });
@@ -2879,6 +3575,18 @@
2879
3575
  renderPanelContent();
2880
3576
  updateConfigCode();
2881
3577
  });
3578
+ document.getElementById('logoBackgroundEnabled')?.addEventListener('change', (e) => {
3579
+ widgetConfig.landing.logoBackgroundEnabled = e.target.checked;
3580
+ const colorControl = document.getElementById('logoBackgroundColorControl');
3581
+ if (colorControl) colorControl.style.display = e.target.checked ? 'block' : 'none';
3582
+ renderPanelContent();
3583
+ updateConfigCode();
3584
+ });
3585
+ document.getElementById('logoBackgroundColor')?.addEventListener('input', (e) => {
3586
+ widgetConfig.landing.logoBackgroundColor = e.target.value;
3587
+ renderPanelContent();
3588
+ updateConfigCode();
3589
+ });
2882
3590
  }
2883
3591
 
2884
3592
  if (elementType === 'landingTitle') {
@@ -2907,14 +3615,17 @@
2907
3615
  });
2908
3616
  }
2909
3617
 
2910
- if (elementType === 'modeCard') {
2911
- document.getElementById('voiceCardTitle')?.addEventListener('input', (e) => {
2912
- widgetConfig.landing.voiceCardTitle = e.target.value;
3618
+ if (elementType === 'landingBackground') {
3619
+ document.getElementById('landingBgColor')?.addEventListener('input', (e) => {
3620
+ widgetConfig.landing.backgroundColor = e.target.value;
2913
3621
  renderPanelContent();
2914
3622
  updateConfigCode();
2915
3623
  });
2916
- document.getElementById('voiceCardDesc')?.addEventListener('input', (e) => {
2917
- widgetConfig.landing.voiceCardDesc = e.target.value;
3624
+ }
3625
+
3626
+ if (elementType === 'modeCard') {
3627
+ document.getElementById('voiceCardTitle')?.addEventListener('input', (e) => {
3628
+ widgetConfig.landing.voiceCardTitle = e.target.value;
2918
3629
  renderPanelContent();
2919
3630
  updateConfigCode();
2920
3631
  });
@@ -2923,11 +3634,6 @@
2923
3634
  renderPanelContent();
2924
3635
  updateConfigCode();
2925
3636
  });
2926
- document.getElementById('textCardDesc')?.addEventListener('input', (e) => {
2927
- widgetConfig.landing.textCardDesc = e.target.value;
2928
- renderPanelContent();
2929
- updateConfigCode();
2930
- });
2931
3637
  document.getElementById('modeCardBg')?.addEventListener('input', (e) => {
2932
3638
  widgetConfig.landing.modeCardBackgroundColor = e.target.value;
2933
3639
  renderPanelContent();
@@ -2936,34 +3642,526 @@
2936
3642
  }
2937
3643
  }
2938
3644
 
3645
+ // Actual widget instance
3646
+ let actualWidgetInstance = null;
3647
+ let updateWidgetTimeout = null;
3648
+ let widgetManuallyClosed = false; // Track if widget was manually closed by user
3649
+ const AGENT_ID = 'agent_20e0b3047';
3650
+ const APP_ID = 'app_8LGyEp2cS6vn4GLad8WLNRlyrOq0MrvDRPXk';
3651
+
3652
+ function getTTPChatWidget() {
3653
+ // SDK exposes TTPChatWidget as window.TTPAgentSDK.TTPChatWidget
3654
+ if (window.TTPAgentSDK && window.TTPAgentSDK.TTPChatWidget) {
3655
+ return window.TTPAgentSDK.TTPChatWidget;
3656
+ }
3657
+ // Fallback for direct access (if SDK exposes it directly)
3658
+ if (window.TTPChatWidget) {
3659
+ return window.TTPChatWidget;
3660
+ }
3661
+ return null;
3662
+ }
3663
+
3664
+ // Default configuration values (used to compare and only show changed values)
3665
+ const defaultConfig = {
3666
+ direction: 'ltr',
3667
+ button: {
3668
+ size: 'medium',
3669
+ shape: 'circle',
3670
+ backgroundColor: '#FFFFFF',
3671
+ hoverColor: '#D3D3D3',
3672
+ shadow: true,
3673
+ shadowColor: 'rgba(0,0,0,0.15)'
3674
+ },
3675
+ icon: {
3676
+ type: 'custom',
3677
+ customImage: 'https://talktopc.com/logo192.png',
3678
+ size: 'medium',
3679
+ backgroundColor: '#FFFFFF'
3680
+ },
3681
+ panel: {
3682
+ width: 360,
3683
+ height: 550,
3684
+ borderRadius: 24,
3685
+ backgroundColor: '#FFFFFF',
3686
+ border: '1px solid #E5E7EB'
3687
+ },
3688
+ position: {
3689
+ vertical: 'bottom',
3690
+ horizontal: 'left',
3691
+ offset: { x: 20, y: 20 }
3692
+ },
3693
+ header: {
3694
+ title: 'Chat Assistant',
3695
+ backgroundColor: '#7C3AED',
3696
+ textColor: '#FFFFFF',
3697
+ showCloseButton: true,
3698
+ onlineIndicatorText: 'Online',
3699
+ onlineIndicatorColor: '#FFFFFF',
3700
+ onlineIndicatorDotColor: '#10b981'
3701
+ },
3702
+ messages: {
3703
+ userBackgroundColor: '#E5E7EB',
3704
+ agentBackgroundColor: '#F3F4F6',
3705
+ textColor: '#1F2937',
3706
+ userTextColor: '#1F2937',
3707
+ agentTextColor: '#1F2937',
3708
+ userAvatarIcon: '👤',
3709
+ agentAvatarIcon: '🤖',
3710
+ fontSize: '14px',
3711
+ borderRadius: 16
3712
+ },
3713
+ text: {
3714
+ sendButtonText: '→',
3715
+ sendButtonColor: '#7C3AED',
3716
+ sendButtonHoverColor: '#6D28D9',
3717
+ inputPlaceholder: 'Type your message...',
3718
+ inputFocusColor: '#7C3AED'
3719
+ },
3720
+ voice: {
3721
+ micButtonColor: '#7C3AED',
3722
+ micButtonActiveColor: '#EF4444',
3723
+ speakerButtonColor: '#FFFFFF',
3724
+ endCallButtonColor: '#ef4444',
3725
+ avatarBackgroundColor: '#667eea',
3726
+ avatarType: 'icon',
3727
+ avatarIcon: '🤖',
3728
+ avatarImageUrl: '',
3729
+ startCallButtonText: 'Start Call',
3730
+ startCallButtonColor: '#667eea',
3731
+ startCallButtonTextColor: '#FFFFFF',
3732
+ statusTitleColor: '#1e293b',
3733
+ statusSubtitleColor: '#64748b',
3734
+ statusDotColor: '#10b981',
3735
+ statusText: 'Listening...',
3736
+ liveTranscriptTextColor: '#64748b',
3737
+ liveTranscriptFontSize: '14px',
3738
+ liveIndicatorDotColor: '#10b981',
3739
+ liveIndicatorTextColor: '#10b981',
3740
+ waveformType: 'waveform',
3741
+ waveformIcon: '🎤',
3742
+ waveformImageUrl: ''
3743
+ },
3744
+ landing: {
3745
+ backgroundColor: 'linear-gradient(180deg, #ffffff 0%, rgba(168, 85, 247, 0.03) 100%)',
3746
+ logo: '🤖',
3747
+ logoType: 'icon',
3748
+ logoIcon: '🤖',
3749
+ logoImageUrl: '',
3750
+ logoBackgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
3751
+ logoBackgroundEnabled: true,
3752
+ title: 'Welcome to AI Assistant',
3753
+ subtitle: 'Choose how you\'d like to interact',
3754
+ voiceCardTitle: 'Voice Call',
3755
+ textCardTitle: 'Text Chat',
3756
+ titleColor: '#1e293b',
3757
+ subtitleColor: '#64748b',
3758
+ modeCardBackgroundColor: '#FFFFFF'
3759
+ },
3760
+ promptAnimation: {
3761
+ enabled: false,
3762
+ text: 'Try me!',
3763
+ backgroundColor: 'linear-gradient(135deg, #7c3aed, #4f46e5)',
3764
+ textColor: '#ffffff',
3765
+ animationType: 'bounce',
3766
+ showShimmer: true,
3767
+ showPulseRings: true,
3768
+ hideAfterClick: true,
3769
+ hideAfterSeconds: null,
3770
+ position: 'top'
3771
+ }
3772
+ };
3773
+
3774
+ // Helper function to deep compare objects and return only changed properties
3775
+ function getChangedProperties(current, defaults) {
3776
+ if (current === null || current === undefined) {
3777
+ return undefined;
3778
+ }
3779
+
3780
+ if (typeof current !== 'object' || Array.isArray(current)) {
3781
+ // Primitive values or arrays - compare directly
3782
+ return JSON.stringify(current) === JSON.stringify(defaults) ? undefined : current;
3783
+ }
3784
+
3785
+ // Object comparison
3786
+ const result = {};
3787
+ let hasChanges = false;
3788
+
3789
+ // Check all keys in current object
3790
+ for (const key in current) {
3791
+ if (current.hasOwnProperty(key)) {
3792
+ const currentValue = current[key];
3793
+ const defaultValue = defaults[key];
3794
+
3795
+ if (defaultValue === undefined) {
3796
+ // Property doesn't exist in defaults - include it
3797
+ result[key] = currentValue;
3798
+ hasChanges = true;
3799
+ } else if (typeof currentValue === 'object' && currentValue !== null && !Array.isArray(currentValue) &&
3800
+ typeof defaultValue === 'object' && defaultValue !== null && !Array.isArray(defaultValue)) {
3801
+ // Nested object - recurse
3802
+ const nestedChanges = getChangedProperties(currentValue, defaultValue);
3803
+ if (nestedChanges !== undefined) {
3804
+ result[key] = nestedChanges;
3805
+ hasChanges = true;
3806
+ }
3807
+ } else {
3808
+ // Primitive or array comparison
3809
+ if (JSON.stringify(currentValue) !== JSON.stringify(defaultValue)) {
3810
+ result[key] = currentValue;
3811
+ hasChanges = true;
3812
+ }
3813
+ }
3814
+ }
3815
+ }
3816
+
3817
+ return hasChanges ? result : undefined;
3818
+ }
3819
+
2939
3820
  function updateConfigCode() {
2940
3821
  const codeOutput = document.getElementById('configCode');
2941
3822
 
2942
- // Create a clean config object with required fields
3823
+ // Start with required fields
2943
3824
  const displayConfig = {
2944
- agentId: 'your_agent_id', // Required - replace with your agent ID
2945
- appId: 'your_app_id', // Required - replace with your app ID
2946
- direction: widgetConfig.direction,
2947
- button: widgetConfig.button,
2948
- icon: widgetConfig.icon,
2949
- panel: widgetConfig.panel,
2950
- header: widgetConfig.header,
2951
- messages: widgetConfig.messages,
2952
- text: widgetConfig.text,
2953
- voice: widgetConfig.voice,
2954
- landing: widgetConfig.landing,
2955
- position: widgetConfig.position
3825
+ agentId: AGENT_ID,
3826
+ appId: APP_ID
2956
3827
  };
2957
3828
 
2958
- // Format as proper JavaScript object with comments
3829
+ // Only add properties that differ from defaults
3830
+ const changedDirection = widgetConfig.direction !== defaultConfig.direction ? widgetConfig.direction : undefined;
3831
+ if (changedDirection !== undefined) {
3832
+ displayConfig.direction = changedDirection;
3833
+ }
3834
+
3835
+ const changedButton = getChangedProperties(widgetConfig.button, defaultConfig.button);
3836
+ if (changedButton !== undefined) {
3837
+ displayConfig.button = changedButton;
3838
+ }
3839
+
3840
+ const changedIcon = getChangedProperties(widgetConfig.icon, defaultConfig.icon);
3841
+ if (changedIcon !== undefined) {
3842
+ displayConfig.icon = changedIcon;
3843
+ }
3844
+
3845
+ const changedPanel = getChangedProperties(widgetConfig.panel, defaultConfig.panel);
3846
+ if (changedPanel !== undefined) {
3847
+ displayConfig.panel = changedPanel;
3848
+ }
3849
+
3850
+ const changedPosition = getChangedProperties(widgetConfig.position, defaultConfig.position);
3851
+ if (changedPosition !== undefined) {
3852
+ displayConfig.position = changedPosition;
3853
+ }
3854
+
3855
+ const changedHeader = getChangedProperties(widgetConfig.header, defaultConfig.header);
3856
+ if (changedHeader !== undefined) {
3857
+ displayConfig.header = changedHeader;
3858
+ }
3859
+
3860
+ const changedMessages = getChangedProperties(widgetConfig.messages, defaultConfig.messages);
3861
+ if (changedMessages !== undefined) {
3862
+ displayConfig.messages = changedMessages;
3863
+ }
3864
+
3865
+ const changedText = getChangedProperties(widgetConfig.text, defaultConfig.text);
3866
+ if (changedText !== undefined) {
3867
+ displayConfig.text = changedText;
3868
+ }
3869
+
3870
+ const changedVoice = getChangedProperties(widgetConfig.voice, defaultConfig.voice);
3871
+ if (changedVoice !== undefined) {
3872
+ displayConfig.voice = changedVoice;
3873
+ }
3874
+
3875
+ const changedLanding = getChangedProperties(widgetConfig.landing, defaultConfig.landing);
3876
+ if (changedLanding !== undefined) {
3877
+ displayConfig.landing = changedLanding;
3878
+ }
3879
+
3880
+ const changedPromptAnimation = getChangedProperties(widgetConfig.promptAnimation, defaultConfig.promptAnimation);
3881
+ if (changedPromptAnimation !== undefined) {
3882
+ displayConfig.promptAnimation = changedPromptAnimation;
3883
+ }
3884
+
3885
+ // Format as proper JavaScript object
2959
3886
  let configStr = JSON.stringify(displayConfig, null, 2);
2960
3887
 
2961
- // Add helpful comments
2962
- configStr = configStr
2963
- .replace(/"agentId": "your_agent_id"/g, '"agentId": "your_agent_id" // Required: Your agent ID')
2964
- .replace(/"appId": "your_app_id"/g, '"appId": "your_app_id" // Required: Your app ID');
3888
+ codeOutput.textContent = `const widget = new TTPAgentSDK.TTPChatWidget(${configStr});`;
2965
3889
 
2966
- codeOutput.textContent = `const widget = new TTPChatWidget(${configStr});`;
3890
+ // Always update actual widget when config changes (with debounce)
3891
+ if (getTTPChatWidget()) {
3892
+ // Debounce widget updates to avoid rapid recreations
3893
+ if (updateWidgetTimeout) {
3894
+ clearTimeout(updateWidgetTimeout);
3895
+ }
3896
+ updateWidgetTimeout = setTimeout(() => {
3897
+ updateActualWidget();
3898
+ }, 300); // Wait 300ms after last change
3899
+ } else {
3900
+ // Only warn if not during initial load (when SDK might not be loaded yet)
3901
+ if (!isInitializing) {
3902
+ console.warn('TTPChatWidget not available, cannot update actual widget');
3903
+ }
3904
+ }
3905
+ }
3906
+
3907
+ // Function to sync actual widget state with mock panel state
3908
+ // Only called when mock panel state changes, not on customization changes
3909
+ function syncWidgetWithMockPanel() {
3910
+ if (!actualWidgetInstance) return;
3911
+
3912
+ setTimeout(() => {
3913
+ // Check if widget uses Shadow DOM (default behavior)
3914
+ const shadowHost = document.getElementById('ttp-widget-shadow-host');
3915
+ let widgetPanel;
3916
+
3917
+ if (shadowHost && shadowHost.shadowRoot) {
3918
+ // Widget uses Shadow DOM - access elements through shadow root
3919
+ widgetPanel = shadowHost.shadowRoot.querySelector('[id^="ttp-chat-widget-panel"], #text-chat-panel');
3920
+ } else {
3921
+ // Widget uses regular DOM - access elements directly
3922
+ widgetPanel = document.querySelector('[id^="ttp-chat-widget-panel"], #text-chat-panel');
3923
+ }
3924
+
3925
+ if (widgetPanel && actualWidgetInstance) {
3926
+ const isCurrentlyOpen = widgetPanel.classList.contains('open');
3927
+
3928
+ if (panelOpen) {
3929
+ // Mock panel is open - ensure widget is open (unless user manually closed it)
3930
+ if (!isCurrentlyOpen && !widgetManuallyClosed) {
3931
+ if (typeof actualWidgetInstance.open === 'function') {
3932
+ try {
3933
+ actualWidgetInstance.open();
3934
+ widgetManuallyClosed = false; // Reset since we're opening it
3935
+ } catch (e) {
3936
+ console.warn('⚠️ Error opening widget:', e);
3937
+ }
3938
+ } else if (typeof actualWidgetInstance.togglePanel === 'function') {
3939
+ try {
3940
+ actualWidgetInstance.togglePanel();
3941
+ widgetManuallyClosed = false; // Reset since we're opening it
3942
+ } catch (e) {
3943
+ console.warn('⚠️ Error opening widget:', e);
3944
+ }
3945
+ }
3946
+ }
3947
+ } else {
3948
+ // Mock panel is closed - ensure widget is closed
3949
+ if (isCurrentlyOpen) {
3950
+ // Widget is open, so close it
3951
+ widgetManuallyClosed = false; // Reset since we're closing via mock panel
3952
+ if (typeof actualWidgetInstance.togglePanel === 'function') {
3953
+ try {
3954
+ // togglePanel will close it if it's open
3955
+ actualWidgetInstance.togglePanel();
3956
+ } catch (e) {
3957
+ console.warn('⚠️ Error closing widget:', e);
3958
+ // Fallback: directly manipulate the DOM
3959
+ widgetPanel.classList.remove('open');
3960
+ if (actualWidgetInstance) {
3961
+ actualWidgetInstance.isOpen = false;
3962
+ }
3963
+ }
3964
+ } else if (typeof actualWidgetInstance.close === 'function') {
3965
+ try {
3966
+ actualWidgetInstance.close();
3967
+ } catch (e) {
3968
+ console.warn('⚠️ Error closing widget:', e);
3969
+ // Fallback: directly manipulate the DOM
3970
+ widgetPanel.classList.remove('open');
3971
+ if (actualWidgetInstance) {
3972
+ actualWidgetInstance.isOpen = false;
3973
+ }
3974
+ }
3975
+ } else {
3976
+ // Fallback: directly manipulate the DOM
3977
+ widgetPanel.classList.remove('open');
3978
+ if (actualWidgetInstance) {
3979
+ actualWidgetInstance.isOpen = false;
3980
+ }
3981
+ }
3982
+ }
3983
+ }
3984
+ }
3985
+ }, 100); // Small delay to ensure widget is ready
3986
+ }
3987
+
3988
+ // Listen for widget close events to track manual closes
3989
+ function setupWidgetCloseTracking() {
3990
+ if (!actualWidgetInstance) return;
3991
+
3992
+ setTimeout(() => {
3993
+ const shadowHost = document.getElementById('ttp-widget-shadow-host');
3994
+ let closeBtn;
3995
+
3996
+ if (shadowHost && shadowHost.shadowRoot) {
3997
+ closeBtn = shadowHost.shadowRoot.querySelector('[id*="close"], .close-btn, [class*="close"]');
3998
+ } else {
3999
+ closeBtn = document.querySelector('[id*="close"], .close-btn, [class*="close"]');
4000
+ }
4001
+
4002
+ if (closeBtn) {
4003
+ // Remove any existing listeners by cloning
4004
+ const newCloseBtn = closeBtn.cloneNode(true);
4005
+ closeBtn.parentNode.replaceChild(newCloseBtn, closeBtn);
4006
+
4007
+ newCloseBtn.addEventListener('click', () => {
4008
+ widgetManuallyClosed = true;
4009
+ // Don't sync immediately - let the widget close naturally
4010
+ setTimeout(() => {
4011
+ // Only sync if mock panel is closed (user wants it closed)
4012
+ if (!panelOpen) {
4013
+ syncWidgetWithMockPanel();
4014
+ }
4015
+ }, 100);
4016
+ });
4017
+ }
4018
+ }, 300);
4019
+ }
4020
+
4021
+ function updateActualWidget() {
4022
+ const TTPChatWidget = getTTPChatWidget();
4023
+ if (!TTPChatWidget) {
4024
+ console.warn('TTPChatWidget not available yet');
4025
+ return;
4026
+ }
4027
+
4028
+ // Destroy existing widget if it exists
4029
+ if (actualWidgetInstance) {
4030
+ try {
4031
+ // Try to call destroy method if it exists
4032
+ if (typeof actualWidgetInstance.destroy === 'function') {
4033
+ actualWidgetInstance.destroy();
4034
+ }
4035
+
4036
+ // Remove widget elements from DOM (multiple possible selectors)
4037
+ const selectors = [
4038
+ '[id^="ttp-chat-widget-button"]',
4039
+ '[id^="ttp-chat-widget-panel"]',
4040
+ '[id*="ttp-chat"]',
4041
+ '.ttp-chat-widget-button',
4042
+ '.ttp-chat-widget-panel',
4043
+ '[class*="ttp-chat"]'
4044
+ ];
4045
+
4046
+ let removedCount = 0;
4047
+ selectors.forEach(selector => {
4048
+ const elements = document.querySelectorAll(selector);
4049
+ elements.forEach(el => {
4050
+ if (el && el.parentNode) {
4051
+ el.parentNode.removeChild(el);
4052
+ removedCount++;
4053
+ }
4054
+ });
4055
+ });
4056
+
4057
+ actualWidgetInstance = null;
4058
+
4059
+ // Wait a bit to ensure DOM is cleaned up before creating new widget
4060
+ setTimeout(() => {
4061
+ createNewWidget(TTPChatWidget);
4062
+ }, 150);
4063
+ } catch (e) {
4064
+ console.warn('Error removing old widget:', e);
4065
+ actualWidgetInstance = null;
4066
+ setTimeout(() => {
4067
+ createNewWidget(TTPChatWidget);
4068
+ }, 150);
4069
+ }
4070
+ } else {
4071
+ createNewWidget(TTPChatWidget);
4072
+ }
4073
+ }
4074
+
4075
+ function createNewWidget(TTPChatWidget) {
4076
+
4077
+ // Create config for actual widget - use the same config as mock but positioned differently
4078
+ // Deep copy to avoid mutations and ensure fresh config
4079
+ const actualConfig = {
4080
+ agentId: AGENT_ID,
4081
+ appId: APP_ID,
4082
+ direction: widgetConfig.direction,
4083
+ button: JSON.parse(JSON.stringify(widgetConfig.button)),
4084
+ icon: JSON.parse(JSON.stringify(widgetConfig.icon)),
4085
+ panel: JSON.parse(JSON.stringify(widgetConfig.panel)),
4086
+ header: JSON.parse(JSON.stringify(widgetConfig.header)), // Deep copy header to ensure color is included
4087
+ messages: JSON.parse(JSON.stringify(widgetConfig.messages)),
4088
+ text: JSON.parse(JSON.stringify(widgetConfig.text)),
4089
+ voice: JSON.parse(JSON.stringify(widgetConfig.voice)),
4090
+ landing: JSON.parse(JSON.stringify(widgetConfig.landing)),
4091
+ promptAnimation: JSON.parse(JSON.stringify(widgetConfig.promptAnimation || {
4092
+ enabled: false,
4093
+ text: 'Try me!',
4094
+ backgroundColor: 'linear-gradient(135deg, #7c3aed, #4f46e5)',
4095
+ textColor: '#ffffff',
4096
+ animationType: 'bounce',
4097
+ showShimmer: true,
4098
+ showPulseRings: true,
4099
+ hideAfterClick: true,
4100
+ hideAfterSeconds: null,
4101
+ position: 'top'
4102
+ })), // Include promptAnimation config
4103
+ behavior: {
4104
+ startOpen: panelOpen, // Sync with mock panel state
4105
+ autoOpen: panelOpen, // Sync with mock panel state
4106
+ mode: 'unified'
4107
+ },
4108
+ position: {
4109
+ ...widgetConfig.position,
4110
+ // Use the same position as configured (no inversion)
4111
+ offset: {
4112
+ x: widgetConfig.position.offset.x,
4113
+ y: widgetConfig.position.offset.y
4114
+ }
4115
+ }
4116
+ };
4117
+
4118
+ try {
4119
+ // Reset manual close tracking when widget is recreated (unless user manually closed it)
4120
+ // Don't reset if widget was manually closed and mock panel is also closed
4121
+ if (panelOpen) {
4122
+ widgetManuallyClosed = false;
4123
+ }
4124
+
4125
+ // Debug: Log the config being passed to widget
4126
+ console.log('🔧 Creating widget with config:', {
4127
+ ...actualConfig,
4128
+ promptAnimation: actualConfig.promptAnimation
4129
+ });
4130
+
4131
+ actualWidgetInstance = new TTPChatWidget(actualConfig);
4132
+
4133
+ // Debug: Verify promptAnimation was received
4134
+ if (actualWidgetInstance && actualWidgetInstance.config) {
4135
+ console.log('✅ Widget created. promptAnimation config:', actualWidgetInstance.config.promptAnimation);
4136
+ }
4137
+
4138
+ // Setup tracking for manual closes
4139
+ setupWidgetCloseTracking();
4140
+
4141
+ // Only sync if mock panel is open (don't force open if user closed it)
4142
+ if (panelOpen && !widgetManuallyClosed) {
4143
+ setTimeout(() => {
4144
+ syncWidgetWithMockPanel();
4145
+ }, 200);
4146
+ }
4147
+ } catch (error) {
4148
+ console.error('❌ Error creating actual widget:', error);
4149
+ console.error('Error details:', error.stack);
4150
+ console.error('Config used:', actualConfig);
4151
+ alert('Failed to create widget. Check console for details. Error: ' + error.message);
4152
+ }
4153
+ }
4154
+
4155
+ function initActualWidget() {
4156
+ const TTPChatWidget = getTTPChatWidget();
4157
+ if (!TTPChatWidget) {
4158
+ setTimeout(initActualWidget, 100);
4159
+ return;
4160
+ }
4161
+
4162
+ updateActualWidget();
4163
+ // Mark initialization as complete
4164
+ isInitializing = false;
2967
4165
  }
2968
4166
 
2969
4167
  function resetToDefaults() {
@@ -2992,19 +4190,26 @@
2992
4190
  direction: 'ltr',
2993
4191
  position: {
2994
4192
  vertical: 'bottom',
2995
- horizontal: 'right',
4193
+ horizontal: 'left',
2996
4194
  offset: { x: 20, y: 20 }
2997
4195
  },
2998
4196
  header: {
2999
4197
  title: 'Chat Assistant',
3000
4198
  backgroundColor: '#7C3AED',
3001
4199
  textColor: '#FFFFFF',
3002
- showCloseButton: true
4200
+ showCloseButton: true,
4201
+ onlineIndicatorText: 'Online',
4202
+ onlineIndicatorColor: '#FFFFFF',
4203
+ onlineIndicatorDotColor: '#10b981'
3003
4204
  },
3004
4205
  messages: {
3005
4206
  userBackgroundColor: '#E5E7EB',
3006
4207
  agentBackgroundColor: '#F3F4F6',
3007
- textColor: '#1F2937',
4208
+ textColor: '#1F2937', // Fallback for backward compatibility
4209
+ userTextColor: '#1F2937',
4210
+ agentTextColor: '#1F2937',
4211
+ userAvatarIcon: '👤',
4212
+ agentAvatarIcon: '🤖',
3008
4213
  fontSize: '14px',
3009
4214
  borderRadius: 16
3010
4215
  },
@@ -3018,6 +4223,8 @@
3018
4223
  voice: {
3019
4224
  micButtonColor: '#7C3AED',
3020
4225
  micButtonActiveColor: '#EF4444',
4226
+ speakerButtonColor: '#FFFFFF',
4227
+ endCallButtonColor: '#ef4444',
3021
4228
  avatarBackgroundColor: '#667eea',
3022
4229
  avatarType: 'icon',
3023
4230
  avatarIcon: '🤖',
@@ -3027,6 +4234,12 @@
3027
4234
  startCallButtonTextColor: '#FFFFFF',
3028
4235
  statusTitleColor: '#1e293b',
3029
4236
  statusSubtitleColor: '#64748b',
4237
+ statusDotColor: '#10b981',
4238
+ statusText: 'Listening...',
4239
+ liveTranscriptTextColor: '#64748b',
4240
+ liveTranscriptFontSize: '14px',
4241
+ liveIndicatorDotColor: '#10b981',
4242
+ liveIndicatorTextColor: '#10b981',
3030
4243
  waveformType: 'waveform',
3031
4244
  waveformIcon: '🎤',
3032
4245
  waveformImageUrl: ''
@@ -3036,24 +4249,21 @@
3036
4249
  logoType: 'icon',
3037
4250
  logoIcon: '🤖',
3038
4251
  logoImageUrl: '',
4252
+ logoBackgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
4253
+ logoBackgroundEnabled: true,
4254
+ backgroundColor: 'linear-gradient(180deg, #ffffff 0%, rgba(168, 85, 247, 0.03) 100%)',
3039
4255
  title: 'Welcome to AI Assistant',
3040
4256
  subtitle: 'Choose how you\'d like to interact',
3041
4257
  voiceCardTitle: 'Voice Call',
3042
- voiceCardDesc: 'Start a voice conversation',
3043
4258
  textCardTitle: 'Text Chat',
3044
- textCardDesc: 'Chat via text messages',
3045
4259
  titleColor: '#1e293b',
3046
4260
  subtitleColor: '#64748b',
3047
4261
  modeCardBackgroundColor: '#FFFFFF'
3048
- },
3049
- position: {
3050
- vertical: 'bottom',
3051
- horizontal: 'right',
3052
- offset: { x: 20, y: 20 }
3053
4262
  }
3054
4263
  };
3055
4264
 
3056
4265
  initMockWidget();
4266
+ updateMockWidgetPosition();
3057
4267
  updateConfigCode();
3058
4268
  selectedElement = null;
3059
4269
  document.querySelectorAll('.element-highlight').forEach(el => {
@@ -3072,6 +4282,13 @@
3072
4282
  if (panelOpen) {
3073
4283
  renderPanelContent();
3074
4284
  }
4285
+ // Update prompt bubble visibility based on panel state
4286
+ const mockButton = document.getElementById('mockButton');
4287
+ if (mockButton) {
4288
+ updatePromptBubble(mockButton);
4289
+ }
4290
+ // Sync actual widget state with mock panel
4291
+ syncWidgetWithMockPanel();
3075
4292
  });
3076
4293
 
3077
4294
  // Make panel selectable (click on panel border/background)
@@ -3100,8 +4317,57 @@
3100
4317
  }
3101
4318
  }
3102
4319
 
4320
+ // Initialize mock widget and position
3103
4321
  initMockWidget();
4322
+ // Ensure position is set after initialization
4323
+ setTimeout(() => {
4324
+ updateMockWidgetPosition();
4325
+ }, 50);
3104
4326
  updateConfigCode();
4327
+
4328
+ // Initialize actual widget when SDK loads
4329
+ window.checkAndInitWidget = function() {
4330
+ const TTPChatWidget = getTTPChatWidget();
4331
+ if (TTPChatWidget) {
4332
+ initActualWidget();
4333
+ // Mark initialization as complete once SDK is ready
4334
+ isInitializing = false;
4335
+ } else {
4336
+ setTimeout(window.checkAndInitWidget, 100);
4337
+ }
4338
+ };
4339
+
4340
+ // Start checking for SDK (will also be called when script loads)
4341
+ window.checkAndInitWidget();
4342
+
4343
+ // Debug helper - expose widget instance globally for inspection
4344
+ window.debugWidget = function() {
4345
+ console.log('=== Widget Debug Info ===');
4346
+ console.log('actualWidgetInstance:', actualWidgetInstance);
4347
+ console.log('window.TTPAgentSDK:', window.TTPAgentSDK);
4348
+ console.log('TTPChatWidget available:', !!getTTPChatWidget());
4349
+ const widgetElements = document.querySelectorAll('[id*="ttp"], [class*="ttp"]');
4350
+ console.log('Widget elements in DOM:', widgetElements.length);
4351
+ widgetElements.forEach((el, idx) => {
4352
+ const style = window.getComputedStyle(el);
4353
+ console.log(`Element ${idx + 1}:`, {
4354
+ id: el.id,
4355
+ className: el.className,
4356
+ display: style.display,
4357
+ visibility: style.visibility,
4358
+ opacity: style.opacity,
4359
+ zIndex: style.zIndex
4360
+ });
4361
+ });
4362
+ return {
4363
+ instance: actualWidgetInstance,
4364
+ elements: Array.from(widgetElements),
4365
+ sdk: window.TTPAgentSDK
4366
+ };
4367
+ };
3105
4368
  </script>
4369
+
4370
+ <!-- Load the widget SDK -->
4371
+ <script src="/dist/agent-widget.js?v=2.34.2" onload="if (typeof window.checkAndInitWidget === 'function') setTimeout(window.checkAndInitWidget, 100);" onerror="console.error('❌ Failed to load SDK script from /dist/agent-widget.js'); alert('Failed to load SDK. Check console for details.');"></script>
3106
4372
  </body>
3107
4373
  </html>