@crestapps/ai-chat-ui 1.0.0-preview.8 → 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
@@ -1010,7 +1010,7 @@ window.openAIChatManager = function () {
1010
1010
  var name = doc.fileName || 'Document';
1011
1011
  if (name.length > 20) name = name.substring(0, 17) + '...';
1012
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 || '') + '">';
1013
- 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> ';
1014
1014
  html += this.escapeHtml(name);
1015
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>';
1016
1016
  html += '</span>';
@@ -1021,7 +1021,7 @@ window.openAIChatManager = function () {
1021
1021
  var errorMsg = failedItem.error || 'Upload failed';
1022
1022
  if (failedName.length > 15) failedName = failedName.substring(0, 12) + '...';
1023
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) + '">';
1024
- 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> ';
1025
1025
  html += this.escapeHtml(failedName);
1026
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>';
1027
1027
  html += '</span>';
@@ -1032,7 +1032,7 @@ window.openAIChatManager = function () {
1032
1032
  html += '</span>';
1033
1033
  }
1034
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">';
1035
- html += '<i class="fa-solid fa-paperclip"></i>';
1035
+ html += '<span class="fa-solid fa-paperclip"></span>';
1036
1036
  if (this.documents.length === 0 && !this.isUploading) {
1037
1037
  html += ' <span>Attach files</span>';
1038
1038
  }
@@ -1664,10 +1664,10 @@ window.openAIChatManager = function () {
1664
1664
  }
1665
1665
  if (this.isRecording) {
1666
1666
  this.micButton.classList.add('stt-recording');
1667
- this.micButton.innerHTML = '<i class="fa-solid fa-stop"></i>';
1667
+ this.micButton.innerHTML = '<span class="fa-solid fa-stop"></span>';
1668
1668
  } else {
1669
1669
  this.micButton.classList.remove('stt-recording');
1670
- this.micButton.innerHTML = '<i class="fa-solid fa-microphone"></i>';
1670
+ this.micButton.innerHTML = '<span class="fa-solid fa-microphone"></span>';
1671
1671
  }
1672
1672
  },
1673
1673
  streamMessage: function streamMessage(profileId, trimmedPrompt, sessionProfileId) {
@@ -1933,8 +1933,25 @@ window.openAIChatManager = function () {
1933
1933
  }
1934
1934
  this.stopAudio(false);
1935
1935
  },
