@crestapps/ai-chat-ui 1.0.0-preview-22.6 → 1.0.0-preview.7

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/ai-chat.js CHANGED
@@ -34,7 +34,25 @@ window.openAIChatManager = function () {
34
34
  thumbsDownTitle: 'Thumbs down',
35
35
  copyTitle: 'Click here to copy response to clipboard.',
36
36
  codeCopiedText: 'Copied!',
37
- messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"'msg-' + index\" class=\"ai-chat-message-item\">\n <div>\n <div v-if=\"message.role === 'user'\" class=\"ai-chat-msg-role ai-chat-msg-role-user\">{{ userLabel }}</div>\n <div v-else-if=\"message.role !== 'indicator'\" :class=\"getAssistantRoleClasses(message)\">\n <span :class=\"getAssistantIconClasses(message, index)\"><i :class=\"getAssistantIcon(message)\"></i></span>\n {{ getAssistantLabel(message) }}\n </div>\n <div class=\"ai-chat-message-body lh-base\">\n <h4 v-if=\"message.title\">{{ message.title }}</h4>\n <div v-html=\"message.htmlContent\"></div>\n <span class=\"message-buttons-container\" v-if=\"!isIndicator(message)\">\n <template v-if=\"metricsEnabled && message.role === 'assistant'\">\n <span class=\"ai-chat-message-assistant-feedback\" :data-message-id=\"message.id\">\n <button class=\"btn btn-sm btn-link text-success p-0 me-2 button-message-toolbox rate-up-btn\" @click=\"rateMessage(message, true, $event)\" :title=\"thumbsUpTitle\">\n <i class=\"fa-regular fa-thumbs-up\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-danger p-0 me-2 button-message-toolbox rate-down-btn\" @click=\"rateMessage(message, false, $event)\" :title=\"thumbsDownTitle\">\n <i class=\"fa-regular fa-thumbs-down\"></i>\n </button>\n </span>\n </template>\n <button v-if=\"textToSpeechEnabled && message.role === 'assistant' && !message.isStreaming\" class=\"btn btn-sm btn-link text-secondary p-0 me-1 button-message-toolbox\" :class=\"{ 'tts-playing': ttsPlayingMessageIndex === index }\" @click=\"toggleMessageTts(message, index)\" :title=\"ttsPlayingMessageIndex === index ? 'Pause audio' : 'Read aloud'\">\n <i :class=\"ttsPlayingMessageIndex === index ? 'fa-solid fa-circle-pause' : 'fa-solid fa-circle-play'\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" :title=\"copyTitle\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div v-for=\"notification in notifications\" :key=\"'notif-' + notification.type\" class=\"ai-chat-notification\" :class=\"'ai-chat-notification-' + (notification.type || 'info') + ' ' + (notification.cssClass || '')\">\n <div class=\"ai-chat-notification-content\">\n <i v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></i>\n <span class=\"ai-chat-notification-text\">{{ notification.content }}</span>\n <button v-if=\"notification.dismissible\" class=\"btn btn-sm btn-link p-0 ms-2 ai-chat-notification-dismiss\" @click=\"dismissNotification(notification.type)\" title=\"Dismiss\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div v-if=\"notification.actions && notification.actions.length\" class=\"ai-chat-notification-actions\">\n <button v-for=\"action in notification.actions\" :key=\"action.name\" class=\"btn btn-sm\" :class=\"action.cssClass || 'btn-outline-secondary'\" @click=\"handleNotificationAction(notification.type, action.name)\">\n <i v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></i>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
37
+ widget: {
38
+ chatWidgetContainer: null,
39
+ chatWidgetStateName: null,
40
+ chatHistorySection: null,
41
+ showHistoryButton: null,
42
+ closeHistoryButton: null,
43
+ newChatButton: null,
44
+ toggleButtonSelector: null,
45
+ resetSizeButtonSelector: null,
46
+ dragHandleSelector: '.ai-chat-widget-header',
47
+ enableDragging: true,
48
+ enableResizing: true,
49
+ persistLayout: true,
50
+ defaultWidth: null,
51
+ defaultHeight: null,
52
+ minWidth: 320,
53
+ minHeight: 420
54
+ },
55
+ messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"'msg-' + index\" class=\"ai-chat-message-item\">\n <div>\n <div v-if=\"message.role === 'user'\" class=\"ai-chat-msg-role ai-chat-msg-role-user\">{{ userLabel }}</div>\n <div v-else-if=\"message.role !== 'indicator'\" :class=\"getAssistantRoleClasses(message)\">\n <span :class=\"getAssistantIconClasses(message, index)\"><i :class=\"getAssistantIcon(message)\"></i></span>\n {{ getAssistantLabel(message) }}\n </div>\n <div class=\"ai-chat-message-body lh-base\">\n <h4 v-if=\"message.title\">{{ message.title }}</h4>\n <div v-html=\"message.htmlContent\"></div>\n <span class=\"message-buttons-container\" v-if=\"!isIndicator(message)\">\n <template v-if=\"metricsEnabled && message.role === 'assistant'\">\n <span class=\"ai-chat-message-assistant-feedback\" :data-message-id=\"message.id\">\n <button class=\"btn btn-sm btn-link text-success p-0 me-2 button-message-toolbox rate-up-btn\" @click=\"rateMessage(message, true, $event)\" :title=\"thumbsUpTitle\">\n <i class=\"fa-regular fa-thumbs-up\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-danger p-0 me-2 button-message-toolbox rate-down-btn\" @click=\"rateMessage(message, false, $event)\" :title=\"thumbsDownTitle\">\n <i class=\"fa-regular fa-thumbs-down\"></i>\n </button>\n </span>\n </template>\n <button v-if=\"textToSpeechEnabled && !isConversationMode && message.role === 'assistant' && !message.isStreaming\" class=\"btn btn-sm btn-link text-secondary p-0 me-1 button-message-toolbox\" :class=\"{ 'tts-playing': ttsPlayingMessageIndex === index }\" @click=\"toggleMessageTts(message, index)\" :title=\"ttsPlayingMessageIndex === index ? 'Pause audio' : 'Read aloud'\">\n <i :class=\"ttsPlayingMessageIndex === index ? 'fa-solid fa-circle-pause' : 'fa-solid fa-circle-play'\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" :title=\"copyTitle\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div v-for=\"notification in notifications\" :key=\"'notif-' + notification.type\" class=\"ai-chat-notification\" :class=\"'ai-chat-notification-' + (notification.type || 'info') + ' ' + (notification.cssClass || '')\">\n <div class=\"ai-chat-notification-content\">\n <i v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></i>\n <span class=\"ai-chat-notification-text\">{{ notification.content }}</span>\n <button v-if=\"notification.dismissible\" class=\"btn btn-sm btn-link p-0 ms-2 ai-chat-notification-dismiss\" @click=\"dismissNotification(notification.type)\" title=\"Dismiss\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div v-if=\"notification.actions && notification.actions.length\" class=\"ai-chat-notification-actions\">\n <button v-for=\"action in notification.actions\" :key=\"action.name\" class=\"btn btn-sm\" :class=\"action.cssClass || 'btn-outline-secondary'\" @click=\"handleNotificationAction(notification.type, action.name)\">\n <i v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></i>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
38
56
  indicatorTemplate: "\n <div class=\"ai-chat-msg-role ai-chat-msg-role-assistant\">\n <span class=\"ai-streaming-icon\"><i class=\"fa fa-robot\" style=\"display: inline-block;\"></i></span>\n Assistant\n </div>\n "
39
57
  };
40
58
 
@@ -54,6 +72,19 @@ window.openAIChatManager = function () {
54
72
  span.textContent = text;
55
73
  return span.innerHTML;
56
74
  }
