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

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
@@ -52,8 +52,8 @@ window.openAIChatManager = function () {
52
52
  minWidth: 320,
53
53
  minHeight: 420
54
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 ",
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 "
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)\"><span :class=\"getAssistantIcon(message)\"></span></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 <span class=\"fa-regular fa-thumbs-up\"></span>\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 <span class=\"fa-regular fa-thumbs-down\"></span>\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 }\" :data-tts-message-index=\"index\" @click=\"toggleMessageTts(message, index)\" :title=\"ttsPlayingMessageIndex === index ? 'Pause audio' : 'Read aloud'\">\n <span :class=\"ttsPlayingMessageIndex === index ? 'fa-solid fa-circle-pause' : 'fa-solid fa-circle-play'\"></span>\n </button>\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" :title=\"copyTitle\">\n <span class=\"fa-solid fa-copy\"></span>\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 <span v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></span>\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 <span class=\"fa-solid fa-xmark\"></span>\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 <span v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></span>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
56
+ indicatorTemplate: "\n <div class=\"ai-chat-msg-role ai-chat-msg-role-assistant\">\n <span class=\"ai-streaming-icon\"><span class=\"fa fa-robot\" style=\"display: inline-block;\"></span></span>\n Assistant\n </div>\n "
57
57
  };
58
58
 
59
59
  // Sanitize URLs to prevent javascript: protocol injection.
@@ -140,7 +140,7 @@ window.openAIChatManager = function () {
140
140
  highlighted = escapeHtmlEntities(code);
141
141
  }
142
142
  var langDisplay = lang ? escapeHtmlEntities(lang) : 'code';
143
- return "<div class=\"ai-code-block\"><div class=\"ai-code-header\"><span class=\"ai-code-lang\"><i class=\"fa-solid fa-code\"></i> ".concat(langDisplay, "</span><button type=\"button\" class=\"ai-code-copy-btn\" title=\"Copy code\"><i class=\"fa-regular fa-copy\"></i></button></div><pre><code class=\"hljs").concat(lang ? ' language-' + lang : '', "\">").concat(highlighted, "</code></pre></div>");
143
+ return "<div class=\"ai-code-block\"><div class=\"ai-code-header\"><span class=\"ai-code-lang\"><span class=\"fa-solid fa-code\"></span> ".concat(langDisplay, "</span><button type=\"button\" class=\"ai-code-copy-btn\" title=\"Copy code\"><span class=\"fa-regular fa-copy\"></span></button></div><pre><code class=\"hljs").concat(lang ? ' language-' + lang : '', "\">").concat(highlighted, "</code></pre></div>");
144
144
  };
145
145
 
146
146
  // Custom image renderer for generated images with thumbnail styling and download button.
@@ -150,7 +150,7 @@ window.openAIChatManager = function () {
150
150
  if (!src) return '';
151
151
  var alt = data.text || defaultConfig.generatedImageAltText;
152
152
  var maxWidth = defaultConfig.generatedImageMaxWidth;
153
- return "<div class=\"generated-image-container\">\n <img src=\"".concat(src, "\" alt=\"").concat(alt, "\" class=\"img-thumbnail\" style=\"max-width: ").concat(maxWidth, "px; height: auto;\" />\n <div class=\"mt-2\">\n <a href=\"").concat(src, "\" target=\"_blank\" download=\"").concat(alt, "\" title=\"").concat(defaultConfig.downloadImageTitle, "\" class=\"btn btn-sm btn-outline-secondary ai-download-image\">\n <i class=\"fa-solid fa-download\"></i>\n </a>\n </div>\n </div>");
153
+ return "<div class=\"generated-image-container\">\n <img src=\"".concat(src, "\" alt=\"").concat(alt, "\" class=\"img-thumbnail\" style=\"max-width: ").concat(maxWidth, "px; height: auto;\" />\n <div class=\"mt-2\">\n <a href=\"").concat(src, "\" target=\"_blank\" download=\"").concat(alt, "\" title=\"").concat(defaultConfig.downloadImageTitle, "\" class=\"btn btn-sm btn-outline-secondary ai-download-image\">\n <span class=\"fa-solid fa-download\"></span>\n </a>\n </div>\n </div>");
154
154
  };
155
155
 
156
156
  // Chart counter for unique IDs