1936
- synthesizeSpeech: function synthesizeSpeech(text, cacheIndex) {
1936
+ updateTtsPlaybackButtons: function updateTtsPlaybackButtons() {
1937
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;
1938
1955
  if (!this.textToSpeechEnabled || !text || !this.connection) {
1939
1956
  return;
1940
1957
  }
@@ -1943,12 +1960,16 @@ window.openAIChatManager = function () {
1943
1960
  this._ttsCacheIndex = cacheIndex !== undefined ? cacheIndex : -1;
1944
1961
  this.connection.invoke("SynthesizeSpeech", this.getProfileId(), this.getSessionId(), text, this.ttsVoiceName)["catch"](function (err) {
1945
1962
  console.error("TTS synthesis error:", err);
1946
- _this1.isPlayingAudio = false;
1947
- _this1.ttsPlayingMessageIndex = -1;
1948
- _this1._ttsCacheIndex = -1;
1963
+ _this10.isPlayingAudio = false;
1964
+ _this10.ttsPlayingMessageIndex = -1;
1965
+ _this10._ttsCacheIndex = -1;
1966
+ _this10.$nextTick(function () {
1967
+ return _this10.updateTtsPlaybackButtons();
1968
+ });
1949
1969
  });
1950
1970
  },
1951
1971
  toggleMessageTts: function toggleMessageTts(message, index) {
1972
+ var _this11 = this;
1952
1973
  if (this.ttsPlayingMessageIndex === index) {
1953
1974
  this.stopAudio();
1954
1975
  return;
@@ -1960,6 +1981,9 @@ window.openAIChatManager = function () {
1960
1981
  }
1961
1982
  }));
1962
1983
  this.ttsPlayingMessageIndex = index;
1984
+ this.$nextTick(function () {
1985
+ return _this11.updateTtsPlaybackButtons();
1986
+ });
1963
1987
  if (this.ttsAudioCache[index]) {
1964
1988
  this.playAudioBlob(this.ttsAudioCache[index]);
1965
1989
  return;
@@ -1967,10 +1991,14 @@ window.openAIChatManager = function () {
1967
1991
  this.synthesizeSpeech(message.content, index);
1968
1992
  },
1969
1993
  playCollectedAudio: function playCollectedAudio() {
1994
+ var _this12 = this;
1970
1995
  if (this.audioChunks.length === 0) {
1971
1996
  if (!this.currentAudioElement && this.audioPlayQueue.length === 0) {
1972
1997
  this.isPlayingAudio = false;
1973
1998
  this.ttsPlayingMessageIndex = -1;
1999
+ this.$nextTick(function () {
2000
+ return _this12.updateTtsPlaybackButtons();
2001
+ });
1974
2002
  }
1975
2003
  return;
1976
2004
  }
@@ -2009,45 +2037,53 @@ window.openAIChatManager = function () {
2009
2037
  this.playAudioBlob(blob);
2010
2038
  },
2011
2039
  playAudioBlob: function playAudioBlob(blob) {
2012
- var _this10 = this;
2040
+ var _this13 = this;
2013
2041
  var url = URL.createObjectURL(blob);
2014
2042
  var audio = new Audio(url);
2015
2043
  this.currentAudioUrl = url;
2016
2044
  this.currentAudioElement = audio;
2017
2045
  this.isPlayingAudio = true;
2018
2046
  audio.addEventListener('ended', function () {
2019
- _this10.currentAudioElement = null;
2020
- _this10.currentAudioUrl = null;
2047
+ _this13.currentAudioElement = null;
2048
+ _this13.currentAudioUrl = null;
2021
2049
  URL.revokeObjectURL(url);
2022
- _this10.playNextInQueue();
2050
+ _this13.playNextInQueue();
2023
2051
  });
2024
2052
  audio.addEventListener('error', function () {
2025
- _this10.currentAudioElement = null;
2026
- _this10.currentAudioUrl = null;
2053
+ _this13.currentAudioElement = null;
2054
+ _this13.currentAudioUrl = null;
2027
2055
  URL.revokeObjectURL(url);
2028
- _this10.playNextInQueue();
2056
+ _this13.playNextInQueue();
2029
2057
  });
2030
2058
  audio.play()["catch"](function (err) {
2031
2059
  console.error("Audio playback error:", err);
2032
- _this10.currentAudioElement = null;
2033
- _this10.currentAudioUrl = null;
2060
+ _this13.currentAudioElement = null;
2061
+ _this13.currentAudioUrl = null;
2034
2062
  URL.revokeObjectURL(url);
2035
- _this10.audioPlayQueue = [];
2036
- _this10.isPlayingAudio = false;
2037
- _this10.ttsPlayingMessageIndex = -1;
2063
+ _this13.audioPlayQueue = [];
2064
+ _this13.isPlayingAudio = false;
2065
+ _this13.ttsPlayingMessageIndex = -1;
2066
+ _this13.$nextTick(function () {
2067
+ return _this13.updateTtsPlaybackButtons();
2068
+ });
2038
2069
  });
2039
2070
  },
2040
2071
  playNextInQueue: function playNextInQueue() {
2072
+ var _this14 = this;
2041
2073
  if (this.audioPlayQueue.length > 0) {
2042
2074
  var nextBlob = this.audioPlayQueue.shift();
2043
2075
  this.playAudioBlob(nextBlob);
2044
2076
  } else {
2045
2077
  this.isPlayingAudio = false;
2046
2078
  this.ttsPlayingMessageIndex = -1;
2079
+ this.$nextTick(function () {
2080
+ return _this14.updateTtsPlaybackButtons();
2081
+ });
2047
2082
  this.conversationModeOnAudioEnded();
2048
2083
  }
2049
2084
  },
2050
2085
  stopAudio: function stopAudio() {
2086
+ var _this15 = this;
2051
2087
  if (this.currentAudioElement) {
2052
2088
  this.currentAudioElement.pause();
2053
2089
  this.currentAudioElement.currentTime = 0;
@@ -2061,6 +2097,9 @@ window.openAIChatManager = function () {
2061
2097
  this.audioPlayQueue = [];
2062
2098
  this.isPlayingAudio = false;
2063
2099
  this.ttsPlayingMessageIndex = -1;
2100
+ this.$nextTick(function () {
2101
+ return _this15.updateTtsPlaybackButtons();
2102
+ });
2064
2103
  },
2065
2104
  toggleConversationMode: function toggleConversationMode() {
2066
2105
  if (this.isConversationMode) {
@@ -2070,7 +2109,7 @@ window.openAIChatManager = function () {
2070
2109
  }
2071
2110
  },
2072
2111
  startConversationMode: function startConversationMode() {
2073
- var _this11 = this;
2112
+ var _this16 = this;
2074
2113
  if (!this.conversationModeEnabled || this.isConversationMode || !this.connection) {
2075
2114
  return;
2076
2115
  }
@@ -2090,12 +2129,12 @@ window.openAIChatManager = function () {
2090
2129
  }
2091
2130
  }).then(function (stream) {
2092
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';
2093
- _this11.mediaRecorder = new MediaRecorder(stream, {
2132
+ _this16.mediaRecorder = new MediaRecorder(stream, {
2094
2133
  mimeType: mimeType,
2095
2134
  audioBitsPerSecond: 128000
2096
2135
  });
2097
- _this11._conversationSubject = new signalR.Subject();
2098
- _this11._conversationStream = stream;
2136
+ _this16._conversationSubject = new signalR.Subject();
2137
+ _this16._conversationStream = stream;
2099
2138
 
2100
2139
  // Create an AnalyserNode for volume-based interrupt detection.
2101
2140
  // During TTS playback, detect when the user speaks above
@@ -2104,20 +2143,20 @@ window.openAIChatManager = function () {
2104
2143
  // speaker echo so the STT stream has no gaps.
2105
2144
  var AudioCtx = window.AudioContext || window.webkitAudioContext;
2106
2145
  if (AudioCtx) {
2107
- _this11._conversationAudioCtx = new AudioCtx();
2108
- _this11._conversationAnalyser = _this11._conversationAudioCtx.createAnalyser();
2109
- _this11._conversationAnalyser.fftSize = 256;
2110
- var micSource = _this11._conversationAudioCtx.createMediaStreamSource(stream);
2111
- 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);
2112
2151
  }
2113
2152
  var pendingChunk = Promise.resolve();
2114
- var analyser = _this11._conversationAnalyser;
2153
+ var analyser = _this16._conversationAnalyser;
2115
2154
  var interruptVolumeThreshold = 30;
2116
- _this11.mediaRecorder.addEventListener('dataavailable', function (e) {
2155
+ _this16.mediaRecorder.addEventListener('dataavailable', function (e) {
2117
2156
  if (e.data && e.data.size > 0) {
2118
2157
  // During TTS playback, check mic volume to detect
2119
2158
  // user interruption (speaking above threshold).
2120
- if (_this11.isPlayingAudio && analyser) {
2159
+ if (_this16.isPlayingAudio && analyser) {
2121
2160
  var freqData = new Uint8Array(analyser.frequencyBinCount);
2122
2161
  analyser.getByteFrequencyData(freqData);
2123
2162
  var sum = 0;
@@ -2127,7 +2166,7 @@ window.openAIChatManager = function () {
2127
2166
  var avg = sum / freqData.length;
2128
2167
  if (avg >= interruptVolumeThreshold) {
2129
2168
  // User is speaking — interrupt TTS playback.
2130
- _this11.stopAudio();
2169
+ _this16.stopAudio();
2131
2170
  }
2132
2171
  }
2133
2172
 
@@ -2149,7 +2188,7 @@ window.openAIChatManager = function () {
2149
2188
  }, '');
2150
2189
  base64 = btoa(binaryString);
2151
2190
  try {
2152
- _this11._conversationSubject.next(base64);
2191
+ _this16._conversationSubject.next(base64);
2153
2192
  } catch (err) {
2154
2193
  // Subject may have been completed already.
2155
2194
  }
@@ -2160,28 +2199,28 @@ window.openAIChatManager = function () {
2160
2199
  })));
2161
2200
  }
2162
2201
  });
2163
- _this11.mediaRecorder.addEventListener('stop', function () {
2202
+ _this16.mediaRecorder.addEventListener('stop', function () {
2164
2203
  stream.getTracks().forEach(function (track) {
2165
2204
  return track.stop();
2166
2205
  });
2167
2206
  pendingChunk.then(function () {
2168
2207
  try {
2169
- _this11._conversationSubject.complete();
2208
+ _this16._conversationSubject.complete();
2170
2209
  } catch (err) {
2171
2210
  // Already completed.
2172
2211
  }
2173
2212
  });
2174
2213
  });
2175
- var profileId = _this11.getProfileId();
2176
- var sessionId = _this11.getSessionId() || '';
2214
+ var profileId = _this16.getProfileId();
2215
+ var sessionId = _this16.getSessionId() || '';
2177
2216
  var language = navigator.language || document.documentElement.lang || 'en-US';
2178
- _this11.connection.send("StartConversation", profileId, sessionId, _this11._conversationSubject, mimeType, language);
2179
- _this11.mediaRecorder.start(250);
2180
- _this11.isRecording = true;
2217
+ _this16.connection.send("StartConversation", profileId, sessionId, _this16._conversationSubject, mimeType, language);
2218
+ _this16.mediaRecorder.start(250);
2219
+ _this16.isRecording = true;
2181
2220
  })["catch"](function (err) {
2182
2221
  console.error('Microphone access denied:', err);
2183
- _this11.isConversationMode = false;
2184
- _this11.updateConversationButton();
2222
+ _this16.isConversationMode = false;
2223
+ _this16.updateConversationButton();
2185
2224
  });
2186
2225
  },
2187
2226
  stopConversationMode: function stopConversationMode() {
@@ -2308,7 +2347,7 @@ window.openAIChatManager = function () {
2308
2347
  return removedCount;
2309
2348
  },
2310
2349
  receiveNotification: function receiveNotification(notification) {
2311
- var _this12 = this;
2350
+ var _this17 = this;
2312
2351
  if (!notification || !notification.type) {
2313
2352
  return;
2314
2353
  }
@@ -2323,7 +2362,7 @@ window.openAIChatManager = function () {
2323
2362
  }
2324
2363
  this.scheduleNotificationDismiss(notification);
2325
2364
  this.$nextTick(function () {
2326
- _this12.scrollToBottom();
2365
+ _this17.scrollToBottom();
2327
2366
  });
2328
2367
  },
2329
2368
  updateNotification: function updateNotification(notification) {
@@ -2340,12 +2379,12 @@ window.openAIChatManager = function () {
2340
2379
  }
2341
2380
  },
2342
2381
  scheduleNotificationDismiss: function scheduleNotificationDismiss(notification) {
2343
- var _this13 = this;
2382
+ var _this18 = this;
2344
2383
  if (!notification || !notification.type || !notification.autoDismissMs || notification.autoDismissMs <= 0) {
2345
2384
  return;
2346
2385
  }
2347
2386
  this.notificationDismissTimers[notification.type] = setTimeout(function () {
2348
- _this13.removeNotification(notification.type);
2387
+ _this18.removeNotification(notification.type);
2349
2388
  }, notification.autoDismissMs);
2350
2389
  },
2351
2390
  clearNotificationDismiss: function clearNotificationDismiss(notificationType) {
@@ -2375,12 +2414,12 @@ window.openAIChatManager = function () {
2375
2414
  });
2376
2415
  },
2377
2416
  scrollToBottom: function scrollToBottom() {
2378
- var _this14 = this;
2417
+ var _this19 = this;
2379
2418
  if (!this.autoScroll) {
2380
2419
  return;
2381
2420
  }
2382
2421
  setTimeout(function () {
2383
- _this14.chatContainer.scrollTop = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight;
2422
+ _this19.chatContainer.scrollTop = _this19.chatContainer.scrollHeight - _this19.chatContainer.clientHeight;
2384
2423
  }, 50);
2385
2424
  },
2386
2425
  handleUserInput: function handleUserInput(event) {
@@ -2422,7 +2461,7 @@ window.openAIChatManager = function () {
2422
2461
  });
2423
2462
  },
2424
2463
  initializeApp: function initializeApp() {
2425
- var _this15 = this;
2464
+ var _this20 = this;
2426
2465
  this.inputElement = document.querySelector(config.inputElementSelector);
2427
2466
  this.buttonElement = document.querySelector(config.sendButtonElementSelector);
2428
2467
  this.chatContainer = document.querySelector(config.chatContainerElementSelector);
@@ -2451,7 +2490,7 @@ window.openAIChatManager = function () {
2451
2490
  fileInput.accept = config.allowedExtensions;
2452
2491
  }
2453
2492
  fileInput.addEventListener('change', function (e) {
2454
- return _this15.handleFileInputChange(e);
2493
+ return _this20.handleFileInputChange(e);
2455
2494
  });
2456
2495
  this.documentBar.parentElement.appendChild(fileInput);
2457
2496
 
@@ -2459,13 +2498,13 @@ window.openAIChatManager = function () {
2459
2498
  var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
2460
2499
  if (inputArea) {
2461
2500
  inputArea.addEventListener('dragover', function (e) {
2462
- return _this15.handleDragOver(e);
2501
+ return _this20.handleDragOver(e);
2463
2502
  });
2464
2503
  inputArea.addEventListener('dragleave', function (e) {
2465
- return _this15.handleDragLeave(e);
2504
+ return _this20.handleDragLeave(e);
2466
2505
  });
2467
2506
  inputArea.addEventListener('drop', function (e) {
2468
- return _this15.handleDrop(e);
2507
+ return _this20.handleDrop(e);
2469
2508
  });
2470
2509
  }
2471
2510
  }
@@ -2473,55 +2512,55 @@ window.openAIChatManager = function () {
2473
2512
 
2474
2513
  // Pause auto-scroll when the user manually scrolls up during streaming.
2475
2514
  this.chatContainer.addEventListener('scroll', function () {
2476
- if (!_this15.stream) {
2515
+ if (!_this20.stream) {
2477
2516
  return;
2478
2517
  }
2479
2518
  var threshold = 30;
2480
- var atBottom = _this15.chatContainer.scrollHeight - _this15.chatContainer.clientHeight - _this15.chatContainer.scrollTop <= threshold;
2481
- _this15.autoScroll = atBottom;
2519
+ var atBottom = _this20.chatContainer.scrollHeight - _this20.chatContainer.clientHeight - _this20.chatContainer.scrollTop <= threshold;
2520
+ _this20.autoScroll = atBottom;
2482
2521
  });
2483
2522
  this.inputElement.addEventListener('keydown', function (event) {
2484
- if (_this15.stream != null) {
2523
+ if (_this20.stream != null) {
2485
2524
  return;
2486
2525
  }
2487
2526
  if (event.key === "Enter" && !event.shiftKey) {
2488
2527
  event.preventDefault();
2489
- _this15.buttonElement.click();
2528
+ _this20.buttonElement.click();
2490
2529
  }
2491
2530
  });
2492
2531
  this.inputElement.addEventListener('input', function (e) {
2493
- _this15.handleUserInput(e);
2532
+ _this20.handleUserInput(e);
2494
2533
  if (e.target.value.trim()) {
2495
- _this15.buttonElement.removeAttribute('disabled');
2534
+ _this20.buttonElement.removeAttribute('disabled');
2496
2535
  } else {
2497
- _this15.buttonElement.setAttribute('disabled', true);
2536
+ _this20.buttonElement.setAttribute('disabled', true);
2498
2537
  }
2499
2538
  });
2500
2539
  this.buttonElement.addEventListener('click', function () {
2501
- if (_this15.stream != null) {
2502
- _this15.stream.dispose();
2503
- _this15.stream = null;
2504
- _this15.streamingFinished();
2505
- _this15.hideTypingIndicator();
2540
+ if (_this20.stream != null) {
2541
+ _this20.stream.dispose();
2542
+ _this20.stream = null;
2543
+ _this20.streamingFinished();
2544
+ _this20.hideTypingIndicator();
2506
2545
 
2507
2546
  // Clean up: remove empty assistant message or stop streaming animation.
2508
- if (_this15.messages.length > 0) {
2509
- var lastMsg = _this15.messages[_this15.messages.length - 1];
2547
+ if (_this20.messages.length > 0) {
2548
+ var lastMsg = _this20.messages[_this20.messages.length - 1];
2510
2549
  if (lastMsg.role === 'assistant' && !lastMsg.content) {
2511
- _this15.messages.pop();
2550
+ _this20.messages.pop();
2512
2551
  } else if (lastMsg.isStreaming) {
2513
2552
  lastMsg.isStreaming = false;
2514
2553
  }
2515
2554
  }
2516
2555
  return;
2517
2556
  }
2518
- _this15.sendMessage();
2557
+ _this20.sendMessage();
2519
2558
  });
2520
2559
  var promptGenerators = document.getElementsByClassName('profile-generated-prompt');
2521
2560
  for (var i = 0; i < promptGenerators.length; i++) {
2522
2561
  promptGenerators[i].addEventListener('click', function (e) {
2523
2562
  e.preventDefault();
2524
- _this15.generatePrompt(e.target);
2563
+ _this20.generatePrompt(e.target);
2525
2564
  });
2526
2565
  }
2527
2566
  var chatSessions = document.getElementsByClassName('chat-session-history-item');
@@ -2533,8 +2572,8 @@ window.openAIChatManager = function () {
2533
2572
  console.error('an element with the class chat-session-history-item with no data-session-id set.');
2534
2573
  return;
2535
2574
  }
2536
- _this15.loadSession(sessionId);
2537
- _this15.showChatScreen();
2575
+ _this20.loadSession(sessionId);
2576
+ _this20.showChatScreen();
2538
2577
  });
2539
2578
  }
2540
2579
  for (var _i5 = 0; _i5 < config.messages.length; _i5++) {
@@ -2543,7 +2582,7 @@ window.openAIChatManager = function () {
2543
2582
 
2544
2583
  // Update feedback icons in the DOM after initial messages have rendered.
2545
2584
  this.$nextTick(function () {
2546
- _this15.refreshAllFeedbackIcons();
2585
+ _this20.refreshAllFeedbackIcons();
2547
2586
  });
2548
2587
 
2549
2588
  // Delegate click for code block copy buttons.
@@ -2561,9 +2600,9 @@ window.openAIChatManager = function () {
2561
2600
  if (codeEl) {
2562
2601
  navigator.clipboard.writeText(codeEl.textContent);
2563
2602
  var copiedText = config.codeCopiedText || 'Copied!';
2564
- btn.innerHTML = '<i class="fa-solid fa-check"></i> ' + copiedText;
2603
+ btn.innerHTML = '<span class="fa-solid fa-check"></span> ' + copiedText;
2565
2604
  setTimeout(function () {
2566
- btn.innerHTML = '<i class="fa-regular fa-copy"></i>';
2605
+ btn.innerHTML = '<span class="fa-regular fa-copy"></span>';
2567
2606
  }, 2000);
2568
2607
  }
2569
2608
  });
@@ -2575,7 +2614,7 @@ window.openAIChatManager = function () {
2575
2614
  if (this.micButton) {
2576
2615
  this.micButton.style.display = '';
2577
2616
  this.micButton.addEventListener('click', function () {
2578
- _this15.toggleRecording();
2617
+ _this20.toggleRecording();
2579
2618
  });
2580
2619
  }
2581
2620
  }
@@ -2585,7 +2624,7 @@ window.openAIChatManager = function () {
2585
2624
  this.conversationButton = document.querySelector(config.conversationButtonElementSelector);
2586
2625
  if (this.conversationButton) {
2587
2626
  this.conversationButton.addEventListener('click', function () {
2588
- _this15.toggleConversationMode();
2627
+ _this20.toggleConversationMode();
2589
2628
  });
2590
2629
  }
2591
2630
  }
@@ -2617,7 +2656,7 @@ window.openAIChatManager = function () {
2617
2656
  }
2618
2657
  },
2619
2658
  initializeWidget: function initializeWidget() {
2620
- var _this16 = this;
2659
+ var _this21 = this;
2621
2660
  if (!config.widget.chatWidgetContainer) {
2622
2661
  console.error('The widget chatWidgetContainer is required.');
2623
2662
  return;
@@ -2648,9 +2687,9 @@ window.openAIChatManager = function () {
2648
2687
  chatWidgetContainer.style.minHeight = (parsePixelValue(config.widget.minHeight) || 420) + 'px';
2649
2688
  if (typeof ResizeObserver !== 'undefined') {
2650
2689
  this.widgetResizeObserver = new ResizeObserver(function () {
2651
- _this16.persistWidgetPanelSize();
2652
- _this16.constrainWidgetElementsToViewport();
2653
- _this16.updateWidgetResetSizeButtonVisibility();
2690
+ _this21.persistWidgetPanelSize();
2691
+ _this21.constrainWidgetElementsToViewport();
2692
+ _this21.updateWidgetResetSizeButtonVisibility();
2654
2693
  });
2655
2694
  this.widgetResizeObserver.observe(chatWidgetContainer);
2656
2695
  }
@@ -2669,14 +2708,14 @@ window.openAIChatManager = function () {
2669
2708
  if (this.widgetResetSizeButtonElement) {
2670
2709
  this.widgetResetSizeButtonElement.addEventListener('click', function (event) {
2671
2710
  event.preventDefault();
2672
- _this16.resetWidgetSize();
2711
+ _this21.resetWidgetSize();
2673
2712
  });
2674
2713
  }
2675
2714
  this.restoreWidgetLayout();
2676
2715
  if (!this.widgetViewportResizeHandler) {
2677
2716
  this.widgetViewportResizeHandler = function () {
2678
- _this16.constrainWidgetElementsToViewport();
2679
- _this16.updateWidgetResetSizeButtonVisibility();
2717
+ _this21.constrainWidgetElementsToViewport();
2718
+ _this21.updateWidgetResetSizeButtonVisibility();
2680
2719
  };
2681
2720
  window.addEventListener('resize', this.widgetViewportResizeHandler);
2682
2721
  }
@@ -2690,14 +2729,14 @@ window.openAIChatManager = function () {
2690
2729
  var showHistoryButton = document.querySelector(config.widget.showHistoryButton);
2691
2730
  if (showHistoryButton) {
2692
2731
  showHistoryButton.addEventListener('click', function () {
2693
- _this16.chatHistorySection.classList.toggle('show');
2732
+ _this21.chatHistorySection.classList.toggle('show');
2694
2733
  });
2695
2734
  }
2696
2735
  if (config.widget.closeHistoryButton) {
2697
2736
  var closeHistoryButton = document.querySelector(config.widget.closeHistoryButton);
2698
2737
  if (closeHistoryButton) {
2699
2738
  closeHistoryButton.addEventListener('click', function () {
2700
- _this16.showChatScreen();
2739
+ _this21.showChatScreen();
2701
2740
  });
2702
2741
  }
2703
2742
  }
@@ -2706,8 +2745,8 @@ window.openAIChatManager = function () {
2706
2745
  var newChatButton = document.querySelector(config.widget.newChatButton);
2707
2746
  if (newChatButton) {
2708
2747
  newChatButton.addEventListener('click', function () {
2709
- _this16.resetSession();
2710
- _this16.showChatScreen();
2748
+ _this21.resetSession();
2749
+ _this21.showChatScreen();
2711
2750
  });
2712
2751
  }
2713
2752
  }
@@ -2735,11 +2774,11 @@ window.openAIChatManager = function () {
2735
2774
  var upBtn = container.querySelector('.rate-up-btn');
2736
2775
  var downBtn = container.querySelector('.rate-down-btn');
2737
2776
 
2738
- // Font Awesome SVG+JS replaces <i> with <svg>, so we must replace
2739
- // 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.
2740
2779
  if (upBtn) {
2741
2780
  var upClass = userRating === true ? 'fa-solid fa-thumbs-up' : 'fa-regular fa-thumbs-up';
2742
- var upIcon = document.createElement('i');
2781
+ var upIcon = document.createElement('span');
2743
2782
  upIcon.className = upClass;
2744
2783
  upIcon.style.fontSize = '0.9rem';
2745
2784
  upBtn.textContent = '';
@@ -2747,19 +2786,12 @@ window.openAIChatManager = function () {
2747
2786
  }
2748
2787
  if (downBtn) {
2749
2788
  var downClass = userRating === false ? 'fa-solid fa-thumbs-down' : 'fa-regular fa-thumbs-down';
2750
- var downIcon = document.createElement('i');
2789
+ var downIcon = document.createElement('span');
2751
2790
  downIcon.className = downClass;
2752
2791
  downIcon.style.fontSize = '0.9rem';
2753
2792
  downBtn.textContent = '';
2754
2793
  downBtn.appendChild(downIcon);
2755
2794
  }
2756
-
2757
- // Trigger Font Awesome SVG+JS to convert the new <i> elements.
2758
- if (window.FontAwesome && FontAwesome.dom && FontAwesome.dom.i2svg) {
2759
- FontAwesome.dom.i2svg({
2760
- node: container
2761
- });
2762
- }
2763
2795
  },
2764
2796
  refreshAllFeedbackIcons: function refreshAllFeedbackIcons() {
2765
2797
  var containers = this.$el.querySelectorAll('.ai-chat-message-assistant-feedback');
@@ -2843,17 +2875,17 @@ window.openAIChatManager = function () {
2843
2875
  }
2844
2876
  },
2845
2877
  mounted: function mounted() {
2846
- var _this17 = this;
2878
+ var _this22 = this;
2847
2879
  _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
2848
2880
  return _regenerator().w(function (_context6) {
2849
2881
  while (1) switch (_context6.n) {
2850
2882
  case 0:
2851
2883
  _context6.n = 1;
2852
- return _this17.startConnection();
2884
+ return _this22.startConnection();
2853
2885
  case 1:
2854
- _this17.initializeApp();
2886
+ _this22.initializeApp();
2855
2887
  if (hasWidgetConfig) {
2856
- _this17.initializeWidget();
2888
+ _this22.initializeWidget();
2857
2889
  }
2858
2890
  case 2:
2859
2891
  return _context6.a(2);