ttp-agent-sdk 2.34.6 → 2.34.8

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.
@@ -13660,7 +13660,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
13660
13660
  this.landingScreen.setupEventHandlers({
13661
13661
  onSelectVoice: function () {
13662
13662
  var _onSelectVoice = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
13663
- var _this6$voiceInterface4, _this6$voiceInterface5, _this6$voiceInterface6, _this6$voiceInterface8, _this6$config$behavio, idleState, isDomainError, statusTitle, _this6$voiceInterface7, titleText, domainErrorTitle, _widgetMode, _this6$voiceInterface0, _this6$config$behavio2, _this6$voiceInterface9, _widgetMode2, _t;
13663
+ var _this6$voiceInterface4, _this6$voiceInterface5, _this6$voiceInterface6, _this6$voiceInterface7, _this6$shadowRoot, floatingButton, _this6$voiceInterface8, _this6$voiceInterface0, _this6$config$behavio, idleState, isDomainError, statusTitle, _this6$voiceInterface9, titleText, domainErrorTitle, _widgetMode, _this6$voiceInterface10, _this6$voiceInterface11, _this6$config$behavio2, _this6$voiceInterface1, _this6$voiceInterface12, _this6$shadowRoot2, existingBar, _floatingButton, _widgetMode2, _t;
13664
13664
  return _regenerator().w(function (_context) {
13665
13665
  while (1) switch (_context.p = _context.n) {
13666
13666
  case 0:
@@ -13694,30 +13694,52 @@ var TTPChatWidget = /*#__PURE__*/function () {
13694
13694
  _this6.voiceInterface.showConnectingState();
13695
13695
  }
13696
13696
 
13697
- // Start the call
13697
+ // CRITICAL: On mobile, show minimized bar immediately BEFORE blocking operations
13698
+ // This ensures UI is visible while permission/connection happens
13699
+ if (!((_this6$voiceInterface5 = _this6.voiceInterface) !== null && _this6$voiceInterface5 !== void 0 && _this6$voiceInterface5.isMobile && (_this6$voiceInterface6 = _this6.voiceInterface) !== null && _this6$voiceInterface6 !== void 0 && _this6$voiceInterface6.createMobileMinimizedBar)) {
13700
+ _context.n = 3;
13701
+ break;
13702
+ }
13703
+ // Hide floating button immediately so minimized bar is visible
13704
+ floatingButton = ((_this6$shadowRoot = _this6.shadowRoot) === null || _this6$shadowRoot === void 0 ? void 0 : _this6$shadowRoot.getElementById('text-chat-button')) || document.getElementById('text-chat-button');
13705
+ if (floatingButton) {
13706
+ floatingButton.style.display = 'none';
13707
+ }
13708
+ // Create minimized bar with connecting state
13709
+ _this6.voiceInterface.createMobileMinimizedBar();
13710
+ // Show connecting status in minimized bar
13711
+ _this6.voiceInterface.updateMobileStatus('connecting');
13712
+ // Ensure UI is painted before starting heavy operations
13698
13713
  _context.n = 3;
13699
- return _this6.startVoiceCall();
13714
+ return new Promise(function (resolve) {
13715
+ return requestAnimationFrame(function () {
13716
+ return requestAnimationFrame(resolve);
13717
+ });
13718
+ });
13700
13719
  case 3:
13701
- if (!((_this6$voiceInterface5 = _this6.voiceInterface) !== null && _this6$voiceInterface5 !== void 0 && _this6$voiceInterface5.isActive)) {
13702
- _context.n = 4;
13720
+ _context.n = 4;
13721
+ return _this6.startVoiceCall();
13722
+ case 4:
13723
+ if (!((_this6$voiceInterface7 = _this6.voiceInterface) !== null && _this6$voiceInterface7 !== void 0 && _this6$voiceInterface7.isActive)) {
13724
+ _context.n = 5;
13703
13725
  break;
13704
13726
  }
13705
13727
  console.log('✅ Voice call started successfully');
13706
- _context.n = 6;
13728
+ _context.n = 7;
13707
13729
  break;
13708
- case 4:
13730
+ case 5:
13709
13731
  // This shouldn't happen if startVoiceCall() throws properly, but just in case
13710
13732
  console.warn('⚠️ startVoiceCall() returned but call is not active');
13711
13733
 
13712
13734
  // CRITICAL: Check if we're showing a domain error - if so, stay on voice interface
13713
13735
  // We check by looking at the status title text content
13714
- idleState = (_this6$voiceInterface6 = _this6.voiceInterface) === null || _this6$voiceInterface6 === void 0 || (_this6$voiceInterface6 = _this6$voiceInterface6.shadowRoot) === null || _this6$voiceInterface6 === void 0 ? void 0 : _this6$voiceInterface6.getElementById('voiceIdleState');
13736
+ idleState = (_this6$voiceInterface8 = _this6.voiceInterface) === null || _this6$voiceInterface8 === void 0 || (_this6$voiceInterface8 = _this6$voiceInterface8.shadowRoot) === null || _this6$voiceInterface8 === void 0 ? void 0 : _this6$voiceInterface8.getElementById('voiceIdleState');
13715
13737
  isDomainError = false;
13716
13738
  if (idleState && idleState.style.display !== 'none') {
13717
13739
  statusTitle = idleState.querySelector('.voice-status-title');
13718
13740
  if (statusTitle) {
13719
13741
  titleText = statusTitle.textContent.trim();
13720
- domainErrorTitle = (_this6$voiceInterface7 = _this6.voiceInterface) === null || _this6$voiceInterface7 === void 0 ? void 0 : _this6$voiceInterface7.t('domainNotValidated');
13742
+ domainErrorTitle = (_this6$voiceInterface9 = _this6.voiceInterface) === null || _this6$voiceInterface9 === void 0 ? void 0 : _this6$voiceInterface9.t('domainNotValidated');
13721
13743
  console.log('🔍 Checking for domain error - titleText:', titleText, 'domainErrorTitle:', domainErrorTitle);
13722
13744
  if (titleText === domainErrorTitle || titleText.includes('Domain not') || titleText.includes('not whitelisted')) {
13723
13745
  isDomainError = true;
@@ -13726,7 +13748,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
13726
13748
  }
13727
13749
  }
13728
13750
  if (!isDomainError) {
13729
- _context.n = 5;
13751
+ _context.n = 6;
13730
13752
  break;
13731
13753
  }
13732
13754
  // Set currentView to voice so we don't return to landing
@@ -13736,42 +13758,55 @@ var TTPChatWidget = /*#__PURE__*/function () {
13736
13758
  // Don't reset connecting state or return to landing - stay on voice interface showing error
13737
13759
  console.log('✅ Staying on voice interface with domain error displayed');
13738
13760
  return _context.a(2);
13739
- case 5:
13761
+ case 6:
13740
13762
  // Not a domain error - reset connecting state and return to landing
13741
13763
  console.log('⚠️ Not a domain error - returning to landing screen');
13742
- if ((_this6$voiceInterface8 = _this6.voiceInterface) !== null && _this6$voiceInterface8 !== void 0 && _this6$voiceInterface8.resetConnectingState) {
13764
+ if ((_this6$voiceInterface0 = _this6.voiceInterface) !== null && _this6$voiceInterface0 !== void 0 && _this6$voiceInterface0.resetConnectingState) {
13743
13765
  _this6.voiceInterface.resetConnectingState();
13744
13766
  }
13745
13767
  _widgetMode = ((_this6$config$behavio = _this6.config.behavior) === null || _this6$config$behavio === void 0 ? void 0 : _this6$config$behavio.mode) || 'unified';
13746
13768
  if (_widgetMode === 'unified') {
13747
13769
  _this6.showLanding();
13748
13770
  }
13749
- case 6:
13750
- _context.n = 9;
13751
- break;
13752
13771
  case 7:
13753
- _context.p = 7;
13772
+ _context.n = 10;
13773
+ break;
13774
+ case 8:
13775
+ _context.p = 8;
13754
13776
  _t = _context.v;
13755
13777
  if (!(_t.isServerRejection || _t.name === 'ServerRejected')) {
13756
- _context.n = 8;
13778
+ _context.n = 9;
13757
13779
  break;
13758
13780
  }
13759
13781
  // Server rejection is expected - the error handler will show domain error message
13760
13782
  // Reset connecting state but don't log as error
13761
- if ((_this6$voiceInterface9 = _this6.voiceInterface) !== null && _this6$voiceInterface9 !== void 0 && _this6$voiceInterface9.resetConnectingState) {
13783
+ if ((_this6$voiceInterface1 = _this6.voiceInterface) !== null && _this6$voiceInterface1 !== void 0 && _this6$voiceInterface1.resetConnectingState) {
13762
13784
  _this6.voiceInterface.resetConnectingState();
13763
13785
  }
13764
13786
  // Don't show toast for server rejection - domain error handler will show message
13765
13787
  return _context.a(2);
13766
- case 8:
13788
+ case 9:
13767
13789
  // Log other errors (permission denied, etc.)
13768
13790
  console.error('❌ Failed to start voice call:', _t);
13769
13791
 
13770
13792
  // Reset connecting state on error
13771
- if ((_this6$voiceInterface0 = _this6.voiceInterface) !== null && _this6$voiceInterface0 !== void 0 && _this6$voiceInterface0.resetConnectingState) {
13793
+ if ((_this6$voiceInterface10 = _this6.voiceInterface) !== null && _this6$voiceInterface10 !== void 0 && _this6$voiceInterface10.resetConnectingState) {
13772
13794
  _this6.voiceInterface.resetConnectingState();
13773
13795
  }
13774
13796
 
13797
+ // Clean up mobile bar if it was created early
13798
+ if ((_this6$voiceInterface11 = _this6.voiceInterface) !== null && _this6$voiceInterface11 !== void 0 && _this6$voiceInterface11.isMobile) {
13799
+ existingBar = document.getElementById('mobile-voice-call-bar-container');
13800
+ if (existingBar && (_this6$voiceInterface12 = _this6.voiceInterface) !== null && _this6$voiceInterface12 !== void 0 && _this6$voiceInterface12.removeMobileMinimizedBar) {
13801
+ _this6.voiceInterface.removeMobileMinimizedBar();
13802
+ }
13803
+ // Restore floating button if it was hidden
13804
+ _floatingButton = ((_this6$shadowRoot2 = _this6.shadowRoot) === null || _this6$shadowRoot2 === void 0 ? void 0 : _this6$shadowRoot2.getElementById('text-chat-button')) || document.getElementById('text-chat-button');
13805
+ if (_floatingButton) {
13806
+ _floatingButton.style.display = '';
13807
+ }
13808
+ }
13809
+
13775
13810
  // Show user-friendly error message (unless it's a user cancellation)
13776
13811
  if (_t.message !== 'Call cancelled by user') {
13777
13812
  _this6.showErrorToast(_t.message || 'Failed to start voice call. Please try again.', 'error');
@@ -13782,15 +13817,15 @@ var TTPChatWidget = /*#__PURE__*/function () {
13782
13817
  if (_widgetMode2 === 'unified') {
13783
13818
  _this6.showLanding();
13784
13819
  }
13785
- case 9:
13786
- _context.p = 9;
13820
+ case 10:
13821
+ _context.p = 10;
13787
13822
  // ✅ Always reset flag, even if showVoice() or startVoiceCall() throws
13788
13823
  _this6.isStartingCall = false;
13789
- return _context.f(9);
13790
- case 10:
13824
+ return _context.f(10);
13825
+ case 11:
13791
13826
  return _context.a(2);
13792
13827
  }
13793
- }, _callee, null, [[2, 7, 9, 10]]);
13828
+ }, _callee, null, [[2, 8, 10, 11]]);
13794
13829
  }));
13795
13830
  function onSelectVoice() {
13796
13831
  return _onSelectVoice.apply(this, arguments);
@@ -15865,6 +15900,48 @@ var VoiceInterface = /*#__PURE__*/function () {
15865
15900
  return defaults[key] || '';
15866
15901
  }
15867
15902
 
15903
+ /**
15904
+ * Update all timer elements with the given time text
15905
+ * Always updates both desktop and compact timers to ensure consistency
15906
+ */
15907
+ }, {
15908
+ key: "_updateTimerElements",
15909
+ value: function _updateTimerElements(timerText, elapsed) {
15910
+ // Update legacy desktop timer if it exists
15911
+ var timerEl = this.shadowRoot.getElementById('voiceTimer');
15912
+ if (timerEl) {
15913
+ timerEl.textContent = timerText;
15914
+ }
15915
+
15916
+ // Always update BOTH timer elements - the visibility optimization was causing bugs
15917
+ // where the collapsed view timer wasn't updating
15918
+ var desktopTimerText = this.shadowRoot.getElementById('desktopTimerText');
15919
+ var compactTimerText = this.shadowRoot.getElementById('compactTimerText');
15920
+ if (desktopTimerText) {
15921
+ desktopTimerText.textContent = timerText;
15922
+ }
15923
+ if (compactTimerText) {
15924
+ compactTimerText.textContent = timerText;
15925
+ }
15926
+
15927
+ // Debug logging (only in first 2 seconds)
15928
+ if (!compactTimerText && !desktopTimerText && elapsed < 2000) {
15929
+ console.warn('⚠️ Timer elements not found - compactTimerText:', !!compactTimerText, 'desktopTimerText:', !!desktopTimerText, 'elapsed:', elapsed);
15930
+ var allTimers = this.shadowRoot.querySelectorAll('[id*="Timer"], [id*="timer"]');
15931
+ console.log('Found timer-like elements:', Array.from(allTimers).map(function (el) {
15932
+ return el.id;
15933
+ }));
15934
+ }
15935
+
15936
+ // Update mobile duration badge
15937
+ if (this.isMobile) {
15938
+ var mobileDurationText = document.getElementById('mobileDurationText');
15939
+ var mobileHeaderDuration = document.getElementById('mobileHeaderDuration');
15940
+ if (mobileDurationText) mobileDurationText.textContent = timerText;
15941
+ if (mobileHeaderDuration) mobileHeaderDuration.textContent = timerText;
15942
+ }
15943
+ }
15944
+
15868
15945
  /**
15869
15946
  * Generate HTML for voice interface
15870
15947
  * Delegates to templates module
@@ -16090,7 +16167,7 @@ var VoiceInterface = /*#__PURE__*/function () {
16090
16167
  value: (function () {
16091
16168
  var _proceedWithVoiceCall = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
16092
16169
  var _this3 = this;
16093
- var isResumeCall, panel, header, toggleText, _voiceInterface, originalSection, compactSection, idleState, _panel, fallbackPanel, sampleRate, mediaStream, floatingButton, fallbackButton, _idleState, _panel2, _fallbackPanel, deviceInfo, _idleState2, activeState, voiceInterface, signedUrl, connected, serverRejected, originalOnError, originalOnDisconnected, attempts, _this$sdk$voiceSDK, error, _idleState3, _panel3, _fallbackPanel2, _floatingButton, _fallbackButton, updateTimer, _idleState4, _panel4, _fallbackPanel3, _floatingButton2, _fallbackButton2, _updateTimer, _deviceInfo, _t2, _t3, _t4, _t5, _t6, _t7;
16170
+ var isResumeCall, panel, header, toggleText, _voiceInterface, originalSection, compactSection, idleState, _panel, fallbackPanel, sampleRate, mediaStream, floatingButton, fallbackButton, existingBar, _floatingButton, _fallbackButton, _idleState, _panel2, _fallbackPanel, deviceInfo, _idleState2, activeState, voiceInterface, signedUrl, connected, serverRejected, originalOnError, originalOnDisconnected, attempts, _this$sdk$voiceSDK, error, _existingBar, _existingBar2, _idleState3, _panel3, _fallbackPanel2, _floatingButton2, _fallbackButton2, updateTimer, _existingBar3, _existingBar4, _idleState4, _panel4, _fallbackPanel3, _floatingButton3, _fallbackButton3, _updateTimer, _deviceInfo, _t2, _t3, _t4, _t5, _t6, _t7;
16094
16171
  return _regenerator().w(function (_context5) {
16095
16172
  while (1) switch (_context5.p = _context5.n) {
16096
16173
  case 0:
@@ -16205,6 +16282,23 @@ var VoiceInterface = /*#__PURE__*/function () {
16205
16282
  console.error('❌ Mobile: Permission denied before connecting:', _t2);
16206
16283
  // Permission was denied - reset connecting state and restore UI
16207
16284
  this.resetConnectingState();
16285
+
16286
+ // Remove mobile bar if it was created early
16287
+ existingBar = document.getElementById('mobile-voice-call-bar-container');
16288
+ if (existingBar) {
16289
+ this.removeMobileMinimizedBar();
16290
+ }
16291
+
16292
+ // Restore floating button visibility
16293
+ _floatingButton = this.shadowRoot.getElementById('text-chat-button');
16294
+ if (_floatingButton) {
16295
+ _floatingButton.style.display = '';
16296
+ } else {
16297
+ _fallbackButton = document.getElementById('text-chat-button');
16298
+ if (_fallbackButton) {
16299
+ _fallbackButton.style.display = '';
16300
+ }
16301
+ }
16208
16302
  _idleState = this.shadowRoot.getElementById('voiceIdleState');
16209
16303
  if (_idleState) {
16210
16304
  _idleState.style.display = 'flex';
@@ -16338,6 +16432,11 @@ var VoiceInterface = /*#__PURE__*/function () {
16338
16432
  _context5.n = 15;
16339
16433
  break;
16340
16434
  }
16435
+ // ✅ Connection is ready (hello_ack received) - update status BEFORE audio can start
16436
+ // This ensures status is "listening" when agent starts speaking
16437
+ if (this.isMobile) {
16438
+ this.updateMobileStatus('listening');
16439
+ }
16341
16440
  return _context5.a(3, 17);
16342
16441
  case 15:
16343
16442
  _context5.n = 16;
@@ -16377,6 +16476,17 @@ var VoiceInterface = /*#__PURE__*/function () {
16377
16476
  break;
16378
16477
  }
16379
16478
  console.log('✅ WebSocket ready, starting listening...');
16479
+
16480
+ // ✅ CRITICAL: Update status to "listening" IMMEDIATELY after hello_ack
16481
+ // This ensures status is correct BEFORE agent starts sending audio
16482
+ // Agent can start sending audio immediately after hello_ack, so we need
16483
+ // to update status now, not after startListening() completes
16484
+ if (this.isMobile) {
16485
+ // Status was already updated to "listening" in the hello_ack wait loop above
16486
+ // But ensure it's set here too in case we didn't go through that path
16487
+ this.updateMobileStatus('listening');
16488
+ }
16489
+
16380
16490
  // CRITICAL: startListening() will request microphone permission
16381
16491
  // This must be called directly from user interaction (button click)
16382
16492
  // to work on mobile browsers
@@ -16410,13 +16520,17 @@ var VoiceInterface = /*#__PURE__*/function () {
16410
16520
  console.log('🎤 Started listening - permission granted');
16411
16521
  this.isActive = true;
16412
16522
 
16413
- // ONLY NOW that permission is granted and call is active, create mobile bar (mobile only)
16523
+ // Update mobile bar if it already exists (created early), otherwise create it
16414
16524
  if (this.isMobile) {
16415
- console.log('📱 Mobile device - permission granted, creating minimized bar');
16416
-
16417
- // Panel and floating button are already hidden from permission request step
16418
- // Just create the mobile minimized bar
16419
- this.createMobileMinimizedBar();
16525
+ _existingBar = document.getElementById('mobile-voice-call-bar-container');
16526
+ if (!_existingBar) {
16527
+ console.log('📱 Mobile device - permission granted, creating minimized bar');
16528
+ // Panel and floating button are already hidden from permission request step
16529
+ // Just create the mobile minimized bar
16530
+ this.createMobileMinimizedBar();
16531
+ } else {
16532
+ console.log('📱 Mobile device - bar already exists, updating status');
16533
+ }
16420
16534
 
16421
16535
  // Initialize messages with welcome message
16422
16536
  if (this.messages.length === 0) {
@@ -16427,7 +16541,7 @@ var VoiceInterface = /*#__PURE__*/function () {
16427
16541
  }
16428
16542
  // Start waveform animation
16429
16543
  this.startWaveformAnimation();
16430
- // Update status
16544
+ // Update status to listening (was connecting before)
16431
16545
  this.updateMobileStatus('listening');
16432
16546
  } else {
16433
16547
  // Desktop: initialize UI
@@ -16456,6 +16570,11 @@ var VoiceInterface = /*#__PURE__*/function () {
16456
16570
  // Permission was denied or error occurred - reset connecting state and restore UI
16457
16571
  this.resetConnectingState();
16458
16572
  if (this.isMobile) {
16573
+ // Remove mobile bar if it was created early
16574
+ _existingBar2 = document.getElementById('mobile-voice-call-bar-container');
16575
+ if (_existingBar2) {
16576
+ this.removeMobileMinimizedBar();
16577
+ }
16459
16578
  // Restore idle state so user can try again
16460
16579
  _idleState3 = this.shadowRoot.getElementById('voiceIdleState');
16461
16580
  if (_idleState3) {
@@ -16472,13 +16591,13 @@ var VoiceInterface = /*#__PURE__*/function () {
16472
16591
  }
16473
16592
  }
16474
16593
  // Ensure floating button is visible
16475
- _floatingButton = this.shadowRoot.getElementById('text-chat-button');
16476
- if (_floatingButton) {
16477
- _floatingButton.style.display = '';
16594
+ _floatingButton2 = this.shadowRoot.getElementById('text-chat-button');
16595
+ if (_floatingButton2) {
16596
+ _floatingButton2.style.display = '';
16478
16597
  } else {
16479
- _fallbackButton = document.getElementById('text-chat-button');
16480
- if (_fallbackButton) {
16481
- _fallbackButton.style.display = '';
16598
+ _fallbackButton2 = document.getElementById('text-chat-button');
16599
+ if (_fallbackButton2) {
16600
+ _fallbackButton2.style.display = '';
16482
16601
  }
16483
16602
  }
16484
16603
  }
@@ -16487,45 +16606,27 @@ var VoiceInterface = /*#__PURE__*/function () {
16487
16606
  case 29:
16488
16607
  // Start timer
16489
16608
  this.callStartTime = Date.now();
16609
+ console.log('⏱️ Starting call timer at', new Date(this.callStartTime).toISOString());
16490
16610
 
16491
16611
  // Timer update function - updates all timer elements
16492
16612
  updateTimer = function updateTimer() {
16613
+ if (!_this3.callStartTime) {
16614
+ console.warn('⚠️ Timer update called but callStartTime is not set');
16615
+ return;
16616
+ }
16493
16617
  var elapsed = Date.now() - _this3.callStartTime;
16494
16618
  var minutes = Math.floor(elapsed / 60000);
16495
16619
  var seconds = Math.floor(elapsed % 60000 / 1000);
16496
16620
  var timerText = "".concat(minutes.toString().padStart(2, '0'), ":").concat(seconds.toString().padStart(2, '0'));
16497
-
16498
- // Update desktop timer (old and new)
16499
- var timerEl = _this3.shadowRoot.getElementById('voiceTimer');
16500
- if (timerEl) {
16501
- timerEl.textContent = timerText;
16502
- }
16503
- // Update timer in compact or old layout (update both independently)
16504
- var compactTimerText = _this3.shadowRoot.getElementById('compactTimerText');
16505
- var desktopTimerText = _this3.shadowRoot.getElementById('desktopTimerText');
16506
-
16507
- // Debug logging
16508
- if (!compactTimerText && !desktopTimerText) {
16509
- console.warn('⚠️ Timer elements not found - compactTimerText:', !!compactTimerText, 'desktopTimerText:', !!desktopTimerText);
16510
- }
16511
- if (compactTimerText) {
16512
- compactTimerText.textContent = timerText;
16513
- }
16514
- if (desktopTimerText) {
16515
- desktopTimerText.textContent = timerText;
16516
- }
16517
-
16518
- // Update mobile duration badge
16519
- if (_this3.isMobile) {
16520
- var mobileDurationText = document.getElementById('mobileDurationText');
16521
- var mobileHeaderDuration = document.getElementById('mobileHeaderDuration');
16522
- if (mobileDurationText) mobileDurationText.textContent = timerText;
16523
- if (mobileHeaderDuration) mobileHeaderDuration.textContent = timerText;
16524
- }
16525
- }; // Update immediately (don't wait for first interval)
16526
- updateTimer();
16527
- // Then update every second
16528
- this.callTimerInterval = setInterval(updateTimer, 1000);
16621
+ _this3._updateTimerElements(timerText, elapsed);
16622
+ }; // Wait a tiny bit for DOM to be ready, then start timer
16623
+ setTimeout(function () {
16624
+ console.log('⏱️ Starting timer interval');
16625
+ // Update immediately (don't wait for first interval)
16626
+ updateTimer();
16627
+ // Then update every second
16628
+ _this3.callTimerInterval = setInterval(updateTimer, 1000);
16629
+ }, 100);
16529
16630
  _context5.n = 38;
16530
16631
  break;
16531
16632
  case 30:
@@ -16551,13 +16652,17 @@ var VoiceInterface = /*#__PURE__*/function () {
16551
16652
  console.log('🎤 Started listening - permission granted');
16552
16653
  this.isActive = true;
16553
16654
 
16554
- // ONLY NOW that permission is granted and call is active, create mobile bar (mobile only)
16655
+ // Update mobile bar if it already exists (created early), otherwise create it
16555
16656
  if (this.isMobile) {
16556
- console.log('📱 Mobile device - permission granted, creating minimized bar');
16557
-
16558
- // Panel and floating button are already hidden from permission request step
16559
- // Just create the mobile minimized bar
16560
- this.createMobileMinimizedBar();
16657
+ _existingBar3 = document.getElementById('mobile-voice-call-bar-container');
16658
+ if (!_existingBar3) {
16659
+ console.log('📱 Mobile device - permission granted, creating minimized bar');
16660
+ // Panel and floating button are already hidden from permission request step
16661
+ // Just create the mobile minimized bar
16662
+ this.createMobileMinimizedBar();
16663
+ } else {
16664
+ console.log('📱 Mobile device - bar already exists, updating status');
16665
+ }
16561
16666
 
16562
16667
  // Initialize messages with welcome message
16563
16668
  if (this.messages.length === 0) {
@@ -16568,7 +16673,7 @@ var VoiceInterface = /*#__PURE__*/function () {
16568
16673
  }
16569
16674
  // Start waveform animation
16570
16675
  this.startWaveformAnimation();
16571
- // Update status
16676
+ // Update status to listening (was connecting before)
16572
16677
  this.updateMobileStatus('listening');
16573
16678
  } else {
16574
16679
  // Desktop: initialize UI
@@ -16597,6 +16702,11 @@ var VoiceInterface = /*#__PURE__*/function () {
16597
16702
  // Permission was denied or error occurred - reset connecting state and restore UI
16598
16703
  this.resetConnectingState();
16599
16704
  if (this.isMobile) {
16705
+ // Remove mobile bar if it was created early
16706
+ _existingBar4 = document.getElementById('mobile-voice-call-bar-container');
16707
+ if (_existingBar4) {
16708
+ this.removeMobileMinimizedBar();
16709
+ }
16600
16710
  // Restore idle state so user can try again
16601
16711
  _idleState4 = this.shadowRoot.getElementById('voiceIdleState');
16602
16712
  if (_idleState4) {
@@ -16613,13 +16723,13 @@ var VoiceInterface = /*#__PURE__*/function () {
16613
16723
  }
16614
16724
  }
16615
16725
  // Ensure floating button is visible
16616
- _floatingButton2 = this.shadowRoot.getElementById('text-chat-button');
16617
- if (_floatingButton2) {
16618
- _floatingButton2.style.display = '';
16726
+ _floatingButton3 = this.shadowRoot.getElementById('text-chat-button');
16727
+ if (_floatingButton3) {
16728
+ _floatingButton3.style.display = '';
16619
16729
  } else {
16620
- _fallbackButton2 = document.getElementById('text-chat-button');
16621
- if (_fallbackButton2) {
16622
- _fallbackButton2.style.display = '';
16730
+ _fallbackButton3 = document.getElementById('text-chat-button');
16731
+ if (_fallbackButton3) {
16732
+ _fallbackButton3.style.display = '';
16623
16733
  }
16624
16734
  }
16625
16735
  }
@@ -16628,45 +16738,27 @@ var VoiceInterface = /*#__PURE__*/function () {
16628
16738
  case 37:
16629
16739
  // Start timer
16630
16740
  this.callStartTime = Date.now();
16741
+ console.log('⏱️ Starting call timer at', new Date(this.callStartTime).toISOString());
16631
16742
 
16632
16743
  // Timer update function - updates all timer elements
16633
16744
  _updateTimer = function _updateTimer() {
16745
+ if (!_this3.callStartTime) {
16746
+ console.warn('⚠️ Timer update called but callStartTime is not set');
16747
+ return;
16748
+ }
16634
16749
  var elapsed = Date.now() - _this3.callStartTime;
16635
16750
  var minutes = Math.floor(elapsed / 60000);
16636
16751
  var seconds = Math.floor(elapsed % 60000 / 1000);
16637
16752
  var timerText = "".concat(minutes.toString().padStart(2, '0'), ":").concat(seconds.toString().padStart(2, '0'));
16638
-
16639
- // Update desktop timer (old and new)
16640
- var timerEl = _this3.shadowRoot.getElementById('voiceTimer');
16641
- if (timerEl) {
16642
- timerEl.textContent = timerText;
16643
- }
16644
- // Update timer in compact or old layout (update both independently)
16645
- var compactTimerText = _this3.shadowRoot.getElementById('compactTimerText');
16646
- var desktopTimerText = _this3.shadowRoot.getElementById('desktopTimerText');
16647
-
16648
- // Debug logging
16649
- if (!compactTimerText && !desktopTimerText) {
16650
- console.warn('⚠️ Timer elements not found - compactTimerText:', !!compactTimerText, 'desktopTimerText:', !!desktopTimerText);
16651
- }
16652
- if (compactTimerText) {
16653
- compactTimerText.textContent = timerText;
16654
- }
16655
- if (desktopTimerText) {
16656
- desktopTimerText.textContent = timerText;
16657
- }
16658
-
16659
- // Update mobile duration badge
16660
- if (_this3.isMobile) {
16661
- var mobileDurationText = document.getElementById('mobileDurationText');
16662
- var mobileHeaderDuration = document.getElementById('mobileHeaderDuration');
16663
- if (mobileDurationText) mobileDurationText.textContent = timerText;
16664
- if (mobileHeaderDuration) mobileHeaderDuration.textContent = timerText;
16665
- }
16666
- }; // Update immediately (don't wait for first interval)
16667
- _updateTimer();
16668
- // Then update every second
16669
- this.callTimerInterval = setInterval(_updateTimer, 1000);
16753
+ _this3._updateTimerElements(timerText, elapsed);
16754
+ }; // Wait a tiny bit for DOM to be ready, then start timer
16755
+ setTimeout(function () {
16756
+ console.log('⏱️ Starting timer interval');
16757
+ // Update immediately (don't wait for first interval)
16758
+ _updateTimer();
16759
+ // Then update every second
16760
+ _this3.callTimerInterval = setInterval(_updateTimer, 1000);
16761
+ }, 100);
16670
16762
  case 38:
16671
16763
  console.log('✅ Voice call started successfully');
16672
16764
  _context5.n = 44;
@@ -18589,7 +18681,10 @@ var Mobile = /*#__PURE__*/function () {
18589
18681
  dotColor = this.config.statusDotColor || '#10b981';
18590
18682
  } else {
18591
18683
  // Use dynamic status based on state
18592
- if (status === 'listening' || status === 'recording') {
18684
+ if (status === 'connecting') {
18685
+ displayText = this.t('connecting') || 'Connecting...';
18686
+ dotColor = '#3b82f6'; // Blue for connecting
18687
+ } else if (status === 'listening' || status === 'recording') {
18593
18688
  displayText = this.t('listening') || 'Listening...';
18594
18689
  dotColor = '#22c55e';
18595
18690
  } else if (status === 'speaking') {
@@ -23056,9 +23151,16 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23056
23151
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
23057
23152
  _classCallCheck(this, AudioPlayer);
23058
23153
  _this = _callSuper(this, AudioPlayer);
23154
+
23155
+ // Version identification log
23156
+ console.log('%c🔊 AUDIO PLAYER - SILENT BUFFER PRIMING FIX ENABLED', 'background: #10b981; color: white; font-size: 14px; font-weight: bold; padding: 4px 8px; border-radius: 4px;');
23157
+ console.log('%cAndroid Greeting Cut-Off Fix: Silent buffer priming (100ms) + dynamic startup buffer', 'background: #d1fae5; color: #065f46; font-size: 12px; padding: 2px 6px; border-radius: 3px;');
23158
+ console.log('%cBuild: 2026-01-30-SILENT-BUFFER-PRIMING | Look for [scheduleWAV] and priming logs', 'background: #e0e7ff; color: #1e40af; font-size: 11px; padding: 2px 6px; border-radius: 3px;');
23159
+ console.log('');
23059
23160
  _this.config = config;
23060
23161
  _this.audioContext = null;
23061
23162
  _this.gainNode = null; // GainNode for volume/mute control
23163
+ _this._audioContextPrimed = false; // Track if AudioContext has been primed with silent buffer
23062
23164
 
23063
23165
  // Track temporary listener in waitForAudioContextReady() for cleanup
23064
23166
  _this._waitForReadyStateHandler = null;
@@ -23516,13 +23618,19 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
23516
23618
  currentTime = _this4.audioContext.currentTime; // Ensure we don't schedule in the past, but NEVER decrease nextStartTime
23517
23619
  // This prevents overlapping audio by maintaining sequential ordering
23518
23620
  if (_this4.scheduledBuffers === 0) {
23519
- // First chunk: Add startup buffer on mobile to account for AudioContext initialization latency
23520
- // On mobile, when AudioContext transitions from suspended to running, there's startup delay
23521
- // Scheduling at time 0 causes the beginning to be skipped due to this latency
23621
+ // First chunk: Use dynamic startup buffer based on audio priming status
23522
23622
  isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
23523
- startupBuffer = isMobile ? 0.1 : 0; // 100ms buffer on mobile
23623
+ startupBuffer = 0.03; // 30ms default for responses
23624
+ if (isMobile) {
23625
+ if (_this4._audioContextPrimed) {
23626
+ startupBuffer = 0; // Primed - no buffer needed
23627
+ } else {
23628
+ startupBuffer = 2.0; // Not primed yet - 2000ms safety fallback
23629
+ console.warn('⚠️ AudioPlayer: Audio not primed yet, using 2000ms fallback buffer');
23630
+ }
23631
+ }
23524
23632
  _this4.nextStartTime = currentTime + startupBuffer;
23525
- console.log("\uD83C\uDFB5 [scheduleWAV] FIRST FRAME - state: ".concat(_this4.audioContext.state, ", currentTime: ").concat(currentTime, ", nextStartTime: ").concat(_this4.nextStartTime), isMobile ? '(mobile)' : '(desktop)');
23633
+ console.log("\uD83C\uDFB5 [scheduleWAV] FIRST FRAME - primed: ".concat(_this4._audioContextPrimed, ", buffer: ").concat(startupBuffer * 1000, "ms"));
23526
23634
  } else {
23527
23635
  // Subsequent chunks: ensure nextStartTime is not in the past
23528
23636
  // If nextStartTime is already in the future, use it (seamless playback)
@@ -24472,6 +24580,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24472
24580
  this.gainNode.gain.value = 1.0; // Default to full volume
24473
24581
  this.gainNode.connect(this.audioContext.destination);
24474
24582
  console.log('✅ AudioPlayer: GainNode created for volume control');
24583
+
24584
+ // Prime audio hardware for Android
24585
+ this._primeAudioContext();
24475
24586
  } catch (error) {
24476
24587
  // Fallback to default if browser doesn't support custom sample rate
24477
24588
  console.error("\u274C Failed to create AudioContext:", error);
@@ -24486,13 +24597,65 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24486
24597
  this.gainNode.gain.value = 1.0; // Default to full volume
24487
24598
  this.gainNode.connect(this.audioContext.destination);
24488
24599
  console.log('✅ AudioPlayer: GainNode created for volume control (fallback)');
24600
+
24601
+ // Prime audio hardware for Android
24602
+ this._primeAudioContext();
24489
24603
  }
24490
24604
  }
24491
24605
 
24606
+ /**
24607
+ * Prime AudioContext with silent buffer to initialize Android hardware
24608
+ * This prevents the first audio frame from being cut off on Android devices
24609
+ */
24610
+ }, {
24611
+ key: "_primeAudioContext",
24612
+ value: (function () {
24613
+ var _primeAudioContext2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
24614
+ var sampleRate, silentBuffer, source, _t3;
24615
+ return _regenerator().w(function (_context6) {
24616
+ while (1) switch (_context6.p = _context6.n) {
24617
+ case 0:
24618
+ _context6.p = 0;
24619
+ if (!(!this.audioContext || this._audioContextPrimed)) {
24620
+ _context6.n = 1;
24621
+ break;
24622
+ }
24623
+ return _context6.a(2);
24624
+ case 1:
24625
+ sampleRate = this.audioContext.sampleRate;
24626
+ silentBuffer = this.audioContext.createBuffer(1, sampleRate * 0.1, sampleRate);
24627
+ source = this.audioContext.createBufferSource();
24628
+ source.buffer = silentBuffer;
24629
+ source.connect(this.audioContext.destination);
24630
+ source.start();
24631
+ _context6.n = 2;
24632
+ return new Promise(function (resolve) {
24633
+ return setTimeout(resolve, 100);
24634
+ });
24635
+ case 2:
24636
+ this._audioContextPrimed = true;
24637
+ console.log('✅ AudioPlayer: Audio hardware primed with silent buffer');
24638
+ _context6.n = 4;
24639
+ break;
24640
+ case 3:
24641
+ _context6.p = 3;
24642
+ _t3 = _context6.v;
24643
+ console.error('❌ AudioPlayer: Audio priming failed:', _t3);
24644
+ case 4:
24645
+ return _context6.a(2);
24646
+ }
24647
+ }, _callee5, this, [[0, 3]]);
24648
+ }));
24649
+ function _primeAudioContext() {
24650
+ return _primeAudioContext2.apply(this, arguments);
24651
+ }
24652
+ return _primeAudioContext;
24653
+ }()
24492
24654
  /**
24493
24655
  * Clean up AudioContext and its event listeners
24494
24656
  * Ensures listeners are removed before context is closed/nullified to prevent race conditions
24495
24657
  */
24658
+ )
24496
24659
  }, {
24497
24660
  key: "_cleanupAudioContext",
24498
24661
  value: function _cleanupAudioContext() {
@@ -24527,6 +24690,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24527
24690
 
24528
24691
  // Set to null after cleanup
24529
24692
  this.audioContext = null;
24693
+ this._audioContextPrimed = false;
24530
24694
  }
24531
24695
 
24532
24696
  /**
@@ -24595,35 +24759,35 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24595
24759
  }, {
24596
24760
  key: "processQueue",
24597
24761
  value: (function () {
24598
- var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
24762
+ var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
24599
24763
  var _this8 = this;
24600
- var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t3;
24601
- return _regenerator().w(function (_context6) {
24602
- while (1) switch (_context6.p = _context6.n) {
24764
+ var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t4;
24765
+ return _regenerator().w(function (_context7) {
24766
+ while (1) switch (_context7.p = _context7.n) {
24603
24767
  case 0:
24604
24768
  if (!this._isStopped) {
24605
- _context6.n = 1;
24769
+ _context7.n = 1;
24606
24770
  break;
24607
24771
  }
24608
24772
  console.log('🛑 AudioPlayer: Not processing queue - playback was stopped (barge-in)');
24609
- return _context6.a(2);
24773
+ return _context7.a(2);
24610
24774
  case 1:
24611
24775
  if (!(this.isProcessingQueue || this.audioQueue.length === 0)) {
24612
- _context6.n = 2;
24776
+ _context7.n = 2;
24613
24777
  break;
24614
24778
  }
24615
- return _context6.a(2);
24779
+ return _context7.a(2);
24616
24780
  case 2:
24617
24781
  this.isProcessingQueue = true;
24618
24782
  audioBlob = this.audioQueue.shift();
24619
24783
  if (audioBlob) {
24620
- _context6.n = 3;
24784
+ _context7.n = 3;
24621
24785
  break;
24622
24786
  }
24623
24787
  this.isProcessingQueue = false;
24624
- return _context6.a(2);
24788
+ return _context7.a(2);
24625
24789
  case 3:
24626
- _context6.p = 3;
24790
+ _context7.p = 3;
24627
24791
  wasFirstPlay = !this.isPlaying && this.currentSource === null; // Initialize audio context if needed
24628
24792
  if (!this.audioContext) {
24629
24793
  this.initializeAudioContext();
@@ -24632,18 +24796,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24632
24796
  // ✅ NEW: Wait for AudioContext to be in 'running' state before proceeding
24633
24797
  // This is critical for mobile devices where AudioContext initialization takes time
24634
24798
  // Replaces fixed timeouts with event-driven waiting
24635
- _context6.n = 4;
24799
+ _context7.n = 4;
24636
24800
  return this.waitForAudioContextReady();
24637
24801
  case 4:
24638
24802
  audioContext = this.audioContext; // Decode audio
24639
- _context6.n = 5;
24803
+ _context7.n = 5;
24640
24804
  return audioBlob.arrayBuffer();
24641
24805
  case 5:
24642
- arrayBuffer = _context6.v;
24643
- _context6.n = 6;
24806
+ arrayBuffer = _context7.v;
24807
+ _context7.n = 6;
24644
24808
  return audioContext.decodeAudioData(arrayBuffer);
24645
24809
  case 6:
24646
- audioBuffer = _context6.v;
24810
+ audioBuffer = _context7.v;
24647
24811
  shouldEmitStart = wasFirstPlay && !this.isPlaying && this.currentSource === null; // Create source
24648
24812
  source = audioContext.createBufferSource();
24649
24813
  source.buffer = audioBuffer;
@@ -24686,14 +24850,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24686
24850
  // Start playback - AudioContext should now be fully ready
24687
24851
 
24688
24852
  source.start();
24689
- _context6.n = 8;
24853
+ _context7.n = 8;
24690
24854
  break;
24691
24855
  case 7:
24692
- _context6.p = 7;
24693
- _t3 = _context6.v;
24694
- console.error('❌ AudioPlayer v2: Error processing queue:', _t3);
24856
+ _context7.p = 7;
24857
+ _t4 = _context7.v;
24858
+ console.error('❌ AudioPlayer v2: Error processing queue:', _t4);
24695
24859
  this.currentSource = null;
24696
- this.emit('playbackError', _t3);
24860
+ this.emit('playbackError', _t4);
24697
24861
 
24698
24862
  // Try next chunk
24699
24863
 
@@ -24708,9 +24872,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24708
24872
  this.emit('playbackStopped');
24709
24873
  }
24710
24874
  case 8:
24711
- return _context6.a(2);
24875
+ return _context7.a(2);
24712
24876
  }
24713
- }, _callee5, this, [[3, 7]]);
24877
+ }, _callee6, this, [[3, 7]]);
24714
24878
  }));
24715
24879
  function processQueue() {
24716
24880
  return _processQueue.apply(this, arguments);
@@ -24984,39 +25148,39 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
24984
25148
  }, {
24985
25149
  key: "resumeAudioContext",
24986
25150
  value: (function () {
24987
- var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
24988
- var _t4;
24989
- return _regenerator().w(function (_context7) {
24990
- while (1) switch (_context7.p = _context7.n) {
25151
+ var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
25152
+ var _t5;
25153
+ return _regenerator().w(function (_context8) {
25154
+ while (1) switch (_context8.p = _context8.n) {
24991
25155
  case 0:
24992
25156
  if (!(!this.audioContext || this.audioContext.state === 'closed')) {
24993
- _context7.n = 2;
25157
+ _context8.n = 2;
24994
25158
  break;
24995
25159
  }
24996
- _context7.n = 1;
25160
+ _context8.n = 1;
24997
25161
  return this.initializeAudioContext();
24998
25162
  case 1:
24999
- return _context7.a(2);
25163
+ return _context8.a(2);
25000
25164
  case 2:
25001
25165
  if (!(this.audioContext.state === 'suspended')) {
25002
- _context7.n = 6;
25166
+ _context8.n = 6;
25003
25167
  break;
25004
25168
  }
25005
- _context7.p = 3;
25006
- _context7.n = 4;
25169
+ _context8.p = 3;
25170
+ _context8.n = 4;
25007
25171
  return this.audioContext.resume();
25008
25172
  case 4:
25009
25173
  console.log('✅ AudioPlayer v2: AudioContext resumed after mic permission');
25010
- _context7.n = 6;
25174
+ _context8.n = 6;
25011
25175
  break;
25012
25176
  case 5:
25013
- _context7.p = 5;
25014
- _t4 = _context7.v;
25015
- console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t4);
25177
+ _context8.p = 5;
25178
+ _t5 = _context8.v;
25179
+ console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t5);
25016
25180
  case 6:
25017
- return _context7.a(2);
25181
+ return _context8.a(2);
25018
25182
  }
25019
- }, _callee6, this, [[3, 5]]);
25183
+ }, _callee7, this, [[3, 5]]);
25020
25184
  }));
25021
25185
  function resumeAudioContext() {
25022
25186
  return _resumeAudioContext.apply(this, arguments);
@@ -25255,6 +25419,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
25255
25419
  _this.transcriptQueue = []; // Queue of transcripts for history
25256
25420
  _this.currentPlayingSentenceId = null; // Sentence ID currently being played
25257
25421
  _this.audioFrameCount = 0; // Track number of audio frames received
25422
+ _this.lastAudioStartTime = 0; // Timestamp of last audio_start message (to prevent premature stop_playing)
25258
25423
 
25259
25424
  // Screen wake lock - keeps screen on during voice calls (mobile)
25260
25425
  _this.wakeLock = null;
@@ -26087,10 +26252,25 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26087
26252
  /**
26088
26253
  * Send hello message with format negotiation
26089
26254
  */
26255
+ /**
26256
+ * Detect browser language from navigator
26257
+ * @returns {Object} Object with language and fullLocale properties
26258
+ */
26090
26259
  )
26260
+ }, {
26261
+ key: "detectBrowserLocale",
26262
+ value: function detectBrowserLocale() {
26263
+ var _navigator$languages, _fullLocale$split$;
26264
+ var fullLocale = navigator.language || ((_navigator$languages = navigator.languages) === null || _navigator$languages === void 0 ? void 0 : _navigator$languages[0]) || 'en-US';
26265
+ var language = ((_fullLocale$split$ = fullLocale.split('-')[0]) === null || _fullLocale$split$ === void 0 ? void 0 : _fullLocale$split$.toLowerCase()) || 'en';
26266
+ return {
26267
+ language: language,
26268
+ fullLocale: fullLocale
26269
+ };
26270
+ }
26091
26271
  }, {
26092
26272
  key: "sendHelloMessage",
26093
- value: (function () {
26273
+ value: function () {
26094
26274
  var _sendHelloMessage = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
26095
26275
  var inputFormat, requestedOutputFormat, inputError, outputError, helloMessage;
26096
26276
  return _regenerator().w(function (_context3) {
@@ -26164,6 +26344,10 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26164
26344
  helloMessage.agentSettingsOverride = this.config.agentSettingsOverride;
26165
26345
  }
26166
26346
 
26347
+ // Automatically detect browser language
26348
+ helloMessage.locale = this.detectBrowserLocale();
26349
+ console.log('🌍 VoiceSDK v2: Auto-detected locale:', helloMessage.locale);
26350
+
26167
26351
  // Optional: Variables (Map<String, String>)
26168
26352
  console.log('🔍 VoiceSDK v2: Checking variables:', {
26169
26353
  hasVariables: !!this.config.variables,
@@ -26233,7 +26417,6 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26233
26417
  /**
26234
26418
  * Handle incoming WebSocket message
26235
26419
  */
26236
- )
26237
26420
  }, {
26238
26421
  key: "handleMessage",
26239
26422
  value: function handleMessage(event) {
@@ -26310,6 +26493,16 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26310
26493
  this.stopAudioPlayback();
26311
26494
  break;
26312
26495
  case 'stop_playing':
26496
+ // CRITICAL: Ignore stop_playing messages that arrive too soon after audio_start
26497
+ // Backend sometimes sends stop_playing immediately after audio_start, which cuts sentences prematurely
26498
+ // Only honor stop_playing if it's been at least 200ms since the last audio_start
26499
+ var timeSinceAudioStart = Date.now() - this.lastAudioStartTime;
26500
+ var MIN_STOP_PLAYING_DELAY_MS = 200; // 200ms grace period after audio_start
26501
+
26502
+ if (timeSinceAudioStart < MIN_STOP_PLAYING_DELAY_MS) {
26503
+ console.warn("\u26A0\uFE0F VoiceSDK v2: Ignoring premature stop_playing (".concat(timeSinceAudioStart, "ms after audio_start, minimum ").concat(MIN_STOP_PLAYING_DELAY_MS, "ms required)"));
26504
+ break;
26505
+ }
26313
26506
  this.emit('stopPlaying', message);
26314
26507
  this.stopAudioPlayback();
26315
26508
  break;
@@ -26318,6 +26511,9 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26318
26511
  // Store the text in AudioPlayer for synced display when audio actually starts playing
26319
26512
  console.log('📝 VoiceSDK v2: Received audio_start with text:', message.text);
26320
26513
 
26514
+ // CRITICAL: Record timestamp to prevent premature stop_playing messages from cutting sentences
26515
+ this.lastAudioStartTime = Date.now();
26516
+
26321
26517
  // NOTE: We do NOT stop current audio here - that only happens on user barge-in (stop_playing)
26322
26518
  // If audio is already playing, the new sentence will queue and wait for current one to finish
26323
26519
  // This allows sentences to play sequentially without interruption
@@ -26451,7 +26647,149 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
26451
26647
  this.emit('message', message);
26452
26648
  }
26453
26649
  } catch (error) {
26650
+ var _error$message, _error$message2;
26651
+ // Enhanced error logging to help debug JSON parsing issues
26652
+ var rawData = typeof event.data === 'string' ? event.data : String(event.data);
26653
+ var errorPosition = ((_error$message = error.message) === null || _error$message === void 0 || (_error$message = _error$message.match(/position (\d+)/)) === null || _error$message === void 0 ? void 0 : _error$message[1]) || 'unknown';
26654
+ var position = parseInt(errorPosition, 10);
26454
26655
  console.error('VoiceSDK v2: Error parsing message:', error);
26656
+ console.error('VoiceSDK v2: Raw message length:', rawData.length);
26657
+ console.error('VoiceSDK v2: Error at position:', position);
26658
+
26659
+ // Show context around the error position
26660
+ if (position !== 'unknown' && position > 0) {
26661
+ var start = Math.max(0, position - 50);
26662
+ var end = Math.min(rawData.length, position + 50);
26663
+ var context = rawData.substring(start, end);
26664
+ var relativePos = position - start;
26665
+ var marker = ' '.repeat(relativePos) + '^';
26666
+ console.error('VoiceSDK v2: Context around error:');
26667
+ console.error(context);
26668
+ console.error(marker);
26669
+
26670
+ // Show the problematic character(s)
26671
+ var problematicChar = rawData.charAt(position);
26672
+ var charCode = problematicChar.charCodeAt(0);
26673
+ console.error("VoiceSDK v2: Problematic character: \"".concat(problematicChar, "\" (charCode: ").concat(charCode, ")"));
26674
+
26675
+ // Check for common control characters
26676
+ if (charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13) {
26677
+ console.error('VoiceSDK v2: This is an unescaped control character. The backend should escape it in JSON strings.');
26678
+ }
26679
+ }
26680
+
26681
+ // Try to detect if this was a client_tool_call message
26682
+ var looksLikeToolCall = rawData.includes('client_tool_call') || rawData.includes('"t":"client_tool_call"');
26683
+ if (looksLikeToolCall) {
26684
+ console.error('⚠️ VoiceSDK v2: CRITICAL - Failed to parse client_tool_call message!');
26685
+ console.error('⚠️ VoiceSDK v2: This means a tool execution (like form filling) was lost due to JSON parsing error.');
26686
+ console.error('⚠️ VoiceSDK v2: The backend sent malformed JSON with unescaped control characters.');
26687
+
26688
+ // Try to extract tool name from raw string (basic regex attempt)
26689
+ var toolNameMatch = rawData.match(/"toolName"\s*:\s*"([^"]+)"/);
26690
+ if (toolNameMatch) {
26691
+ console.error("\u26A0\uFE0F VoiceSDK v2: Attempted tool: \"".concat(toolNameMatch[1], "\""));
26692
+ }
26693
+
26694
+ // Try to extract toolCallId
26695
+ var toolCallIdMatch = rawData.match(/"toolCallId"\s*:\s*"([^"]+)"/);
26696
+ if (toolCallIdMatch) {
26697
+ console.error("\u26A0\uFE0F VoiceSDK v2: Tool call ID: \"".concat(toolCallIdMatch[1], "\""));
26698
+ }
26699
+ }
26700
+
26701
+ // Attempt recovery: sanitize control characters and try parsing again
26702
+ if ((_error$message2 = error.message) !== null && _error$message2 !== void 0 && _error$message2.includes('Bad control character')) {
26703
+ console.warn('VoiceSDK v2: Attempting to recover by sanitizing control characters...');
26704
+ try {
26705
+ // Replace unescaped control characters (except \t, \n, \r which are sometimes valid)
26706
+ // This is a best-effort recovery - may not work for all cases
26707
+ var sanitized = rawData;
26708
+ // Replace control characters that aren't properly escaped
26709
+ // Look for control chars that aren't part of \n, \t, \r, \\ sequences
26710
+ sanitized = sanitized.replace(/([^\\])([\x00-\x08\x0B\x0C\x0E-\x1F])/g, function (match, prev, ctrl) {
26711
+ var code = ctrl.charCodeAt(0);
26712
+ // Map common control chars to escape sequences
26713
+ var escapes = {
26714
+ 0: "\\u0000",
26715
+ 1: "\\u0001",
26716
+ 2: "\\u0002",
26717
+ 3: "\\u0003",
26718
+ 4: "\\u0004",
26719
+ 5: "\\u0005",
26720
+ 6: "\\u0006",
26721
+ 7: "\\u0007",
26722
+ 8: '\\b',
26723
+ 11: "\\u000B",
26724
+ 12: '\\f',
26725
+ 14: "\\u000E",
26726
+ 15: "\\u000F",
26727
+ 16: "\\u0010",
26728
+ 17: "\\u0011",
26729
+ 18: "\\u0012",
26730
+ 19: "\\u0013",
26731
+ 20: "\\u0014",
26732
+ 21: "\\u0015",
26733
+ 22: "\\u0016",
26734
+ 23: "\\u0017",
26735
+ 24: "\\u0018",
26736
+ 25: "\\u0019",
26737
+ 26: "\\u001A",
26738
+ 27: "\\u001B",
26739
+ 28: "\\u001C",
26740
+ 29: "\\u001D",
26741
+ 30: "\\u001E",
26742
+ 31: "\\u001F"
26743
+ };
26744
+ return prev + (escapes[code] || "\\u".concat(code.toString(16).padStart(4, '0')));
26745
+ });
26746
+ var recoveredMessage = JSON.parse(sanitized);
26747
+ console.warn('✅ VoiceSDK v2: Successfully recovered message after sanitization!');
26748
+ console.warn('✅ VoiceSDK v2: Processing recovered message...');
26749
+
26750
+ // Process the recovered message
26751
+ if (recoveredMessage.t === 'client_tool_call' && this.clientToolsRegistry) {
26752
+ console.warn('✅ VoiceSDK v2: Recovered client_tool_call - executing tool');
26753
+ this.clientToolsRegistry.handleToolCall(recoveredMessage);
26754
+ } else {
26755
+ // Re-process through normal flow
26756
+ var _message = recoveredMessage;
26757
+ if (_message.t !== 'audio') {
26758
+ console.log('📥 VoiceSDK v2: Received message type:', _message.t, 'Full message:', JSON.stringify(_message).substring(0, 200));
26759
+ }
26760
+ if (!this.conversationId && _message.conversationId) {
26761
+ this.conversationId = _message.conversationId;
26762
+ console.log('🔍 VoiceSDK v2: Captured conversationId from message:', this.conversationId);
26763
+ this.emit('conversationIdChanged', this.conversationId);
26764
+ }
26765
+ if (_message.t !== 'audio') {
26766
+ console.log('📨 VoiceSDK v2: Received message type:', _message.t, _message);
26767
+ }
26768
+ // Re-enter switch statement logic (simplified - just handle client_tool_call)
26769
+ if (_message.t === 'client_tool_call') {
26770
+ this.clientToolsRegistry.handleToolCall(_message);
26771
+ } else {
26772
+ this.emit('message', _message);
26773
+ }
26774
+ }
26775
+ return; // Successfully recovered, exit error handler
26776
+ } catch (recoveryError) {
26777
+ console.error('❌ VoiceSDK v2: Recovery attempt failed:', recoveryError);
26778
+ }
26779
+ }
26780
+
26781
+ // Show first 500 chars of raw message for debugging
26782
+ console.error('VoiceSDK v2: First 500 chars of raw message:', rawData.substring(0, 500));
26783
+
26784
+ // Emit error event so listeners can handle it
26785
+ this.emit('error', {
26786
+ type: 'json_parse_error',
26787
+ error: error,
26788
+ rawMessage: rawData.substring(0, 1000),
26789
+ // Include first 1000 chars for debugging
26790
+ position: position,
26791
+ wasToolCall: looksLikeToolCall
26792
+ });
26455
26793
  }
26456
26794
  }
26457
26795