@@ -164,7 +164,7 @@ window.openAIChatManager = function () {
164
164
  // its DOM update to render charts it didn't create itself.
165
165
  window.__chartConfigs = window.__chartConfigs || {};
166
166
  function createChartHtml(chartId) {
167
- return "<div class=\"chart-container\" style=\"position: relative; width: 100%; max-width: 560px; min-height: 420px;\">" + "<canvas id=\"".concat(chartId, "\"></canvas>") + "</div>" + "<div class=\"mt-2\">" + "<button type=\"button\" class=\"btn btn-sm btn-outline-secondary download-chart-btn\" data-chart-id=\"".concat(chartId, "\" title=\"").concat(defaultConfig.downloadChartTitle, "\">") + "<i class=\"fa-solid fa-download\"></i> ".concat(defaultConfig.downloadChartButtonText) + "</button>" + "</div>";
167
+ return "<div class=\"chart-container\" style=\"position: relative; width: 100%; max-width: 560px; min-height: 420px;\">" + "<canvas id=\"".concat(chartId, "\"></canvas>") + "</div>" + "<div class=\"mt-2\">" + "<button type=\"button\" class=\"btn btn-sm btn-outline-secondary download-chart-btn\" data-chart-id=\"".concat(chartId, "\" title=\"").concat(defaultConfig.downloadChartTitle, "\">") + "<span class=\"fa-solid fa-download\"></span> ".concat(defaultConfig.downloadChartButtonText) + "</button>" + "</div>";
168
168
  }
169
169
 
170
170
  // Register [chart:{...json...}] as a native marked block extension so the
@@ -482,8 +482,8 @@ window.openAIChatManager = function () {
482
482
  if (partialLayout.panel !== undefined) {
483
483
  nextLayout.panel = Object.assign({}, currentLayout.panel || {}, partialLayout.panel || {});
484
484
  }
485
- if (partialLayout.toggle !== undefined) {
486
- nextLayout.toggle = Object.assign({}, currentLayout.toggle || {}, partialLayout.toggle || {});
485
+ if (partialLayout.position !== undefined) {
486
+ nextLayout.position = Object.assign({}, currentLayout.position || {}, partialLayout.position || {});
487
487
  }
488
488
  localStorage.setItem(this.chatWidgetLayoutKey, JSON.stringify(nextLayout));
489
489
  },
@@ -501,6 +501,25 @@ window.openAIChatManager = function () {
501
501
  height: Math.round(rect.height || parsePixelValue(computed.height) || fallbackHeight || 0)
502
502
  };
503
503
  },
504
+ getElementPosition: function getElementPosition(element) {
505
+ if (!element) {
506
+ return {
507
+ left: 0,
508
+ top: 0
509
+ };
510
+ }
511
+ var rect = element.getBoundingClientRect();
512
+ var computed = window.getComputedStyle(element);
513
+ var inlineLeft = parsePixelValue(element.style.left);
514
+ var inlineTop = parsePixelValue(element.style.top);
515
+ var computedLeft = parsePixelValue(computed.left);
516
+ var computedTop = parsePixelValue(computed.top);
517
+ var isHidden = computed.display === 'none';
518
+ return {
519
+ left: Math.round(isHidden && Number.isFinite(inlineLeft) ? inlineLeft : rect.left || inlineLeft || computedLeft || 0),
520
+ top: Math.round(isHidden && Number.isFinite(inlineTop) ? inlineTop : rect.top || inlineTop || computedTop || 0)
521
+ };
522
+ },
504
523
  clampWidgetPosition: function clampWidgetPosition(left, top, width, height) {
505
524
  var viewportPadding = 8;
506
525
  var maxLeft = Math.max(viewportPadding, window.innerWidth - width - viewportPadding);
@@ -525,15 +544,29 @@ window.openAIChatManager = function () {
525
544
  height: this.widgetDefaultHeight || parsePixelValue(config.widget.defaultHeight) || 520
526
545
  };
527
546
  },
547
+ getWidgetToggleGap: function getWidgetToggleGap() {
548
+ return 8;
549
+ },
550
+ isWidgetPanelVisible: function isWidgetPanelVisible() {
551
+ if (!this.widgetContainerElement) {
552
+ return false;
553
+ }
554
+ var computed = window.getComputedStyle(this.widgetContainerElement);
555
+ return this.widgetContainerElement.classList.contains('open') && computed.display !== 'none';
556
+ },
528
557
  persistWidgetPanelPosition: function persistWidgetPanelPosition() {
529
558
  if (!this.widgetContainerElement || !config.widget.persistLayout) {
530
559
  return;
531
560
  }
532
- var panelRect = this.widgetContainerElement.getBoundingClientRect();
561
+ var panelDefaultSize = this.getWidgetDefaultSize();
562
+ var panelPosition = this.getElementPosition(this.widgetContainerElement);
563
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
564
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
565
+ var linkedPosition = this.clampWidgetPosition(panelPosition.left + panelSize.width - toggleSize.width, panelPosition.top + panelSize.height + this.getWidgetToggleGap(), toggleSize.width, toggleSize.height);
533
566
  this.saveStoredWidgetLayout({
534
- panel: {
535
- left: Math.round(panelRect.left),
536
- top: Math.round(panelRect.top)
567
+ position: {
568
+ left: linkedPosition.left,
569
+ top: linkedPosition.top
537
570
  }
538
571
  });
539
572
  },
@@ -543,9 +576,9 @@ window.openAIChatManager = function () {
543
576
  }
544
577
  var toggleRect = this.widgetToggleButtonElement.getBoundingClientRect();
545
578
  this.saveStoredWidgetLayout({
546
- toggle: {
547
- left: Math.round(toggleRect.left),
548
- top: Math.round(toggleRect.top)
579
+ position: {
580
+ left: this.getElementPosition(this.widgetToggleButtonElement).left,
581
+ top: this.getElementPosition(this.widgetToggleButtonElement).top
549
582
  }
550
583
  });
551
584
  },
@@ -562,6 +595,36 @@ window.openAIChatManager = function () {
562
595
  }
563
596
  });
564
597
  },
598
+ syncTogglePositionToPanel: function syncTogglePositionToPanel() {
599
+ var persistLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
600
+ if (!this.widgetContainerElement || !this.widgetToggleButtonElement) {
601
+ return;
602
+ }
603
+ var panelDefaultSize = this.getWidgetDefaultSize();
604
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
605
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
606
+ var panelPosition = this.getElementPosition(this.widgetContainerElement);
607
+ var linkedPosition = this.clampWidgetPosition(panelPosition.left + panelSize.width - toggleSize.width, panelPosition.top + panelSize.height + this.getWidgetToggleGap(), toggleSize.width, toggleSize.height);
608
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, linkedPosition.left, linkedPosition.top);
609
+ if (persistLayout && config.widget.persistLayout) {
610
+ this.persistWidgetTogglePosition();
611
+ }
612
+ },
613
+ syncPanelPositionToToggle: function syncPanelPositionToToggle() {
614
+ var persistLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
615
+ if (!this.widgetContainerElement || !this.widgetToggleButtonElement) {
616
+ return;
617
+ }
618
+ var panelDefaultSize = this.getWidgetDefaultSize();
619
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
620
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
621
+ var togglePosition = this.getElementPosition(this.widgetToggleButtonElement);
622
+ var linkedPosition = this.clampWidgetPosition(togglePosition.left + toggleSize.width - panelSize.width, togglePosition.top - panelSize.height - this.getWidgetToggleGap(), panelSize.width, panelSize.height);
623
+ this.applyAbsolutePosition(this.widgetContainerElement, linkedPosition.left, linkedPosition.top);
624
+ if (persistLayout && config.widget.persistLayout) {
625
+ this.persistWidgetPanelPosition();
626
+ }
627
+ },
565
628
  updateWidgetResetSizeButtonVisibility: function updateWidgetResetSizeButtonVisibility() {
566
629
  if (!this.widgetResetSizeButtonElement) {
567
630
  return;
@@ -599,11 +662,15 @@ window.openAIChatManager = function () {
599
662
  this.widgetContainerElement.style.removeProperty('height');
600
663
  }
601
664
  }
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)) {
665
+ if (this.widgetToggleButtonElement && config.widget.enableDragging && Number.isFinite(storedLayout.position && storedLayout.position.left) && Number.isFinite(storedLayout.position && storedLayout.position.top)) {
666
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, storedLayout.position.left, storedLayout.position.top);
667
+ this.syncPanelPositionToToggle(false);
668
+ } else if (this.widgetToggleButtonElement && config.widget.enableDragging && Number.isFinite(storedLayout.toggle && storedLayout.toggle.left) && Number.isFinite(storedLayout.toggle && storedLayout.toggle.top)) {
606
669
  this.applyAbsolutePosition(this.widgetToggleButtonElement, storedLayout.toggle.left, storedLayout.toggle.top);
670
+ this.syncPanelPositionToToggle(false);
671
+ } else if (this.widgetContainerElement && config.widget.enableDragging && Number.isFinite(storedLayout.panel && storedLayout.panel.left) && Number.isFinite(storedLayout.panel && storedLayout.panel.top)) {
672
+ this.applyAbsolutePosition(this.widgetContainerElement, storedLayout.panel.left, storedLayout.panel.top);
673
+ this.syncTogglePositionToPanel(false);
607
674
  }
