ttp-agent-sdk 2.34.7 → 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.
- package/dist/agent-widget.dev.js +411 -97
- package/dist/agent-widget.dev.js.map +1 -1
- package/dist/agent-widget.esm.js +1 -1
- package/dist/agent-widget.esm.js.map +1 -1
- package/dist/agent-widget.js +1 -1
- package/dist/agent-widget.js.map +1 -1
- package/dist/demos/index.html +15 -0
- package/dist/demos/widget-customization-dev.html +4389 -0
- package/dist/examples/test-index.html +15 -0
- package/dist/examples/widget-customization-dev.html +4389 -0
- package/examples/test-index.html +15 -0
- package/examples/widget-customization-dev.html +4389 -0
- package/package.json +1 -1
package/dist/agent-widget.dev.js
CHANGED
|
@@ -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$
|
|
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
|
-
//
|
|
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
|
|
13714
|
+
return new Promise(function (resolve) {
|
|
13715
|
+
return requestAnimationFrame(function () {
|
|
13716
|
+
return requestAnimationFrame(resolve);
|
|
13717
|
+
});
|
|
13718
|
+
});
|
|
13700
13719
|
case 3:
|
|
13701
|
-
|
|
13702
|
-
|
|
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 =
|
|
13728
|
+
_context.n = 7;
|
|
13707
13729
|
break;
|
|
13708
|
-
case
|
|
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$
|
|
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$
|
|
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 =
|
|
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
|
|
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$
|
|
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.
|
|
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 =
|
|
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$
|
|
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
|
|
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$
|
|
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
|
|
13786
|
-
_context.p =
|
|
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(
|
|
13790
|
-
case
|
|
13824
|
+
return _context.f(10);
|
|
13825
|
+
case 11:
|
|
13791
13826
|
return _context.a(2);
|
|
13792
13827
|
}
|
|
13793
|
-
}, _callee, null, [[2,
|
|
13828
|
+
}, _callee, null, [[2, 8, 10, 11]]);
|
|
13794
13829
|
}));
|
|
13795
13830
|
function onSelectVoice() {
|
|
13796
13831
|
return _onSelectVoice.apply(this, arguments);
|
|
@@ -16132,7 +16167,7 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16132
16167
|
value: (function () {
|
|
16133
16168
|
var _proceedWithVoiceCall = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
|
|
16134
16169
|
var _this3 = this;
|
|
16135
|
-
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,
|
|
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;
|
|
16136
16171
|
return _regenerator().w(function (_context5) {
|
|
16137
16172
|
while (1) switch (_context5.p = _context5.n) {
|
|
16138
16173
|
case 0:
|
|
@@ -16247,6 +16282,23 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16247
16282
|
console.error('❌ Mobile: Permission denied before connecting:', _t2);
|
|
16248
16283
|
// Permission was denied - reset connecting state and restore UI
|
|
16249
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
|
+
}
|
|
16250
16302
|
_idleState = this.shadowRoot.getElementById('voiceIdleState');
|
|
16251
16303
|
if (_idleState) {
|
|
16252
16304
|
_idleState.style.display = 'flex';
|
|
@@ -16380,6 +16432,11 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16380
16432
|
_context5.n = 15;
|
|
16381
16433
|
break;
|
|
16382
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
|
+
}
|
|
16383
16440
|
return _context5.a(3, 17);
|
|
16384
16441
|
case 15:
|
|
16385
16442
|
_context5.n = 16;
|
|
@@ -16419,6 +16476,17 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16419
16476
|
break;
|
|
16420
16477
|
}
|
|
16421
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
|
+
|
|
16422
16490
|
// CRITICAL: startListening() will request microphone permission
|
|
16423
16491
|
// This must be called directly from user interaction (button click)
|
|
16424
16492
|
// to work on mobile browsers
|
|
@@ -16452,13 +16520,17 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16452
16520
|
console.log('🎤 Started listening - permission granted');
|
|
16453
16521
|
this.isActive = true;
|
|
16454
16522
|
|
|
16455
|
-
//
|
|
16523
|
+
// Update mobile bar if it already exists (created early), otherwise create it
|
|
16456
16524
|
if (this.isMobile) {
|
|
16457
|
-
|
|
16458
|
-
|
|
16459
|
-
|
|
16460
|
-
|
|
16461
|
-
|
|
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
|
+
}
|
|
16462
16534
|
|
|
16463
16535
|
// Initialize messages with welcome message
|
|
16464
16536
|
if (this.messages.length === 0) {
|
|
@@ -16469,7 +16541,7 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16469
16541
|
}
|
|
16470
16542
|
// Start waveform animation
|
|
16471
16543
|
this.startWaveformAnimation();
|
|
16472
|
-
// Update status
|
|
16544
|
+
// Update status to listening (was connecting before)
|
|
16473
16545
|
this.updateMobileStatus('listening');
|
|
16474
16546
|
} else {
|
|
16475
16547
|
// Desktop: initialize UI
|
|
@@ -16498,6 +16570,11 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16498
16570
|
// Permission was denied or error occurred - reset connecting state and restore UI
|
|
16499
16571
|
this.resetConnectingState();
|
|
16500
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
|
+
}
|
|
16501
16578
|
// Restore idle state so user can try again
|
|
16502
16579
|
_idleState3 = this.shadowRoot.getElementById('voiceIdleState');
|
|
16503
16580
|
if (_idleState3) {
|
|
@@ -16514,13 +16591,13 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16514
16591
|
}
|
|
16515
16592
|
}
|
|
16516
16593
|
// Ensure floating button is visible
|
|
16517
|
-
|
|
16518
|
-
if (
|
|
16519
|
-
|
|
16594
|
+
_floatingButton2 = this.shadowRoot.getElementById('text-chat-button');
|
|
16595
|
+
if (_floatingButton2) {
|
|
16596
|
+
_floatingButton2.style.display = '';
|
|
16520
16597
|
} else {
|
|
16521
|
-
|
|
16522
|
-
if (
|
|
16523
|
-
|
|
16598
|
+
_fallbackButton2 = document.getElementById('text-chat-button');
|
|
16599
|
+
if (_fallbackButton2) {
|
|
16600
|
+
_fallbackButton2.style.display = '';
|
|
16524
16601
|
}
|
|
16525
16602
|
}
|
|
16526
16603
|
}
|
|
@@ -16575,13 +16652,17 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16575
16652
|
console.log('🎤 Started listening - permission granted');
|
|
16576
16653
|
this.isActive = true;
|
|
16577
16654
|
|
|
16578
|
-
//
|
|
16655
|
+
// Update mobile bar if it already exists (created early), otherwise create it
|
|
16579
16656
|
if (this.isMobile) {
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
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
|
+
}
|
|
16585
16666
|
|
|
16586
16667
|
// Initialize messages with welcome message
|
|
16587
16668
|
if (this.messages.length === 0) {
|
|
@@ -16592,7 +16673,7 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16592
16673
|
}
|
|
16593
16674
|
// Start waveform animation
|
|
16594
16675
|
this.startWaveformAnimation();
|
|
16595
|
-
// Update status
|
|
16676
|
+
// Update status to listening (was connecting before)
|
|
16596
16677
|
this.updateMobileStatus('listening');
|
|
16597
16678
|
} else {
|
|
16598
16679
|
// Desktop: initialize UI
|
|
@@ -16621,6 +16702,11 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16621
16702
|
// Permission was denied or error occurred - reset connecting state and restore UI
|
|
16622
16703
|
this.resetConnectingState();
|
|
16623
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
|
+
}
|
|
16624
16710
|
// Restore idle state so user can try again
|
|
16625
16711
|
_idleState4 = this.shadowRoot.getElementById('voiceIdleState');
|
|
16626
16712
|
if (_idleState4) {
|
|
@@ -16637,13 +16723,13 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
16637
16723
|
}
|
|
16638
16724
|
}
|
|
16639
16725
|
// Ensure floating button is visible
|
|
16640
|
-
|
|
16641
|
-
if (
|
|
16642
|
-
|
|
16726
|
+
_floatingButton3 = this.shadowRoot.getElementById('text-chat-button');
|
|
16727
|
+
if (_floatingButton3) {
|
|
16728
|
+
_floatingButton3.style.display = '';
|
|
16643
16729
|
} else {
|
|
16644
|
-
|
|
16645
|
-
if (
|
|
16646
|
-
|
|
16730
|
+
_fallbackButton3 = document.getElementById('text-chat-button');
|
|
16731
|
+
if (_fallbackButton3) {
|
|
16732
|
+
_fallbackButton3.style.display = '';
|
|
16647
16733
|
}
|
|
16648
16734
|
}
|
|
16649
16735
|
}
|
|
@@ -18595,7 +18681,10 @@ var Mobile = /*#__PURE__*/function () {
|
|
|
18595
18681
|
dotColor = this.config.statusDotColor || '#10b981';
|
|
18596
18682
|
} else {
|
|
18597
18683
|
// Use dynamic status based on state
|
|
18598
|
-
if (status === '
|
|
18684
|
+
if (status === 'connecting') {
|
|
18685
|
+
displayText = this.t('connecting') || 'Connecting...';
|
|
18686
|
+
dotColor = '#3b82f6'; // Blue for connecting
|
|
18687
|
+
} else if (status === 'listening' || status === 'recording') {
|
|
18599
18688
|
displayText = this.t('listening') || 'Listening...';
|
|
18600
18689
|
dotColor = '#22c55e';
|
|
18601
18690
|
} else if (status === 'speaking') {
|
|
@@ -23062,9 +23151,16 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23062
23151
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
23063
23152
|
_classCallCheck(this, AudioPlayer);
|
|
23064
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('');
|
|
23065
23160
|
_this.config = config;
|
|
23066
23161
|
_this.audioContext = null;
|
|
23067
23162
|
_this.gainNode = null; // GainNode for volume/mute control
|
|
23163
|
+
_this._audioContextPrimed = false; // Track if AudioContext has been primed with silent buffer
|
|
23068
23164
|
|
|
23069
23165
|
// Track temporary listener in waitForAudioContextReady() for cleanup
|
|
23070
23166
|
_this._waitForReadyStateHandler = null;
|
|
@@ -23522,13 +23618,19 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23522
23618
|
currentTime = _this4.audioContext.currentTime; // Ensure we don't schedule in the past, but NEVER decrease nextStartTime
|
|
23523
23619
|
// This prevents overlapping audio by maintaining sequential ordering
|
|
23524
23620
|
if (_this4.scheduledBuffers === 0) {
|
|
23525
|
-
// First chunk:
|
|
23526
|
-
// On mobile, when AudioContext transitions from suspended to running, there's startup delay
|
|
23527
|
-
// 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
|
|
23528
23622
|
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
23529
|
-
startupBuffer =
|
|
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
|
+
}
|
|
23530
23632
|
_this4.nextStartTime = currentTime + startupBuffer;
|
|
23531
|
-
console.log("\uD83C\uDFB5 [scheduleWAV] FIRST FRAME -
|
|
23633
|
+
console.log("\uD83C\uDFB5 [scheduleWAV] FIRST FRAME - primed: ".concat(_this4._audioContextPrimed, ", buffer: ").concat(startupBuffer * 1000, "ms"));
|
|
23532
23634
|
} else {
|
|
23533
23635
|
// Subsequent chunks: ensure nextStartTime is not in the past
|
|
23534
23636
|
// If nextStartTime is already in the future, use it (seamless playback)
|
|
@@ -24478,6 +24580,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24478
24580
|
this.gainNode.gain.value = 1.0; // Default to full volume
|
|
24479
24581
|
this.gainNode.connect(this.audioContext.destination);
|
|
24480
24582
|
console.log('✅ AudioPlayer: GainNode created for volume control');
|
|
24583
|
+
|
|
24584
|
+
// Prime audio hardware for Android
|
|
24585
|
+
this._primeAudioContext();
|
|
24481
24586
|
} catch (error) {
|
|
24482
24587
|
// Fallback to default if browser doesn't support custom sample rate
|
|
24483
24588
|
console.error("\u274C Failed to create AudioContext:", error);
|
|
@@ -24492,13 +24597,65 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24492
24597
|
this.gainNode.gain.value = 1.0; // Default to full volume
|
|
24493
24598
|
this.gainNode.connect(this.audioContext.destination);
|
|
24494
24599
|
console.log('✅ AudioPlayer: GainNode created for volume control (fallback)');
|
|
24600
|
+
|
|
24601
|
+
// Prime audio hardware for Android
|
|
24602
|
+
this._primeAudioContext();
|
|
24495
24603
|
}
|
|
24496
24604
|
}
|
|
24497
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
|
+
}()
|
|
24498
24654
|
/**
|
|
24499
24655
|
* Clean up AudioContext and its event listeners
|
|
24500
24656
|
* Ensures listeners are removed before context is closed/nullified to prevent race conditions
|
|
24501
24657
|
*/
|
|
24658
|
+
)
|
|
24502
24659
|
}, {
|
|
24503
24660
|
key: "_cleanupAudioContext",
|
|
24504
24661
|
value: function _cleanupAudioContext() {
|
|
@@ -24533,6 +24690,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24533
24690
|
|
|
24534
24691
|
// Set to null after cleanup
|
|
24535
24692
|
this.audioContext = null;
|
|
24693
|
+
this._audioContextPrimed = false;
|
|
24536
24694
|
}
|
|
24537
24695
|
|
|
24538
24696
|
/**
|
|
@@ -24601,35 +24759,35 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24601
24759
|
}, {
|
|
24602
24760
|
key: "processQueue",
|
|
24603
24761
|
value: (function () {
|
|
24604
|
-
var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
24762
|
+
var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
|
|
24605
24763
|
var _this8 = this;
|
|
24606
|
-
var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source,
|
|
24607
|
-
return _regenerator().w(function (
|
|
24608
|
-
while (1) switch (
|
|
24764
|
+
var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t4;
|
|
24765
|
+
return _regenerator().w(function (_context7) {
|
|
24766
|
+
while (1) switch (_context7.p = _context7.n) {
|
|
24609
24767
|
case 0:
|
|
24610
24768
|
if (!this._isStopped) {
|
|
24611
|
-
|
|
24769
|
+
_context7.n = 1;
|
|
24612
24770
|
break;
|
|
24613
24771
|
}
|
|
24614
24772
|
console.log('🛑 AudioPlayer: Not processing queue - playback was stopped (barge-in)');
|
|
24615
|
-
return
|
|
24773
|
+
return _context7.a(2);
|
|
24616
24774
|
case 1:
|
|
24617
24775
|
if (!(this.isProcessingQueue || this.audioQueue.length === 0)) {
|
|
24618
|
-
|
|
24776
|
+
_context7.n = 2;
|
|
24619
24777
|
break;
|
|
24620
24778
|
}
|
|
24621
|
-
return
|
|
24779
|
+
return _context7.a(2);
|
|
24622
24780
|
case 2:
|
|
24623
24781
|
this.isProcessingQueue = true;
|
|
24624
24782
|
audioBlob = this.audioQueue.shift();
|
|
24625
24783
|
if (audioBlob) {
|
|
24626
|
-
|
|
24784
|
+
_context7.n = 3;
|
|
24627
24785
|
break;
|
|
24628
24786
|
}
|
|
24629
24787
|
this.isProcessingQueue = false;
|
|
24630
|
-
return
|
|
24788
|
+
return _context7.a(2);
|
|
24631
24789
|
case 3:
|
|
24632
|
-
|
|
24790
|
+
_context7.p = 3;
|
|
24633
24791
|
wasFirstPlay = !this.isPlaying && this.currentSource === null; // Initialize audio context if needed
|
|
24634
24792
|
if (!this.audioContext) {
|
|
24635
24793
|
this.initializeAudioContext();
|
|
@@ -24638,18 +24796,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24638
24796
|
// ✅ NEW: Wait for AudioContext to be in 'running' state before proceeding
|
|
24639
24797
|
// This is critical for mobile devices where AudioContext initialization takes time
|
|
24640
24798
|
// Replaces fixed timeouts with event-driven waiting
|
|
24641
|
-
|
|
24799
|
+
_context7.n = 4;
|
|
24642
24800
|
return this.waitForAudioContextReady();
|
|
24643
24801
|
case 4:
|
|
24644
24802
|
audioContext = this.audioContext; // Decode audio
|
|
24645
|
-
|
|
24803
|
+
_context7.n = 5;
|
|
24646
24804
|
return audioBlob.arrayBuffer();
|
|
24647
24805
|
case 5:
|
|
24648
|
-
arrayBuffer =
|
|
24649
|
-
|
|
24806
|
+
arrayBuffer = _context7.v;
|
|
24807
|
+
_context7.n = 6;
|
|
24650
24808
|
return audioContext.decodeAudioData(arrayBuffer);
|
|
24651
24809
|
case 6:
|
|
24652
|
-
audioBuffer =
|
|
24810
|
+
audioBuffer = _context7.v;
|
|
24653
24811
|
shouldEmitStart = wasFirstPlay && !this.isPlaying && this.currentSource === null; // Create source
|
|
24654
24812
|
source = audioContext.createBufferSource();
|
|
24655
24813
|
source.buffer = audioBuffer;
|
|
@@ -24692,14 +24850,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24692
24850
|
// Start playback - AudioContext should now be fully ready
|
|
24693
24851
|
|
|
24694
24852
|
source.start();
|
|
24695
|
-
|
|
24853
|
+
_context7.n = 8;
|
|
24696
24854
|
break;
|
|
24697
24855
|
case 7:
|
|
24698
|
-
|
|
24699
|
-
|
|
24700
|
-
console.error('❌ AudioPlayer v2: Error processing queue:',
|
|
24856
|
+
_context7.p = 7;
|
|
24857
|
+
_t4 = _context7.v;
|
|
24858
|
+
console.error('❌ AudioPlayer v2: Error processing queue:', _t4);
|
|
24701
24859
|
this.currentSource = null;
|
|
24702
|
-
this.emit('playbackError',
|
|
24860
|
+
this.emit('playbackError', _t4);
|
|
24703
24861
|
|
|
24704
24862
|
// Try next chunk
|
|
24705
24863
|
|
|
@@ -24714,9 +24872,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24714
24872
|
this.emit('playbackStopped');
|
|
24715
24873
|
}
|
|
24716
24874
|
case 8:
|
|
24717
|
-
return
|
|
24875
|
+
return _context7.a(2);
|
|
24718
24876
|
}
|
|
24719
|
-
},
|
|
24877
|
+
}, _callee6, this, [[3, 7]]);
|
|
24720
24878
|
}));
|
|
24721
24879
|
function processQueue() {
|
|
24722
24880
|
return _processQueue.apply(this, arguments);
|
|
@@ -24990,39 +25148,39 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24990
25148
|
}, {
|
|
24991
25149
|
key: "resumeAudioContext",
|
|
24992
25150
|
value: (function () {
|
|
24993
|
-
var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
24994
|
-
var
|
|
24995
|
-
return _regenerator().w(function (
|
|
24996
|
-
while (1) switch (
|
|
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) {
|
|
24997
25155
|
case 0:
|
|
24998
25156
|
if (!(!this.audioContext || this.audioContext.state === 'closed')) {
|
|
24999
|
-
|
|
25157
|
+
_context8.n = 2;
|
|
25000
25158
|
break;
|
|
25001
25159
|
}
|
|
25002
|
-
|
|
25160
|
+
_context8.n = 1;
|
|
25003
25161
|
return this.initializeAudioContext();
|
|
25004
25162
|
case 1:
|
|
25005
|
-
return
|
|
25163
|
+
return _context8.a(2);
|
|
25006
25164
|
case 2:
|
|
25007
25165
|
if (!(this.audioContext.state === 'suspended')) {
|
|
25008
|
-
|
|
25166
|
+
_context8.n = 6;
|
|
25009
25167
|
break;
|
|
25010
25168
|
}
|
|
25011
|
-
|
|
25012
|
-
|
|
25169
|
+
_context8.p = 3;
|
|
25170
|
+
_context8.n = 4;
|
|
25013
25171
|
return this.audioContext.resume();
|
|
25014
25172
|
case 4:
|
|
25015
25173
|
console.log('✅ AudioPlayer v2: AudioContext resumed after mic permission');
|
|
25016
|
-
|
|
25174
|
+
_context8.n = 6;
|
|
25017
25175
|
break;
|
|
25018
25176
|
case 5:
|
|
25019
|
-
|
|
25020
|
-
|
|
25021
|
-
console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:',
|
|
25177
|
+
_context8.p = 5;
|
|
25178
|
+
_t5 = _context8.v;
|
|
25179
|
+
console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t5);
|
|
25022
25180
|
case 6:
|
|
25023
|
-
return
|
|
25181
|
+
return _context8.a(2);
|
|
25024
25182
|
}
|
|
25025
|
-
},
|
|
25183
|
+
}, _callee7, this, [[3, 5]]);
|
|
25026
25184
|
}));
|
|
25027
25185
|
function resumeAudioContext() {
|
|
25028
25186
|
return _resumeAudioContext.apply(this, arguments);
|
|
@@ -25261,6 +25419,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
25261
25419
|
_this.transcriptQueue = []; // Queue of transcripts for history
|
|
25262
25420
|
_this.currentPlayingSentenceId = null; // Sentence ID currently being played
|
|
25263
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)
|
|
25264
25423
|
|
|
25265
25424
|
// Screen wake lock - keeps screen on during voice calls (mobile)
|
|
25266
25425
|
_this.wakeLock = null;
|
|
@@ -26334,6 +26493,16 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
26334
26493
|
this.stopAudioPlayback();
|
|
26335
26494
|
break;
|
|
26336
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
|
+
}
|
|
26337
26506
|
this.emit('stopPlaying', message);
|
|
26338
26507
|
this.stopAudioPlayback();
|
|
26339
26508
|
break;
|
|
@@ -26342,6 +26511,9 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
26342
26511
|
// Store the text in AudioPlayer for synced display when audio actually starts playing
|
|
26343
26512
|
console.log('📝 VoiceSDK v2: Received audio_start with text:', message.text);
|
|
26344
26513
|
|
|
26514
|
+
// CRITICAL: Record timestamp to prevent premature stop_playing messages from cutting sentences
|
|
26515
|
+
this.lastAudioStartTime = Date.now();
|
|
26516
|
+
|
|
26345
26517
|
// NOTE: We do NOT stop current audio here - that only happens on user barge-in (stop_playing)
|
|
26346
26518
|
// If audio is already playing, the new sentence will queue and wait for current one to finish
|
|
26347
26519
|
// This allows sentences to play sequentially without interruption
|
|
@@ -26475,7 +26647,149 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
26475
26647
|
this.emit('message', message);
|
|
26476
26648
|
}
|
|
26477
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);
|
|
26478
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
|
+
});
|
|
26479
26793
|
}
|
|
26480
26794
|
}
|
|
26481
26795
|
|