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.
- package/dist/agent-widget.dev.js +501 -163
- 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);
|
|
@@ -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,
|
|
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
|
-
//
|
|
16523
|
+
// Update mobile bar if it already exists (created early), otherwise create it
|
|
16414
16524
|
if (this.isMobile) {
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
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
|
-
|
|
16476
|
-
if (
|
|
16477
|
-
|
|
16594
|
+
_floatingButton2 = this.shadowRoot.getElementById('text-chat-button');
|
|
16595
|
+
if (_floatingButton2) {
|
|
16596
|
+
_floatingButton2.style.display = '';
|
|
16478
16597
|
} else {
|
|
16479
|
-
|
|
16480
|
-
if (
|
|
16481
|
-
|
|
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
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
//
|
|
16504
|
-
|
|
16505
|
-
|
|
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
|
-
//
|
|
16655
|
+
// Update mobile bar if it already exists (created early), otherwise create it
|
|
16555
16656
|
if (this.isMobile) {
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
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
|
-
|
|
16617
|
-
if (
|
|
16618
|
-
|
|
16726
|
+
_floatingButton3 = this.shadowRoot.getElementById('text-chat-button');
|
|
16727
|
+
if (_floatingButton3) {
|
|
16728
|
+
_floatingButton3.style.display = '';
|
|
16619
16729
|
} else {
|
|
16620
|
-
|
|
16621
|
-
if (
|
|
16622
|
-
|
|
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
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
//
|
|
16645
|
-
|
|
16646
|
-
|
|
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 === '
|
|
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:
|
|
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 =
|
|
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 -
|
|
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
|
|
24762
|
+
var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
|
|
24599
24763
|
var _this8 = this;
|
|
24600
|
-
var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source,
|
|
24601
|
-
return _regenerator().w(function (
|
|
24602
|
-
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) {
|
|
24603
24767
|
case 0:
|
|
24604
24768
|
if (!this._isStopped) {
|
|
24605
|
-
|
|
24769
|
+
_context7.n = 1;
|
|
24606
24770
|
break;
|
|
24607
24771
|
}
|
|
24608
24772
|
console.log('🛑 AudioPlayer: Not processing queue - playback was stopped (barge-in)');
|
|
24609
|
-
return
|
|
24773
|
+
return _context7.a(2);
|
|
24610
24774
|
case 1:
|
|
24611
24775
|
if (!(this.isProcessingQueue || this.audioQueue.length === 0)) {
|
|
24612
|
-
|
|
24776
|
+
_context7.n = 2;
|
|
24613
24777
|
break;
|
|
24614
24778
|
}
|
|
24615
|
-
return
|
|
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
|
-
|
|
24784
|
+
_context7.n = 3;
|
|
24621
24785
|
break;
|
|
24622
24786
|
}
|
|
24623
24787
|
this.isProcessingQueue = false;
|
|
24624
|
-
return
|
|
24788
|
+
return _context7.a(2);
|
|
24625
24789
|
case 3:
|
|
24626
|
-
|
|
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
|
-
|
|
24799
|
+
_context7.n = 4;
|
|
24636
24800
|
return this.waitForAudioContextReady();
|
|
24637
24801
|
case 4:
|
|
24638
24802
|
audioContext = this.audioContext; // Decode audio
|
|
24639
|
-
|
|
24803
|
+
_context7.n = 5;
|
|
24640
24804
|
return audioBlob.arrayBuffer();
|
|
24641
24805
|
case 5:
|
|
24642
|
-
arrayBuffer =
|
|
24643
|
-
|
|
24806
|
+
arrayBuffer = _context7.v;
|
|
24807
|
+
_context7.n = 6;
|
|
24644
24808
|
return audioContext.decodeAudioData(arrayBuffer);
|
|
24645
24809
|
case 6:
|
|
24646
|
-
audioBuffer =
|
|
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
|
-
|
|
24853
|
+
_context7.n = 8;
|
|
24690
24854
|
break;
|
|
24691
24855
|
case 7:
|
|
24692
|
-
|
|
24693
|
-
|
|
24694
|
-
console.error('❌ AudioPlayer v2: Error processing queue:',
|
|
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',
|
|
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
|
|
24875
|
+
return _context7.a(2);
|
|
24712
24876
|
}
|
|
24713
|
-
},
|
|
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
|
|
24988
|
-
var
|
|
24989
|
-
return _regenerator().w(function (
|
|
24990
|
-
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) {
|
|
24991
25155
|
case 0:
|
|
24992
25156
|
if (!(!this.audioContext || this.audioContext.state === 'closed')) {
|
|
24993
|
-
|
|
25157
|
+
_context8.n = 2;
|
|
24994
25158
|
break;
|
|
24995
25159
|
}
|
|
24996
|
-
|
|
25160
|
+
_context8.n = 1;
|
|
24997
25161
|
return this.initializeAudioContext();
|
|
24998
25162
|
case 1:
|
|
24999
|
-
return
|
|
25163
|
+
return _context8.a(2);
|
|
25000
25164
|
case 2:
|
|
25001
25165
|
if (!(this.audioContext.state === 'suspended')) {
|
|
25002
|
-
|
|
25166
|
+
_context8.n = 6;
|
|
25003
25167
|
break;
|
|
25004
25168
|
}
|
|
25005
|
-
|
|
25006
|
-
|
|
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
|
-
|
|
25174
|
+
_context8.n = 6;
|
|
25011
25175
|
break;
|
|
25012
25176
|
case 5:
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
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);
|
|
25016
25180
|
case 6:
|
|
25017
|
-
return
|
|
25181
|
+
return _context8.a(2);
|
|
25018
25182
|
}
|
|
25019
|
-
},
|
|
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:
|
|
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
|
|