608
675
  this.constrainWidgetElementsToViewport();
609
676
  this.updateWidgetResetSizeButtonVisibility();
@@ -637,8 +704,14 @@ window.openAIChatManager = function () {
637
704
  return;
638
705
  }
639
706
  if (layoutKey === 'panel') {
707
+ _this.syncTogglePositionToPanel(false);
640
708
  _this.persistWidgetPanelPosition();
641
709
  } else if (layoutKey === 'toggle') {
710
+ _this.syncPanelPositionToToggle(false);
711
+ _this.persistWidgetTogglePosition();
712
+ }
713
+ if (config.widget.persistLayout) {
714
+ _this.persistWidgetPanelPosition();
642
715
  _this.persistWidgetTogglePosition();
643
716
  }
644
717
  if (suppressClickOnDrag) {
@@ -664,6 +737,11 @@ window.openAIChatManager = function () {
664
737
  var currentSize = _this.getElementSize(element, layoutKey === 'panel' ? defaultSize.width : 52, layoutKey === 'panel' ? defaultSize.height : 52);
665
738
  var nextPosition = _this.clampWidgetPosition(startLeft + deltaX, startTop + deltaY, currentSize.width, currentSize.height);
666
739
  _this.applyAbsolutePosition(element, nextPosition.left, nextPosition.top);
740
+ if (layoutKey === 'panel') {
741
+ _this.syncTogglePositionToPanel(false);
742
+ } else if (layoutKey === 'toggle') {
743
+ _this.syncPanelPositionToToggle(false);
744
+ }
667
745
  };
668
746
  handle.addEventListener('pointerdown', function (event) {
669
747
  if (event.button !== 0) {
@@ -690,23 +768,26 @@ window.openAIChatManager = function () {
690
768
  });
691
769
  },
692
770
  constrainWidgetElementsToViewport: function constrainWidgetElementsToViewport() {
693
- if (this.widgetContainerElement && this.widgetContainerElement.style.left) {
771
+ if (this.widgetContainerElement && this.widgetContainerElement.style.left && this.isWidgetPanelVisible()) {
694
772
  var panelDefaultSize = this.getWidgetDefaultSize();
695
773
  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);
774
+ var panelPosition = this.getElementPosition(this.widgetContainerElement);
775
+ var clampedPanel = this.clampWidgetPosition(panelPosition.left, panelPosition.top, panelSize.width, panelSize.height);
698
776
  this.applyAbsolutePosition(this.widgetContainerElement, clampedPanel.left, clampedPanel.top);
777
+ this.syncTogglePositionToPanel(false);
699
778
  if (config.widget.persistLayout) {
700
779
  this.persistWidgetPanelPosition();
780
+ this.persistWidgetTogglePosition();
701
781
  }
702
- }
703
- if (this.widgetToggleButtonElement && this.widgetToggleButtonElement.style.left) {
782
+ } else if (this.widgetToggleButtonElement && this.widgetToggleButtonElement.style.left) {
704
783
  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);
784
+ var togglePosition = this.getElementPosition(this.widgetToggleButtonElement);
785
+ var clampedToggle = this.clampWidgetPosition(togglePosition.left, togglePosition.top, toggleSize.width, toggleSize.height);
707
786
  this.applyAbsolutePosition(this.widgetToggleButtonElement, clampedToggle.left, clampedToggle.top);
787
+ this.syncPanelPositionToToggle(false);
708
788
  if (config.widget.persistLayout) {
709
789
  this.persistWidgetTogglePosition();
790
+ this.persistWidgetPanelPosition();
710
791
  }
711
792
  }
712
793
  },
@@ -929,7 +1010,7 @@ window.openAIChatManager = function () {
929
1010
  var name = doc.fileName || 'Document';
930
1011
  if (name.length > 20) name = name.substring(0, 17) + '...';
931
1012
  html += '<span class="badge bg-secondary bg-opacity-25 text-dark d-inline-flex align-items-center gap-1 px-2 py-1" style="font-size: 0.8rem;" title="' + this.escapeHtml(doc.fileName || '') + '">';
932
- html += '<i class="fa-solid fa-file-lines" style="font-size: 0.7rem;"></i> ';
1013
+ html += '<span class="fa-solid fa-file-lines" style="font-size: 0.7rem;"></span> ';
933
1014
  html += this.escapeHtml(name);
934
1015
  html += ' <button type="button" class="btn-close btn-close-sm ms-1" style="font-size: 0.5rem;" data-doc-index="' + i + '" aria-label="Remove"></button>';
935
1016
  html += '</span>';
@@ -940,7 +1021,7 @@ window.openAIChatManager = function () {
940
1021
  var errorMsg = failedItem.error || 'Upload failed';
941
1022
  if (failedName.length > 15) failedName = failedName.substring(0, 12) + '...';
942
1023
  html += '<span class="badge bg-danger bg-opacity-25 text-danger d-inline-flex align-items-center gap-1 px-2 py-1" style="font-size: 0.8rem;" title="' + this.escapeHtml((failedItem.fileName || '') + ': ' + errorMsg) + '">';
943
- html += '<i class="fa-solid fa-circle-exclamation" style="font-size: 0.7rem;"></i> ';
1024
+ html += '<span class="fa-solid fa-circle-exclamation" style="font-size: 0.7rem;"></span> ';
944
1025
  html += this.escapeHtml(failedName);
945
1026
  html += ' <button type="button" class="btn-close btn-close-sm ms-1" style="font-size: 0.5rem;" data-error-index="' + m + '" aria-label="Dismiss"></button>';
946
1027
  html += '</span>';
@@ -951,7 +1032,7 @@ window.openAIChatManager = function () {
951
1032
  html += '</span>';
952
1033
  }
953
1034
  html += '<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill ai-chat-doc-add-btn d-inline-flex align-items-center gap-1" style="font-size: 0.75rem; padding: 0.15rem 0.5rem;" title="Attach documents">';
954
- html += '<i class="fa-solid fa-paperclip"></i>';
1035
+ html += '<span class="fa-solid fa-paperclip"></span>';
955
1036
  if (this.documents.length === 0 && !this.isUploading) {
956
1037
  html += ' <span>Attach files</span>';
957
1038
  }
@@ -1583,10 +1664,10 @@ window.openAIChatManager = function () {
1583
1664
  }
1584
1665
  if (this.isRecording) {
1585
1666
  this.micButton.classList.add('stt-recording');
1586
- this.micButton.innerHTML = '<i class="fa-solid fa-stop"></i>';
1667
+ this.micButton.innerHTML = '<span class="fa-solid fa-stop"></span>';
1587
1668
  } else {
1588
1669
  this.micButton.classList.remove('stt-recording');
1589
- this.micButton.innerHTML = '<i class="fa-solid fa-microphone"></i>';
1670
+ this.micButton.innerHTML = '<span class="fa-solid fa-microphone"></span>';
1590
1671
  }
1591
1672
  },
1592
1673
  streamMessage: function streamMessage(profileId, trimmedPrompt, sessionProfileId) {
@@ -1852,8 +1933,25 @@ window.openAIChatManager = function () {
1852
1933
  }
1853
1934
  this.stopAudio(false);
1854
1935
  },
1855
- synthesizeSpeech: function synthesizeSpeech(text, cacheIndex) {
1936
+ updateTtsPlaybackButtons: function updateTtsPlaybackButtons() {
1856
1937
  var _this1 = this;
1938
+ if (!this.chatContainer) {
1939
+ return;
1940
+ }
1941
+ var buttons = this.chatContainer.querySelectorAll('[data-tts-message-index]');
1942
+ buttons.forEach(function (button) {
1943
+ var buttonIndex = Number(button.getAttribute('data-tts-message-index'));
1944
+ var isPlaying = buttonIndex === _this1.ttsPlayingMessageIndex;
1945
+ var iconHtml = isPlaying ? '<span class="fa-solid fa-circle-pause"></span>' : '<span class="fa-solid fa-circle-play"></span>';
1946
+ button.classList.toggle('tts-playing', isPlaying);
1947
+ button.setAttribute('title', isPlaying ? 'Pause audio' : 'Read aloud');
1948
+ button.replaceChildren(DOMPurify.sanitize(iconHtml, {
1949
+ RETURN_DOM_FRAGMENT: true
1950
+ }));
1951
+ });
1952
+ },
1953
+ synthesizeSpeech: function synthesizeSpeech(text, cacheIndex) {
1954
+ var _this10 = this;
1857
1955
  if (!this.textToSpeechEnabled || !text || !this.connection) {
1858
1956
  return;
1859
1957
  }
@@ -1862,12 +1960,16 @@ window.openAIChatManager = function () {
1862
1960
  this._ttsCacheIndex = cacheIndex !== undefined ? cacheIndex : -1;
1863
1961
  this.connection.invoke("SynthesizeSpeech", this.getProfileId(), this.getSessionId(), text, this.ttsVoiceName)["catch"](function (err) {
1864
1962
  console.error("TTS synthesis error:", err);
1865
- _this1.isPlayingAudio = false;
1866
- _this1.ttsPlayingMessageIndex = -1;
1867
- _this1._ttsCacheIndex = -1;
1963
+ _this10.isPlayingAudio = false;
1964
+ _this10.ttsPlayingMessageIndex = -1;
1965
+ _this10._ttsCacheIndex = -1;
1966
+ _this10.$nextTick(function () {
1967
+ return _this10.updateTtsPlaybackButtons();
1968
+ });
1868
1969
  });
1869
1970
  },
1870
1971
  toggleMessageTts: function toggleMessageTts(message, index) {
1972
+ var _this11 = this;
1871
1973
  if (this.ttsPlayingMessageIndex === index) {
1872
1974
  this.stopAudio();
1873
1975
  return;
@@ -1879,6 +1981,9 @@ window.openAIChatManager = function () {
1879
1981
  }
1880
1982
  }));
1881
1983
  this.ttsPlayingMessageIndex = index;
1984
+ this.$nextTick(function () {
1985
+ return _this11.updateTtsPlaybackButtons();
1986
+ });
1882
1987
  if (this.ttsAudioCache[index]) {
1883
1988
  this.playAudioBlob(this.ttsAudioCache[index]);
1884
1989
  return;
@@ -1886,10 +1991,14 @@ window.openAIChatManager = function () {
1886
1991
  this.synthesizeSpeech(message.content, index);
1887
1992
  },
1888
1993
  playCollectedAudio: function playCollectedAudio() {
1994
+ var _this12 = this;
1889
1995
  if (this.audioChunks.length === 0) {
1890
1996
  if (!this.currentAudioElement && this.audioPlayQueue.length === 0) {
1891
1997
  this.isPlayingAudio = false;
1892
1998
  this.ttsPlayingMessageIndex = -1;
1999
+ this.$nextTick(function () {
2000
+ return _this12.updateTtsPlaybackButtons();
2001
+ });
1893
2002
  }
1894
2003
  return;
1895
2004
  }
@@ -1928,45 +2037,53 @@ window.openAIChatManager = function () {
1928
2037
  this.playAudioBlob(blob);
1929
2038
  },
1930
2039
  playAudioBlob: function playAudioBlob(blob) {
1931
- var _this10 = this;
2040
+ var _this13 = this;
1932
2041
  var url = URL.createObjectURL(blob);
1933
2042
  var audio = new Audio(url);
1934
2043
  this.currentAudioUrl = url;
1935
2044
  this.currentAudioElement = audio;
1936
2045
  this.isPlayingAudio = true;
1937
2046
  audio.addEventListener('ended', function () {
1938
- _this10.currentAudioElement = null;
1939
- _this10.currentAudioUrl = null;
2047
+ _this13.currentAudioElement = null;
2048
+ _this13.currentAudioUrl = null;
1940
2049
  URL.revokeObjectURL(url);
1941
- _this10.playNextInQueue();
2050
+ _this13.playNextInQueue();
1942
2051
  });
1943
2052
  audio.addEventListener('error', function () {
1944
- _this10.currentAudioElement = null;
1945
- _this10.currentAudioUrl = null;
2053
+ _this13.currentAudioElement = null;
2054
+ _this13.currentAudioUrl = null;
1946
2055
  URL.revokeObjectURL(url);
1947
- _this10.playNextInQueue();
2056
+ _this13.playNextInQueue();
1948
2057
  });
1949
2058
  audio.play()["catch"](function (err) {
1950
2059
  console.error("Audio playback error:", err);
1951
- _this10.currentAudioElement = null;
1952
- _this10.currentAudioUrl = null;
2060
+ _this13.currentAudioElement = null;
2061
+ _this13.currentAudioUrl = null;
1953
2062
  URL.revokeObjectURL(url);
1954
- _this10.audioPlayQueue = [];
1955
- _this10.isPlayingAudio = false;
1956
- _this10.ttsPlayingMessageIndex = -1;
2063
+ _this13.audioPlayQueue = [];
2064
+ _this13.isPlayingAudio = false;
2065
+ _this13.ttsPlayingMessageIndex = -1;
2066
+ _this13.$nextTick(function () {
2067
+ return _this13.updateTtsPlaybackButtons();
2068
+ });
1957
2069
  });
1958
2070
  },
1959
2071
  playNextInQueue: function playNextInQueue() {
2072
+ var _this14 = this;
1960
2073
  if (this.audioPlayQueue.length > 0) {
1961
2074
  var nextBlob = this.audioPlayQueue.shift();
1962
2075
  this.playAudioBlob(nextBlob);
1963
2076
  } else {
1964
2077
  this.isPlayingAudio = false;
1965
2078
  this.ttsPlayingMessageIndex = -1;
2079
+ this.$nextTick(function () {
2080
+ return _this14.updateTtsPlaybackButtons();
2081
+ });
1966
2082
  this.conversationModeOnAudioEnded();
1967
2083
  }
1968
2084
  },
1969
2085
  stopAudio: function stopAudio() {
2086
+ var _this15 = this;
1970
2087
  if (this.currentAudioElement) {
1971
2088
  this.currentAudioElement.pause();
1972
2089
  this.currentAudioElement.currentTime = 0;
@@ -1980,6 +2097,9 @@ window.openAIChatManager = function () {
1980
2097
  this.audioPlayQueue = [];
1981
2098
  this.isPlayingAudio = false;
1982
2099
  this.ttsPlayingMessageIndex = -1;
2100
+ this.$nextTick(function () {
2101
+ return _this15.updateTtsPlaybackButtons();
2102
+ });
1983
2103
  },
1984
2104
  toggleConversationMode: function toggleConversationMode() {
1985
2105
  if (this.isConversationMode) {
@@ -1989,7 +2109,7 @@ window.openAIChatManager = function () {
1989
2109
  }
1990
2110
  },
1991
2111
  startConversationMode: function startConversationMode() {
1992
- var _this11 = this;
2112
+ var _this16 = this;
1993
2113
  if (!this.conversationModeEnabled || this.isConversationMode || !this.connection) {
1994
2114
  return;
1995
2115
  }
@@ -2009,12 +2129,12 @@ window.openAIChatManager = function () {
2009
2129
  }
2010
2130
  }).then(function (stream) {
2011
2131
  var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
2012
- _this11.mediaRecorder = new MediaRecorder(stream, {
2132
+ _this16.mediaRecorder = new MediaRecorder(stream, {
2013
2133
  mimeType: mimeType,
2014
2134
  audioBitsPerSecond: 128000
2015
2135
  });
2016
- _this11._conversationSubject = new signalR.Subject();
2017
- _this11._conversationStream = stream;
2136
+ _this16._conversationSubject = new signalR.Subject();
2137
+ _this16._conversationStream = stream;
2018
2138
 
2019
2139
  // Create an AnalyserNode for volume-based interrupt detection.
2020
2140
  // During TTS playback, detect when the user speaks above
@@ -2023,20 +2143,20 @@ window.openAIChatManager = function () {
2023
2143
  // speaker echo so the STT stream has no gaps.
2024
2144
  var AudioCtx = window.AudioContext || window.webkitAudioContext;
2025
2145
  if (AudioCtx) {
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);
2146
+ _this16._conversationAudioCtx = new AudioCtx();
2147
+ _this16._conversationAnalyser = _this16._conversationAudioCtx.createAnalyser();
2148
+ _this16._conversationAnalyser.fftSize = 256;
2149
+ var micSource = _this16._conversationAudioCtx.createMediaStreamSource(stream);
2150
+ micSource.connect(_this16._conversationAnalyser);
2031
2151
  }
2032
2152
  var pendingChunk = Promise.resolve();
2033
- var analyser = _this11._conversationAnalyser;
2153
+ var analyser = _this16._conversationAnalyser;
2034
2154
  var interruptVolumeThreshold = 30;
2035
- _this11.mediaRecorder.addEventListener('dataavailable', function (e) {
2155
+ _this16.mediaRecorder.addEventListener('dataavailable', function (e) {
2036
2156
  if (e.data && e.data.size > 0) {
2037
2157
  // During TTS playback, check mic volume to detect
2038
2158
  // user interruption (speaking above threshold).
2039
- if (_this11.isPlayingAudio && analyser) {
2159
+ if (_this16.isPlayingAudio && analyser) {
2040
2160
  var freqData = new Uint8Array(analyser.frequencyBinCount);
2041
2161
  analyser.getByteFrequencyData(freqData);
2042
2162
  var sum = 0;
@@ -2046,7 +2166,7 @@ window.openAIChatManager = function () {
2046
2166
  var avg = sum / freqData.length;
2047
2167
  if (avg >= interruptVolumeThreshold) {
2048
2168
  // User is speaking — interrupt TTS playback.
2049
- _this11.stopAudio();
2169
+ _this16.stopAudio();
2050
2170
  }
2051
2171
  }
2052
2172
 
@@ -2068,7 +2188,7 @@ window.openAIChatManager = function () {
2068
2188
  }, '');
2069
2189
  base64 = btoa(binaryString);
2070
2190
  try {
2071
- _this11._conversationSubject.next(base64);
2191
+ _this16._conversationSubject.next(base64);
2072
2192
  } catch (err) {
2073
2193
  // Subject may have been completed already.
2074
2194
  }
@@ -2079,28 +2199,28 @@ window.openAIChatManager = function () {
2079
2199
  })));
2080
2200
  }
2081
2201
  });
2082
- _this11.mediaRecorder.addEventListener('stop', function () {
2202
+ _this16.mediaRecorder.addEventListener('stop', function () {
2083
2203
  stream.getTracks().forEach(function (track) {
2084
2204
  return track.stop();
2085
2205
  });
2086
2206
  pendingChunk.then(function () {
2087
2207
  try {
2088
- _this11._conversationSubject.complete();
2208
+ _this16._conversationSubject.complete();
2089
2209
  } catch (err) {
2090
2210
  // Already completed.
2091
2211
  }
2092
2212
  });
2093
2213
  });
2094
- var profileId = _this11.getProfileId();
2095
- var sessionId = _this11.getSessionId() || '';
2214
+ var profileId = _this16.getProfileId();
2215
+ var sessionId = _this16.getSessionId() || '';
2096
2216
  var language = navigator.language || document.documentElement.lang || 'en-US';
2097
- _this11.connection.send("StartConversation", profileId, sessionId, _this11._conversationSubject, mimeType, language);
2098
- _this11.mediaRecorder.start(250);
2099
- _this11.isRecording = true;
2217
+ _this16.connection.send("StartConversation", profileId, sessionId, _this16._conversationSubject, mimeType, language);
2218
+ _this16.mediaRecorder.start(250);
2219
+ _this16.isRecording = true;
2100
2220
  })["catch"](function (err) {
2101
2221
  console.error('Microphone access denied:', err);
2102
- _this11.isConversationMode = false;
2103
- _this11.updateConversationButton();
2222
+ _this16.isConversationMode = false;
2223
+ _this16.updateConversationButton();
2104
2224
  });
2105
2225
  },
2106
2226
  stopConversationMode: function stopConversationMode() {
@@ -2227,7 +2347,7 @@ window.openAIChatManager = function () {
2227
2347
  return removedCount;
2228
2348
  },
2229
2349
  receiveNotification: function receiveNotification(notification) {
2230
- var _this12 = this;
2350
+ var _this17 = this;
2231
2351
  if (!notification || !notification.type) {
2232
2352
  return;
2233
2353
  }
@@ -2242,7 +2362,7 @@ window.openAIChatManager = function () {
2242
2362
  }
2243
2363
  this.scheduleNotificationDismiss(notification);
2244
2364
  this.$nextTick(function () {
2245
- _this12.scrollToBottom();
2365
+ _this17.scrollToBottom();
2246
2366
  });
2247
2367
  },
2248
2368
  updateNotification: function updateNotification(notification) {
@@ -2259,12 +2379,12 @@ window.openAIChatManager = function () {
2259
2379
  }
2260
2380
  },
2261
2381
  scheduleNotificationDismiss: function scheduleNotificationDismiss(notification) {
2262
- var _this13 = this;
2382
+ var _this18 = this;
2263
2383
  if (!notification || !notification.type || !notification.autoDismissMs || notification.autoDismissMs <= 0) {
2264
2384
  return;
2265
2385
  }
2266
2386
  this.notificationDismissTimers[notification.type] = setTimeout(function () {
2267
- _this13.removeNotification(notification.type);
2387
+ _this18.removeNotification(notification.type);
2268
2388
  }, notification.autoDismissMs);
2269
2389
  },
2270
2390
  clearNotificationDismiss: function clearNotificationDismiss(notificationType) {
@@ -2294,12 +2414,12 @@ window.openAIChatManager = function () {
2294
2414
  });
2295
2415
  },
2296
2416
  scrollToBottom: function scrollToBottom() {
2297
- var _this14 = this;
2417
+ var _this19 = this;
2298
2418
  if (!this.autoScroll) {
2299
2419
  return;
2300
2420
  }
2301
2421
  setTimeout(function () {
2302
- _this14.chatContainer.scrollTop = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight;
2422
+ _this19.chatContainer.scrollTop = _this19.chatContainer.scrollHeight - _this19.chatContainer.clientHeight;
2303
2423
  }, 50);
2304
2424
  },
2305
2425
  handleUserInput: function handleUserInput(event) {
@@ -2341,7 +2461,7 @@ window.openAIChatManager = function () {
2341
2461
  });
2342
2462
  },
2343
2463
  initializeApp: function initializeApp() {
2344
- var _this15 = this;
2464
+ var _this20 = this;
2345
2465
  this.inputElement = document.querySelector(config.inputElementSelector);
2346
2466
  this.buttonElement = document.querySelector(config.sendButtonElementSelector);
2347
2467
  this.chatContainer = document.querySelector(config.chatContainerElementSelector);
@@ -2370,7 +2490,7 @@ window.openAIChatManager = function () {
2370
2490
  fileInput.accept = config.allowedExtensions;
2371
2491
  }
2372
2492
  fileInput.addEventListener('change', function (e) {
2373
- return _this15.handleFileInputChange(e);
2493
+ return _this20.handleFileInputChange(e);
2374
2494
  });
2375
2495
  this.documentBar.parentElement.appendChild(fileInput);
2376
2496
 
@@ -2378,13 +2498,13 @@ window.openAIChatManager = function () {
2378
2498
  var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
2379
2499
  if (inputArea) {
2380
2500
  inputArea.addEventListener('dragover', function (e) {
2381
- return _this15.handleDragOver(e);
2501
+ return _this20.handleDragOver(e);
2382
2502
  });
2383
2503
  inputArea.addEventListener('dragleave', function (e) {
2384
- return _this15.handleDragLeave(e);
2504
+ return _this20.handleDragLeave(e);
2385
2505
  });
2386
2506
  inputArea.addEventListener('drop', function (e) {
2387
- return _this15.handleDrop(e);
2507
+ return _this20.handleDrop(e);
2388
2508
  });
2389
2509
  }
2390
2510
  }
@@ -2392,55 +2512,55 @@ window.openAIChatManager = function () {
2392
2512
 
2393
2513
  // Pause auto-scroll when the user manually scrolls up during streaming.
2394
2514
  this.chatContainer.addEventListener('scroll', function () {
2395
- if (!_this15.stream) {
2515
+ if (!_this20.stream) {
2396
2516
  return;
2397
2517
  }
2398
2518
  var threshold = 30;
2399
- var atBottom = _this15.chatContainer.scrollHeight - _this15.chatContainer.clientHeight - _this15.chatContainer.scrollTop <= threshold;
2400
- _this15.autoScroll = atBottom;
2519
+ var atBottom = _this20.chatContainer.scrollHeight - _this20.chatContainer.clientHeight - _this20.chatContainer.scrollTop <= threshold;
2520
+ _this20.autoScroll = atBottom;
2401
2521
  });
2402
2522
  this.inputElement.addEventListener('keydown', function (event) {
2403
- if (_this15.stream != null) {
2523
+ if (_this20.stream != null) {
2404
2524
  return;
2405
2525
  }
2406
2526
  if (event.key === "Enter" && !event.shiftKey) {
2407
2527
  event.preventDefault();
2408
- _this15.buttonElement.click();
2528
+ _this20.buttonElement.click();
2409
2529
  }
2410
2530
  });
2411
2531
  this.inputElement.addEventListener('input', function (e) {
2412
- _this15.handleUserInput(e);
2532
+ _this20.handleUserInput(e);
2413
2533
  if (e.target.value.trim()) {
2414
- _this15.buttonElement.removeAttribute('disabled');
2534
+ _this20.buttonElement.removeAttribute('disabled');
2415
2535
  } else {
2416
- _this15.buttonElement.setAttribute('disabled', true);
2536
+ _this20.buttonElement.setAttribute('disabled', true);
2417
2537
  }
2418
2538
  });
2419
2539
  this.buttonElement.addEventListener('click', function () {
2420
- if (_this15.stream != null) {
2421
- _this15.stream.dispose();
2422
- _this15.stream = null;
2423
- _this15.streamingFinished();
2424
- _this15.hideTypingIndicator();
2540
+ if (_this20.stream != null) {
2541
+ _this20.stream.dispose();
2542
+ _this20.stream = null;
2543
+ _this20.streamingFinished();
2544
+ _this20.hideTypingIndicator();
2425
2545
 
2426
2546
  // Clean up: remove empty assistant message or stop streaming animation.
2427
- if (_this15.messages.length > 0) {
2428
- var lastMsg = _this15.messages[_this15.messages.length - 1];
2547
+ if (_this20.messages.length > 0) {
2548
+ var lastMsg = _this20.messages[_this20.messages.length - 1];
2429
2549
  if (lastMsg.role === 'assistant' && !lastMsg.content) {
2430
- _this15.messages.pop();
2550
+ _this20.messages.pop();
2431
2551
  } else if (lastMsg.isStreaming) {
2432
2552
  lastMsg.isStreaming = false;
2433
2553
  }
2434
2554
  }
2435
2555
  return;
2436
2556
  }
2437
- _this15.sendMessage();
2557
+ _this20.sendMessage();
2438
2558
  });
2439
2559
  var promptGenerators = document.getElementsByClassName('profile-generated-prompt');
2440
2560
  for (var i = 0; i < promptGenerators.length; i++) {
2441
2561
  promptGenerators[i].addEventListener('click', function (e) {
2442
2562
  e.preventDefault();
2443
- _this15.generatePrompt(e.target);
2563
+ _this20.generatePrompt(e.target);
2444
2564
  });
2445
2565
  }
2446
2566
  var chatSessions = document.getElementsByClassName('chat-session-history-item');
@@ -2452,8 +2572,8 @@ window.openAIChatManager = function () {
2452
2572
  console.error('an element with the class chat-session-history-item with no data-session-id set.');
2453
2573
  return;
2454
2574
  }
2455
- _this15.loadSession(sessionId);
2456
- _this15.showChatScreen();
2575
+ _this20.loadSession(sessionId);
2576
+ _this20.showChatScreen();
2457
2577
  });
2458
2578
  }
2459
2579
  for (var _i5 = 0; _i5 < config.messages.length; _i5++) {
@@ -2462,7 +2582,7 @@ window.openAIChatManager = function () {
2462
2582
 
2463
2583
  // Update feedback icons in the DOM after initial messages have rendered.
2464
2584
  this.$nextTick(function () {
2465
- _this15.refreshAllFeedbackIcons();
2585
+ _this20.refreshAllFeedbackIcons();
2466
2586
  });
2467
2587
 
2468
2588
  // Delegate click for code block copy buttons.
@@ -2480,9 +2600,9 @@ window.openAIChatManager = function () {
2480
2600
  if (codeEl) {
2481
2601
  navigator.clipboard.writeText(codeEl.textContent);
2482
2602
  var copiedText = config.codeCopiedText || 'Copied!';
2483
- btn.innerHTML = '<i class="fa-solid fa-check"></i> ' + copiedText;
2603
+ btn.innerHTML = '<span class="fa-solid fa-check"></span> ' + copiedText;
2484
2604
  setTimeout(function () {
2485
- btn.innerHTML = '<i class="fa-regular fa-copy"></i>';
2605
+ btn.innerHTML = '<span class="fa-regular fa-copy"></span>';
2486
2606
  }, 2000);
2487
2607
  }
2488
2608
  });
@@ -2494,7 +2614,7 @@ window.openAIChatManager = function () {
2494
2614
  if (this.micButton) {
2495
2615
  this.micButton.style.display = '';
2496
2616
  this.micButton.addEventListener('click', function () {
2497
- _this15.toggleRecording();
2617
+ _this20.toggleRecording();
2498
2618
  });
2499
2619
  }
2500
2620
  }
@@ -2504,7 +2624,7 @@ window.openAIChatManager = function () {
2504
2624
  this.conversationButton = document.querySelector(config.conversationButtonElementSelector);
2505
2625
  if (this.conversationButton) {
2506
2626
  this.conversationButton.addEventListener('click', function () {
2507
- _this15.toggleConversationMode();
2627
+ _this20.toggleConversationMode();
2508
2628
  });
2509
2629
  }
2510
2630
  }
@@ -2536,7 +2656,7 @@ window.openAIChatManager = function () {
2536
2656
  }
2537
2657
  },
2538
2658
  initializeWidget: function initializeWidget() {
2539
- var _this16 = this;
2659
+ var _this21 = this;
2540
2660
  if (!config.widget.chatWidgetContainer) {
2541
2661
  console.error('The widget chatWidgetContainer is required.');
2542
2662
  return;
@@ -2567,9 +2687,9 @@ window.openAIChatManager = function () {
2567
2687
  chatWidgetContainer.style.minHeight = (parsePixelValue(config.widget.minHeight) || 420) + 'px';
2568
2688
  if (typeof ResizeObserver !== 'undefined') {
2569
2689
  this.widgetResizeObserver = new ResizeObserver(function () {
2570
- _this16.persistWidgetPanelSize();
2571
- _this16.constrainWidgetElementsToViewport();
2572
- _this16.updateWidgetResetSizeButtonVisibility();
2690
+ _this21.persistWidgetPanelSize();
2691
+ _this21.constrainWidgetElementsToViewport();
2692
+ _this21.updateWidgetResetSizeButtonVisibility();
2573
2693
  });
2574
2694
  this.widgetResizeObserver.observe(chatWidgetContainer);
2575
2695
  }
@@ -2588,14 +2708,14 @@ window.openAIChatManager = function () {
2588
2708
  if (this.widgetResetSizeButtonElement) {
2589
2709
  this.widgetResetSizeButtonElement.addEventListener('click', function (event) {
2590
2710
  event.preventDefault();
2591
- _this16.resetWidgetSize();
2711
+ _this21.resetWidgetSize();
2592
2712
  });
2593
2713
  }
2594
2714
  this.restoreWidgetLayout();
2595
2715
  if (!this.widgetViewportResizeHandler) {
2596
2716
  this.widgetViewportResizeHandler = function () {
2597
- _this16.constrainWidgetElementsToViewport();
2598
- _this16.updateWidgetResetSizeButtonVisibility();
2717
+ _this21.constrainWidgetElementsToViewport();
2718
+ _this21.updateWidgetResetSizeButtonVisibility();
2599
2719
  };
2600
2720
  window.addEventListener('resize', this.widgetViewportResizeHandler);
2601
2721
  }
@@ -2609,14 +2729,14 @@ window.openAIChatManager = function () {
2609
2729
  var showHistoryButton = document.querySelector(config.widget.showHistoryButton);
2610
2730
  if (showHistoryButton) {
2611
2731
  showHistoryButton.addEventListener('click', function () {
2612
- _this16.chatHistorySection.classList.toggle('show');
2732
+ _this21.chatHistorySection.classList.toggle('show');
2613
2733
  });
2614
2734
  }
2615
2735
  if (config.widget.closeHistoryButton) {
2616
2736
  var closeHistoryButton = document.querySelector(config.widget.closeHistoryButton);
2617
2737
  if (closeHistoryButton) {
2618
2738
  closeHistoryButton.addEventListener('click', function () {
2619
- _this16.showChatScreen();
2739
+ _this21.showChatScreen();
2620
2740
  });
2621
2741
  }
2622
2742
  }
@@ -2625,8 +2745,8 @@ window.openAIChatManager = function () {
2625
2745
  var newChatButton = document.querySelector(config.widget.newChatButton);
2626
2746
  if (newChatButton) {
2627
2747
  newChatButton.addEventListener('click', function () {
2628
- _this16.resetSession();
2629
- _this16.showChatScreen();
2748
+ _this21.resetSession();
2749
+ _this21.showChatScreen();
2630
2750
  });
2631
2751
  }
2632
2752
  }
@@ -2654,11 +2774,11 @@ window.openAIChatManager = function () {
2654
2774
  var upBtn = container.querySelector('.rate-up-btn');
2655
2775
  var downBtn = container.querySelector('.rate-down-btn');
2656
2776
 
2657
- // Font Awesome SVG+JS replaces <i> with <svg>, so we must replace
2658
- // the entire button content and let Font Awesome re-process.
2777
+ // Keep feedback icons as simple span-based Font Awesome markup so
2778
+ // later state updates do not depend on i2svg replacement.
2659
2779
  if (upBtn) {
2660
2780
  var upClass = userRating === true ? 'fa-solid fa-thumbs-up' : 'fa-regular fa-thumbs-up';
2661
- var upIcon = document.createElement('i');
2781
+ var upIcon = document.createElement('span');
2662
2782
  upIcon.className = upClass;
2663
2783
  upIcon.style.fontSize = '0.9rem';
2664
2784
  upBtn.textContent = '';
@@ -2666,19 +2786,12 @@ window.openAIChatManager = function () {
2666
2786
  }
2667
2787
  if (downBtn) {
2668
2788
  var downClass = userRating === false ? 'fa-solid fa-thumbs-down' : 'fa-regular fa-thumbs-down';
2669
- var downIcon = document.createElement('i');
2789
+ var downIcon = document.createElement('span');
2670
2790
  downIcon.className = downClass;
2671
2791
  downIcon.style.fontSize = '0.9rem';
2672
2792
  downBtn.textContent = '';
2673
2793
  downBtn.appendChild(downIcon);
2674
2794
  }
2675
-
2676
- // Trigger Font Awesome SVG+JS to convert the new <i> elements.
2677
- if (window.FontAwesome && FontAwesome.dom && FontAwesome.dom.i2svg) {
2678
- FontAwesome.dom.i2svg({
2679
- node: container
2680
- });
2681
- }
2682
2795
  },
2683
2796
  refreshAllFeedbackIcons: function refreshAllFeedbackIcons() {
2684
2797
  var containers = this.$el.querySelectorAll('.ai-chat-message-assistant-feedback');
@@ -2762,17 +2875,17 @@ window.openAIChatManager = function () {
2762
2875
  }
2763
2876
  },
2764
2877
  mounted: function mounted() {
2765
- var _this17 = this;
2878
+ var _this22 = this;
2766
2879
  _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
2767
2880
  return _regenerator().w(function (_context6) {
2768
2881
  while (1) switch (_context6.n) {
2769
2882
  case 0:
2770
2883
  _context6.n = 1;
2771
- return _this17.startConnection();
2884
+ return _this22.startConnection();
2772
2885
  case 1:
2773
- _this17.initializeApp();
2886
+ _this22.initializeApp();
2774
2887
  if (hasWidgetConfig) {
2775
- _this17.initializeWidget();
2888
+ _this22.initializeWidget();
2776
2889
  }
2777
2890
  case 2:
2778
2891
  return _context6.a(2);