75
+ function parsePixelValue(value) {
76
+ if (typeof value === 'number' && Number.isFinite(value)) {
77
+ return value;
78
+ }
79
+ if (typeof value !== 'string') {
80
+ return null;
81
+ }
82
+ var parsed = parseFloat(value);
83
+ return Number.isFinite(parsed) ? parsed : null;
84
+ }
85
+ function clamp(value, min, max) {
86
+ return Math.min(Math.max(value, min), max);
87
+ }
57
88
  function normalizeReference(reference) {
58
89
  var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$link;
59
90
  if (!reference || _typeof(reference) !== 'object') {
@@ -330,6 +361,8 @@ window.openAIChatManager = function () {
330
361
  }
331
362
  var initialize = function initialize(instanceConfig) {
332
363
  var config = Object.assign({}, defaultConfig, instanceConfig);
364
+ config.widget = Object.assign({}, defaultConfig.widget || {}, instanceConfig && instanceConfig.widget ? instanceConfig.widget : {});
365
+ var hasWidgetConfig = !!(instanceConfig && instanceConfig.widget && instanceConfig.widget.chatWidgetContainer && instanceConfig.widget.chatWidgetStateName);
333
366
  // Keep defaultConfig in sync so renderers use overridden values
334
367
  defaultConfig = config;
335
368
  if (!config.signalRHubUrl) {
@@ -363,7 +396,15 @@ window.openAIChatManager = function () {
363
396
  isPlaceholderVisible: true,
364
397
  chatWidgetStateName: null,
365
398
  chatWidgetStateSession: null,
399
+ chatWidgetLayoutKey: null,
366
400
  chatHistorySection: null,
401
+ widgetContainerElement: null,
402
+ widgetToggleButtonElement: null,
403
+ widgetResetSizeButtonElement: null,
404
+ widgetResizeObserver: null,
405
+ widgetViewportResizeHandler: null,
406
+ widgetDefaultWidth: null,
407
+ widgetDefaultHeight: null,
367
408
  widgetIsInitialized: false,
368
409
  isStreaming: false,
369
410
  isNavigatingAway: false,
@@ -394,9 +435,11 @@ window.openAIChatManager = function () {
394
435
  audioPlayQueue: [],
395
436
  isPlayingAudio: false,
396
437
  currentAudioElement: null,
438
+ currentAudioUrl: null,
397
439
  ttsButton: null,
398
440
  ttsPlayingMessageIndex: -1,
399
441
  ttsAudioCache: {},
442
+ ttsInstanceId: 'ai-chat-' + Math.random().toString(36).slice(2),
400
443
  singleResponseMode: !!config.singleResponseMode,
401
444
  conversationModeEnabled: config.chatMode === 'Conversation',
402
445
  conversationButton: null,
@@ -418,6 +461,255 @@ window.openAIChatManager = function () {
418
461
  handleBeforeUnload: function handleBeforeUnload() {
419
462
  this.isNavigatingAway = true;
420
463
  },
464
+ getStoredWidgetLayout: function getStoredWidgetLayout() {
465
+ if (!this.chatWidgetLayoutKey || !config.widget.persistLayout) {
466
+ return {};
467
+ }
468
+ try {
469
+ var storedLayout = localStorage.getItem(this.chatWidgetLayoutKey);
470
+ return storedLayout ? JSON.parse(storedLayout) || {} : {};
471
+ } catch (error) {
472
+ console.warn('Failed to read widget layout state.', error);
473
+ return {};
474
+ }
475
+ },
476
+ saveStoredWidgetLayout: function saveStoredWidgetLayout(partialLayout) {
477
+ if (!this.chatWidgetLayoutKey || !config.widget.persistLayout) {
478
+ return;
479
+ }
480
+ var currentLayout = this.getStoredWidgetLayout();
481
+ var nextLayout = Object.assign({}, currentLayout);
482
+ if (partialLayout.panel !== undefined) {
483
+ nextLayout.panel = Object.assign({}, currentLayout.panel || {}, partialLayout.panel || {});
484
+ }
485
+ if (partialLayout.toggle !== undefined) {
486
+ nextLayout.toggle = Object.assign({}, currentLayout.toggle || {}, partialLayout.toggle || {});
487
+ }
488
+ localStorage.setItem(this.chatWidgetLayoutKey, JSON.stringify(nextLayout));
489
+ },
490
+ getElementSize: function getElementSize(element, fallbackWidth, fallbackHeight) {
491
+ if (!element) {
492
+ return {
493
+ width: fallbackWidth || 0,
494
+ height: fallbackHeight || 0
495
+ };
496
+ }
497
+ var rect = element.getBoundingClientRect();
498
+ var computed = window.getComputedStyle(element);
499
+ return {
500
+ width: Math.round(rect.width || parsePixelValue(computed.width) || fallbackWidth || 0),
501
+ height: Math.round(rect.height || parsePixelValue(computed.height) || fallbackHeight || 0)
502
+ };
503
+ },
504
+ clampWidgetPosition: function clampWidgetPosition(left, top, width, height) {
505
+ var viewportPadding = 8;
506
+ var maxLeft = Math.max(viewportPadding, window.innerWidth - width - viewportPadding);
507
+ var maxTop = Math.max(viewportPadding, window.innerHeight - height - viewportPadding);
508
+ return {
509
+ left: Math.round(clamp(left, viewportPadding, maxLeft)),
510
+ top: Math.round(clamp(top, viewportPadding, maxTop))
511
+ };
512
+ },
513
+ applyAbsolutePosition: function applyAbsolutePosition(element, left, top) {
514
+ if (!element) {
515
+ return;
516
+ }
517
+ element.style.left = left + 'px';
518
+ element.style.top = top + 'px';
519
+ element.style.right = 'auto';
520
+ element.style.bottom = 'auto';
521
+ },
522
+ getWidgetDefaultSize: function getWidgetDefaultSize() {
523
+ return {
524
+ width: this.widgetDefaultWidth || parsePixelValue(config.widget.defaultWidth) || 380,
525
+ height: this.widgetDefaultHeight || parsePixelValue(config.widget.defaultHeight) || 520
526
+ };
527
+ },
528
+ persistWidgetPanelPosition: function persistWidgetPanelPosition() {
529
+ if (!this.widgetContainerElement || !config.widget.persistLayout) {
530
+ return;
531
+ }
532
+ var panelRect = this.widgetContainerElement.getBoundingClientRect();
533
+ this.saveStoredWidgetLayout({
534
+ panel: {
535
+ left: Math.round(panelRect.left),
536
+ top: Math.round(panelRect.top)
537
+ }
538
+ });
539
+ },
540
+ persistWidgetTogglePosition: function persistWidgetTogglePosition() {
541
+ if (!this.widgetToggleButtonElement || !config.widget.persistLayout) {
542
+ return;
543
+ }
544
+ var toggleRect = this.widgetToggleButtonElement.getBoundingClientRect();
545
+ this.saveStoredWidgetLayout({
546
+ toggle: {
547
+ left: Math.round(toggleRect.left),
548
+ top: Math.round(toggleRect.top)
549
+ }
550
+ });
551
+ },
552
+ persistWidgetPanelSize: function persistWidgetPanelSize() {
553
+ if (!this.widgetContainerElement || !config.widget.enableResizing || !config.widget.persistLayout) {
554
+ return;
555
+ }
556
+ var defaultSize = this.getWidgetDefaultSize();
557
+ var currentSize = this.getElementSize(this.widgetContainerElement, defaultSize.width, defaultSize.height);
558
+ this.saveStoredWidgetLayout({
559
+ panel: {
560
+ width: Math.abs(currentSize.width - defaultSize.width) <= 1 ? null : currentSize.width,
561
+ height: Math.abs(currentSize.height - defaultSize.height) <= 1 ? null : currentSize.height
562
+ }
563
+ });
564
+ },
565
+ updateWidgetResetSizeButtonVisibility: function updateWidgetResetSizeButtonVisibility() {
566
+ if (!this.widgetResetSizeButtonElement) {
567
+ return;
568
+ }
569
+ if (!config.widget.enableResizing || !this.widgetContainerElement) {
570
+ this.widgetResetSizeButtonElement.classList.add('d-none');
571
+ return;
572
+ }
573
+ var defaultSize = this.getWidgetDefaultSize();
574
+ var currentSize = this.getElementSize(this.widgetContainerElement, defaultSize.width, defaultSize.height);
575
+ var isResized = Math.abs(currentSize.width - defaultSize.width) > 1 || Math.abs(currentSize.height - defaultSize.height) > 1 || !!this.widgetContainerElement.style.width || !!this.widgetContainerElement.style.height;
576
+ this.widgetResetSizeButtonElement.classList.toggle('d-none', !isResized);
577
+ },
578
+ resetWidgetSize: function resetWidgetSize() {
579
+ if (!this.widgetContainerElement || !config.widget.enableResizing) {
580
+ return;
581
+ }
582
+ this.widgetContainerElement.style.removeProperty('width');
583
+ this.widgetContainerElement.style.removeProperty('height');
584
+ this.persistWidgetPanelSize();
585
+ this.constrainWidgetElementsToViewport();
586
+ this.updateWidgetResetSizeButtonVisibility();
587
+ },
588
+ restoreWidgetLayout: function restoreWidgetLayout() {
589
+ var storedLayout = this.getStoredWidgetLayout();
590
+ if (this.widgetContainerElement && config.widget.enableResizing) {
591
+ if (Number.isFinite(storedLayout.panel && storedLayout.panel.width)) {
592
+ this.widgetContainerElement.style.width = storedLayout.panel.width + 'px';
593
+ } else {
594
+ this.widgetContainerElement.style.removeProperty('width');
595
+ }
596
+ if (Number.isFinite(storedLayout.panel && storedLayout.panel.height)) {
597
+ this.widgetContainerElement.style.height = storedLayout.panel.height + 'px';
598
+ } else {
599
+ this.widgetContainerElement.style.removeProperty('height');
600
+ }
601
+ }
602
+ if (this.widgetContainerElement && config.widget.enableDragging && Number.isFinite(storedLayout.panel && storedLayout.panel.left) && Number.isFinite(storedLayout.panel && storedLayout.panel.top)) {
603
+ this.applyAbsolutePosition(this.widgetContainerElement, storedLayout.panel.left, storedLayout.panel.top);
604
+ }
605
+ if (this.widgetToggleButtonElement && config.widget.enableDragging && Number.isFinite(storedLayout.toggle && storedLayout.toggle.left) && Number.isFinite(storedLayout.toggle && storedLayout.toggle.top)) {
606
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, storedLayout.toggle.left, storedLayout.toggle.top);
607
+ }
608
+ this.constrainWidgetElementsToViewport();
609
+ this.updateWidgetResetSizeButtonVisibility();
610
+ },
611
+ attachDraggableWidgetElement: function attachDraggableWidgetElement(element, handle, layoutKey, suppressClickOnDrag) {
612
+ var _this = this;
613
+ if (!element || !handle || !config.widget.enableDragging) {
614
+ return;
615
+ }
616
+ var activePointerId = null;
617
+ var startX = 0;
618
+ var startY = 0;
619
+ var startLeft = 0;
620
+ var startTop = 0;
621
+ var dragged = false;
622
+ var dragThreshold = 4;
623
+ var interactiveSelector = 'button, a, input, textarea, select, option, label';
624
+ var _stopDragging = function stopDragging(event) {
625
+ if (activePointerId === null) {
626
+ return;
627
+ }
628
+ if (event && event.pointerId !== undefined && event.pointerId !== activePointerId) {
629
+ return;
630
+ }
631
+ document.removeEventListener('pointermove', onPointerMove);
632
+ document.removeEventListener('pointerup', _stopDragging);
633
+ document.removeEventListener('pointercancel', _stopDragging);
634
+ element.classList.remove('ai-chat-widget-dragging');
635
+ activePointerId = null;
636
+ if (!dragged) {
637
+ return;
638
+ }
639
+ if (layoutKey === 'panel') {
640
+ _this.persistWidgetPanelPosition();
641
+ } else if (layoutKey === 'toggle') {
642
+ _this.persistWidgetTogglePosition();
643
+ }
644
+ if (suppressClickOnDrag) {
645
+ element.dataset.dragSuppressClick = 'true';
646
+ window.setTimeout(function () {
647
+ if (element.dataset.dragSuppressClick === 'true') {
648
+ element.dataset.dragSuppressClick = 'false';
649
+ }
650
+ }, 0);
651
+ }
652
+ };
653
+ var onPointerMove = function onPointerMove(event) {
654
+ if (activePointerId === null || event.pointerId !== activePointerId) {
655
+ return;
656
+ }
657
+ var deltaX = event.clientX - startX;
658
+ var deltaY = event.clientY - startY;
659
+ if (!dragged && Math.hypot(deltaX, deltaY) < dragThreshold) {
660
+ return;
661
+ }
662
+ dragged = true;
663
+ var defaultSize = _this.getWidgetDefaultSize();
664
+ var currentSize = _this.getElementSize(element, layoutKey === 'panel' ? defaultSize.width : 52, layoutKey === 'panel' ? defaultSize.height : 52);
665
+ var nextPosition = _this.clampWidgetPosition(startLeft + deltaX, startTop + deltaY, currentSize.width, currentSize.height);
666
+ _this.applyAbsolutePosition(element, nextPosition.left, nextPosition.top);
667
+ };
668
+ handle.addEventListener('pointerdown', function (event) {
669
+ if (event.button !== 0) {
670
+ return;
671
+ }
672
+ if (layoutKey === 'panel' && event.target.closest(interactiveSelector)) {
673
+ return;
674
+ }
675
+ var rect = element.getBoundingClientRect();
676
+ startX = event.clientX;
677
+ startY = event.clientY;
678
+ startLeft = rect.left;
679
+ startTop = rect.top;
680
+ dragged = false;
681
+ activePointerId = event.pointerId;
682
+ element.classList.add('ai-chat-widget-dragging');
683
+ if (handle.setPointerCapture) {
684
+ handle.setPointerCapture(event.pointerId);
685
+ }
686
+ document.addEventListener('pointermove', onPointerMove);
687
+ document.addEventListener('pointerup', _stopDragging);
688
+ document.addEventListener('pointercancel', _stopDragging);
689
+ event.preventDefault();
690
+ });
691
+ },
692
+ constrainWidgetElementsToViewport: function constrainWidgetElementsToViewport() {
693
+ if (this.widgetContainerElement && this.widgetContainerElement.style.left) {
694
+ var panelDefaultSize = this.getWidgetDefaultSize();
695
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
696
+ var panelRect = this.widgetContainerElement.getBoundingClientRect();
697
+ var clampedPanel = this.clampWidgetPosition(panelRect.left, panelRect.top, panelSize.width, panelSize.height);
698
+ this.applyAbsolutePosition(this.widgetContainerElement, clampedPanel.left, clampedPanel.top);
699
+ if (config.widget.persistLayout) {
700
+ this.persistWidgetPanelPosition();
701
+ }
702
+ }
703
+ if (this.widgetToggleButtonElement && this.widgetToggleButtonElement.style.left) {
704
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
705
+ var toggleRect = this.widgetToggleButtonElement.getBoundingClientRect();
706
+ var clampedToggle = this.clampWidgetPosition(toggleRect.left, toggleRect.top, toggleSize.width, toggleSize.height);
707
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, clampedToggle.left, clampedToggle.top);
708
+ if (config.widget.persistLayout) {
709
+ this.persistWidgetTogglePosition();
710
+ }
711
+ }
712
+ },
421
713
  handleDragOver: function handleDragOver(e) {
422
714
  if (!config.sessionDocumentsEnabled) return;
423
715
  e.preventDefault();
@@ -458,7 +750,7 @@ window.openAIChatManager = function () {
458
750
  e.target.value = '';
459
751
  },
460
752
  uploadFiles: function uploadFiles(files) {
461
- var _this = this;
753
+ var _this2 = this;
462
754
  return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
463
755
  var sessionId, profileId, formData, i, response, errorText, uploadError, result, j, _t;
464
756
  return _regenerator().w(function (_context) {
@@ -470,8 +762,8 @@ window.openAIChatManager = function () {
470
762
  }
471
763
  return _context.a(2);
472
764
  case 1:
473
- sessionId = _this.getSessionId();
474
- profileId = _this.getProfileId();
765
+ sessionId = _this2.getSessionId();
766
+ profileId = _this2.getProfileId();
475
767
  if (!(!sessionId && !profileId)) {
476
768
  _context.n = 2;
477
769
  break;
@@ -479,9 +771,9 @@ window.openAIChatManager = function () {
479
771
  console.warn('Cannot upload documents without a session or profile.');
480
772
  return _context.a(2);
481
773
  case 2:
482
- _this.isUploading = true;
483
- _this.uploadErrors = [];
484
- _this.renderDocumentBar();
774
+ _this2.isUploading = true;
775
+ _this2.uploadErrors = [];
776
+ _this2.renderDocumentBar();
485
777
  _context.p = 3;
486
778
  formData = new FormData();
487
779
  if (sessionId) {
@@ -507,9 +799,9 @@ window.openAIChatManager = function () {
507
799
  return response.text();
508
800
  case 5:
509
801
  errorText = _context.v;
510
- uploadError = _this.extractReadableErrorMessage(errorText, 'Upload failed. Please try again.');
802
+ uploadError = _this2.extractReadableErrorMessage(errorText, 'Upload failed. Please try again.');
511
803
  console.error('Upload failed:', errorText);
512
- _this.uploadErrors = [{
804
+ _this2.uploadErrors = [{
513
805
  fileName: '',
514
806
  error: uploadError
515
807
  }];
@@ -521,15 +813,15 @@ window.openAIChatManager = function () {
521
813
  result = _context.v;
522
814
  // If the server created a new session, initialize it.
523
815
  if (result.sessionId && !sessionId) {
524
- _this.initializeSession(result.sessionId);
816
+ _this2.initializeSession(result.sessionId);
525
817
  }
526
818
  if (result.uploaded && result.uploaded.length > 0) {
527
819
  for (j = 0; j < result.uploaded.length; j++) {
528
- _this.documents.push(result.uploaded[j]);
820
+ _this2.documents.push(result.uploaded[j]);
529
821
  }
530
822
  }
531
823
  if (result.failed && result.failed.length > 0) {
532
- _this.uploadErrors = result.failed;
824
+ _this2.uploadErrors = result.failed;
533
825
  }
534
826
  _context.n = 9;
535
827
  break;
@@ -537,14 +829,14 @@ window.openAIChatManager = function () {
537
829
  _context.p = 8;
538
830
  _t = _context.v;
539
831
  console.error('Upload error:', _t);
540
- _this.uploadErrors = [{
832
+ _this2.uploadErrors = [{
541
833
  fileName: '',
542
834
  error: 'Upload failed. Please try again.'
543
835
  }];
544
836
  case 9:
545
837
  _context.p = 9;
546
- _this.isUploading = false;
547
- _this.renderDocumentBar();
838
+ _this2.isUploading = false;
839
+ _this2.renderDocumentBar();
548
840
  return _context.f(9);
549
841
  case 10:
550
842
  return _context.a(2);
@@ -553,7 +845,7 @@ window.openAIChatManager = function () {
553
845
  }))();
554
846
  },
555
847
  removeDocument: function removeDocument(doc) {
556
- var _this2 = this;
848
+ var _this3 = this;
557
849
  return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
558
850
  var sessionId, response, idx, errorText, removeError, _t2;
559
851
  return _regenerator().w(function (_context2) {
@@ -566,7 +858,7 @@ window.openAIChatManager = function () {
566
858
  return _context2.a(2);
567
859
  case 1:
568
860
  _context2.p = 1;
569
- sessionId = _this2.getSessionId();
861
+ sessionId = _this3.getSessionId();
570
862
  _context2.n = 2;
571
863
  return fetch(config.removeDocumentUrl, {
572
864
  method: 'POST',
@@ -584,8 +876,8 @@ window.openAIChatManager = function () {
584
876
  _context2.n = 3;
585
877
  break;
586
878
  }
587
- idx = _this2.documents.indexOf(doc);
588
- if (idx > -1) _this2.documents.splice(idx, 1);
879
+ idx = _this3.documents.indexOf(doc);
880
+ if (idx > -1) _this3.documents.splice(idx, 1);
589
881
  _context2.n = 5;
590
882
  break;
591
883
  case 3:
@@ -593,13 +885,13 @@ window.openAIChatManager = function () {
593
885
  return response.text();
594
886
  case 4:
595
887
  errorText = _context2.v;
596
- removeError = _this2.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
888
+ removeError = _this3.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
597
889
  console.error('Failed to remove document:', response.status, errorText);
598
- _this2.uploadErrors = [{
890
+ _this3.uploadErrors = [{
599
891
  fileName: doc.fileName || '',
600
892
  error: removeError
601
893
  }];
602
- _this2.renderDocumentBar();
894
+ _this3.renderDocumentBar();
603
895
  case 5:
604
896
  _context2.n = 7;
605
897
  break;
@@ -607,11 +899,11 @@ window.openAIChatManager = function () {
607
899
  _context2.p = 6;
608
900
  _t2 = _context2.v;
609
901
  console.error('Remove document error:', _t2);
610
- _this2.uploadErrors = [{
902
+ _this3.uploadErrors = [{
611
903
  fileName: doc.fileName || '',
612
904
  error: 'Failed to remove document. Please try again.'
613
905
  }];
614
- _this2.renderDocumentBar();
906
+ _this3.renderDocumentBar();
615
907
  case 7:
616
908
  return _context2.a(2);
617
909
  }
@@ -781,139 +1073,139 @@ window.openAIChatManager = function () {
781
1073
  return !!message && message.isStreaming && index === this.lastAssistantIndex && !(appearance && appearance.disableStreamingAnimation);
782
1074
  },
783
1075
  startConnection: function startConnection() {
784
- var _this3 = this;
1076
+ var _this4 = this;
785
1077
  return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
786
1078
  var _t3;
787
1079
  return _regenerator().w(function (_context3) {
788
1080
  while (1) switch (_context3.p = _context3.n) {
789
1081
  case 0:
790
- _this3.connection = new signalR.HubConnectionBuilder().withUrl(config.signalRHubUrl).withAutomaticReconnect().build();
1082
+ _this4.connection = new signalR.HubConnectionBuilder().withUrl(config.signalRHubUrl).withAutomaticReconnect().build();
791
1083
 
792
1084
  // Allow long-running operations (e.g., multi-step MCP tool calls)
793
1085
  // without the client disconnecting prematurely.
794
- _this3.connection.serverTimeoutInMilliseconds = 600000;
795
- _this3.connection.keepAliveIntervalInMilliseconds = 15000;
796
- _this3.connection.on("LoadSession", function (data) {
1086
+ _this4.connection.serverTimeoutInMilliseconds = 600000;
1087
+ _this4.connection.keepAliveIntervalInMilliseconds = 15000;
1088
+ _this4.connection.on("LoadSession", function (data) {
797
1089
  var _data$messages;
798
- _this3.initializeSession(data.sessionId, true);
799
- _this3.messages = [];
800
- _this3.documents = data.documents || [];
1090
+ _this4.initializeSession(data.sessionId, true);
1091
+ _this4.messages = [];
1092
+ _this4.documents = data.documents || [];
801
1093
  ((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
802
- _this3.addMessage(msg);
803
- _this3.$nextTick(function () {
1094
+ _this4.addMessage(msg);
1095
+ _this4.$nextTick(function () {
804
1096
  renderChartsInMessage(msg);
805
1097
  });
806
1098
  });
807
1099
 
808
1100
  // Update feedback icons in the DOM after all messages have rendered.
809
- _this3.$nextTick(function () {
810
- _this3.refreshAllFeedbackIcons();
1101
+ _this4.$nextTick(function () {
1102
+ _this4.refreshAllFeedbackIcons();
811
1103
  });
812
1104
 
813
1105
  // When the session is new (no messages) and an initial prompt is configured,
814
1106
  // automatically send it as the first user message to trigger an AI response.
815
- if (_this3.messages.length === 0 && config.initialPrompt) {
816
- _this3.prompt = config.initialPrompt;
817
- _this3.sendMessage();
1107
+ if (_this4.messages.length === 0 && config.initialPrompt) {
1108
+ _this4.prompt = config.initialPrompt;
1109
+ _this4.sendMessage();
818
1110
  }
819
1111
  });
820
- _this3.connection.on("ReceiveError", function (error) {
1112
+ _this4.connection.on("ReceiveError", function (error) {
821
1113
  console.error("SignalR Error: ", error);
822
- if (_this3.isRecording) {
823
- _this3.stopRecording();
1114
+ if (_this4.isRecording) {
1115
+ _this4.stopRecording();
824
1116
  }
825
1117
 
826
1118
  // If this is a widget with a stale cached session (e.g., profile was deleted),
827
1119
  // clear the cached session and start fresh with the current profile.
828
- if (_this3.widgetIsInitialized && !_this3.isSessionStarted && !_this3._attemptedSessionRecovery) {
829
- _this3._attemptedSessionRecovery = true;
830
- localStorage.removeItem(_this3.chatWidgetStateSession);
831
- _this3.startNewSession();
1120
+ if (_this4.widgetIsInitialized && !_this4.isSessionStarted && !_this4._attemptedSessionRecovery) {
1121
+ _this4._attemptedSessionRecovery = true;
1122
+ localStorage.removeItem(_this4.chatWidgetStateSession);
1123
+ _this4.startNewSession();
832
1124
  }
833
1125
  });
834
- _this3.connection.on("MessageRated", function (messageId, userRating) {
835
- var msg = _this3.messages.find(function (m) {
1126
+ _this4.connection.on("MessageRated", function (messageId, userRating) {
1127
+ var msg = _this4.messages.find(function (m) {
836
1128
  return m.id === messageId;
837
1129
  });
838
1130
  if (msg) {
839
1131
  msg.userRating = userRating;
840
1132
  }
841
1133
  });
842
- _this3.connection.on("ReceiveTranscript", function (sessionId, text, isFinal) {
843
- if (_this3.isConversationMode) {
1134
+ _this4.connection.on("ReceiveTranscript", function (sessionId, text, isFinal) {
1135
+ if (_this4.isConversationMode) {
844
1136
  if (!isFinal && text) {
845
- _this3._conversationPartialTranscript = text;
1137
+ _this4._conversationPartialTranscript = text;
846
1138
  var escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
847
1139
  var html = '<p class="ai-partial-transcript">' + escaped + '</p>';
848
1140
 
849
1141
  // Show partial transcript as a live user message.
850
- if (!_this3._conversationPartialMessage) {
851
- _this3.hidePlaceholder();
852
- _this3._conversationPartialMessage = {
1142
+ if (!_this4._conversationPartialMessage) {
1143
+ _this4.hidePlaceholder();
1144
+ _this4._conversationPartialMessage = {
853
1145
  role: 'user',
854
1146
  content: text,
855
1147
  htmlContent: html,
856
1148
  isPartial: true
857
1149
  };
858
- _this3.messages.push(_this3._conversationPartialMessage);
1150
+ _this4.messages.push(_this4._conversationPartialMessage);
859
1151
  } else {
860
- _this3._conversationPartialMessage.content = text;
861
- _this3._conversationPartialMessage.htmlContent = html;
1152
+ _this4._conversationPartialMessage.content = text;
1153
+ _this4._conversationPartialMessage.htmlContent = html;
862
1154
  }
863
- _this3.scrollToBottom();
1155
+ _this4.scrollToBottom();
864
1156
  }
865
1157
  return;
866
1158
  }
867
- if (text && !_this3._audioInputSent) {
868
- _this3.prompt = _this3.preRecordingPrompt + text;
869
- if (_this3.inputElement) {
870
- _this3.inputElement.value = _this3.prompt;
871
- _this3.inputElement.dispatchEvent(new Event('input'));
1159
+ if (text && !_this4._audioInputSent) {
1160
+ _this4.prompt = _this4.preRecordingPrompt + text;
1161
+ if (_this4.inputElement) {
1162
+ _this4.inputElement.value = _this4.prompt;
1163
+ _this4.inputElement.dispatchEvent(new Event('input'));
872
1164
  }
873
1165
  }
874
1166
  });
875
- _this3.connection.on("ReceiveConversationUserMessage", function (sessionId, text) {
1167
+ _this4.connection.on("ReceiveConversationUserMessage", function (sessionId, text) {
876
1168
  if (text) {
877
- _this3.stopAudio();
1169
+ _this4.stopAudio();
878
1170
 
879
1171
  // If there's an interrupted assistant message still streaming,
880
1172
  // mark it as done to stop the spinner animation.
881
- if (_this3._conversationAssistantMessage) {
882
- var oldMsg = _this3.messages[_this3._conversationAssistantMessage.index];
1173
+ if (_this4._conversationAssistantMessage) {
1174
+ var oldMsg = _this4.messages[_this4._conversationAssistantMessage.index];
883
1175
  if (oldMsg) {
884
1176
  oldMsg.isStreaming = false;
885
1177
  }
886
- _this3._conversationAssistantMessage = null;
1178
+ _this4._conversationAssistantMessage = null;
887
1179
  }
888
1180
 
889
1181
  // Replace the partial transcript message with the final one.
890
- if (_this3._conversationPartialMessage) {
1182
+ if (_this4._conversationPartialMessage) {
891
1183
  var escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
892
- _this3._conversationPartialMessage.content = text;
893
- _this3._conversationPartialMessage.htmlContent = '<p>' + escaped + '</p>';
894
- _this3._conversationPartialMessage.isPartial = false;
895
- _this3._conversationPartialMessage = null;
1184
+ _this4._conversationPartialMessage.content = text;
1185
+ _this4._conversationPartialMessage.htmlContent = '<p>' + escaped + '</p>';
1186
+ _this4._conversationPartialMessage.isPartial = false;
1187
+ _this4._conversationPartialMessage = null;
896
1188
  } else {
897
- _this3.addMessage({
1189
+ _this4.addMessage({
898
1190
  role: 'user',
899
1191
  content: text
900
1192
  });
901
1193
  }
902
- _this3.scrollToBottom();
1194
+ _this4.scrollToBottom();
903
1195
  }
904
1196
  });
905
- _this3.connection.on("ReceiveConversationAssistantToken", function (sessionId, messageId, token, responseId, appearance) {
906
- if (!_this3._conversationAssistantMessage) {
907
- _this3.stopAudio();
908
- _this3.hideTypingIndicator();
1197
+ _this4.connection.on("ReceiveConversationAssistantToken", function (sessionId, messageId, token, responseId, appearance) {
1198
+ if (!_this4._conversationAssistantMessage) {
1199
+ _this4.stopAudio();
1200
+ _this4.hideTypingIndicator();
909
1201
 
910
1202
  // Ensure no stale streaming indicators remain from prior messages.
911
- for (var j = 0; j < _this3.messages.length; j++) {
912
- if (_this3.messages[j].isStreaming) {
913
- _this3.messages[j].isStreaming = false;
1203
+ for (var j = 0; j < _this4.messages.length; j++) {
1204
+ if (_this4.messages[j].isStreaming) {
1205
+ _this4.messages[j].isStreaming = false;
914
1206
  }
915
1207
  }
916
- var msgIndex = _this3.messages.length;
1208
+ var msgIndex = _this4.messages.length;
917
1209
  var newMessage = {
918
1210
  id: messageId,
919
1211
  role: "assistant",
@@ -921,72 +1213,72 @@ window.openAIChatManager = function () {
921
1213
  htmlContent: "",
922
1214
  isStreaming: true,
923
1215
  userRating: null,
924
- appearance: _this3.normalizeAssistantAppearance(appearance)
1216
+ appearance: _this4.normalizeAssistantAppearance(appearance)
925
1217
  };
926
- _this3.messages.push(newMessage);
927
- _this3._conversationAssistantMessage = {
1218
+ _this4.messages.push(newMessage);
1219
+ _this4._conversationAssistantMessage = {
928
1220
  index: msgIndex,
929
1221
  content: ''
930
1222
  };
931
1223
  }
932
- _this3._conversationAssistantMessage.content += token;
933
- var msg = _this3.messages[_this3._conversationAssistantMessage.index];
1224
+ _this4._conversationAssistantMessage.content += token;
1225
+ var msg = _this4.messages[_this4._conversationAssistantMessage.index];
934
1226
  if (msg) {
935
1227
  if (!msg.appearance) {
936
- msg.appearance = _this3.normalizeAssistantAppearance(appearance);
1228
+ msg.appearance = _this4.normalizeAssistantAppearance(appearance);
937
1229
  }
938
- msg.content = _this3._conversationAssistantMessage.content;
1230
+ msg.content = _this4._conversationAssistantMessage.content;
939
1231
  msg.htmlContent = parseMarkdownContent(msg.content, msg);
940
- _this3.$nextTick(function () {
1232
+ _this4.$nextTick(function () {
941
1233
  renderChartsInMessage(msg);
942
- _this3.scrollToBottom();
1234
+ _this4.scrollToBottom();
943
1235
  });
944
1236
  }
945
1237
  });
946
- _this3.connection.on("ReceiveConversationAssistantComplete", function (sessionId, messageId) {
947
- if (_this3._conversationAssistantMessage) {
948
- var msg = _this3.messages[_this3._conversationAssistantMessage.index];
1238
+ _this4.connection.on("ReceiveConversationAssistantComplete", function (sessionId, messageId) {
1239
+ if (_this4._conversationAssistantMessage) {
1240
+ var msg = _this4.messages[_this4._conversationAssistantMessage.index];
949
1241
  if (msg) {
950
1242
  msg.isStreaming = false;
951
1243
  }
952
- _this3._conversationAssistantMessage = null;
1244
+ _this4._conversationAssistantMessage = null;
953
1245
  }
954
1246
  });
955
- _this3.connection.on("ReceiveAudioChunk", function (sessionId, base64Audio, contentType) {
1247
+ _this4.connection.on("ReceiveAudioChunk", function (sessionId, base64Audio, contentType) {
956
1248
  if (base64Audio) {
957
1249
  var binaryString = atob(base64Audio);
958
1250
  var bytes = new Uint8Array(binaryString.length);
959
1251
  for (var i = 0; i < binaryString.length; i++) {
960
1252
  bytes[i] = binaryString.charCodeAt(i);
961
1253
  }
962
- _this3.audioChunks.push(bytes);
1254
+ _this4.audioChunks.push(bytes);
963
1255
  }
964
1256
  });
965
- _this3.connection.on("ReceiveAudioComplete", function (sessionId) {
966
- _this3.playCollectedAudio();
1257
+ _this4.connection.on("ReceiveAudioComplete", function (sessionId) {
1258
+ _this4.playCollectedAudio();
967
1259
  });
968
- _this3.connection.on("ReceiveNotification", function (notification) {
969
- _this3.receiveNotification(notification);
1260
+ _this4.connection.on("ReceiveNotification", function (notification) {
1261
+ _this4.receiveNotification(notification);
970
1262
  });
971
- _this3.connection.on("UpdateNotification", function (notification) {
972
- _this3.updateNotification(notification);
1263
+ _this4.connection.on("UpdateNotification", function (notification) {
1264
+ _this4.updateNotification(notification);
973
1265
  });
974
- _this3.connection.on("RemoveNotification", function (notificationType) {
975
- _this3.removeNotification(notificationType);
1266
+ _this4.connection.on("RemoveNotification", function (notificationType) {
1267
+ _this4.removeNotification(notificationType);
976
1268
  });
977
- _this3.connection.onreconnecting(function () {
1269
+ _this4.connection.onreconnecting(function () {
978
1270
  console.warn("SignalR: reconnecting...");
979
1271
  });
980
- _this3.connection.onreconnected(function () {
1272
+ _this4.connection.onreconnected(function () {
981
1273
  console.info("SignalR: reconnected.");
982
- if (_this3.isSessionStarted) {
983
- _this3.reloadCurrentSession();
1274
+ if (_this4.isSessionStarted) {
1275
+ _this4.reloadCurrentSession();
984
1276
  } else if (config.autoCreateSession) {
985
- _this3.startNewSession();
1277
+ _this4.startNewSession();
986
1278
  }
987
1279
  });
988
- _this3.connection.onclose(function (error) {
989
- if (_this3.isNavigatingAway) {
1280
+ _this4.connection.onclose(function (error) {
1281
+ if (_this4.isNavigatingAway) {
990
1282
  return;
991
1283
  }
992
1284
  if (error) {
@@ -995,7 +1287,7 @@ window.openAIChatManager = function () {
995
1287
  });
996
1288
  _context3.p = 1;
997
1289
  _context3.n = 2;
998
- return _this3.connection.start();
1290
+ return _this4.connection.start();
999
1291
  case 2:
1000
1292
  _context3.n = 4;
1001
1293
  break;
@@ -1010,7 +1302,7 @@ window.openAIChatManager = function () {
1010
1302
  }))();
1011
1303
  },
1012
1304
  addMessageInternal: function addMessageInternal(message) {
1013
- var _this4 = this;
1305
+ var _this5 = this;
1014
1306
  if (message.role === 'assistant') {
1015
1307
  message.appearance = this.normalizeAssistantAppearance(message.appearance);
1016
1308
  }
@@ -1024,7 +1316,7 @@ window.openAIChatManager = function () {
1024
1316
  }));
1025
1317
  this.messages.push(message);
1026
1318
  this.$nextTick(function () {
1027
- _this4.fireEvent(new CustomEvent("addedOpenAIPromotMessage", {
1319
+ _this5.fireEvent(new CustomEvent("addedOpenAIPromotMessage", {
1028
1320
  detail: {
1029
1321
  message: message
1030
1322
  }
@@ -1032,7 +1324,7 @@ window.openAIChatManager = function () {
1032
1324
  });
1033
1325
  },
1034
1326
  addMessage: function addMessage(message) {
1035
- var _this5 = this;
1327
+ var _this6 = this;
1036
1328
  // Ensure userRating is always defined for Vue reactivity.
1037
1329
  if (message.userRating === undefined) {
1038
1330
  message.userRating = null;
@@ -1120,17 +1412,17 @@ window.openAIChatManager = function () {
1120
1412
  this.$nextTick(function () {
1121
1413
  // Render any pending charts once the DOM is updated
1122
1414
  renderChartsInMessage(message);
1123
- _this5.scrollToBottom();
1415
+ _this6.scrollToBottom();
1124
1416
  });
1125
1417
  },
1126
1418
  addMessages: function addMessages(messages) {
1127
- var _this6 = this;
1419
+ var _this7 = this;
1128
1420
  for (var i = 0; i < messages.length; i++) {
1129
1421
  this.addMessageInternal(messages[i]);
1130
1422
  }
1131
1423
  this.hidePlaceholder();
1132
1424
  this.$nextTick(function () {
1133
- _this6.scrollToBottom();
1425
+ _this7.scrollToBottom();
1134
1426
  });
1135
1427
  },
1136
1428
  hidePlaceholder: function hidePlaceholder() {
@@ -1207,7 +1499,7 @@ window.openAIChatManager = function () {
1207
1499
  this.prompt = '';
1208
1500
  },
1209
1501
  startRecording: function startRecording() {
1210
- var _this7 = this;
1502
+ var _this8 = this;
1211
1503
  if (this.isRecording || !this.connection) {
1212
1504
  return;
1213
1505
  }
@@ -1219,17 +1511,17 @@ window.openAIChatManager = function () {
1219
1511
  }
1220
1512
  }).then(function (stream) {
1221
1513
  var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
1222
- _this7.mediaRecorder = new MediaRecorder(stream, {
1514
+ _this8.mediaRecorder = new MediaRecorder(stream, {
1223
1515
  mimeType: mimeType,
1224
1516
  audioBitsPerSecond: 128000
1225
1517
  });
1226
- _this7.preRecordingPrompt = _this7.prompt;
1227
- _this7._audioInputSent = false;
1518
+ _this8.preRecordingPrompt = _this8.prompt;
1519
+ _this8._audioInputSent = false;
1228
1520
  var subject = new signalR.Subject();
1229
- var profileId = _this7.getProfileId();
1230
- var sessionId = _this7.getSessionId() || '';
1521
+ var profileId = _this8.getProfileId();
1522
+ var sessionId = _this8.getSessionId() || '';
1231
1523
  var pendingChunk = Promise.resolve();
1232
- _this7.mediaRecorder.addEventListener('dataavailable', function (e) {
1524
+ _this8.mediaRecorder.addEventListener('dataavailable', function (e) {
1233
1525
  if (e.data && e.data.size > 0) {
1234
1526
  pendingChunk = pendingChunk.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
1235
1527
  var data, uint8Array, binaryString, base64;
@@ -1253,7 +1545,7 @@ window.openAIChatManager = function () {
1253
1545
  })));
1254
1546
  }
1255
1547
  });
1256
- _this7.mediaRecorder.addEventListener('stop', function () {
1548
+ _this8.mediaRecorder.addEventListener('stop', function () {
1257
1549
  stream.getTracks().forEach(function (track) {
1258
1550
  return track.stop();
1259
1551
  });
@@ -1262,10 +1554,10 @@ window.openAIChatManager = function () {
1262
1554
  });
1263
1555
  });
1264
1556
  var language = navigator.language || document.documentElement.lang || 'en-US';
1265
- _this7.connection.send("SendAudioStream", profileId, sessionId, subject, mimeType, language);
1266
- _this7.mediaRecorder.start(250);
1267
- _this7.isRecording = true;
1268
- _this7.updateMicButton();
1557
+ _this8.connection.send("SendAudioStream", profileId, sessionId, subject, mimeType, language);
1558
+ _this8.mediaRecorder.start(250);
1559
+ _this8.isRecording = true;
1560
+ _this8.updateMicButton();
1269
1561
  })["catch"](function (err) {
1270
1562
  console.error('Microphone access denied:', err);
1271
1563
  });
@@ -1298,7 +1590,7 @@ window.openAIChatManager = function () {
1298
1590
  }
1299
1591
  },
1300
1592
  streamMessage: function streamMessage(profileId, trimmedPrompt, sessionProfileId) {
1301
- var _this8 = this;
1593
+ var _this9 = this;
1302
1594
  if (this.stream) {
1303
1595
  this.stream.dispose();
1304
1596
  this.stream = null;
@@ -1315,14 +1607,14 @@ window.openAIChatManager = function () {
1315
1607
  var currentSessionId = this.getSessionId();
1316
1608
  this.stream = this.connection.stream("SendMessage", profileId, trimmedPrompt, currentSessionId, sessionProfileId).subscribe({
1317
1609
  next: function next(chunk) {
1318
- var message = _this8.messages[messageIndex];
1610
+ var message = _this9.messages[messageIndex];
1319
1611
  if (!message) {
1320
1612
  if (chunk.sessionId && !currentSessionId) {
1321
- _this8.initializeSession(chunk.sessionId);
1613
+ _this9.initializeSession(chunk.sessionId);
1322
1614
  }
1323
- _this8.hideTypingIndicator();
1615
+ _this9.hideTypingIndicator();
1324
1616
  // Re-assign the index after hiding the typing indicator.
1325
- messageIndex = _this8.messages.length;
1617
+ messageIndex = _this9.messages.length;
1326
1618
  var newMessage = {
1327
1619
  id: chunk.messageId,
1328
1620
  role: "assistant",
@@ -1332,7 +1624,7 @@ window.openAIChatManager = function () {
1332
1624
  isStreaming: true,
1333
1625
  userRating: null
1334
1626
  };
1335
- _this8.messages.push(newMessage);
1627
+ _this9.messages.push(newMessage);
1336
1628
  message = newMessage;
1337
1629
  }
1338
1630
  if (chunk.title && (!message.title || message.title !== chunk.title)) {
@@ -1372,46 +1664,46 @@ window.openAIChatManager = function () {
1372
1664
  // Update the existing message
1373
1665
  message.content = content;
1374
1666
  message.htmlContent = parseMarkdownContent(content, message);
1375
- _this8.messages[messageIndex] = message;
1376
- _this8.$nextTick(function () {
1667
+ _this9.messages[messageIndex] = message;
1668
+ _this9.$nextTick(function () {
1377
1669
  renderChartsInMessage(message);
1378
- _this8.scrollToBottom();
1670
+ _this9.scrollToBottom();
1379
1671
  });
1380
1672
  },
1381
1673
  complete: function complete() {
1382
- var _this8$stream;
1383
- _this8.processReferences(references, messageIndex);
1384
- _this8.streamingFinished();
1385
- var msg = _this8.messages[messageIndex];
1674
+ var _this9$stream;
1675
+ _this9.processReferences(references, messageIndex);
1676
+ _this9.streamingFinished();
1677
+ var msg = _this9.messages[messageIndex];
1386
1678
  if (msg) {
1387
1679
  msg.isStreaming = false;
1388
1680
  }
1389
1681
  if (!msg || !msg.content) {
1390
1682
  // No content received at all.
1391
- _this8.hideTypingIndicator();
1683
+ _this9.hideTypingIndicator();
1392
1684
  }
1393
1685
 
1394
1686
  // Trigger text-to-speech only in conversation mode.
1395
- if (_this8.isConversationMode && _this8.textToSpeechEnabled && msg && msg.content) {
1396
- _this8.synthesizeSpeech(msg.content);
1687
+ if (_this9.isConversationMode && _this9.textToSpeechEnabled && msg && msg.content) {
1688
+ _this9.synthesizeSpeech(msg.content);
1397
1689
  }
1398
- (_this8$stream = _this8.stream) === null || _this8$stream === void 0 || _this8$stream.dispose();
1399
- _this8.stream = null;
1690
+ (_this9$stream = _this9.stream) === null || _this9$stream === void 0 || _this9$stream.dispose();
1691
+ _this9.stream = null;
1400
1692
  },
1401
1693
  error: function error(err) {
1402
- var _this8$stream2;
1403
- _this8.processReferences(references, messageIndex);
1404
- _this8.streamingFinished();
1405
- var msg = _this8.messages[messageIndex];
1694
+ var _this9$stream2;
1695
+ _this9.processReferences(references, messageIndex);
1696
+ _this9.streamingFinished();
1697
+ var msg = _this9.messages[messageIndex];
1406
1698
  if (msg) {
1407
1699
  msg.isStreaming = false;
1408
1700
  }
1409
- _this8.hideTypingIndicator();
1410
- if (!_this8.isNavigatingAway) {
1411
- _this8.addMessage(_this8.getServiceDownMessage());
1701
+ _this9.hideTypingIndicator();
1702
+ if (!_this9.isNavigatingAway) {
1703
+ _this9.addMessage(_this9.getServiceDownMessage());
1412
1704
  }
1413
- (_this8$stream2 = _this8.stream) === null || _this8$stream2 === void 0 || _this8$stream2.dispose();
1414
- _this8.stream = null;
1705
+ (_this9$stream2 = _this9.stream) === null || _this9$stream2 === void 0 || _this9$stream2.dispose();
1706
+ _this9.stream = null;
1415
1707
  console.error("Stream error:", err);
1416
1708
  }
1417
1709
  });
@@ -1425,7 +1717,7 @@ window.openAIChatManager = function () {
1425
1717
  return newMessage;
1426
1718
  },
1427
1719
  processReferences: function processReferences(references, messageIndex) {
1428
- var _this9 = this;
1720
+ var _this0 = this;
1429
1721
  references = normalizeReferences(references);
1430
1722
  if (Object.keys(references).length) {
1431
1723
  var message = this.messages[messageIndex];
@@ -1510,7 +1802,7 @@ window.openAIChatManager = function () {
1510
1802
  this.messages[messageIndex] = message;
1511
1803
  this.$nextTick(function () {
1512
1804
  renderChartsInMessage(message);
1513
- _this9.scrollToBottom();
1805
+ _this0.scrollToBottom();
1514
1806
  });
1515
1807
  }
1516
1808
  },
@@ -1553,8 +1845,15 @@ window.openAIChatManager = function () {
1553
1845
  }
1554
1846
  }
1555
1847
  },
1848
+ handleExternalTtsStop: function handleExternalTtsStop(e) {
1849
+ var _e$detail;
1850
+ if ((e === null || e === void 0 || (_e$detail = e.detail) === null || _e$detail === void 0 ? void 0 : _e$detail.sourceId) === this.ttsInstanceId) {
1851
+ return;
1852
+ }
1853
+ this.stopAudio(false);
1854
+ },
1556
1855
  synthesizeSpeech: function synthesizeSpeech(text, cacheIndex) {
1557
- var _this0 = this;
1856
+ var _this1 = this;
1558
1857
  if (!this.textToSpeechEnabled || !text || !this.connection) {
1559
1858
  return;
1560
1859
  }
@@ -1563,9 +1862,9 @@ window.openAIChatManager = function () {
1563
1862
  this._ttsCacheIndex = cacheIndex !== undefined ? cacheIndex : -1;
1564
1863
  this.connection.invoke("SynthesizeSpeech", this.getProfileId(), this.getSessionId(), text, this.ttsVoiceName)["catch"](function (err) {
1565
1864
  console.error("TTS synthesis error:", err);
1566
- _this0.isPlayingAudio = false;
1567
- _this0.ttsPlayingMessageIndex = -1;
1568
- _this0._ttsCacheIndex = -1;
1865
+ _this1.isPlayingAudio = false;
1866
+ _this1.ttsPlayingMessageIndex = -1;
1867
+ _this1._ttsCacheIndex = -1;
1569
1868
  });
1570
1869
  },
1571
1870
  toggleMessageTts: function toggleMessageTts(message, index) {
@@ -1573,7 +1872,12 @@ window.openAIChatManager = function () {
1573
1872
  this.stopAudio();
1574
1873
  return;
1575
1874
  }
1576
- this.stopAudio();
1875
+ this.stopAudio(false);
1876
+ window.dispatchEvent(new CustomEvent('crestapps-ai-chat-stop-tts', {
1877
+ detail: {
1878
+ sourceId: this.ttsInstanceId
1879
+ }
1880
+ }));
1577
1881
  this.ttsPlayingMessageIndex = index;
1578
1882
  if (this.ttsAudioCache[index]) {
1579
1883
  this.playAudioBlob(this.ttsAudioCache[index]);
@@ -1585,6 +1889,7 @@ window.openAIChatManager = function () {
1585
1889
  if (this.audioChunks.length === 0) {
1586
1890
  if (!this.currentAudioElement && this.audioPlayQueue.length === 0) {
1587
1891
  this.isPlayingAudio = false;
1892
+ this.ttsPlayingMessageIndex = -1;
1588
1893
  }
1589
1894
  return;
1590
1895
  }
@@ -1623,26 +1928,32 @@ window.openAIChatManager = function () {
1623
1928
  this.playAudioBlob(blob);
1624
1929
  },
1625
1930
  playAudioBlob: function playAudioBlob(blob) {
1626
- var _this1 = this;
1931
+ var _this10 = this;
1627
1932
  var url = URL.createObjectURL(blob);
1628
1933
  var audio = new Audio(url);
1934
+ this.currentAudioUrl = url;
1629
1935
  this.currentAudioElement = audio;
1630
1936
  this.isPlayingAudio = true;
1631
1937
  audio.addEventListener('ended', function () {
1938
+ _this10.currentAudioElement = null;
1939
+ _this10.currentAudioUrl = null;
1632
1940
  URL.revokeObjectURL(url);
1633
- _this1.currentAudioElement = null;
1634
- _this1.playNextInQueue();
1941
+ _this10.playNextInQueue();
1635
1942
  });
1636
1943
  audio.addEventListener('error', function () {
1944
+ _this10.currentAudioElement = null;
1945
+ _this10.currentAudioUrl = null;
1637
1946
  URL.revokeObjectURL(url);
1638
- _this1.currentAudioElement = null;
1639
- _this1.playNextInQueue();
1947
+ _this10.playNextInQueue();
1640
1948
  });
1641
1949
  audio.play()["catch"](function (err) {
1642
1950
  console.error("Audio playback error:", err);
1951
+ _this10.currentAudioElement = null;
1952
+ _this10.currentAudioUrl = null;
1643
1953
  URL.revokeObjectURL(url);
1644
- _this1.currentAudioElement = null;
1645
- _this1.isPlayingAudio = false;
1954
+ _this10.audioPlayQueue = [];
1955
+ _this10.isPlayingAudio = false;
1956
+ _this10.ttsPlayingMessageIndex = -1;
1646
1957
  });
1647
1958
  },
1648
1959
  playNextInQueue: function playNextInQueue() {
@@ -1661,6 +1972,10 @@ window.openAIChatManager = function () {
1661
1972
  this.currentAudioElement.currentTime = 0;
1662
1973
  this.currentAudioElement = null;
1663
1974
  }
1975
+ if (this.currentAudioUrl) {
1976
+ URL.revokeObjectURL(this.currentAudioUrl);
1977
+ this.currentAudioUrl = null;
1978
+ }
1664
1979
  this.audioChunks = [];
1665
1980
  this.audioPlayQueue = [];
1666
1981
  this.isPlayingAudio = false;
@@ -1674,7 +1989,7 @@ window.openAIChatManager = function () {
1674
1989
  }
1675
1990
  },
1676
1991
  startConversationMode: function startConversationMode() {
1677
- var _this10 = this;
1992
+ var _this11 = this;
1678
1993
  if (!this.conversationModeEnabled || this.isConversationMode || !this.connection) {
1679
1994
  return;
1680
1995
  }
@@ -1694,12 +2009,12 @@ window.openAIChatManager = function () {
1694
2009
  }
1695
2010
  }).then(function (stream) {
1696
2011
  var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
1697
- _this10.mediaRecorder = new MediaRecorder(stream, {
2012
+ _this11.mediaRecorder = new MediaRecorder(stream, {
1698
2013
  mimeType: mimeType,
1699
2014
  audioBitsPerSecond: 128000
1700
2015
  });
1701
- _this10._conversationSubject = new signalR.Subject();
1702
- _this10._conversationStream = stream;
2016
+ _this11._conversationSubject = new signalR.Subject();
2017
+ _this11._conversationStream = stream;
1703
2018
 
1704
2019
  // Create an AnalyserNode for volume-based interrupt detection.
1705
2020
  // During TTS playback, detect when the user speaks above
@@ -1708,20 +2023,20 @@ window.openAIChatManager = function () {
1708
2023
  // speaker echo so the STT stream has no gaps.
1709
2024
  var AudioCtx = window.AudioContext || window.webkitAudioContext;
1710
2025
  if (AudioCtx) {
1711
- _this10._conversationAudioCtx = new AudioCtx();
1712
- _this10._conversationAnalyser = _this10._conversationAudioCtx.createAnalyser();
1713
- _this10._conversationAnalyser.fftSize = 256;
1714
- var micSource = _this10._conversationAudioCtx.createMediaStreamSource(stream);
1715
- micSource.connect(_this10._conversationAnalyser);
2026
+ _this11._conversationAudioCtx = new AudioCtx();
2027
+ _this11._conversationAnalyser = _this11._conversationAudioCtx.createAnalyser();
2028
+ _this11._conversationAnalyser.fftSize = 256;
2029
+ var micSource = _this11._conversationAudioCtx.createMediaStreamSource(stream);
2030
+ micSource.connect(_this11._conversationAnalyser);
1716
2031
  }
1717
2032
  var pendingChunk = Promise.resolve();
1718
- var analyser = _this10._conversationAnalyser;
2033
+ var analyser = _this11._conversationAnalyser;
1719
2034
  var interruptVolumeThreshold = 30;
1720
- _this10.mediaRecorder.addEventListener('dataavailable', function (e) {
2035
+ _this11.mediaRecorder.addEventListener('dataavailable', function (e) {
1721
2036
  if (e.data && e.data.size > 0) {
1722
2037
  // During TTS playback, check mic volume to detect
1723
2038
  // user interruption (speaking above threshold).
1724
- if (_this10.isPlayingAudio && analyser) {
2039
+ if (_this11.isPlayingAudio && analyser) {
1725
2040
  var freqData = new Uint8Array(analyser.frequencyBinCount);
1726
2041
  analyser.getByteFrequencyData(freqData);
1727
2042
  var sum = 0;
@@ -1731,7 +2046,7 @@ window.openAIChatManager = function () {
1731
2046
  var avg = sum / freqData.length;
1732
2047
  if (avg >= interruptVolumeThreshold) {
1733
2048
  // User is speaking — interrupt TTS playback.
1734
- _this10.stopAudio();
2049
+ _this11.stopAudio();
1735
2050
  }
1736
2051
  }
1737
2052
 
@@ -1753,7 +2068,7 @@ window.openAIChatManager = function () {
1753
2068
  }, '');
1754
2069
  base64 = btoa(binaryString);
1755
2070
  try {
1756
- _this10._conversationSubject.next(base64);
2071
+ _this11._conversationSubject.next(base64);
1757
2072
  } catch (err) {
1758
2073
  // Subject may have been completed already.
1759
2074
  }
@@ -1764,28 +2079,28 @@ window.openAIChatManager = function () {
1764
2079
  })));
1765
2080
  }
1766
2081
  });
1767
- _this10.mediaRecorder.addEventListener('stop', function () {
2082
+ _this11.mediaRecorder.addEventListener('stop', function () {
1768
2083
  stream.getTracks().forEach(function (track) {
1769
2084
  return track.stop();
1770
2085
  });
1771
2086
  pendingChunk.then(function () {
1772
2087
  try {
1773
- _this10._conversationSubject.complete();
2088
+ _this11._conversationSubject.complete();
1774
2089
  } catch (err) {
1775
2090
  // Already completed.
1776
2091
  }
1777
2092
  });
1778
2093
  });
1779
- var profileId = _this10.getProfileId();
1780
- var sessionId = _this10.getSessionId() || '';
2094
+ var profileId = _this11.getProfileId();
2095
+ var sessionId = _this11.getSessionId() || '';
1781
2096
  var language = navigator.language || document.documentElement.lang || 'en-US';
1782
- _this10.connection.send("StartConversation", profileId, sessionId, _this10._conversationSubject, mimeType, language);
1783
- _this10.mediaRecorder.start(250);
1784
- _this10.isRecording = true;
2097
+ _this11.connection.send("StartConversation", profileId, sessionId, _this11._conversationSubject, mimeType, language);
2098
+ _this11.mediaRecorder.start(250);
2099
+ _this11.isRecording = true;
1785
2100
  })["catch"](function (err) {
1786
2101
  console.error('Microphone access denied:', err);
1787
- _this10.isConversationMode = false;
1788
- _this10.updateConversationButton();
2102
+ _this11.isConversationMode = false;
2103
+ _this11.updateConversationButton();
1789
2104
  });
1790
2105
  },
1791
2106
  stopConversationMode: function stopConversationMode() {
@@ -1912,7 +2227,7 @@ window.openAIChatManager = function () {
1912
2227
  return removedCount;
1913
2228
  },
1914
2229
  receiveNotification: function receiveNotification(notification) {
1915
- var _this11 = this;
2230
+ var _this12 = this;
1916
2231
  if (!notification || !notification.type) {
1917
2232
  return;
1918
2233
  }
@@ -1927,7 +2242,7 @@ window.openAIChatManager = function () {
1927
2242
  }
1928
2243
  this.scheduleNotificationDismiss(notification);
1929
2244
  this.$nextTick(function () {
1930
- _this11.scrollToBottom();
2245
+ _this12.scrollToBottom();
1931
2246
  });
1932
2247
  },
1933
2248
  updateNotification: function updateNotification(notification) {
@@ -1944,12 +2259,12 @@ window.openAIChatManager = function () {
1944
2259
  }
1945
2260
  },
1946
2261
  scheduleNotificationDismiss: function scheduleNotificationDismiss(notification) {
1947
- var _this12 = this;
2262
+ var _this13 = this;
1948
2263
  if (!notification || !notification.type || !notification.autoDismissMs || notification.autoDismissMs <= 0) {
1949
2264
  return;
1950
2265
  }
1951
2266
  this.notificationDismissTimers[notification.type] = setTimeout(function () {
1952
- _this12.removeNotification(notification.type);
2267
+ _this13.removeNotification(notification.type);
1953
2268
  }, notification.autoDismissMs);
1954
2269
  },
1955
2270
  clearNotificationDismiss: function clearNotificationDismiss(notificationType) {
@@ -1979,12 +2294,12 @@ window.openAIChatManager = function () {
1979
2294
  });
1980
2295
  },
1981
2296
  scrollToBottom: function scrollToBottom() {
1982
- var _this13 = this;
2297
+ var _this14 = this;
1983
2298
  if (!this.autoScroll) {
1984
2299
  return;
1985
2300
  }
1986
2301
  setTimeout(function () {
1987
- _this13.chatContainer.scrollTop = _this13.chatContainer.scrollHeight - _this13.chatContainer.clientHeight;
2302
+ _this14.chatContainer.scrollTop = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight;
1988
2303
  }, 50);
1989
2304
  },
1990
2305
  handleUserInput: function handleUserInput(event) {
@@ -2026,16 +2341,16 @@ window.openAIChatManager = function () {
2026
2341
  });
2027
2342
  },
2028
2343
  initializeApp: function initializeApp() {
2029
- var _this14 = this;
2344
+ var _this15 = this;
2030
2345
  this.inputElement = document.querySelector(config.inputElementSelector);
2031
2346
  this.buttonElement = document.querySelector(config.sendButtonElementSelector);
2032
2347
  this.chatContainer = document.querySelector(config.chatContainerElementSelector);
2033
2348
  this.placeholder = document.querySelector(config.placeholderElementSelector);
2034
2349
  this.applyOrchestratorAvailability();
2035
2350
  var sessionId = this.getSessionId();
2036
- if (!config.widget && sessionId) {
2351
+ if (!hasWidgetConfig && sessionId) {
2037
2352
  this.loadSession(sessionId);
2038
- } else if (this.isOrchestratorAvailable() && config.autoCreateSession && !config.widget && !sessionId) {
2353
+ } else if (this.isOrchestratorAvailable() && config.autoCreateSession && !hasWidgetConfig && !sessionId) {
2039
2354
  this.startNewSession();
2040
2355
  }
2041
2356
 
@@ -2055,7 +2370,7 @@ window.openAIChatManager = function () {
2055
2370
  fileInput.accept = config.allowedExtensions;
2056
2371
  }
2057
2372
  fileInput.addEventListener('change', function (e) {
2058
- return _this14.handleFileInputChange(e);
2373
+ return _this15.handleFileInputChange(e);
2059
2374
  });
2060
2375
  this.documentBar.parentElement.appendChild(fileInput);
2061
2376
 
@@ -2063,13 +2378,13 @@ window.openAIChatManager = function () {
2063
2378
  var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
2064
2379
  if (inputArea) {
2065
2380
  inputArea.addEventListener('dragover', function (e) {
2066
- return _this14.handleDragOver(e);
2381
+ return _this15.handleDragOver(e);
2067
2382
  });
2068
2383
  inputArea.addEventListener('dragleave', function (e) {
2069
- return _this14.handleDragLeave(e);
2384
+ return _this15.handleDragLeave(e);
2070
2385
  });
2071
2386
  inputArea.addEventListener('drop', function (e) {
2072
- return _this14.handleDrop(e);
2387
+ return _this15.handleDrop(e);
2073
2388
  });
2074
2389
  }
2075
2390
  }
@@ -2077,55 +2392,55 @@ window.openAIChatManager = function () {
2077
2392
 
2078
2393
  // Pause auto-scroll when the user manually scrolls up during streaming.
2079
2394
  this.chatContainer.addEventListener('scroll', function () {
2080
- if (!_this14.stream) {
2395
+ if (!_this15.stream) {
2081
2396
  return;
2082
2397
  }
2083
2398
  var threshold = 30;
2084
- var atBottom = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight - _this14.chatContainer.scrollTop <= threshold;
2085
- _this14.autoScroll = atBottom;
2399
+ var atBottom = _this15.chatContainer.scrollHeight - _this15.chatContainer.clientHeight - _this15.chatContainer.scrollTop <= threshold;
2400
+ _this15.autoScroll = atBottom;
2086
2401
  });
2087
2402
  this.inputElement.addEventListener('keydown', function (event) {
2088
- if (_this14.stream != null) {
2403
+ if (_this15.stream != null) {
2089
2404
  return;
2090
2405
  }
2091
2406
  if (event.key === "Enter" && !event.shiftKey) {
2092
2407
  event.preventDefault();
2093
- _this14.buttonElement.click();
2408
+ _this15.buttonElement.click();
2094
2409
  }
2095
2410
  });
2096
2411
  this.inputElement.addEventListener('input', function (e) {
2097
- _this14.handleUserInput(e);
2412
+ _this15.handleUserInput(e);
2098
2413
  if (e.target.value.trim()) {
2099
- _this14.buttonElement.removeAttribute('disabled');
2414
+ _this15.buttonElement.removeAttribute('disabled');
2100
2415
  } else {
2101
- _this14.buttonElement.setAttribute('disabled', true);
2416
+ _this15.buttonElement.setAttribute('disabled', true);
2102
2417
  }
2103
2418
  });
2104
2419
  this.buttonElement.addEventListener('click', function () {
2105
- if (_this14.stream != null) {
2106
- _this14.stream.dispose();
2107
- _this14.stream = null;
2108
- _this14.streamingFinished();
2109
- _this14.hideTypingIndicator();
2420
+ if (_this15.stream != null) {
2421
+ _this15.stream.dispose();
2422
+ _this15.stream = null;
2423
+ _this15.streamingFinished();
2424
+ _this15.hideTypingIndicator();
2110
2425
 
2111
2426
  // Clean up: remove empty assistant message or stop streaming animation.
2112
- if (_this14.messages.length > 0) {
2113
- var lastMsg = _this14.messages[_this14.messages.length - 1];
2427
+ if (_this15.messages.length > 0) {
2428
+ var lastMsg = _this15.messages[_this15.messages.length - 1];
2114
2429
  if (lastMsg.role === 'assistant' && !lastMsg.content) {
2115
- _this14.messages.pop();
2430
+ _this15.messages.pop();
2116
2431
  } else if (lastMsg.isStreaming) {
2117
2432
  lastMsg.isStreaming = false;
2118
2433
  }
2119
2434
  }
2120
2435
  return;
2121
2436
  }
2122
- _this14.sendMessage();
2437
+ _this15.sendMessage();
2123
2438
  });
2124
2439
  var promptGenerators = document.getElementsByClassName('profile-generated-prompt');
2125
2440
  for (var i = 0; i < promptGenerators.length; i++) {
2126
2441
  promptGenerators[i].addEventListener('click', function (e) {
2127
2442
  e.preventDefault();
2128
- _this14.generatePrompt(e.target);
2443
+ _this15.generatePrompt(e.target);
2129
2444
  });
2130
2445
  }
2131
2446
  var chatSessions = document.getElementsByClassName('chat-session-history-item');
@@ -2137,8 +2452,8 @@ window.openAIChatManager = function () {
2137
2452
  console.error('an element with the class chat-session-history-item with no data-session-id set.');
2138
2453
  return;
2139
2454
  }
2140
- _this14.loadSession(sessionId);
2141
- _this14.showChatScreen();
2455
+ _this15.loadSession(sessionId);
2456
+ _this15.showChatScreen();
2142
2457
  });
2143
2458
  }
2144
2459
  for (var _i5 = 0; _i5 < config.messages.length; _i5++) {
@@ -2147,7 +2462,7 @@ window.openAIChatManager = function () {
2147
2462
 
2148
2463
  // Update feedback icons in the DOM after initial messages have rendered.
2149
2464
  this.$nextTick(function () {
2150
- _this14.refreshAllFeedbackIcons();
2465
+ _this15.refreshAllFeedbackIcons();
2151
2466
  });
2152
2467
 
2153
2468
  // Delegate click for code block copy buttons.
@@ -2179,7 +2494,7 @@ window.openAIChatManager = function () {
2179
2494
  if (this.micButton) {
2180
2495
  this.micButton.style.display = '';
2181
2496
  this.micButton.addEventListener('click', function () {
2182
- _this14.toggleRecording();
2497
+ _this15.toggleRecording();
2183
2498
  });
2184
2499
  }
2185
2500
  }
@@ -2189,7 +2504,7 @@ window.openAIChatManager = function () {
2189
2504
  this.conversationButton = document.querySelector(config.conversationButtonElementSelector);
2190
2505
  if (this.conversationButton) {
2191
2506
  this.conversationButton.addEventListener('click', function () {
2192
- _this14.toggleConversationMode();
2507
+ _this15.toggleConversationMode();
2193
2508
  });
2194
2509
  }
2195
2510
  }
@@ -2221,7 +2536,7 @@ window.openAIChatManager = function () {
2221
2536
  }
2222
2537
  },
2223
2538
  initializeWidget: function initializeWidget() {
2224
- var _this15 = this;
2539
+ var _this16 = this;
2225
2540
  if (!config.widget.chatWidgetContainer) {
2226
2541
  console.error('The widget chatWidgetContainer is required.');
2227
2542
  return;
@@ -2239,7 +2554,51 @@ window.openAIChatManager = function () {
2239
2554
  }
2240
2555
  this.chatWidgetStateName = config.widget.chatWidgetStateName;
2241
2556
  this.chatWidgetStateSession = config.widget.chatWidgetStateName + 'Session';
2557
+ this.chatWidgetLayoutKey = config.widget.chatWidgetStateName + 'Layout';
2558
+ this.widgetContainerElement = chatWidgetContainer;
2559
+ this.widgetToggleButtonElement = config.widget.toggleButtonSelector ? document.querySelector(config.widget.toggleButtonSelector) : null;
2560
+ this.widgetResetSizeButtonElement = config.widget.resetSizeButtonSelector ? document.querySelector(config.widget.resetSizeButtonSelector) : null;
2561
+ this.widgetDefaultWidth = parsePixelValue(config.widget.defaultWidth) || parsePixelValue(window.getComputedStyle(chatWidgetContainer).width) || 380;
2562
+ this.widgetDefaultHeight = parsePixelValue(config.widget.defaultHeight) || parsePixelValue(window.getComputedStyle(chatWidgetContainer).height) || 520;
2242
2563
  this.widgetIsInitialized = true;
2564
+ if (config.widget.enableResizing) {
2565
+ chatWidgetContainer.classList.add('ai-chat-widget-resizable');
2566
+ chatWidgetContainer.style.minWidth = (parsePixelValue(config.widget.minWidth) || 320) + 'px';
2567
+ chatWidgetContainer.style.minHeight = (parsePixelValue(config.widget.minHeight) || 420) + 'px';
2568
+ if (typeof ResizeObserver !== 'undefined') {
2569
+ this.widgetResizeObserver = new ResizeObserver(function () {
2570
+ _this16.persistWidgetPanelSize();
2571
+ _this16.constrainWidgetElementsToViewport();
2572
+ _this16.updateWidgetResetSizeButtonVisibility();
2573
+ });
2574
+ this.widgetResizeObserver.observe(chatWidgetContainer);
2575
+ }
2576
+ }
2577
+ var dragHandleSelector = config.widget.dragHandleSelector || '.ai-chat-widget-header';
2578
+ var dragHandle = chatWidgetContainer.querySelector(dragHandleSelector);
2579
+ if (config.widget.enableDragging && dragHandle) {
2580
+ dragHandle.classList.add('ai-chat-widget-drag-handle');
2581
+ chatWidgetContainer.classList.add('ai-chat-widget-draggable');
2582
+ this.attachDraggableWidgetElement(chatWidgetContainer, dragHandle, 'panel', false);
2583
+ }
2584
+ if (config.widget.enableDragging && this.widgetToggleButtonElement) {
2585
+ this.widgetToggleButtonElement.classList.add('ai-chat-widget-draggable');
2586
+ this.attachDraggableWidgetElement(this.widgetToggleButtonElement, this.widgetToggleButtonElement, 'toggle', true);
2587
+ }
2588
+ if (this.widgetResetSizeButtonElement) {
2589
+ this.widgetResetSizeButtonElement.addEventListener('click', function (event) {
2590
+ event.preventDefault();
2591
+ _this16.resetWidgetSize();
2592
+ });
2593
+ }
2594
+ this.restoreWidgetLayout();
2595
+ if (!this.widgetViewportResizeHandler) {
2596
+ this.widgetViewportResizeHandler = function () {
2597
+ _this16.constrainWidgetElementsToViewport();
2598
+ _this16.updateWidgetResetSizeButtonVisibility();
2599
+ };
2600
+ window.addEventListener('resize', this.widgetViewportResizeHandler);
2601
+ }
2243
2602
 
2244
2603
  // Auto-load the last session so the user always sees previous chat history.
2245
2604
  this.reloadCurrentSession();
@@ -2250,14 +2609,14 @@ window.openAIChatManager = function () {
2250
2609
  var showHistoryButton = document.querySelector(config.widget.showHistoryButton);
2251
2610
  if (showHistoryButton) {
2252
2611
  showHistoryButton.addEventListener('click', function () {
2253
- _this15.chatHistorySection.classList.toggle('show');
2612
+ _this16.chatHistorySection.classList.toggle('show');
2254
2613
  });
2255
2614
  }
2256
2615
  if (config.widget.closeHistoryButton) {
2257
2616
  var closeHistoryButton = document.querySelector(config.widget.closeHistoryButton);
2258
2617
  if (closeHistoryButton) {
2259
2618
  closeHistoryButton.addEventListener('click', function () {
2260
- _this15.showChatScreen();
2619
+ _this16.showChatScreen();
2261
2620
  });
2262
2621
  }
2263
2622
  }
@@ -2266,8 +2625,8 @@ window.openAIChatManager = function () {
2266
2625
  var newChatButton = document.querySelector(config.widget.newChatButton);
2267
2626
  if (newChatButton) {
2268
2627
  newChatButton.addEventListener('click', function () {
2269
- _this15.resetSession();
2270
- _this15.showChatScreen();
2628
+ _this16.resetSession();
2629
+ _this16.showChatScreen();
2271
2630
  });
2272
2631
  }
2273
2632
  }
@@ -2403,17 +2762,17 @@ window.openAIChatManager = function () {
2403
2762
  }
2404
2763
  },
2405
2764
  mounted: function mounted() {
2406
- var _this16 = this;
2765
+ var _this17 = this;
2407
2766
  _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
2408
2767
  return _regenerator().w(function (_context6) {
2409
2768
  while (1) switch (_context6.n) {
2410
2769
  case 0:
2411
2770
  _context6.n = 1;
2412
- return _this16.startConnection();
2771
+ return _this17.startConnection();
2413
2772
  case 1:
2414
- _this16.initializeApp();
2415
- if (config.widget) {
2416
- _this16.initializeWidget();
2773
+ _this17.initializeApp();
2774
+ if (hasWidgetConfig) {
2775
+ _this17.initializeWidget();
2417
2776
  }
2418
2777
  case 2:
2419
2778
  return _context6.a(2);
@@ -2421,9 +2780,12 @@ window.openAIChatManager = function () {
2421
2780
  }, _callee6);
2422
2781
  }))();
2423
2782
  window.addEventListener('beforeunload', this.handleBeforeUnload);
2783
+ window.addEventListener('crestapps-ai-chat-stop-tts', this.handleExternalTtsStop);
2424
2784
  },
2425
2785
  beforeUnmount: function beforeUnmount() {
2426
2786
  window.removeEventListener('beforeunload', this.handleBeforeUnload);
2787
+ window.removeEventListener('crestapps-ai-chat-stop-tts', this.handleExternalTtsStop);
2788
+ this.stopAudio(false);
2427
2789
  if (this.stream) {
2428
2790
  this.stream.dispose();
2429
2791
  this.stream = null;