@crestapps/ai-chat-ui 1.0.0-preview-22-preview.1
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/README.md +145 -0
- package/dist/ai-chat.js +2453 -0
- package/dist/ai-chat.min.js +1 -0
- package/dist/chat-interaction.js +2088 -0
- package/dist/chat-interaction.min.js +1 -0
- package/dist/chat-widget.css +232 -0
- package/dist/chat-widget.min.css +1 -0
- package/dist/document-drop-zone.css +101 -0
- package/dist/document-drop-zone.js +77 -0
- package/dist/document-drop-zone.min.css +1 -0
- package/dist/document-drop-zone.min.js +1 -0
- package/dist/technical-name-generator.js +55 -0
- package/dist/technical-name-generator.min.js +1 -0
- package/package.json +59 -0
package/dist/ai-chat.js
ADDED
|
@@ -0,0 +1,2453 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** NOTE: This file is generated by Gulp and should not be edited directly!
|
|
3
|
+
** Any changes made directly to this file will be overwritten next time its asset group is processed by Gulp.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
7
|
+
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
8
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
9
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
10
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
11
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
12
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
13
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
14
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
15
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
16
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
17
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
18
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
19
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
20
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
21
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
22
|
+
window.openAIChatManager = function () {
|
|
23
|
+
// Defaults (can be overridden by instanceConfig)
|
|
24
|
+
var defaultConfig = {
|
|
25
|
+
// UI defaults for generated media
|
|
26
|
+
generatedImageAltText: 'Generated Image',
|
|
27
|
+
generatedImageMaxWidth: 400,
|
|
28
|
+
generatedChartMaxWidth: 900,
|
|
29
|
+
downloadImageTitle: 'Download image',
|
|
30
|
+
downloadChartTitle: 'Download chart as image',
|
|
31
|
+
downloadChartButtonText: 'Download',
|
|
32
|
+
userLabel: 'You',
|
|
33
|
+
assistantLabel: 'Assistant',
|
|
34
|
+
thumbsUpTitle: 'Thumbs up',
|
|
35
|
+
thumbsDownTitle: 'Thumbs down',
|
|
36
|
+
copyTitle: 'Click here to copy response to clipboard.',
|
|
37
|
+
codeCopiedText: 'Copied!',
|
|
38
|
+
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 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 ",
|
|
39
|
+
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 "
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Sanitize URLs to prevent javascript: protocol injection.
|
|
43
|
+
function sanitizeUrl(url) {
|
|
44
|
+
if (!url) return '';
|
|
45
|
+
var trimmed = url.trim();
|
|
46
|
+
if (/^javascript:/i.test(trimmed) || /^vbscript:/i.test(trimmed) || /^data:text\/html/i.test(trimmed)) {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
return url;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Safely HTML-encode a string using the DOM (avoids regex-based HTML filtering).
|
|
53
|
+
function escapeHtmlEntities(text) {
|
|
54
|
+
var span = document.createElement('span');
|
|
55
|
+
span.textContent = text;
|
|
56
|
+
return span.innerHTML;
|
|
57
|
+
}
|
|
58
|
+
function normalizeReference(reference) {
|
|
59
|
+
var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$link;
|
|
60
|
+
if (!reference || _typeof(reference) !== 'object') {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
var normalized = Object.assign({}, reference);
|
|
64
|
+
normalized.index = (_ref = (_normalized$index = normalized.index) !== null && _normalized$index !== void 0 ? _normalized$index : normalized.Index) !== null && _ref !== void 0 ? _ref : 0;
|
|
65
|
+
normalized.text = (_ref2 = (_normalized$text = normalized.text) !== null && _normalized$text !== void 0 ? _normalized$text : normalized.Text) !== null && _ref2 !== void 0 ? _ref2 : null;
|
|
66
|
+
normalized.link = (_ref3 = (_normalized$link = normalized.link) !== null && _normalized$link !== void 0 ? _normalized$link : normalized.Link) !== null && _ref3 !== void 0 ? _ref3 : null;
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
function normalizeReferences(references) {
|
|
70
|
+
if (!references || _typeof(references) !== 'object') {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
var normalized = {};
|
|
74
|
+
for (var _i = 0, _Object$entries = Object.entries(references); _i < _Object$entries.length; _i++) {
|
|
75
|
+
var _normalizeReference;
|
|
76
|
+
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
|
|
77
|
+
key = _Object$entries$_i[0],
|
|
78
|
+
value = _Object$entries$_i[1];
|
|
79
|
+
normalized[key] = (_normalizeReference = normalizeReference(value)) !== null && _normalizeReference !== void 0 ? _normalizeReference : {};
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
var renderer = new marked.Renderer();
|
|
84
|
+
|
|
85
|
+
// Modify the link rendering to open in a new tab
|
|
86
|
+
renderer.link = function (data) {
|
|
87
|
+
var href = sanitizeUrl(data.href);
|
|
88
|
+
if (!href) return data.text || '';
|
|
89
|
+
return "<a href=\"".concat(href, "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(data.text, "</a>");
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Custom code block renderer with highlight.js integration and copy button.
|
|
93
|
+
renderer.code = function (data) {
|
|
94
|
+
var code = data.text || '';
|
|
95
|
+
var lang = (data.lang || '').trim();
|
|
96
|
+
var highlighted = code;
|
|
97
|
+
if (typeof hljs !== 'undefined') {
|
|
98
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
99
|
+
try {
|
|
100
|
+
highlighted = hljs.highlight(code, {
|
|
101
|
+
language: lang
|
|
102
|
+
}).value;
|
|
103
|
+
} catch (_) {}
|
|
104
|
+
} else {
|
|
105
|
+
try {
|
|
106
|
+
highlighted = hljs.highlightAuto(code).value;
|
|
107
|
+
} catch (_) {}
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
highlighted = escapeHtmlEntities(code);
|
|
111
|
+
}
|
|
112
|
+
var langDisplay = lang ? escapeHtmlEntities(lang) : 'code';
|
|
113
|
+
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>");
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Custom image renderer for generated images with thumbnail styling and download button.
|
|
117
|
+
// Handles both URL and data-URI sources (data URIs are converted to blobs for download).
|
|
118
|
+
renderer.image = function (data) {
|
|
119
|
+
var src = sanitizeUrl(data.href);
|
|
120
|
+
if (!src) return '';
|
|
121
|
+
var alt = data.text || defaultConfig.generatedImageAltText;
|
|
122
|
+
var maxWidth = defaultConfig.generatedImageMaxWidth;
|
|
123
|
+
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>");
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Chart counter for unique IDs
|
|
127
|
+
var chartCounter = 0;
|
|
128
|
+
|
|
129
|
+
// Collector for charts discovered during marked parsing.
|
|
130
|
+
var _pendingCharts = [];
|
|
131
|
+
|
|
132
|
+
// Global chart config map: any page (e.g., Chat Interactions) that uses
|
|
133
|
+
// the shared marked instance can call window.renderPendingCharts() after
|
|
134
|
+
// its DOM update to render charts it didn't create itself.
|
|
135
|
+
window.__chartConfigs = window.__chartConfigs || {};
|
|
136
|
+
function createChartHtml(chartId) {
|
|
137
|
+
var chartMaxWidth = defaultConfig.generatedChartMaxWidth;
|
|
138
|
+
return "<div class=\"chart-container\" style=\"position: relative; width: 100%; max-width: ".concat(chartMaxWidth, "px;\">") + "<canvas id=\"".concat(chartId, "\" class=\"img-thumbnail\"></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>";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Register [chart:{...json...}] as a native marked block extension so the
|
|
142
|
+
// markdown parser handles chart markers inline with surrounding text.
|
|
143
|
+
marked.use({
|
|
144
|
+
extensions: [{
|
|
145
|
+
name: 'chart',
|
|
146
|
+
level: 'block',
|
|
147
|
+
start: function start(src) {
|
|
148
|
+
var idx = src.indexOf('[chart:');
|
|
149
|
+
return idx >= 0 ? idx : undefined;
|
|
150
|
+
},
|
|
151
|
+
tokenizer: function tokenizer(src) {
|
|
152
|
+
var extracted = tryExtractChartMarker(src);
|
|
153
|
+
if (!extracted || extracted.startIndex !== 0) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
var chartId = "chat_chart_".concat(++chartCounter);
|
|
157
|
+
return {
|
|
158
|
+
type: 'chart',
|
|
159
|
+
raw: src.substring(0, extracted.endIndex),
|
|
160
|
+
chartId: chartId,
|
|
161
|
+
json: extracted.json
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
renderer: function renderer(token) {
|
|
165
|
+
_pendingCharts.push({
|
|
166
|
+
chartId: token.chartId,
|
|
167
|
+
config: token.json
|
|
168
|
+
});
|
|
169
|
+
window.__chartConfigs[token.chartId] = token.json;
|
|
170
|
+
return createChartHtml(token.chartId);
|
|
171
|
+
}
|
|
172
|
+
}]
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Extract a [chart:{...json...}] marker. This avoids regex issues with nested brackets.
|
|
176
|
+
function tryExtractChartMarker(text) {
|
|
177
|
+
var token = '[chart:';
|
|
178
|
+
var start = text.indexOf(token);
|
|
179
|
+
if (start < 0) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Find JSON object boundary by balancing braces
|
|
184
|
+
var jsonStart = start + token.length;
|
|
185
|
+
var i = jsonStart;
|
|
186
|
+
while (i < text.length && (text[i] === ' ' || text[i] === '\n' || text[i] === '\r' || text[i] === '\t')) {
|
|
187
|
+
i++;
|
|
188
|
+
}
|
|
189
|
+
if (i >= text.length || text[i] !== '{') {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
var depth = 0;
|
|
193
|
+
var inString = false;
|
|
194
|
+
var escape = false;
|
|
195
|
+
for (; i < text.length; i++) {
|
|
196
|
+
var ch = text[i];
|
|
197
|
+
if (inString) {
|
|
198
|
+
if (escape) {
|
|
199
|
+
escape = false;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (ch === '\\') {
|
|
203
|
+
escape = true;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (ch === '"') {
|
|
207
|
+
inString = false;
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (ch === '"') {
|
|
212
|
+
inString = true;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (ch === '{') {
|
|
216
|
+
depth++;
|
|
217
|
+
} else if (ch === '}') {
|
|
218
|
+
depth--;
|
|
219
|
+
if (depth === 0) {
|
|
220
|
+
var jsonEnd = i;
|
|
221
|
+
// Expect closing bracket after JSON
|
|
222
|
+
var closeBracketIndex = text.indexOf(']', jsonEnd + 1);
|
|
223
|
+
if (closeBracketIndex < 0) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
var json = text.substring(jsonStart, jsonEnd + 1).trim();
|
|
227
|
+
return {
|
|
228
|
+
startIndex: start,
|
|
229
|
+
endIndex: closeBracketIndex + 1,
|
|
230
|
+
json: json
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function renderChartsInMessage(message) {
|
|
238
|
+
if (!message || !message._pendingCharts || !message._pendingCharts.length) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Copy and clear pending charts immediately to prevent duplicate renders.
|
|
243
|
+
var charts = _toConsumableArray(message._pendingCharts);
|
|
244
|
+
message._pendingCharts = [];
|
|
245
|
+
|
|
246
|
+
// Defer to requestAnimationFrame so the browser has fully laid out the
|
|
247
|
+
// canvas elements before Chart.js reads their dimensions.
|
|
248
|
+
requestAnimationFrame(function () {
|
|
249
|
+
var _iterator = _createForOfIteratorHelper(charts),
|
|
250
|
+
_step;
|
|
251
|
+
try {
|
|
252
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
253
|
+
var c = _step.value;
|
|
254
|
+
renderChartOnCanvas(c.chartId, c.config);
|
|
255
|
+
}
|
|
256
|
+
} catch (err) {
|
|
257
|
+
_iterator.e(err);
|
|
258
|
+
} finally {
|
|
259
|
+
_iterator.f();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function renderChartOnCanvas(chartId, config) {
|
|
264
|
+
var canvas = document.getElementById(chartId);
|
|
265
|
+
if (!canvas) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (typeof Chart === 'undefined') {
|
|
269
|
+
console.warn('Chart.js is not loaded. To render interactive charts, include the Chart.js library on the page (e.g., <script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>).');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
var _cfg$options;
|
|
274
|
+
if (canvas._chartInstance) {
|
|
275
|
+
canvas._chartInstance.destroy();
|
|
276
|
+
}
|
|
277
|
+
var cfg = typeof config === 'string' ? JSON.parse(config) : config;
|
|
278
|
+
(_cfg$options = cfg.options) !== null && _cfg$options !== void 0 ? _cfg$options : cfg.options = {};
|
|
279
|
+
cfg.options.responsive = true;
|
|
280
|
+
cfg.options.maintainAspectRatio = false;
|
|
281
|
+
canvas._chartInstance = new Chart(canvas, cfg);
|
|
282
|
+
delete window.__chartConfigs[chartId];
|
|
283
|
+
} catch (e) {
|
|
284
|
+
console.error('Error creating chart:', e);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Global function: renders any chart canvases whose configs are in the
|
|
289
|
+
// global __chartConfigs map. Called by pages (e.g., Chat Interactions)
|
|
290
|
+
// that share the marked instance but have their own rendering pipeline.
|
|
291
|
+
window.renderPendingCharts = function () {
|
|
292
|
+
if (typeof Chart === 'undefined') {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
var configs = window.__chartConfigs;
|
|
296
|
+
if (!configs) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
requestAnimationFrame(function () {
|
|
300
|
+
for (var _i2 = 0, _Object$keys = Object.keys(configs); _i2 < _Object$keys.length; _i2++) {
|
|
301
|
+
var chartId = _Object$keys[_i2];
|
|
302
|
+
renderChartOnCanvas(chartId, configs[chartId]);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Parse markdown content via marked (which natively handles [chart:...] markers
|
|
308
|
+
// through the registered extension) and collect pending chart configs for later
|
|
309
|
+
// Chart.js rendering.
|
|
310
|
+
function parseMarkdownContent(content, message) {
|
|
311
|
+
_pendingCharts = [];
|
|
312
|
+
var html = marked.parse(content, {
|
|
313
|
+
renderer: renderer
|
|
314
|
+
});
|
|
315
|
+
message._pendingCharts = _pendingCharts.length > 0 ? _toConsumableArray(_pendingCharts) : [];
|
|
316
|
+
return DOMPurify.sanitize(html, {
|
|
317
|
+
ADD_ATTR: ['target']
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
var initialize = function initialize(instanceConfig) {
|
|
321
|
+
var config = Object.assign({}, defaultConfig, instanceConfig);
|
|
322
|
+
// Keep defaultConfig in sync so renderers use overridden values
|
|
323
|
+
defaultConfig = config;
|
|
324
|
+
if (!config.signalRHubUrl) {
|
|
325
|
+
console.error('The signalRHubUrl is required.');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (!config.appElementSelector) {
|
|
329
|
+
console.error('The appElementSelector is required.');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!config.chatContainerElementSelector) {
|
|
333
|
+
console.error('The chatContainerElementSelector is required.');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (!config.inputElementSelector) {
|
|
337
|
+
console.error('The inputElementSelector is required.');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!config.sendButtonElementSelector) {
|
|
341
|
+
console.error('The sendButtonElementSelector is required.');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
var app = Vue.createApp({
|
|
345
|
+
data: function data() {
|
|
346
|
+
return {
|
|
347
|
+
inputElement: null,
|
|
348
|
+
buttonElement: null,
|
|
349
|
+
chatContainer: null,
|
|
350
|
+
placeholder: null,
|
|
351
|
+
isSessionStarted: false,
|
|
352
|
+
isPlaceholderVisible: true,
|
|
353
|
+
chatWidgetStateName: null,
|
|
354
|
+
chatWidgetStateSession: null,
|
|
355
|
+
chatHistorySection: null,
|
|
356
|
+
widgetIsInitialized: false,
|
|
357
|
+
isStreaming: false,
|
|
358
|
+
isNavigatingAway: false,
|
|
359
|
+
autoScroll: true,
|
|
360
|
+
stream: null,
|
|
361
|
+
messages: [],
|
|
362
|
+
notifications: [],
|
|
363
|
+
prompt: '',
|
|
364
|
+
documents: config.existingDocuments || [],
|
|
365
|
+
isUploading: false,
|
|
366
|
+
uploadErrors: [],
|
|
367
|
+
isDragOver: false,
|
|
368
|
+
documentBar: null,
|
|
369
|
+
metricsEnabled: !!config.metricsEnabled,
|
|
370
|
+
userLabel: config.userLabel,
|
|
371
|
+
assistantLabel: config.assistantLabel,
|
|
372
|
+
thumbsUpTitle: config.thumbsUpTitle,
|
|
373
|
+
thumbsDownTitle: config.thumbsDownTitle,
|
|
374
|
+
copyTitle: config.copyTitle,
|
|
375
|
+
isRecording: false,
|
|
376
|
+
mediaRecorder: null,
|
|
377
|
+
preRecordingPrompt: '',
|
|
378
|
+
micButton: null,
|
|
379
|
+
speechToTextEnabled: config.chatMode === 'AudioInput' || config.chatMode === 'Conversation',
|
|
380
|
+
textToSpeechEnabled: config.chatMode === 'Conversation',
|
|
381
|
+
ttsVoiceName: config.ttsVoiceName || null,
|
|
382
|
+
audioChunks: [],
|
|
383
|
+
audioPlayQueue: [],
|
|
384
|
+
isPlayingAudio: false,
|
|
385
|
+
currentAudioElement: null,
|
|
386
|
+
ttsButton: null,
|
|
387
|
+
conversationModeEnabled: config.chatMode === 'Conversation',
|
|
388
|
+
conversationButton: null,
|
|
389
|
+
isConversationMode: false,
|
|
390
|
+
notificationDismissTimers: {}
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
computed: {
|
|
394
|
+
lastAssistantIndex: function lastAssistantIndex() {
|
|
395
|
+
for (var i = this.messages.length - 1; i >= 0; i--) {
|
|
396
|
+
if (this.messages[i].role === 'assistant') {
|
|
397
|
+
return i;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return -1;
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
methods: {
|
|
404
|
+
handleBeforeUnload: function handleBeforeUnload() {
|
|
405
|
+
this.isNavigatingAway = true;
|
|
406
|
+
},
|
|
407
|
+
handleDragOver: function handleDragOver(e) {
|
|
408
|
+
if (!config.sessionDocumentsEnabled) return;
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
e.stopPropagation();
|
|
411
|
+
this.isDragOver = true;
|
|
412
|
+
var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
|
|
413
|
+
if (inputArea) inputArea.classList.add('ai-chat-drag-over');
|
|
414
|
+
},
|
|
415
|
+
handleDragLeave: function handleDragLeave(e) {
|
|
416
|
+
if (!config.sessionDocumentsEnabled) return;
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
e.stopPropagation();
|
|
419
|
+
this.isDragOver = false;
|
|
420
|
+
var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
|
|
421
|
+
if (inputArea) inputArea.classList.remove('ai-chat-drag-over');
|
|
422
|
+
},
|
|
423
|
+
handleDrop: function handleDrop(e) {
|
|
424
|
+
if (!config.sessionDocumentsEnabled) return;
|
|
425
|
+
e.preventDefault();
|
|
426
|
+
e.stopPropagation();
|
|
427
|
+
this.isDragOver = false;
|
|
428
|
+
var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
|
|
429
|
+
if (inputArea) inputArea.classList.remove('ai-chat-drag-over');
|
|
430
|
+
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
431
|
+
this.uploadFiles(e.dataTransfer.files);
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
triggerFileInput: function triggerFileInput() {
|
|
435
|
+
if (!config.sessionDocumentsEnabled) return;
|
|
436
|
+
var fileInput = document.getElementById('ai-chat-doc-input');
|
|
437
|
+
if (fileInput) fileInput.click();
|
|
438
|
+
},
|
|
439
|
+
handleFileInputChange: function handleFileInputChange(e) {
|
|
440
|
+
var files = e.target.files;
|
|
441
|
+
if (files && files.length > 0) {
|
|
442
|
+
this.uploadFiles(files);
|
|
443
|
+
}
|
|
444
|
+
e.target.value = '';
|
|
445
|
+
},
|
|
446
|
+
uploadFiles: function uploadFiles(files) {
|
|
447
|
+
var _this = this;
|
|
448
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
449
|
+
var sessionId, profileId, formData, i, response, errorText, uploadError, result, j, _t;
|
|
450
|
+
return _regenerator().w(function (_context) {
|
|
451
|
+
while (1) switch (_context.p = _context.n) {
|
|
452
|
+
case 0:
|
|
453
|
+
if (config.uploadDocumentUrl) {
|
|
454
|
+
_context.n = 1;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
return _context.a(2);
|
|
458
|
+
case 1:
|
|
459
|
+
sessionId = _this.getSessionId();
|
|
460
|
+
profileId = _this.getProfileId();
|
|
461
|
+
if (!(!sessionId && !profileId)) {
|
|
462
|
+
_context.n = 2;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
console.warn('Cannot upload documents without a session or profile.');
|
|
466
|
+
return _context.a(2);
|
|
467
|
+
case 2:
|
|
468
|
+
_this.isUploading = true;
|
|
469
|
+
_this.uploadErrors = [];
|
|
470
|
+
_this.renderDocumentBar();
|
|
471
|
+
_context.p = 3;
|
|
472
|
+
formData = new FormData();
|
|
473
|
+
if (sessionId) {
|
|
474
|
+
formData.append('sessionId', sessionId);
|
|
475
|
+
} else {
|
|
476
|
+
formData.append('profileId', profileId);
|
|
477
|
+
}
|
|
478
|
+
for (i = 0; i < files.length; i++) {
|
|
479
|
+
formData.append('files', files[i]);
|
|
480
|
+
}
|
|
481
|
+
_context.n = 4;
|
|
482
|
+
return fetch(config.uploadDocumentUrl, {
|
|
483
|
+
method: 'POST',
|
|
484
|
+
body: formData
|
|
485
|
+
});
|
|
486
|
+
case 4:
|
|
487
|
+
response = _context.v;
|
|
488
|
+
if (response.ok) {
|
|
489
|
+
_context.n = 6;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
_context.n = 5;
|
|
493
|
+
return response.text();
|
|
494
|
+
case 5:
|
|
495
|
+
errorText = _context.v;
|
|
496
|
+
uploadError = _this.extractReadableErrorMessage(errorText, 'Upload failed. Please try again.');
|
|
497
|
+
console.error('Upload failed:', errorText);
|
|
498
|
+
_this.uploadErrors = [{
|
|
499
|
+
fileName: '',
|
|
500
|
+
error: uploadError
|
|
501
|
+
}];
|
|
502
|
+
return _context.a(2);
|
|
503
|
+
case 6:
|
|
504
|
+
_context.n = 7;
|
|
505
|
+
return response.json();
|
|
506
|
+
case 7:
|
|
507
|
+
result = _context.v;
|
|
508
|
+
// If the server created a new session, initialize it.
|
|
509
|
+
if (result.sessionId && !sessionId) {
|
|
510
|
+
_this.initializeSession(result.sessionId);
|
|
511
|
+
}
|
|
512
|
+
if (result.uploaded && result.uploaded.length > 0) {
|
|
513
|
+
for (j = 0; j < result.uploaded.length; j++) {
|
|
514
|
+
_this.documents.push(result.uploaded[j]);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (result.failed && result.failed.length > 0) {
|
|
518
|
+
_this.uploadErrors = result.failed;
|
|
519
|
+
}
|
|
520
|
+
_context.n = 9;
|
|
521
|
+
break;
|
|
522
|
+
case 8:
|
|
523
|
+
_context.p = 8;
|
|
524
|
+
_t = _context.v;
|
|
525
|
+
console.error('Upload error:', _t);
|
|
526
|
+
_this.uploadErrors = [{
|
|
527
|
+
fileName: '',
|
|
528
|
+
error: 'Upload failed. Please try again.'
|
|
529
|
+
}];
|
|
530
|
+
case 9:
|
|
531
|
+
_context.p = 9;
|
|
532
|
+
_this.isUploading = false;
|
|
533
|
+
_this.renderDocumentBar();
|
|
534
|
+
return _context.f(9);
|
|
535
|
+
case 10:
|
|
536
|
+
return _context.a(2);
|
|
537
|
+
}
|
|
538
|
+
}, _callee, null, [[3, 8, 9, 10]]);
|
|
539
|
+
}))();
|
|
540
|
+
},
|
|
541
|
+
removeDocument: function removeDocument(doc) {
|
|
542
|
+
var _this2 = this;
|
|
543
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
544
|
+
var sessionId, response, idx, errorText, removeError, _t2;
|
|
545
|
+
return _regenerator().w(function (_context2) {
|
|
546
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
547
|
+
case 0:
|
|
548
|
+
if (config.removeDocumentUrl) {
|
|
549
|
+
_context2.n = 1;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
return _context2.a(2);
|
|
553
|
+
case 1:
|
|
554
|
+
_context2.p = 1;
|
|
555
|
+
sessionId = _this2.getSessionId();
|
|
556
|
+
_context2.n = 2;
|
|
557
|
+
return fetch(config.removeDocumentUrl, {
|
|
558
|
+
method: 'POST',
|
|
559
|
+
headers: {
|
|
560
|
+
'Content-Type': 'application/json'
|
|
561
|
+
},
|
|
562
|
+
body: JSON.stringify({
|
|
563
|
+
itemId: sessionId,
|
|
564
|
+
documentId: doc.documentId
|
|
565
|
+
})
|
|
566
|
+
});
|
|
567
|
+
case 2:
|
|
568
|
+
response = _context2.v;
|
|
569
|
+
if (!response.ok) {
|
|
570
|
+
_context2.n = 3;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
idx = _this2.documents.indexOf(doc);
|
|
574
|
+
if (idx > -1) _this2.documents.splice(idx, 1);
|
|
575
|
+
_context2.n = 5;
|
|
576
|
+
break;
|
|
577
|
+
case 3:
|
|
578
|
+
_context2.n = 4;
|
|
579
|
+
return response.text();
|
|
580
|
+
case 4:
|
|
581
|
+
errorText = _context2.v;
|
|
582
|
+
removeError = _this2.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
|
|
583
|
+
console.error('Failed to remove document:', response.status, errorText);
|
|
584
|
+
_this2.uploadErrors = [{
|
|
585
|
+
fileName: doc.fileName || '',
|
|
586
|
+
error: removeError
|
|
587
|
+
}];
|
|
588
|
+
_this2.renderDocumentBar();
|
|
589
|
+
case 5:
|
|
590
|
+
_context2.n = 7;
|
|
591
|
+
break;
|
|
592
|
+
case 6:
|
|
593
|
+
_context2.p = 6;
|
|
594
|
+
_t2 = _context2.v;
|
|
595
|
+
console.error('Remove document error:', _t2);
|
|
596
|
+
_this2.uploadErrors = [{
|
|
597
|
+
fileName: doc.fileName || '',
|
|
598
|
+
error: 'Failed to remove document. Please try again.'
|
|
599
|
+
}];
|
|
600
|
+
_this2.renderDocumentBar();
|
|
601
|
+
case 7:
|
|
602
|
+
return _context2.a(2);
|
|
603
|
+
}
|
|
604
|
+
}, _callee2, null, [[1, 6]]);
|
|
605
|
+
}))();
|
|
606
|
+
},
|
|
607
|
+
formatFileSize: function formatFileSize(bytes) {
|
|
608
|
+
if (bytes < 1024) return bytes + ' B';
|
|
609
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
610
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
611
|
+
},
|
|
612
|
+
renderDocumentBar: function renderDocumentBar() {
|
|
613
|
+
if (!this.documentBar) return;
|
|
614
|
+
if (!config.sessionDocumentsEnabled) {
|
|
615
|
+
this.documentBar.classList.add('d-none');
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.documentBar.classList.remove('d-none');
|
|
619
|
+
var html = '<div class="ai-chat-doc-bar p-2">';
|
|
620
|
+
html += '<div class="d-flex flex-wrap align-items-center gap-1">';
|
|
621
|
+
for (var i = 0; i < this.documents.length; i++) {
|
|
622
|
+
var doc = this.documents[i];
|
|
623
|
+
var name = doc.fileName || 'Document';
|
|
624
|
+
if (name.length > 20) name = name.substring(0, 17) + '...';
|
|
625
|
+
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 || '') + '">';
|
|
626
|
+
html += '<i class="fa-solid fa-file-lines" style="font-size: 0.7rem;"></i> ';
|
|
627
|
+
html += this.escapeHtml(name);
|
|
628
|
+
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>';
|
|
629
|
+
html += '</span>';
|
|
630
|
+
}
|
|
631
|
+
for (var m = 0; m < this.uploadErrors.length; m++) {
|
|
632
|
+
var failedItem = this.uploadErrors[m];
|
|
633
|
+
var failedName = failedItem.fileName || 'File';
|
|
634
|
+
var errorMsg = failedItem.error || 'Upload failed';
|
|
635
|
+
if (failedName.length > 15) failedName = failedName.substring(0, 12) + '...';
|
|
636
|
+
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) + '">';
|
|
637
|
+
html += '<i class="fa-solid fa-circle-exclamation" style="font-size: 0.7rem;"></i> ';
|
|
638
|
+
html += this.escapeHtml(failedName);
|
|
639
|
+
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>';
|
|
640
|
+
html += '</span>';
|
|
641
|
+
}
|
|
642
|
+
if (this.isUploading) {
|
|
643
|
+
html += '<span class="badge bg-info bg-opacity-25 text-dark d-inline-flex align-items-center gap-1 px-2 py-1" style="font-size: 0.8rem;">';
|
|
644
|
+
html += '<span class="spinner-border spinner-border-sm" style="width: 0.7rem; height: 0.7rem;"></span> Uploading...';
|
|
645
|
+
html += '</span>';
|
|
646
|
+
}
|
|
647
|
+
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">';
|
|
648
|
+
html += '<i class="fa-solid fa-paperclip"></i>';
|
|
649
|
+
if (this.documents.length === 0 && !this.isUploading) {
|
|
650
|
+
html += ' <span>Attach files</span>';
|
|
651
|
+
}
|
|
652
|
+
html += '</button>';
|
|
653
|
+
html += '</div>';
|
|
654
|
+
if (config.supportedExtensionsText) {
|
|
655
|
+
html += '<div class="small text-muted mt-2">Supported formats: ' + this.escapeHtml(config.supportedExtensionsText) + '</div>';
|
|
656
|
+
}
|
|
657
|
+
html += '</div>';
|
|
658
|
+
this.documentBar.replaceChildren(DOMPurify.sanitize(html, {
|
|
659
|
+
RETURN_DOM_FRAGMENT: true
|
|
660
|
+
}));
|
|
661
|
+
|
|
662
|
+
// Bind remove handlers
|
|
663
|
+
var self = this;
|
|
664
|
+
var closeButtons = this.documentBar.querySelectorAll('[data-doc-index]');
|
|
665
|
+
for (var j = 0; j < closeButtons.length; j++) {
|
|
666
|
+
closeButtons[j].addEventListener('click', function (idx) {
|
|
667
|
+
return function (e) {
|
|
668
|
+
e.preventDefault();
|
|
669
|
+
e.stopPropagation();
|
|
670
|
+
var docToRemove = self.documents[idx];
|
|
671
|
+
if (docToRemove) self.removeDocument(docToRemove);
|
|
672
|
+
};
|
|
673
|
+
}(parseInt(closeButtons[j].getAttribute('data-doc-index'))));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Bind error dismiss handlers
|
|
677
|
+
var errorCloseButtons = this.documentBar.querySelectorAll('[data-error-index]');
|
|
678
|
+
for (var n = 0; n < errorCloseButtons.length; n++) {
|
|
679
|
+
errorCloseButtons[n].addEventListener('click', function (idx) {
|
|
680
|
+
return function (e) {
|
|
681
|
+
e.preventDefault();
|
|
682
|
+
e.stopPropagation();
|
|
683
|
+
self.uploadErrors.splice(idx, 1);
|
|
684
|
+
self.renderDocumentBar();
|
|
685
|
+
};
|
|
686
|
+
}(parseInt(errorCloseButtons[n].getAttribute('data-error-index'))));
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Bind add button
|
|
690
|
+
var addBtn = this.documentBar.querySelector('.ai-chat-doc-add-btn');
|
|
691
|
+
if (addBtn) {
|
|
692
|
+
addBtn.addEventListener('click', function (e) {
|
|
693
|
+
e.preventDefault();
|
|
694
|
+
self.triggerFileInput();
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
escapeHtml: function escapeHtml(text) {
|
|
699
|
+
var div = document.createElement('div');
|
|
700
|
+
div.textContent = text;
|
|
701
|
+
return div.innerHTML;
|
|
702
|
+
},
|
|
703
|
+
extractReadableErrorMessage: function extractReadableErrorMessage(errorText, fallbackMessage) {
|
|
704
|
+
if (!errorText || typeof errorText !== 'string') {
|
|
705
|
+
return fallbackMessage;
|
|
706
|
+
}
|
|
707
|
+
var trimmed = errorText.trim();
|
|
708
|
+
if (!trimmed) {
|
|
709
|
+
return fallbackMessage;
|
|
710
|
+
}
|
|
711
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
712
|
+
try {
|
|
713
|
+
var parsed = JSON.parse(trimmed);
|
|
714
|
+
var message = (parsed === null || parsed === void 0 ? void 0 : parsed.error) || (parsed === null || parsed === void 0 ? void 0 : parsed.message) || (parsed === null || parsed === void 0 ? void 0 : parsed.title) || (parsed === null || parsed === void 0 ? void 0 : parsed.detail);
|
|
715
|
+
return typeof message === 'string' && message.trim() ? message.trim() : fallbackMessage;
|
|
716
|
+
} catch (err) {
|
|
717
|
+
return fallbackMessage;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (/<[^>]+>/.test(trimmed)) {
|
|
721
|
+
return fallbackMessage;
|
|
722
|
+
}
|
|
723
|
+
return trimmed;
|
|
724
|
+
},
|
|
725
|
+
normalizeAssistantAppearance: function normalizeAssistantAppearance(appearance) {
|
|
726
|
+
if (!appearance) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
var label = typeof appearance.label === 'string' ? appearance.label.trim() : '';
|
|
730
|
+
var icon = typeof appearance.icon === 'string' ? appearance.icon.trim() : '';
|
|
731
|
+
var cssClass = typeof appearance.cssClass === 'string' ? appearance.cssClass.trim() : '';
|
|
732
|
+
var disableStreamingAnimation = !!appearance.disableStreamingAnimation;
|
|
733
|
+
if (!label && !icon && !cssClass && !disableStreamingAnimation) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
label: label,
|
|
738
|
+
icon: icon,
|
|
739
|
+
cssClass: cssClass,
|
|
740
|
+
disableStreamingAnimation: disableStreamingAnimation
|
|
741
|
+
};
|
|
742
|
+
},
|
|
743
|
+
getAssistantLabel: function getAssistantLabel(message) {
|
|
744
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
745
|
+
return appearance && appearance.label ? appearance.label : this.assistantLabel;
|
|
746
|
+
},
|
|
747
|
+
getAssistantRoleClasses: function getAssistantRoleClasses(message) {
|
|
748
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
749
|
+
var classes = ['ai-chat-msg-role'];
|
|
750
|
+
if (appearance && appearance.cssClass) {
|
|
751
|
+
classes.push(appearance.cssClass);
|
|
752
|
+
} else {
|
|
753
|
+
classes.push('ai-chat-msg-role-assistant');
|
|
754
|
+
}
|
|
755
|
+
return classes;
|
|
756
|
+
},
|
|
757
|
+
getAssistantIconClasses: function getAssistantIconClasses(message, index) {
|
|
758
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
759
|
+
return [this.shouldAnimateAssistantIcon(message, index) ? 'ai-streaming-icon' : 'ai-bot-icon', appearance && appearance.cssClass ? appearance.cssClass : ''];
|
|
760
|
+
},
|
|
761
|
+
getAssistantIcon: function getAssistantIcon(message) {
|
|
762
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
763
|
+
return appearance && appearance.icon ? appearance.icon : 'fa fa-robot';
|
|
764
|
+
},
|
|
765
|
+
shouldAnimateAssistantIcon: function shouldAnimateAssistantIcon(message, index) {
|
|
766
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
767
|
+
return !!message && message.isStreaming && index === this.lastAssistantIndex && !(appearance && appearance.disableStreamingAnimation);
|
|
768
|
+
},
|
|
769
|
+
startConnection: function startConnection() {
|
|
770
|
+
var _this3 = this;
|
|
771
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
|
|
772
|
+
var _t3;
|
|
773
|
+
return _regenerator().w(function (_context3) {
|
|
774
|
+
while (1) switch (_context3.p = _context3.n) {
|
|
775
|
+
case 0:
|
|
776
|
+
_this3.connection = new signalR.HubConnectionBuilder().withUrl(config.signalRHubUrl).withAutomaticReconnect().build();
|
|
777
|
+
|
|
778
|
+
// Allow long-running operations (e.g., multi-step MCP tool calls)
|
|
779
|
+
// without the client disconnecting prematurely.
|
|
780
|
+
_this3.connection.serverTimeoutInMilliseconds = 600000;
|
|
781
|
+
_this3.connection.keepAliveIntervalInMilliseconds = 15000;
|
|
782
|
+
_this3.connection.on("LoadSession", function (data) {
|
|
783
|
+
var _data$messages;
|
|
784
|
+
_this3.initializeSession(data.sessionId, true);
|
|
785
|
+
_this3.messages = [];
|
|
786
|
+
_this3.documents = data.documents || [];
|
|
787
|
+
((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
|
|
788
|
+
_this3.addMessage(msg);
|
|
789
|
+
_this3.$nextTick(function () {
|
|
790
|
+
renderChartsInMessage(msg);
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// Update feedback icons in the DOM after all messages have rendered.
|
|
795
|
+
_this3.$nextTick(function () {
|
|
796
|
+
_this3.refreshAllFeedbackIcons();
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// When the session is new (no messages) and an initial prompt is configured,
|
|
800
|
+
// automatically send it as the first user message to trigger an AI response.
|
|
801
|
+
if (_this3.messages.length === 0 && config.initialPrompt) {
|
|
802
|
+
_this3.prompt = config.initialPrompt;
|
|
803
|
+
_this3.sendMessage();
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
_this3.connection.on("ReceiveError", function (error) {
|
|
807
|
+
console.error("SignalR Error: ", error);
|
|
808
|
+
if (_this3.isRecording) {
|
|
809
|
+
_this3.stopRecording();
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// If this is a widget with a stale cached session (e.g., profile was deleted),
|
|
813
|
+
// clear the cached session and start fresh with the current profile.
|
|
814
|
+
if (_this3.widgetIsInitialized && !_this3.isSessionStarted && !_this3._attemptedSessionRecovery) {
|
|
815
|
+
_this3._attemptedSessionRecovery = true;
|
|
816
|
+
localStorage.removeItem(_this3.chatWidgetStateSession);
|
|
817
|
+
_this3.startNewSession();
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
_this3.connection.on("MessageRated", function (messageId, userRating) {
|
|
821
|
+
var msg = _this3.messages.find(function (m) {
|
|
822
|
+
return m.id === messageId;
|
|
823
|
+
});
|
|
824
|
+
if (msg) {
|
|
825
|
+
msg.userRating = userRating;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
_this3.connection.on("ReceiveTranscript", function (sessionId, text, isFinal) {
|
|
829
|
+
if (_this3.isConversationMode) {
|
|
830
|
+
if (!isFinal && text) {
|
|
831
|
+
_this3._conversationPartialTranscript = text;
|
|
832
|
+
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
833
|
+
var html = '<p class="ai-partial-transcript">' + escaped + '</p>';
|
|
834
|
+
|
|
835
|
+
// Show partial transcript as a live user message.
|
|
836
|
+
if (!_this3._conversationPartialMessage) {
|
|
837
|
+
_this3.hidePlaceholder();
|
|
838
|
+
_this3._conversationPartialMessage = {
|
|
839
|
+
role: 'user',
|
|
840
|
+
content: text,
|
|
841
|
+
htmlContent: html,
|
|
842
|
+
isPartial: true
|
|
843
|
+
};
|
|
844
|
+
_this3.messages.push(_this3._conversationPartialMessage);
|
|
845
|
+
} else {
|
|
846
|
+
_this3._conversationPartialMessage.content = text;
|
|
847
|
+
_this3._conversationPartialMessage.htmlContent = html;
|
|
848
|
+
}
|
|
849
|
+
_this3.scrollToBottom();
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (text && !_this3._audioInputSent) {
|
|
854
|
+
_this3.prompt = _this3.preRecordingPrompt + text;
|
|
855
|
+
if (_this3.inputElement) {
|
|
856
|
+
_this3.inputElement.value = _this3.prompt;
|
|
857
|
+
_this3.inputElement.dispatchEvent(new Event('input'));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
_this3.connection.on("ReceiveConversationUserMessage", function (sessionId, text) {
|
|
862
|
+
if (text) {
|
|
863
|
+
_this3.stopAudio();
|
|
864
|
+
|
|
865
|
+
// If there's an interrupted assistant message still streaming,
|
|
866
|
+
// mark it as done to stop the spinner animation.
|
|
867
|
+
if (_this3._conversationAssistantMessage) {
|
|
868
|
+
var oldMsg = _this3.messages[_this3._conversationAssistantMessage.index];
|
|
869
|
+
if (oldMsg) {
|
|
870
|
+
oldMsg.isStreaming = false;
|
|
871
|
+
}
|
|
872
|
+
_this3._conversationAssistantMessage = null;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Replace the partial transcript message with the final one.
|
|
876
|
+
if (_this3._conversationPartialMessage) {
|
|
877
|
+
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
878
|
+
_this3._conversationPartialMessage.content = text;
|
|
879
|
+
_this3._conversationPartialMessage.htmlContent = '<p>' + escaped + '</p>';
|
|
880
|
+
_this3._conversationPartialMessage.isPartial = false;
|
|
881
|
+
_this3._conversationPartialMessage = null;
|
|
882
|
+
} else {
|
|
883
|
+
_this3.addMessage({
|
|
884
|
+
role: 'user',
|
|
885
|
+
content: text
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
_this3.scrollToBottom();
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
_this3.connection.on("ReceiveConversationAssistantToken", function (sessionId, messageId, token, responseId, appearance) {
|
|
892
|
+
if (!_this3._conversationAssistantMessage) {
|
|
893
|
+
_this3.stopAudio();
|
|
894
|
+
_this3.hideTypingIndicator();
|
|
895
|
+
|
|
896
|
+
// Ensure no stale streaming indicators remain from prior messages.
|
|
897
|
+
for (var j = 0; j < _this3.messages.length; j++) {
|
|
898
|
+
if (_this3.messages[j].isStreaming) {
|
|
899
|
+
_this3.messages[j].isStreaming = false;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
var msgIndex = _this3.messages.length;
|
|
903
|
+
var newMessage = {
|
|
904
|
+
id: messageId,
|
|
905
|
+
role: "assistant",
|
|
906
|
+
content: "",
|
|
907
|
+
htmlContent: "",
|
|
908
|
+
isStreaming: true,
|
|
909
|
+
userRating: null,
|
|
910
|
+
appearance: _this3.normalizeAssistantAppearance(appearance)
|
|
911
|
+
};
|
|
912
|
+
_this3.messages.push(newMessage);
|
|
913
|
+
_this3._conversationAssistantMessage = {
|
|
914
|
+
index: msgIndex,
|
|
915
|
+
content: ''
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
_this3._conversationAssistantMessage.content += token;
|
|
919
|
+
var msg = _this3.messages[_this3._conversationAssistantMessage.index];
|
|
920
|
+
if (msg) {
|
|
921
|
+
if (!msg.appearance) {
|
|
922
|
+
msg.appearance = _this3.normalizeAssistantAppearance(appearance);
|
|
923
|
+
}
|
|
924
|
+
msg.content = _this3._conversationAssistantMessage.content;
|
|
925
|
+
msg.htmlContent = parseMarkdownContent(msg.content, msg);
|
|
926
|
+
_this3.$nextTick(function () {
|
|
927
|
+
renderChartsInMessage(msg);
|
|
928
|
+
_this3.scrollToBottom();
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
_this3.connection.on("ReceiveConversationAssistantComplete", function (sessionId, messageId) {
|
|
933
|
+
if (_this3._conversationAssistantMessage) {
|
|
934
|
+
var msg = _this3.messages[_this3._conversationAssistantMessage.index];
|
|
935
|
+
if (msg) {
|
|
936
|
+
msg.isStreaming = false;
|
|
937
|
+
}
|
|
938
|
+
_this3._conversationAssistantMessage = null;
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
_this3.connection.on("ReceiveAudioChunk", function (sessionId, base64Audio, contentType) {
|
|
942
|
+
if (base64Audio) {
|
|
943
|
+
var binaryString = atob(base64Audio);
|
|
944
|
+
var bytes = new Uint8Array(binaryString.length);
|
|
945
|
+
for (var i = 0; i < binaryString.length; i++) {
|
|
946
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
947
|
+
}
|
|
948
|
+
_this3.audioChunks.push(bytes);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
_this3.connection.on("ReceiveAudioComplete", function (sessionId) {
|
|
952
|
+
_this3.playCollectedAudio();
|
|
953
|
+
});
|
|
954
|
+
_this3.connection.on("ReceiveNotification", function (notification) {
|
|
955
|
+
_this3.receiveNotification(notification);
|
|
956
|
+
});
|
|
957
|
+
_this3.connection.on("UpdateNotification", function (notification) {
|
|
958
|
+
_this3.updateNotification(notification);
|
|
959
|
+
});
|
|
960
|
+
_this3.connection.on("RemoveNotification", function (notificationType) {
|
|
961
|
+
_this3.removeNotification(notificationType);
|
|
962
|
+
});
|
|
963
|
+
_this3.connection.onreconnecting(function () {
|
|
964
|
+
console.warn("SignalR: reconnecting...");
|
|
965
|
+
});
|
|
966
|
+
_this3.connection.onreconnected(function () {
|
|
967
|
+
console.info("SignalR: reconnected.");
|
|
968
|
+
if (_this3.isSessionStarted) {
|
|
969
|
+
_this3.reloadCurrentSession();
|
|
970
|
+
} else if (config.autoCreateSession) {
|
|
971
|
+
_this3.startNewSession();
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
_this3.connection.onclose(function (error) {
|
|
975
|
+
if (_this3.isNavigatingAway) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (error) {
|
|
979
|
+
console.warn("SignalR connection closed with error:", error.message || error);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
_context3.p = 1;
|
|
983
|
+
_context3.n = 2;
|
|
984
|
+
return _this3.connection.start();
|
|
985
|
+
case 2:
|
|
986
|
+
_context3.n = 4;
|
|
987
|
+
break;
|
|
988
|
+
case 3:
|
|
989
|
+
_context3.p = 3;
|
|
990
|
+
_t3 = _context3.v;
|
|
991
|
+
console.error("SignalR Connection Error: ", _t3);
|
|
992
|
+
case 4:
|
|
993
|
+
return _context3.a(2);
|
|
994
|
+
}
|
|
995
|
+
}, _callee3, null, [[1, 3]]);
|
|
996
|
+
}))();
|
|
997
|
+
},
|
|
998
|
+
addMessageInternal: function addMessageInternal(message) {
|
|
999
|
+
var _this4 = this;
|
|
1000
|
+
if (message.role === 'assistant') {
|
|
1001
|
+
message.appearance = this.normalizeAssistantAppearance(message.appearance);
|
|
1002
|
+
}
|
|
1003
|
+
if (message.content && !message.htmlContent) {
|
|
1004
|
+
message.htmlContent = parseMarkdownContent(message.content, message);
|
|
1005
|
+
}
|
|
1006
|
+
this.fireEvent(new CustomEvent("addingOpenAIPromotMessage", {
|
|
1007
|
+
detail: {
|
|
1008
|
+
message: message
|
|
1009
|
+
}
|
|
1010
|
+
}));
|
|
1011
|
+
this.messages.push(message);
|
|
1012
|
+
this.$nextTick(function () {
|
|
1013
|
+
_this4.fireEvent(new CustomEvent("addedOpenAIPromotMessage", {
|
|
1014
|
+
detail: {
|
|
1015
|
+
message: message
|
|
1016
|
+
}
|
|
1017
|
+
}));
|
|
1018
|
+
});
|
|
1019
|
+
},
|
|
1020
|
+
addMessage: function addMessage(message) {
|
|
1021
|
+
var _this5 = this;
|
|
1022
|
+
// Ensure userRating is always defined for Vue reactivity.
|
|
1023
|
+
if (message.userRating === undefined) {
|
|
1024
|
+
message.userRating = null;
|
|
1025
|
+
}
|
|
1026
|
+
if (message.content) {
|
|
1027
|
+
var processedContent = message.content.trim();
|
|
1028
|
+
message.references = normalizeReferences(message.references);
|
|
1029
|
+
if (message.references && _typeof(message.references) === "object" && Object.keys(message.references).length) {
|
|
1030
|
+
// Only include references that were actually cited in the response.
|
|
1031
|
+
var citedRefs = Object.entries(message.references).filter(function (_ref4) {
|
|
1032
|
+
var _ref5 = _slicedToArray(_ref4, 1),
|
|
1033
|
+
key = _ref5[0];
|
|
1034
|
+
return processedContent.includes(key);
|
|
1035
|
+
});
|
|
1036
|
+
if (citedRefs.length) {
|
|
1037
|
+
// Sort by original index so display indices follow a natural order.
|
|
1038
|
+
citedRefs.sort(function (_ref6, _ref7) {
|
|
1039
|
+
var _ref8 = _slicedToArray(_ref6, 2),
|
|
1040
|
+
a = _ref8[1];
|
|
1041
|
+
var _ref9 = _slicedToArray(_ref7, 2),
|
|
1042
|
+
b = _ref9[1];
|
|
1043
|
+
return a.index - b.index;
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
// Phase 1: Replace all markers with unique placeholders.
|
|
1047
|
+
var displayIndex = 1;
|
|
1048
|
+
var _iterator2 = _createForOfIteratorHelper(citedRefs),
|
|
1049
|
+
_step2;
|
|
1050
|
+
try {
|
|
1051
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
1052
|
+
var _step2$value = _slicedToArray(_step2.value, 2),
|
|
1053
|
+
key = _step2$value[0],
|
|
1054
|
+
value = _step2$value[1];
|
|
1055
|
+
var placeholder = "__CITE_".concat(value.index, "__");
|
|
1056
|
+
processedContent = processedContent.replaceAll(key, placeholder);
|
|
1057
|
+
value._displayIndex = displayIndex++;
|
|
1058
|
+
value._placeholder = placeholder;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Phase 2: Replace placeholders with sequential display indices.
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
_iterator2.e(err);
|
|
1064
|
+
} finally {
|
|
1065
|
+
_iterator2.f();
|
|
1066
|
+
}
|
|
1067
|
+
var _iterator3 = _createForOfIteratorHelper(citedRefs),
|
|
1068
|
+
_step3;
|
|
1069
|
+
try {
|
|
1070
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
1071
|
+
var _step3$value = _slicedToArray(_step3.value, 2),
|
|
1072
|
+
_value = _step3$value[1];
|
|
1073
|
+
processedContent = processedContent.replaceAll(_value._placeholder, "<sup><strong>".concat(_value._displayIndex, "</strong></sup>"));
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// if we have multiple references, add a comma to ensure we don't concatenate numbers.
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
_iterator3.e(err);
|
|
1079
|
+
} finally {
|
|
1080
|
+
_iterator3.f();
|
|
1081
|
+
}
|
|
1082
|
+
processedContent = processedContent.replaceAll('</strong></sup><sup>', '</strong></sup><sup>,</sup><sup>');
|
|
1083
|
+
processedContent += '<br><br>';
|
|
1084
|
+
var _iterator4 = _createForOfIteratorHelper(citedRefs),
|
|
1085
|
+
_step4;
|
|
1086
|
+
try {
|
|
1087
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
1088
|
+
var _step4$value = _slicedToArray(_step4.value, 2),
|
|
1089
|
+
_key = _step4$value[0],
|
|
1090
|
+
_value2 = _step4$value[1];
|
|
1091
|
+
var label = _value2.text || _key;
|
|
1092
|
+
processedContent += _value2.link ? "**".concat(_value2._displayIndex, "**. [").concat(label, "](").concat(_value2.link, ")<br>") : "**".concat(_value2._displayIndex, "**. ").concat(label, "<br>");
|
|
1093
|
+
}
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
_iterator4.e(err);
|
|
1096
|
+
} finally {
|
|
1097
|
+
_iterator4.f();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
message.content = processedContent;
|
|
1102
|
+
message.htmlContent = parseMarkdownContent(processedContent, message);
|
|
1103
|
+
}
|
|
1104
|
+
this.addMessageInternal(message);
|
|
1105
|
+
this.hidePlaceholder();
|
|
1106
|
+
this.$nextTick(function () {
|
|
1107
|
+
// Render any pending charts once the DOM is updated
|
|
1108
|
+
renderChartsInMessage(message);
|
|
1109
|
+
_this5.scrollToBottom();
|
|
1110
|
+
});
|
|
1111
|
+
},
|
|
1112
|
+
addMessages: function addMessages(messages) {
|
|
1113
|
+
var _this6 = this;
|
|
1114
|
+
for (var i = 0; i < messages.length; i++) {
|
|
1115
|
+
this.addMessageInternal(messages[i]);
|
|
1116
|
+
}
|
|
1117
|
+
this.hidePlaceholder();
|
|
1118
|
+
this.$nextTick(function () {
|
|
1119
|
+
_this6.scrollToBottom();
|
|
1120
|
+
});
|
|
1121
|
+
},
|
|
1122
|
+
hidePlaceholder: function hidePlaceholder() {
|
|
1123
|
+
if (this.placeholder) {
|
|
1124
|
+
this.placeholder.classList.add('d-none');
|
|
1125
|
+
}
|
|
1126
|
+
this.isPlaceholderVisible = false;
|
|
1127
|
+
},
|
|
1128
|
+
showPlaceholder: function showPlaceholder() {
|
|
1129
|
+
if (this.placeholder) {
|
|
1130
|
+
this.placeholder.classList.remove('d-none');
|
|
1131
|
+
}
|
|
1132
|
+
this.isPlaceholderVisible = true;
|
|
1133
|
+
},
|
|
1134
|
+
isOrchestratorAvailable: function isOrchestratorAvailable() {
|
|
1135
|
+
return config.isOrchestratorAvailable !== false;
|
|
1136
|
+
},
|
|
1137
|
+
applyOrchestratorAvailability: function applyOrchestratorAvailability() {
|
|
1138
|
+
if (this.isOrchestratorAvailable()) {
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
var unavailableMessage = config.orchestratorUnavailableMessage || "This orchestrator is not currently available.";
|
|
1142
|
+
if (this.inputElement) {
|
|
1143
|
+
this.inputElement.disabled = true;
|
|
1144
|
+
this.inputElement.value = '';
|
|
1145
|
+
this.inputElement.placeholder = unavailableMessage;
|
|
1146
|
+
}
|
|
1147
|
+
if (this.buttonElement) {
|
|
1148
|
+
this.buttonElement.disabled = true;
|
|
1149
|
+
}
|
|
1150
|
+
if (this.micButtonElement) {
|
|
1151
|
+
this.micButtonElement.disabled = true;
|
|
1152
|
+
this.micButtonElement.style.display = 'none';
|
|
1153
|
+
}
|
|
1154
|
+
if (this.conversationButtonElement) {
|
|
1155
|
+
this.conversationButtonElement.disabled = true;
|
|
1156
|
+
this.conversationButtonElement.style.display = 'none';
|
|
1157
|
+
}
|
|
1158
|
+
return false;
|
|
1159
|
+
},
|
|
1160
|
+
fireEvent: function fireEvent(event) {
|
|
1161
|
+
document.dispatchEvent(event);
|
|
1162
|
+
},
|
|
1163
|
+
isIndicator: function isIndicator(message) {
|
|
1164
|
+
return message.role === 'indicator';
|
|
1165
|
+
},
|
|
1166
|
+
sendMessage: function sendMessage() {
|
|
1167
|
+
if (!this.applyOrchestratorAvailability()) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
var trimmedPrompt = this.prompt.trim();
|
|
1171
|
+
if (!trimmedPrompt) {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Stop any active recording before sending.
|
|
1176
|
+
if (this.isRecording) {
|
|
1177
|
+
this.stopRecording();
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Prevent stale ReceiveTranscript events from repopulating the prompt.
|
|
1181
|
+
this._audioInputSent = true;
|
|
1182
|
+
this.addMessage({
|
|
1183
|
+
role: 'user',
|
|
1184
|
+
content: trimmedPrompt
|
|
1185
|
+
});
|
|
1186
|
+
this.streamMessage(this.getProfileId(), trimmedPrompt, null);
|
|
1187
|
+
this.inputElement.value = '';
|
|
1188
|
+
this.prompt = '';
|
|
1189
|
+
},
|
|
1190
|
+
startRecording: function startRecording() {
|
|
1191
|
+
var _this7 = this;
|
|
1192
|
+
if (this.isRecording || !this.connection) {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
navigator.mediaDevices.getUserMedia({
|
|
1196
|
+
audio: {
|
|
1197
|
+
echoCancellation: true,
|
|
1198
|
+
noiseSuppression: true,
|
|
1199
|
+
autoGainControl: true
|
|
1200
|
+
}
|
|
1201
|
+
}).then(function (stream) {
|
|
1202
|
+
var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
|
|
1203
|
+
_this7.mediaRecorder = new MediaRecorder(stream, {
|
|
1204
|
+
mimeType: mimeType,
|
|
1205
|
+
audioBitsPerSecond: 128000
|
|
1206
|
+
});
|
|
1207
|
+
_this7.preRecordingPrompt = _this7.prompt;
|
|
1208
|
+
_this7._audioInputSent = false;
|
|
1209
|
+
var subject = new signalR.Subject();
|
|
1210
|
+
var profileId = _this7.getProfileId();
|
|
1211
|
+
var sessionId = _this7.getSessionId() || '';
|
|
1212
|
+
var pendingChunk = Promise.resolve();
|
|
1213
|
+
_this7.mediaRecorder.addEventListener('dataavailable', function (e) {
|
|
1214
|
+
if (e.data && e.data.size > 0) {
|
|
1215
|
+
pendingChunk = pendingChunk.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
|
|
1216
|
+
var data, uint8Array, binaryString, base64;
|
|
1217
|
+
return _regenerator().w(function (_context4) {
|
|
1218
|
+
while (1) switch (_context4.n) {
|
|
1219
|
+
case 0:
|
|
1220
|
+
_context4.n = 1;
|
|
1221
|
+
return e.data.arrayBuffer();
|
|
1222
|
+
case 1:
|
|
1223
|
+
data = _context4.v;
|
|
1224
|
+
uint8Array = new Uint8Array(data);
|
|
1225
|
+
binaryString = uint8Array.reduce(function (str, _byte) {
|
|
1226
|
+
return str + String.fromCharCode(_byte);
|
|
1227
|
+
}, '');
|
|
1228
|
+
base64 = btoa(binaryString);
|
|
1229
|
+
subject.next(base64);
|
|
1230
|
+
case 2:
|
|
1231
|
+
return _context4.a(2);
|
|
1232
|
+
}
|
|
1233
|
+
}, _callee4);
|
|
1234
|
+
})));
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
_this7.mediaRecorder.addEventListener('stop', function () {
|
|
1238
|
+
stream.getTracks().forEach(function (track) {
|
|
1239
|
+
return track.stop();
|
|
1240
|
+
});
|
|
1241
|
+
pendingChunk.then(function () {
|
|
1242
|
+
return subject.complete();
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
var language = navigator.language || document.documentElement.lang || 'en-US';
|
|
1246
|
+
_this7.connection.send("SendAudioStream", profileId, sessionId, subject, mimeType, language);
|
|
1247
|
+
_this7.mediaRecorder.start(250);
|
|
1248
|
+
_this7.isRecording = true;
|
|
1249
|
+
_this7.updateMicButton();
|
|
1250
|
+
})["catch"](function (err) {
|
|
1251
|
+
console.error('Microphone access denied:', err);
|
|
1252
|
+
});
|
|
1253
|
+
},
|
|
1254
|
+
stopRecording: function stopRecording() {
|
|
1255
|
+
if (!this.isRecording || !this.mediaRecorder) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
this.mediaRecorder.stop();
|
|
1259
|
+
this.isRecording = false;
|
|
1260
|
+
this.updateMicButton();
|
|
1261
|
+
},
|
|
1262
|
+
toggleRecording: function toggleRecording() {
|
|
1263
|
+
if (this.isRecording) {
|
|
1264
|
+
this.stopRecording();
|
|
1265
|
+
} else {
|
|
1266
|
+
this.startRecording();
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
updateMicButton: function updateMicButton() {
|
|
1270
|
+
if (!this.micButton) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
if (this.isRecording) {
|
|
1274
|
+
this.micButton.classList.add('stt-recording');
|
|
1275
|
+
this.micButton.innerHTML = '<i class="fa-solid fa-stop"></i>';
|
|
1276
|
+
} else {
|
|
1277
|
+
this.micButton.classList.remove('stt-recording');
|
|
1278
|
+
this.micButton.innerHTML = '<i class="fa-solid fa-microphone"></i>';
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
streamMessage: function streamMessage(profileId, trimmedPrompt, sessionProfileId) {
|
|
1282
|
+
var _this8 = this;
|
|
1283
|
+
if (this.stream) {
|
|
1284
|
+
this.stream.dispose();
|
|
1285
|
+
this.stream = null;
|
|
1286
|
+
}
|
|
1287
|
+
this.streamingStarted();
|
|
1288
|
+
this.showTypingIndicator();
|
|
1289
|
+
this.autoScroll = true;
|
|
1290
|
+
var content = '';
|
|
1291
|
+
var references = {};
|
|
1292
|
+
var lastResponseId = null;
|
|
1293
|
+
|
|
1294
|
+
// Get the index after showing typing indicator.
|
|
1295
|
+
var messageIndex = this.messages.length;
|
|
1296
|
+
var currentSessionId = this.getSessionId();
|
|
1297
|
+
this.stream = this.connection.stream("SendMessage", profileId, trimmedPrompt, currentSessionId, sessionProfileId).subscribe({
|
|
1298
|
+
next: function next(chunk) {
|
|
1299
|
+
var message = _this8.messages[messageIndex];
|
|
1300
|
+
if (!message) {
|
|
1301
|
+
if (chunk.sessionId && !currentSessionId) {
|
|
1302
|
+
_this8.initializeSession(chunk.sessionId);
|
|
1303
|
+
}
|
|
1304
|
+
_this8.hideTypingIndicator();
|
|
1305
|
+
// Re-assign the index after hiding the typing indicator.
|
|
1306
|
+
messageIndex = _this8.messages.length;
|
|
1307
|
+
var newMessage = {
|
|
1308
|
+
id: chunk.messageId,
|
|
1309
|
+
role: "assistant",
|
|
1310
|
+
title: chunk.title,
|
|
1311
|
+
content: "",
|
|
1312
|
+
htmlContent: "",
|
|
1313
|
+
isStreaming: true,
|
|
1314
|
+
userRating: null
|
|
1315
|
+
};
|
|
1316
|
+
_this8.messages.push(newMessage);
|
|
1317
|
+
message = newMessage;
|
|
1318
|
+
}
|
|
1319
|
+
if (chunk.title && (!message.title || message.title !== chunk.title)) {
|
|
1320
|
+
message.title = chunk.title;
|
|
1321
|
+
}
|
|
1322
|
+
if (chunk.references && _typeof(chunk.references) === "object" && Object.keys(chunk.references).length) {
|
|
1323
|
+
for (var _i3 = 0, _Object$entries2 = Object.entries(chunk.references); _i3 < _Object$entries2.length; _i3++) {
|
|
1324
|
+
var _normalizeReference2;
|
|
1325
|
+
var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i3], 2),
|
|
1326
|
+
key = _Object$entries2$_i[0],
|
|
1327
|
+
value = _Object$entries2$_i[1];
|
|
1328
|
+
references[key] = (_normalizeReference2 = normalizeReference(value)) !== null && _normalizeReference2 !== void 0 ? _normalizeReference2 : {};
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (chunk.content) {
|
|
1332
|
+
// When the responseId changes (e.g., after an internal tool call),
|
|
1333
|
+
// insert a line break to visually separate response segments.
|
|
1334
|
+
if (chunk.responseId && lastResponseId && chunk.responseId !== lastResponseId) {
|
|
1335
|
+
content += '\n\n';
|
|
1336
|
+
}
|
|
1337
|
+
if (chunk.responseId) {
|
|
1338
|
+
lastResponseId = chunk.responseId;
|
|
1339
|
+
}
|
|
1340
|
+
var processedContent = chunk.content;
|
|
1341
|
+
for (var _i4 = 0, _Object$entries3 = Object.entries(references); _i4 < _Object$entries3.length; _i4++) {
|
|
1342
|
+
var _Object$entries3$_i = _slicedToArray(_Object$entries3[_i4], 2),
|
|
1343
|
+
_key2 = _Object$entries3$_i[0],
|
|
1344
|
+
_value3 = _Object$entries3$_i[1];
|
|
1345
|
+
processedContent = processedContent.replaceAll(_key2, "<sup><strong>".concat(_value3.index, "</strong></sup>"));
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Append processed content to the message.
|
|
1349
|
+
// if we have multiple references, add a comma to ensure we don't concatenate numbers.
|
|
1350
|
+
content += processedContent.replaceAll('</strong></sup><sup>', '</strong></sup><sup>,</sup><sup>');
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Update the existing message
|
|
1354
|
+
message.content = content;
|
|
1355
|
+
message.htmlContent = parseMarkdownContent(content, message);
|
|
1356
|
+
_this8.messages[messageIndex] = message;
|
|
1357
|
+
_this8.$nextTick(function () {
|
|
1358
|
+
renderChartsInMessage(message);
|
|
1359
|
+
_this8.scrollToBottom();
|
|
1360
|
+
});
|
|
1361
|
+
},
|
|
1362
|
+
complete: function complete() {
|
|
1363
|
+
var _this8$stream;
|
|
1364
|
+
_this8.processReferences(references, messageIndex);
|
|
1365
|
+
_this8.streamingFinished();
|
|
1366
|
+
var msg = _this8.messages[messageIndex];
|
|
1367
|
+
if (msg) {
|
|
1368
|
+
msg.isStreaming = false;
|
|
1369
|
+
}
|
|
1370
|
+
if (!msg || !msg.content) {
|
|
1371
|
+
// No content received at all.
|
|
1372
|
+
_this8.hideTypingIndicator();
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// Trigger text-to-speech only in conversation mode.
|
|
1376
|
+
if (_this8.isConversationMode && _this8.textToSpeechEnabled && msg && msg.content) {
|
|
1377
|
+
_this8.synthesizeSpeech(msg.content);
|
|
1378
|
+
}
|
|
1379
|
+
(_this8$stream = _this8.stream) === null || _this8$stream === void 0 || _this8$stream.dispose();
|
|
1380
|
+
_this8.stream = null;
|
|
1381
|
+
},
|
|
1382
|
+
error: function error(err) {
|
|
1383
|
+
var _this8$stream2;
|
|
1384
|
+
_this8.processReferences(references, messageIndex);
|
|
1385
|
+
_this8.streamingFinished();
|
|
1386
|
+
var msg = _this8.messages[messageIndex];
|
|
1387
|
+
if (msg) {
|
|
1388
|
+
msg.isStreaming = false;
|
|
1389
|
+
}
|
|
1390
|
+
_this8.hideTypingIndicator();
|
|
1391
|
+
if (!_this8.isNavigatingAway) {
|
|
1392
|
+
_this8.addMessage(_this8.getServiceDownMessage());
|
|
1393
|
+
}
|
|
1394
|
+
(_this8$stream2 = _this8.stream) === null || _this8$stream2 === void 0 || _this8$stream2.dispose();
|
|
1395
|
+
_this8.stream = null;
|
|
1396
|
+
console.error("Stream error:", err);
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
},
|
|
1400
|
+
getServiceDownMessage: function getServiceDownMessage() {
|
|
1401
|
+
var newMessage = {
|
|
1402
|
+
role: "assistant",
|
|
1403
|
+
content: "Our service is currently unavailable. Please try again later. We apologize for the inconvenience.",
|
|
1404
|
+
htmlContent: ""
|
|
1405
|
+
};
|
|
1406
|
+
return newMessage;
|
|
1407
|
+
},
|
|
1408
|
+
processReferences: function processReferences(references, messageIndex) {
|
|
1409
|
+
var _this9 = this;
|
|
1410
|
+
references = normalizeReferences(references);
|
|
1411
|
+
if (Object.keys(references).length) {
|
|
1412
|
+
var message = this.messages[messageIndex];
|
|
1413
|
+
var content = message.content || '';
|
|
1414
|
+
|
|
1415
|
+
// Only include references that were actually cited in the response.
|
|
1416
|
+
// Check both raw [doc:N] markers and already-rendered <sup> tags from streaming.
|
|
1417
|
+
var citedRefs = Object.entries(references).filter(function (_ref1) {
|
|
1418
|
+
var _ref10 = _slicedToArray(_ref1, 2),
|
|
1419
|
+
key = _ref10[0],
|
|
1420
|
+
value = _ref10[1];
|
|
1421
|
+
return content.includes(key) || content.includes("<sup><strong>".concat(value.index, "</strong></sup>"));
|
|
1422
|
+
});
|
|
1423
|
+
if (!citedRefs.length) {
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Sort by original index so display indices follow a natural order.
|
|
1428
|
+
citedRefs.sort(function (_ref11, _ref12) {
|
|
1429
|
+
var _ref13 = _slicedToArray(_ref11, 2),
|
|
1430
|
+
a = _ref13[1];
|
|
1431
|
+
var _ref14 = _slicedToArray(_ref12, 2),
|
|
1432
|
+
b = _ref14[1];
|
|
1433
|
+
return a.index - b.index;
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
// Phase 1: Replace all markers with unique placeholders to avoid collisions during remapping.
|
|
1437
|
+
var processed = content.trim();
|
|
1438
|
+
var displayIndex = 1;
|
|
1439
|
+
var _iterator5 = _createForOfIteratorHelper(citedRefs),
|
|
1440
|
+
_step5;
|
|
1441
|
+
try {
|
|
1442
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
1443
|
+
var _step5$value = _slicedToArray(_step5.value, 2),
|
|
1444
|
+
key = _step5$value[0],
|
|
1445
|
+
value = _step5$value[1];
|
|
1446
|
+
var placeholder = "__CITE_".concat(value.index, "__");
|
|
1447
|
+
processed = processed.replaceAll(key, placeholder);
|
|
1448
|
+
processed = processed.replaceAll("<sup><strong>".concat(value.index, "</strong></sup>"), placeholder);
|
|
1449
|
+
value._displayIndex = displayIndex++;
|
|
1450
|
+
value._placeholder = placeholder;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Phase 2: Replace placeholders with sequential display indices.
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
_iterator5.e(err);
|
|
1456
|
+
} finally {
|
|
1457
|
+
_iterator5.f();
|
|
1458
|
+
}
|
|
1459
|
+
var _iterator6 = _createForOfIteratorHelper(citedRefs),
|
|
1460
|
+
_step6;
|
|
1461
|
+
try {
|
|
1462
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
1463
|
+
var _step6$value = _slicedToArray(_step6.value, 2),
|
|
1464
|
+
_value4 = _step6$value[1];
|
|
1465
|
+
processed = processed.replaceAll(_value4._placeholder, "<sup><strong>".concat(_value4._displayIndex, "</strong></sup>"));
|
|
1466
|
+
}
|
|
1467
|
+
} catch (err) {
|
|
1468
|
+
_iterator6.e(err);
|
|
1469
|
+
} finally {
|
|
1470
|
+
_iterator6.f();
|
|
1471
|
+
}
|
|
1472
|
+
processed = processed.replaceAll('</strong></sup><sup>', '</strong></sup><sup>,</sup><sup>');
|
|
1473
|
+
processed += '<br><br>';
|
|
1474
|
+
var _iterator7 = _createForOfIteratorHelper(citedRefs),
|
|
1475
|
+
_step7;
|
|
1476
|
+
try {
|
|
1477
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
1478
|
+
var _step7$value = _slicedToArray(_step7.value, 2),
|
|
1479
|
+
_key3 = _step7$value[0],
|
|
1480
|
+
_value5 = _step7$value[1];
|
|
1481
|
+
var label = _value5.text || _key3;
|
|
1482
|
+
processed += _value5.link ? "**".concat(_value5._displayIndex, "**. [").concat(label, "](").concat(_value5.link, ")<br>") : "**".concat(_value5._displayIndex, "**. ").concat(label, "<br>");
|
|
1483
|
+
}
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
_iterator7.e(err);
|
|
1486
|
+
} finally {
|
|
1487
|
+
_iterator7.f();
|
|
1488
|
+
}
|
|
1489
|
+
message.content = processed;
|
|
1490
|
+
message.htmlContent = parseMarkdownContent(processed, message);
|
|
1491
|
+
this.messages[messageIndex] = message;
|
|
1492
|
+
this.$nextTick(function () {
|
|
1493
|
+
renderChartsInMessage(message);
|
|
1494
|
+
_this9.scrollToBottom();
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
streamingStarted: function streamingStarted() {
|
|
1499
|
+
var stopIcon = this.buttonElement.getAttribute('data-stop-icon');
|
|
1500
|
+
if (stopIcon) {
|
|
1501
|
+
this.buttonElement.replaceChildren(DOMPurify.sanitize(stopIcon, {
|
|
1502
|
+
RETURN_DOM_FRAGMENT: true
|
|
1503
|
+
}));
|
|
1504
|
+
}
|
|
1505
|
+
if (this.inputElement) {
|
|
1506
|
+
this.inputElement.setAttribute('disabled', 'disabled');
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
streamingFinished: function streamingFinished() {
|
|
1510
|
+
var startIcon = this.buttonElement.getAttribute('data-start-icon');
|
|
1511
|
+
if (startIcon) {
|
|
1512
|
+
this.buttonElement.replaceChildren(DOMPurify.sanitize(startIcon, {
|
|
1513
|
+
RETURN_DOM_FRAGMENT: true
|
|
1514
|
+
}));
|
|
1515
|
+
}
|
|
1516
|
+
if (this.inputElement) {
|
|
1517
|
+
this.inputElement.removeAttribute('disabled');
|
|
1518
|
+
this.inputElement.focus();
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Directly manipulate the DOM to stop all streaming animations.
|
|
1522
|
+
if (this.chatContainer) {
|
|
1523
|
+
var icons = this.chatContainer.querySelectorAll('.ai-streaming-icon');
|
|
1524
|
+
for (var i = 0; i < icons.length; i++) {
|
|
1525
|
+
icons[i].classList.remove('ai-streaming-icon');
|
|
1526
|
+
icons[i].classList.add('ai-bot-icon');
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Also update Vue data for consistency.
|
|
1531
|
+
for (var i = 0; i < this.messages.length; i++) {
|
|
1532
|
+
if (this.messages[i].isStreaming) {
|
|
1533
|
+
this.messages[i].isStreaming = false;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
},
|
|
1537
|
+
synthesizeSpeech: function synthesizeSpeech(text) {
|
|
1538
|
+
var _this0 = this;
|
|
1539
|
+
if (!this.textToSpeechEnabled || !text || !this.connection) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
this.audioChunks = [];
|
|
1543
|
+
this.isPlayingAudio = true;
|
|
1544
|
+
this.connection.invoke("SynthesizeSpeech", this.getProfileId(), this.getSessionId(), text, this.ttsVoiceName)["catch"](function (err) {
|
|
1545
|
+
console.error("TTS synthesis error:", err);
|
|
1546
|
+
_this0.isPlayingAudio = false;
|
|
1547
|
+
});
|
|
1548
|
+
},
|
|
1549
|
+
playCollectedAudio: function playCollectedAudio() {
|
|
1550
|
+
if (this.audioChunks.length === 0) {
|
|
1551
|
+
if (!this.currentAudioElement && this.audioPlayQueue.length === 0) {
|
|
1552
|
+
this.isPlayingAudio = false;
|
|
1553
|
+
}
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
var totalLength = this.audioChunks.reduce(function (sum, chunk) {
|
|
1557
|
+
return sum + chunk.length;
|
|
1558
|
+
}, 0);
|
|
1559
|
+
var combined = new Uint8Array(totalLength);
|
|
1560
|
+
var offset = 0;
|
|
1561
|
+
var _iterator8 = _createForOfIteratorHelper(this.audioChunks),
|
|
1562
|
+
_step8;
|
|
1563
|
+
try {
|
|
1564
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
1565
|
+
var chunk = _step8.value;
|
|
1566
|
+
combined.set(chunk, offset);
|
|
1567
|
+
offset += chunk.length;
|
|
1568
|
+
}
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
_iterator8.e(err);
|
|
1571
|
+
} finally {
|
|
1572
|
+
_iterator8.f();
|
|
1573
|
+
}
|
|
1574
|
+
this.audioChunks = [];
|
|
1575
|
+
var blob = new Blob([combined], {
|
|
1576
|
+
type: 'audio/mp3'
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
// If audio is already playing, queue this blob for sequential playback.
|
|
1580
|
+
if (this.currentAudioElement) {
|
|
1581
|
+
this.audioPlayQueue.push(blob);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
this.playAudioBlob(blob);
|
|
1585
|
+
},
|
|
1586
|
+
playAudioBlob: function playAudioBlob(blob) {
|
|
1587
|
+
var _this1 = this;
|
|
1588
|
+
var url = URL.createObjectURL(blob);
|
|
1589
|
+
var audio = new Audio(url);
|
|
1590
|
+
this.currentAudioElement = audio;
|
|
1591
|
+
this.isPlayingAudio = true;
|
|
1592
|
+
audio.addEventListener('ended', function () {
|
|
1593
|
+
URL.revokeObjectURL(url);
|
|
1594
|
+
_this1.currentAudioElement = null;
|
|
1595
|
+
_this1.playNextInQueue();
|
|
1596
|
+
});
|
|
1597
|
+
audio.addEventListener('error', function () {
|
|
1598
|
+
URL.revokeObjectURL(url);
|
|
1599
|
+
_this1.currentAudioElement = null;
|
|
1600
|
+
_this1.playNextInQueue();
|
|
1601
|
+
});
|
|
1602
|
+
audio.play()["catch"](function (err) {
|
|
1603
|
+
console.error("Audio playback error:", err);
|
|
1604
|
+
URL.revokeObjectURL(url);
|
|
1605
|
+
_this1.currentAudioElement = null;
|
|
1606
|
+
_this1.isPlayingAudio = false;
|
|
1607
|
+
});
|
|
1608
|
+
},
|
|
1609
|
+
playNextInQueue: function playNextInQueue() {
|
|
1610
|
+
if (this.audioPlayQueue.length > 0) {
|
|
1611
|
+
var nextBlob = this.audioPlayQueue.shift();
|
|
1612
|
+
this.playAudioBlob(nextBlob);
|
|
1613
|
+
} else {
|
|
1614
|
+
this.isPlayingAudio = false;
|
|
1615
|
+
this.conversationModeOnAudioEnded();
|
|
1616
|
+
}
|
|
1617
|
+
},
|
|
1618
|
+
stopAudio: function stopAudio() {
|
|
1619
|
+
if (this.currentAudioElement) {
|
|
1620
|
+
this.currentAudioElement.pause();
|
|
1621
|
+
this.currentAudioElement.currentTime = 0;
|
|
1622
|
+
this.currentAudioElement = null;
|
|
1623
|
+
}
|
|
1624
|
+
this.audioChunks = [];
|
|
1625
|
+
this.audioPlayQueue = [];
|
|
1626
|
+
this.isPlayingAudio = false;
|
|
1627
|
+
},
|
|
1628
|
+
toggleConversationMode: function toggleConversationMode() {
|
|
1629
|
+
if (this.isConversationMode) {
|
|
1630
|
+
this.stopConversationMode();
|
|
1631
|
+
} else {
|
|
1632
|
+
this.startConversationMode();
|
|
1633
|
+
}
|
|
1634
|
+
},
|
|
1635
|
+
startConversationMode: function startConversationMode() {
|
|
1636
|
+
var _this10 = this;
|
|
1637
|
+
if (!this.conversationModeEnabled || this.isConversationMode || !this.connection) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
this.isConversationMode = true;
|
|
1641
|
+
this.updateConversationButton();
|
|
1642
|
+
this._conversationPartialTranscript = '';
|
|
1643
|
+
this._conversationAssistantMessage = null;
|
|
1644
|
+
this._conversationPartialMessage = null;
|
|
1645
|
+
|
|
1646
|
+
// Remove any previous conversation ended notification.
|
|
1647
|
+
this.removeNotification('conversation-ended');
|
|
1648
|
+
navigator.mediaDevices.getUserMedia({
|
|
1649
|
+
audio: {
|
|
1650
|
+
echoCancellation: true,
|
|
1651
|
+
noiseSuppression: true,
|
|
1652
|
+
autoGainControl: true
|
|
1653
|
+
}
|
|
1654
|
+
}).then(function (stream) {
|
|
1655
|
+
var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
|
|
1656
|
+
_this10.mediaRecorder = new MediaRecorder(stream, {
|
|
1657
|
+
mimeType: mimeType,
|
|
1658
|
+
audioBitsPerSecond: 128000
|
|
1659
|
+
});
|
|
1660
|
+
_this10._conversationSubject = new signalR.Subject();
|
|
1661
|
+
_this10._conversationStream = stream;
|
|
1662
|
+
|
|
1663
|
+
// Create an AnalyserNode for volume-based interrupt detection.
|
|
1664
|
+
// During TTS playback, detect when the user speaks above
|
|
1665
|
+
// the threshold to stop TTS (interrupt). Audio chunks are
|
|
1666
|
+
// always forwarded — browser echo cancellation handles
|
|
1667
|
+
// speaker echo so the STT stream has no gaps.
|
|
1668
|
+
var AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
1669
|
+
if (AudioCtx) {
|
|
1670
|
+
_this10._conversationAudioCtx = new AudioCtx();
|
|
1671
|
+
_this10._conversationAnalyser = _this10._conversationAudioCtx.createAnalyser();
|
|
1672
|
+
_this10._conversationAnalyser.fftSize = 256;
|
|
1673
|
+
var micSource = _this10._conversationAudioCtx.createMediaStreamSource(stream);
|
|
1674
|
+
micSource.connect(_this10._conversationAnalyser);
|
|
1675
|
+
}
|
|
1676
|
+
var pendingChunk = Promise.resolve();
|
|
1677
|
+
var analyser = _this10._conversationAnalyser;
|
|
1678
|
+
var interruptVolumeThreshold = 30;
|
|
1679
|
+
_this10.mediaRecorder.addEventListener('dataavailable', function (e) {
|
|
1680
|
+
if (e.data && e.data.size > 0) {
|
|
1681
|
+
// During TTS playback, check mic volume to detect
|
|
1682
|
+
// user interruption (speaking above threshold).
|
|
1683
|
+
if (_this10.isPlayingAudio && analyser) {
|
|
1684
|
+
var freqData = new Uint8Array(analyser.frequencyBinCount);
|
|
1685
|
+
analyser.getByteFrequencyData(freqData);
|
|
1686
|
+
var sum = 0;
|
|
1687
|
+
for (var k = 0; k < freqData.length; k++) {
|
|
1688
|
+
sum += freqData[k];
|
|
1689
|
+
}
|
|
1690
|
+
var avg = sum / freqData.length;
|
|
1691
|
+
if (avg >= interruptVolumeThreshold) {
|
|
1692
|
+
// User is speaking — interrupt TTS playback.
|
|
1693
|
+
_this10.stopAudio();
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// Always send audio to STT — browser echo cancellation
|
|
1698
|
+
// handles speaker echo; continuous audio avoids gaps
|
|
1699
|
+
// that increase recognition latency.
|
|
1700
|
+
pendingChunk = pendingChunk.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
|
|
1701
|
+
var data, uint8Array, binaryString, base64;
|
|
1702
|
+
return _regenerator().w(function (_context5) {
|
|
1703
|
+
while (1) switch (_context5.n) {
|
|
1704
|
+
case 0:
|
|
1705
|
+
_context5.n = 1;
|
|
1706
|
+
return e.data.arrayBuffer();
|
|
1707
|
+
case 1:
|
|
1708
|
+
data = _context5.v;
|
|
1709
|
+
uint8Array = new Uint8Array(data);
|
|
1710
|
+
binaryString = uint8Array.reduce(function (str, _byte2) {
|
|
1711
|
+
return str + String.fromCharCode(_byte2);
|
|
1712
|
+
}, '');
|
|
1713
|
+
base64 = btoa(binaryString);
|
|
1714
|
+
try {
|
|
1715
|
+
_this10._conversationSubject.next(base64);
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
// Subject may have been completed already.
|
|
1718
|
+
}
|
|
1719
|
+
case 2:
|
|
1720
|
+
return _context5.a(2);
|
|
1721
|
+
}
|
|
1722
|
+
}, _callee5);
|
|
1723
|
+
})));
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
_this10.mediaRecorder.addEventListener('stop', function () {
|
|
1727
|
+
stream.getTracks().forEach(function (track) {
|
|
1728
|
+
return track.stop();
|
|
1729
|
+
});
|
|
1730
|
+
pendingChunk.then(function () {
|
|
1731
|
+
try {
|
|
1732
|
+
_this10._conversationSubject.complete();
|
|
1733
|
+
} catch (err) {
|
|
1734
|
+
// Already completed.
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1738
|
+
var profileId = _this10.getProfileId();
|
|
1739
|
+
var sessionId = _this10.getSessionId() || '';
|
|
1740
|
+
var language = navigator.language || document.documentElement.lang || 'en-US';
|
|
1741
|
+
_this10.connection.send("StartConversation", profileId, sessionId, _this10._conversationSubject, mimeType, language);
|
|
1742
|
+
_this10.mediaRecorder.start(250);
|
|
1743
|
+
_this10.isRecording = true;
|
|
1744
|
+
})["catch"](function (err) {
|
|
1745
|
+
console.error('Microphone access denied:', err);
|
|
1746
|
+
_this10.isConversationMode = false;
|
|
1747
|
+
_this10.updateConversationButton();
|
|
1748
|
+
});
|
|
1749
|
+
},
|
|
1750
|
+
stopConversationMode: function stopConversationMode() {
|
|
1751
|
+
if (!this.isConversationMode) {
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
this.isConversationMode = false;
|
|
1755
|
+
this.updateConversationButton();
|
|
1756
|
+
|
|
1757
|
+
// Signal the server to cancel all in-progress STT/TTS streams immediately.
|
|
1758
|
+
if (this.connection) {
|
|
1759
|
+
this.connection.invoke("StopConversation")["catch"](function () {});
|
|
1760
|
+
}
|
|
1761
|
+
if (this.isRecording && this.mediaRecorder) {
|
|
1762
|
+
this.mediaRecorder.stop();
|
|
1763
|
+
this.isRecording = false;
|
|
1764
|
+
}
|
|
1765
|
+
this.stopAudio();
|
|
1766
|
+
this._conversationPartialTranscript = '';
|
|
1767
|
+
this._conversationPartialMessage = null;
|
|
1768
|
+
|
|
1769
|
+
// Clean up the AudioContext used for volume monitoring.
|
|
1770
|
+
if (this._conversationAudioCtx) {
|
|
1771
|
+
this._conversationAudioCtx.close()["catch"](function () {});
|
|
1772
|
+
this._conversationAudioCtx = null;
|
|
1773
|
+
this._conversationAnalyser = null;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// Mark any in-flight assistant message as done to stop the spinner.
|
|
1777
|
+
if (this._conversationAssistantMessage) {
|
|
1778
|
+
var msg = this.messages[this._conversationAssistantMessage.index];
|
|
1779
|
+
if (msg) {
|
|
1780
|
+
msg.isStreaming = false;
|
|
1781
|
+
}
|
|
1782
|
+
this._conversationAssistantMessage = null;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Safety net: clear all lingering streaming indicators.
|
|
1786
|
+
for (var i = 0; i < this.messages.length; i++) {
|
|
1787
|
+
if (this.messages[i].isStreaming) {
|
|
1788
|
+
this.messages[i].isStreaming = false;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// Show a "conversation ended" notification system message.
|
|
1793
|
+
this.receiveNotification({
|
|
1794
|
+
type: 'conversation-ended',
|
|
1795
|
+
content: 'Conversation ended.',
|
|
1796
|
+
icon: 'fa-solid fa-circle-check',
|
|
1797
|
+
dismissible: true,
|
|
1798
|
+
autoDismissMs: 5000
|
|
1799
|
+
});
|
|
1800
|
+
},
|
|
1801
|
+
updateConversationButton: function updateConversationButton() {
|
|
1802
|
+
if (!this.conversationButton) {
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
if (this.isConversationMode) {
|
|
1806
|
+
this.conversationButton.classList.add('active', 'btn-primary');
|
|
1807
|
+
this.conversationButton.classList.remove('btn-dark', 'btn-outline-dark', 'btn-outline-secondary');
|
|
1808
|
+
this.conversationButton.title = this.conversationButton.getAttribute('data-end-title') || 'End Conversation';
|
|
1809
|
+
var endHtml = this.conversationButton.getAttribute('data-end-html');
|
|
1810
|
+
if (endHtml) {
|
|
1811
|
+
this.conversationButton.replaceChildren(DOMPurify.sanitize(endHtml, {
|
|
1812
|
+
RETURN_DOM_FRAGMENT: true
|
|
1813
|
+
}));
|
|
1814
|
+
}
|
|
1815
|
+
} else {
|
|
1816
|
+
this.conversationButton.classList.remove('active', 'btn-primary');
|
|
1817
|
+
this.conversationButton.classList.remove('btn-dark', 'btn-outline-secondary');
|
|
1818
|
+
this.conversationButton.classList.add('btn-outline-dark');
|
|
1819
|
+
this.conversationButton.blur();
|
|
1820
|
+
this.conversationButton.title = this.conversationButton.getAttribute('data-start-title') || 'Start Conversation';
|
|
1821
|
+
var startHtml = this.conversationButton.getAttribute('data-start-html');
|
|
1822
|
+
if (startHtml) {
|
|
1823
|
+
this.conversationButton.replaceChildren(DOMPurify.sanitize(startHtml, {
|
|
1824
|
+
RETURN_DOM_FRAGMENT: true
|
|
1825
|
+
}));
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
},
|
|
1829
|
+
conversationModeSendPrompt: function conversationModeSendPrompt() {
|
|
1830
|
+
// Legacy: only used by AudioInput mode's ReceiveTranscript.
|
|
1831
|
+
},
|
|
1832
|
+
conversationModeOnAudioEnded: function conversationModeOnAudioEnded() {
|
|
1833
|
+
// Legacy: in conversation mode, audio playback continuation
|
|
1834
|
+
// is handled by the persistent stream. This method is only
|
|
1835
|
+
// called from playCollectedAudio for non-conversation TTS.
|
|
1836
|
+
},
|
|
1837
|
+
generatePrompt: function generatePrompt(element) {
|
|
1838
|
+
if (!element) {
|
|
1839
|
+
console.error('The element paramter is required.');
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
var templateProfileId = element.getAttribute('data-profile-id');
|
|
1843
|
+
var sessionId = this.getSessionId();
|
|
1844
|
+
var sessionProfileId = this.getProfileId();
|
|
1845
|
+
if (!templateProfileId || !sessionId) {
|
|
1846
|
+
console.error('The given element is missing data-profile-id or the session has not yet started.');
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// streamMessage() already shows the typing indicator.
|
|
1851
|
+
this.streamMessage(templateProfileId, null, sessionProfileId);
|
|
1852
|
+
},
|
|
1853
|
+
createSessionUrl: function createSessionUrl(baseUrl, param, value) {
|
|
1854
|
+
var fullUrl = baseUrl.toLowerCase().startsWith('http') ? baseUrl : window.location.origin + baseUrl;
|
|
1855
|
+
var url = new URL(fullUrl);
|
|
1856
|
+
url.searchParams.set(param, value);
|
|
1857
|
+
return url.toString();
|
|
1858
|
+
},
|
|
1859
|
+
showTypingIndicator: function showTypingIndicator() {
|
|
1860
|
+
this.addMessage({
|
|
1861
|
+
role: 'indicator',
|
|
1862
|
+
htmlContent: config.indicatorTemplate
|
|
1863
|
+
});
|
|
1864
|
+
},
|
|
1865
|
+
hideTypingIndicator: function hideTypingIndicator() {
|
|
1866
|
+
var originalLength = this.messages.length;
|
|
1867
|
+
this.messages = this.messages.filter(function (msg) {
|
|
1868
|
+
return msg.role !== 'indicator';
|
|
1869
|
+
});
|
|
1870
|
+
var removedCount = originalLength - this.messages.length;
|
|
1871
|
+
return removedCount;
|
|
1872
|
+
},
|
|
1873
|
+
receiveNotification: function receiveNotification(notification) {
|
|
1874
|
+
var _this11 = this;
|
|
1875
|
+
if (!notification || !notification.type) {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
this.clearNotificationDismiss(notification.type);
|
|
1879
|
+
var existingIndex = this.notifications.findIndex(function (n) {
|
|
1880
|
+
return n.type === notification.type;
|
|
1881
|
+
});
|
|
1882
|
+
if (existingIndex >= 0) {
|
|
1883
|
+
this.notifications.splice(existingIndex, 1, notification);
|
|
1884
|
+
} else {
|
|
1885
|
+
this.notifications.push(notification);
|
|
1886
|
+
}
|
|
1887
|
+
this.scheduleNotificationDismiss(notification);
|
|
1888
|
+
this.$nextTick(function () {
|
|
1889
|
+
_this11.scrollToBottom();
|
|
1890
|
+
});
|
|
1891
|
+
},
|
|
1892
|
+
updateNotification: function updateNotification(notification) {
|
|
1893
|
+
if (!notification || !notification.type) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
this.clearNotificationDismiss(notification.type);
|
|
1897
|
+
var existingIndex = this.notifications.findIndex(function (n) {
|
|
1898
|
+
return n.type === notification.type;
|
|
1899
|
+
});
|
|
1900
|
+
if (existingIndex >= 0) {
|
|
1901
|
+
this.notifications.splice(existingIndex, 1, notification);
|
|
1902
|
+
this.scheduleNotificationDismiss(notification);
|
|
1903
|
+
}
|
|
1904
|
+
},
|
|
1905
|
+
scheduleNotificationDismiss: function scheduleNotificationDismiss(notification) {
|
|
1906
|
+
var _this12 = this;
|
|
1907
|
+
if (!notification || !notification.type || !notification.autoDismissMs || notification.autoDismissMs <= 0) {
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
this.notificationDismissTimers[notification.type] = setTimeout(function () {
|
|
1911
|
+
_this12.removeNotification(notification.type);
|
|
1912
|
+
}, notification.autoDismissMs);
|
|
1913
|
+
},
|
|
1914
|
+
clearNotificationDismiss: function clearNotificationDismiss(notificationType) {
|
|
1915
|
+
var timerId = this.notificationDismissTimers[notificationType];
|
|
1916
|
+
if (!timerId) {
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
clearTimeout(timerId);
|
|
1920
|
+
delete this.notificationDismissTimers[notificationType];
|
|
1921
|
+
},
|
|
1922
|
+
removeNotification: function removeNotification(notificationType) {
|
|
1923
|
+
this.clearNotificationDismiss(notificationType);
|
|
1924
|
+
this.notifications = this.notifications.filter(function (n) {
|
|
1925
|
+
return n.type !== notificationType;
|
|
1926
|
+
});
|
|
1927
|
+
},
|
|
1928
|
+
dismissNotification: function dismissNotification(notificationType) {
|
|
1929
|
+
this.removeNotification(notificationType);
|
|
1930
|
+
},
|
|
1931
|
+
handleNotificationAction: function handleNotificationAction(notificationType, actionName) {
|
|
1932
|
+
if (!this.connection) {
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
var sessionId = this.getSessionId();
|
|
1936
|
+
this.connection.invoke("HandleNotificationAction", sessionId, notificationType, actionName)["catch"](function (err) {
|
|
1937
|
+
console.error("Error handling notification action:", err);
|
|
1938
|
+
});
|
|
1939
|
+
},
|
|
1940
|
+
scrollToBottom: function scrollToBottom() {
|
|
1941
|
+
var _this13 = this;
|
|
1942
|
+
if (!this.autoScroll) {
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
setTimeout(function () {
|
|
1946
|
+
_this13.chatContainer.scrollTop = _this13.chatContainer.scrollHeight - _this13.chatContainer.clientHeight;
|
|
1947
|
+
}, 50);
|
|
1948
|
+
},
|
|
1949
|
+
handleUserInput: function handleUserInput(event) {
|
|
1950
|
+
this.prompt = event.target.value;
|
|
1951
|
+
},
|
|
1952
|
+
getProfileId: function getProfileId() {
|
|
1953
|
+
return this.inputElement.getAttribute('data-profile-id');
|
|
1954
|
+
},
|
|
1955
|
+
setSessionId: function setSessionId(sessionId) {
|
|
1956
|
+
this.inputElement.setAttribute('data-session-id', sessionId || '');
|
|
1957
|
+
},
|
|
1958
|
+
resetSession: function resetSession() {
|
|
1959
|
+
this.stopRecording();
|
|
1960
|
+
this.setSessionId('');
|
|
1961
|
+
this.isSessionStarted = false;
|
|
1962
|
+
this.sessionRating = null;
|
|
1963
|
+
if (this.widgetIsInitialized) {
|
|
1964
|
+
localStorage.removeItem(this.chatWidgetStateSession);
|
|
1965
|
+
}
|
|
1966
|
+
this.messages = [];
|
|
1967
|
+
this.documents = [];
|
|
1968
|
+
if (!config.autoCreateSession) {
|
|
1969
|
+
this.showPlaceholder();
|
|
1970
|
+
}
|
|
1971
|
+
if (config.autoCreateSession) {
|
|
1972
|
+
this.startNewSession();
|
|
1973
|
+
}
|
|
1974
|
+
},
|
|
1975
|
+
startNewSession: function startNewSession() {
|
|
1976
|
+
if (!this.applyOrchestratorAvailability()) {
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
var profileId = this.getProfileId();
|
|
1980
|
+
if (!profileId || !this.connection) {
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
this.connection.invoke("StartSession", profileId, null)["catch"](function (err) {
|
|
1984
|
+
return console.error(err);
|
|
1985
|
+
});
|
|
1986
|
+
},
|
|
1987
|
+
initializeApp: function initializeApp() {
|
|
1988
|
+
var _this14 = this;
|
|
1989
|
+
this.inputElement = document.querySelector(config.inputElementSelector);
|
|
1990
|
+
this.buttonElement = document.querySelector(config.sendButtonElementSelector);
|
|
1991
|
+
this.chatContainer = document.querySelector(config.chatContainerElementSelector);
|
|
1992
|
+
this.placeholder = document.querySelector(config.placeholderElementSelector);
|
|
1993
|
+
this.applyOrchestratorAvailability();
|
|
1994
|
+
var sessionId = this.getSessionId();
|
|
1995
|
+
if (!config.widget && sessionId) {
|
|
1996
|
+
this.loadSession(sessionId);
|
|
1997
|
+
} else if (this.isOrchestratorAvailable() && config.autoCreateSession && !config.widget && !sessionId) {
|
|
1998
|
+
this.startNewSession();
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// Initialize document bar if enabled.
|
|
2002
|
+
if (config.sessionDocumentsEnabled && config.documentBarSelector) {
|
|
2003
|
+
this.documentBar = document.querySelector(config.documentBarSelector);
|
|
2004
|
+
if (this.documentBar) {
|
|
2005
|
+
this.renderDocumentBar();
|
|
2006
|
+
|
|
2007
|
+
// Create hidden file input for document uploads.
|
|
2008
|
+
var fileInput = document.createElement('input');
|
|
2009
|
+
fileInput.type = 'file';
|
|
2010
|
+
fileInput.id = 'ai-chat-doc-input';
|
|
2011
|
+
fileInput.className = 'd-none';
|
|
2012
|
+
fileInput.multiple = true;
|
|
2013
|
+
if (config.allowedExtensions) {
|
|
2014
|
+
fileInput.accept = config.allowedExtensions;
|
|
2015
|
+
}
|
|
2016
|
+
fileInput.addEventListener('change', function (e) {
|
|
2017
|
+
return _this14.handleFileInputChange(e);
|
|
2018
|
+
});
|
|
2019
|
+
this.documentBar.parentElement.appendChild(fileInput);
|
|
2020
|
+
|
|
2021
|
+
// Set up drag-and-drop on the input area.
|
|
2022
|
+
var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
|
|
2023
|
+
if (inputArea) {
|
|
2024
|
+
inputArea.addEventListener('dragover', function (e) {
|
|
2025
|
+
return _this14.handleDragOver(e);
|
|
2026
|
+
});
|
|
2027
|
+
inputArea.addEventListener('dragleave', function (e) {
|
|
2028
|
+
return _this14.handleDragLeave(e);
|
|
2029
|
+
});
|
|
2030
|
+
inputArea.addEventListener('drop', function (e) {
|
|
2031
|
+
return _this14.handleDrop(e);
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// Pause auto-scroll when the user manually scrolls up during streaming.
|
|
2038
|
+
this.chatContainer.addEventListener('scroll', function () {
|
|
2039
|
+
if (!_this14.stream) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
var threshold = 30;
|
|
2043
|
+
var atBottom = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight - _this14.chatContainer.scrollTop <= threshold;
|
|
2044
|
+
_this14.autoScroll = atBottom;
|
|
2045
|
+
});
|
|
2046
|
+
this.inputElement.addEventListener('keydown', function (event) {
|
|
2047
|
+
if (_this14.stream != null) {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
2051
|
+
event.preventDefault();
|
|
2052
|
+
_this14.buttonElement.click();
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
this.inputElement.addEventListener('input', function (e) {
|
|
2056
|
+
_this14.handleUserInput(e);
|
|
2057
|
+
if (e.target.value.trim()) {
|
|
2058
|
+
_this14.buttonElement.removeAttribute('disabled');
|
|
2059
|
+
} else {
|
|
2060
|
+
_this14.buttonElement.setAttribute('disabled', true);
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
this.buttonElement.addEventListener('click', function () {
|
|
2064
|
+
if (_this14.stream != null) {
|
|
2065
|
+
_this14.stream.dispose();
|
|
2066
|
+
_this14.stream = null;
|
|
2067
|
+
_this14.streamingFinished();
|
|
2068
|
+
_this14.hideTypingIndicator();
|
|
2069
|
+
|
|
2070
|
+
// Clean up: remove empty assistant message or stop streaming animation.
|
|
2071
|
+
if (_this14.messages.length > 0) {
|
|
2072
|
+
var lastMsg = _this14.messages[_this14.messages.length - 1];
|
|
2073
|
+
if (lastMsg.role === 'assistant' && !lastMsg.content) {
|
|
2074
|
+
_this14.messages.pop();
|
|
2075
|
+
} else if (lastMsg.isStreaming) {
|
|
2076
|
+
lastMsg.isStreaming = false;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
_this14.sendMessage();
|
|
2082
|
+
});
|
|
2083
|
+
var promptGenerators = document.getElementsByClassName('profile-generated-prompt');
|
|
2084
|
+
for (var i = 0; i < promptGenerators.length; i++) {
|
|
2085
|
+
promptGenerators[i].addEventListener('click', function (e) {
|
|
2086
|
+
e.preventDefault();
|
|
2087
|
+
_this14.generatePrompt(e.target);
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
var chatSessions = document.getElementsByClassName('chat-session-history-item');
|
|
2091
|
+
for (var i = 0; i < chatSessions.length; i++) {
|
|
2092
|
+
chatSessions[i].addEventListener('click', function (e) {
|
|
2093
|
+
e.preventDefault();
|
|
2094
|
+
var sessionId = e.target.getAttribute('data-session-id');
|
|
2095
|
+
if (!sessionId) {
|
|
2096
|
+
console.error('an element with the class chat-session-history-item with no data-session-id set.');
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
_this14.loadSession(sessionId);
|
|
2100
|
+
_this14.showChatScreen();
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
for (var _i5 = 0; _i5 < config.messages.length; _i5++) {
|
|
2104
|
+
this.addMessage(config.messages[_i5]);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
// Update feedback icons in the DOM after initial messages have rendered.
|
|
2108
|
+
this.$nextTick(function () {
|
|
2109
|
+
_this14.refreshAllFeedbackIcons();
|
|
2110
|
+
});
|
|
2111
|
+
|
|
2112
|
+
// Delegate click for code block copy buttons.
|
|
2113
|
+
if (this.chatContainer) {
|
|
2114
|
+
this.chatContainer.addEventListener('click', function (e) {
|
|
2115
|
+
var btn = e.target.closest('.ai-code-copy-btn');
|
|
2116
|
+
if (!btn) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
var block = btn.closest('.ai-code-block') || btn.closest('pre');
|
|
2120
|
+
if (!block) {
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
var codeEl = block.querySelector('code');
|
|
2124
|
+
if (codeEl) {
|
|
2125
|
+
navigator.clipboard.writeText(codeEl.textContent);
|
|
2126
|
+
var copiedText = config.codeCopiedText || 'Copied!';
|
|
2127
|
+
btn.innerHTML = '<i class="fa-solid fa-check"></i> ' + copiedText;
|
|
2128
|
+
setTimeout(function () {
|
|
2129
|
+
btn.innerHTML = '<i class="fa-regular fa-copy"></i>';
|
|
2130
|
+
}, 2000);
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// Initialize speech-to-text microphone button.
|
|
2136
|
+
if (this.speechToTextEnabled && config.micButtonElementSelector) {
|
|
2137
|
+
this.micButton = document.querySelector(config.micButtonElementSelector);
|
|
2138
|
+
if (this.micButton) {
|
|
2139
|
+
this.micButton.style.display = '';
|
|
2140
|
+
this.micButton.addEventListener('click', function () {
|
|
2141
|
+
_this14.toggleRecording();
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
// Initialize conversation mode button.
|
|
2147
|
+
if (this.conversationModeEnabled && config.conversationButtonElementSelector) {
|
|
2148
|
+
this.conversationButton = document.querySelector(config.conversationButtonElementSelector);
|
|
2149
|
+
if (this.conversationButton) {
|
|
2150
|
+
this.conversationButton.addEventListener('click', function () {
|
|
2151
|
+
_this14.toggleConversationMode();
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
loadSession: function loadSession(sessionId) {
|
|
2157
|
+
this.connection.invoke("LoadSession", sessionId)["catch"](function (err) {
|
|
2158
|
+
return console.error(err);
|
|
2159
|
+
});
|
|
2160
|
+
},
|
|
2161
|
+
reloadCurrentSession: function reloadCurrentSession() {
|
|
2162
|
+
var sessionId = this.getSessionId();
|
|
2163
|
+
if (sessionId) {
|
|
2164
|
+
this.loadSession(sessionId);
|
|
2165
|
+
}
|
|
2166
|
+
},
|
|
2167
|
+
initializeSession: function initializeSession(sessionId, force) {
|
|
2168
|
+
if (this.isSessionStarted && !force) {
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
this.fireEvent(new CustomEvent("initializingSessionOpenAIChat", {
|
|
2172
|
+
detail: {
|
|
2173
|
+
sessionId: sessionId
|
|
2174
|
+
}
|
|
2175
|
+
}));
|
|
2176
|
+
this.setSessionId(sessionId);
|
|
2177
|
+
this.isSessionStarted = true;
|
|
2178
|
+
if (this.widgetIsInitialized) {
|
|
2179
|
+
localStorage.setItem(this.chatWidgetStateSession, sessionId);
|
|
2180
|
+
}
|
|
2181
|
+
},
|
|
2182
|
+
initializeWidget: function initializeWidget() {
|
|
2183
|
+
var _this15 = this;
|
|
2184
|
+
if (!config.widget.chatWidgetContainer) {
|
|
2185
|
+
console.error('The widget chatWidgetContainer is required.');
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (!config.widget.chatWidgetStateName) {
|
|
2189
|
+
console.error('The widget chatWidgetStateName is required.');
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
var chatWidgetContainer = document.querySelector(config.widget.chatWidgetContainer);
|
|
2193
|
+
if (!chatWidgetContainer) {
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (config.widget.chatHistorySection) {
|
|
2197
|
+
this.chatHistorySection = document.querySelector(config.widget.chatHistorySection);
|
|
2198
|
+
}
|
|
2199
|
+
this.chatWidgetStateName = config.widget.chatWidgetStateName;
|
|
2200
|
+
this.chatWidgetStateSession = config.widget.chatWidgetStateName + 'Session';
|
|
2201
|
+
this.widgetIsInitialized = true;
|
|
2202
|
+
|
|
2203
|
+
// Auto-load the last session so the user always sees previous chat history.
|
|
2204
|
+
this.reloadCurrentSession();
|
|
2205
|
+
if (config.autoCreateSession && !this.getSessionId()) {
|
|
2206
|
+
this.startNewSession();
|
|
2207
|
+
}
|
|
2208
|
+
if (config.widget.showHistoryButton && this.chatHistorySection) {
|
|
2209
|
+
var showHistoryButton = document.querySelector(config.widget.showHistoryButton);
|
|
2210
|
+
if (showHistoryButton) {
|
|
2211
|
+
showHistoryButton.addEventListener('click', function () {
|
|
2212
|
+
_this15.chatHistorySection.classList.toggle('show');
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
if (config.widget.closeHistoryButton) {
|
|
2216
|
+
var closeHistoryButton = document.querySelector(config.widget.closeHistoryButton);
|
|
2217
|
+
if (closeHistoryButton) {
|
|
2218
|
+
closeHistoryButton.addEventListener('click', function () {
|
|
2219
|
+
_this15.showChatScreen();
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
if (config.widget.newChatButton) {
|
|
2225
|
+
var newChatButton = document.querySelector(config.widget.newChatButton);
|
|
2226
|
+
if (newChatButton) {
|
|
2227
|
+
newChatButton.addEventListener('click', function () {
|
|
2228
|
+
_this15.resetSession();
|
|
2229
|
+
_this15.showChatScreen();
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
},
|
|
2234
|
+
showChatScreen: function showChatScreen() {
|
|
2235
|
+
if (!this.chatHistorySection) {
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
this.chatHistorySection.classList.remove('show');
|
|
2239
|
+
},
|
|
2240
|
+
getSessionId: function getSessionId() {
|
|
2241
|
+
var sessionId = this.inputElement.getAttribute('data-session-id');
|
|
2242
|
+
if (!sessionId && this.widgetIsInitialized) {
|
|
2243
|
+
sessionId = localStorage.getItem(this.chatWidgetStateSession);
|
|
2244
|
+
}
|
|
2245
|
+
return sessionId;
|
|
2246
|
+
},
|
|
2247
|
+
copyResponse: function copyResponse(message) {
|
|
2248
|
+
navigator.clipboard.writeText(message);
|
|
2249
|
+
},
|
|
2250
|
+
updateFeedbackIcons: function updateFeedbackIcons(container, userRating) {
|
|
2251
|
+
if (!container) {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
var upBtn = container.querySelector('.rate-up-btn');
|
|
2255
|
+
var downBtn = container.querySelector('.rate-down-btn');
|
|
2256
|
+
|
|
2257
|
+
// Font Awesome SVG+JS replaces <i> with <svg>, so we must replace
|
|
2258
|
+
// the entire button content and let Font Awesome re-process.
|
|
2259
|
+
if (upBtn) {
|
|
2260
|
+
var upClass = userRating === true ? 'fa-solid fa-thumbs-up' : 'fa-regular fa-thumbs-up';
|
|
2261
|
+
var upIcon = document.createElement('i');
|
|
2262
|
+
upIcon.className = upClass;
|
|
2263
|
+
upIcon.style.fontSize = '0.9rem';
|
|
2264
|
+
upBtn.textContent = '';
|
|
2265
|
+
upBtn.appendChild(upIcon);
|
|
2266
|
+
}
|
|
2267
|
+
if (downBtn) {
|
|
2268
|
+
var downClass = userRating === false ? 'fa-solid fa-thumbs-down' : 'fa-regular fa-thumbs-down';
|
|
2269
|
+
var downIcon = document.createElement('i');
|
|
2270
|
+
downIcon.className = downClass;
|
|
2271
|
+
downIcon.style.fontSize = '0.9rem';
|
|
2272
|
+
downBtn.textContent = '';
|
|
2273
|
+
downBtn.appendChild(downIcon);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// Trigger Font Awesome SVG+JS to convert the new <i> elements.
|
|
2277
|
+
if (window.FontAwesome && FontAwesome.dom && FontAwesome.dom.i2svg) {
|
|
2278
|
+
FontAwesome.dom.i2svg({
|
|
2279
|
+
node: container
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
refreshAllFeedbackIcons: function refreshAllFeedbackIcons() {
|
|
2284
|
+
var containers = this.$el.querySelectorAll('.ai-chat-message-assistant-feedback');
|
|
2285
|
+
for (var i = 0; i < containers.length; i++) {
|
|
2286
|
+
var msgId = containers[i].getAttribute('data-message-id');
|
|
2287
|
+
var msg = this.messages.find(function (m) {
|
|
2288
|
+
return m.id === msgId;
|
|
2289
|
+
});
|
|
2290
|
+
if (msg) {
|
|
2291
|
+
this.updateFeedbackIcons(containers[i], msg.userRating);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
rateMessage: function rateMessage(message, isPositive, event) {
|
|
2296
|
+
var sessionId = this.getSessionId();
|
|
2297
|
+
if (!sessionId || !message.id || !this.connection) {
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// Toggle: clicking the same rating again clears it.
|
|
2302
|
+
var newRating = message.userRating === isPositive ? null : isPositive;
|
|
2303
|
+
message.userRating = newRating;
|
|
2304
|
+
|
|
2305
|
+
// Find the feedback container by message ID for reliable DOM targeting.
|
|
2306
|
+
var feedbackContainer = this.$el.querySelector('.ai-chat-message-assistant-feedback[data-message-id="' + message.id + '"]');
|
|
2307
|
+
this.updateFeedbackIcons(feedbackContainer, newRating);
|
|
2308
|
+
|
|
2309
|
+
// Trigger spark animation after Font Awesome has re-processed icons.
|
|
2310
|
+
if (newRating !== null && feedbackContainer) {
|
|
2311
|
+
setTimeout(function () {
|
|
2312
|
+
var btnClass = isPositive ? '.rate-up-btn' : '.rate-down-btn';
|
|
2313
|
+
var btn = feedbackContainer.querySelector(btnClass);
|
|
2314
|
+
if (btn) {
|
|
2315
|
+
btn.classList.remove('spark-effect');
|
|
2316
|
+
void btn.offsetWidth;
|
|
2317
|
+
btn.classList.add('spark-effect');
|
|
2318
|
+
btn.addEventListener('animationend', function onEnd() {
|
|
2319
|
+
btn.removeEventListener('animationend', onEnd);
|
|
2320
|
+
btn.classList.remove('spark-effect');
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
}, 50);
|
|
2324
|
+
}
|
|
2325
|
+
this.connection.invoke("RateMessage", sessionId, message.id, isPositive)["catch"](function (err) {
|
|
2326
|
+
console.error('Failed to rate message:', err);
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
},
|
|
2330
|
+
watch: {
|
|
2331
|
+
documents: {
|
|
2332
|
+
handler: function handler() {
|
|
2333
|
+
this.renderDocumentBar();
|
|
2334
|
+
},
|
|
2335
|
+
deep: true
|
|
2336
|
+
},
|
|
2337
|
+
isUploading: function isUploading() {
|
|
2338
|
+
this.renderDocumentBar();
|
|
2339
|
+
},
|
|
2340
|
+
isPlayingAudio: function isPlayingAudio() {
|
|
2341
|
+
// Reserved for future use — volume-based interrupt detection
|
|
2342
|
+
// no longer mutes tracks; browser echo cancellation handles echo.
|
|
2343
|
+
},
|
|
2344
|
+
isConversationMode: function isConversationMode(active) {
|
|
2345
|
+
// Hide/show mic button.
|
|
2346
|
+
if (this.micButton) {
|
|
2347
|
+
this.micButton.style.display = active ? 'none' : this.speechToTextEnabled ? '' : 'none';
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// Hide/show send button.
|
|
2351
|
+
if (this.buttonElement) {
|
|
2352
|
+
this.buttonElement.style.display = active ? 'none' : '';
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// Disable/enable textarea.
|
|
2356
|
+
if (this.inputElement) {
|
|
2357
|
+
this.inputElement.disabled = active;
|
|
2358
|
+
if (active) {
|
|
2359
|
+
this.inputElement.placeholder = '';
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
},
|
|
2364
|
+
mounted: function mounted() {
|
|
2365
|
+
var _this16 = this;
|
|
2366
|
+
_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
|
|
2367
|
+
return _regenerator().w(function (_context6) {
|
|
2368
|
+
while (1) switch (_context6.n) {
|
|
2369
|
+
case 0:
|
|
2370
|
+
_context6.n = 1;
|
|
2371
|
+
return _this16.startConnection();
|
|
2372
|
+
case 1:
|
|
2373
|
+
_this16.initializeApp();
|
|
2374
|
+
if (config.widget) {
|
|
2375
|
+
_this16.initializeWidget();
|
|
2376
|
+
}
|
|
2377
|
+
case 2:
|
|
2378
|
+
return _context6.a(2);
|
|
2379
|
+
}
|
|
2380
|
+
}, _callee6);
|
|
2381
|
+
}))();
|
|
2382
|
+
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
2383
|
+
},
|
|
2384
|
+
beforeUnmount: function beforeUnmount() {
|
|
2385
|
+
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
2386
|
+
if (this.stream) {
|
|
2387
|
+
this.stream.dispose();
|
|
2388
|
+
this.stream = null;
|
|
2389
|
+
}
|
|
2390
|
+
if (this.connection) {
|
|
2391
|
+
this.connection.stop();
|
|
2392
|
+
}
|
|
2393
|
+
},
|
|
2394
|
+
template: config.messageTemplate
|
|
2395
|
+
}).mount(config.appElementSelector);
|
|
2396
|
+
return app;
|
|
2397
|
+
};
|
|
2398
|
+
return {
|
|
2399
|
+
initialize: initialize
|
|
2400
|
+
};
|
|
2401
|
+
}();
|
|
2402
|
+
|
|
2403
|
+
// Download chart as image via event delegation (DOMPurify strips inline onclick).
|
|
2404
|
+
document.addEventListener('click', function (e) {
|
|
2405
|
+
var btn = e.target.closest('.download-chart-btn');
|
|
2406
|
+
if (!btn) {
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
var chartId = btn.getAttribute('data-chart-id');
|
|
2410
|
+
var canvas = chartId ? document.getElementById(chartId) : null;
|
|
2411
|
+
if (!canvas) {
|
|
2412
|
+
console.error('Chart canvas not found:', chartId);
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
var link = document.createElement('a');
|
|
2416
|
+
link.download = 'chart-' + chartId + '.png';
|
|
2417
|
+
link.href = canvas.toDataURL('image/png');
|
|
2418
|
+
link.click();
|
|
2419
|
+
});
|
|
2420
|
+
|
|
2421
|
+
// Intercept download clicks for data-URI images and convert to blob downloads.
|
|
2422
|
+
document.addEventListener('click', function (e) {
|
|
2423
|
+
var link = e.target.closest('.ai-download-image');
|
|
2424
|
+
if (!link) {
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
var container = link.closest('.generated-image-container');
|
|
2428
|
+
var img = container === null || container === void 0 ? void 0 : container.querySelector('img');
|
|
2429
|
+
if (!img) {
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
var src = img.src;
|
|
2433
|
+
if (!src || !src.startsWith('data:')) {
|
|
2434
|
+
return; // Normal URL – let the default <a> behaviour handle it.
|
|
2435
|
+
}
|
|
2436
|
+
e.preventDefault();
|
|
2437
|
+
fetch(src).then(function (res) {
|
|
2438
|
+
return res.blob();
|
|
2439
|
+
}).then(function (blob) {
|
|
2440
|
+
var url = URL.createObjectURL(blob);
|
|
2441
|
+
var a = document.createElement('a');
|
|
2442
|
+
a.href = url;
|
|
2443
|
+
a.download = link.getAttribute('download') || 'generated-image.png';
|
|
2444
|
+
document.body.appendChild(a);
|
|
2445
|
+
a.click();
|
|
2446
|
+
document.body.removeChild(a);
|
|
2447
|
+
setTimeout(function () {
|
|
2448
|
+
URL.revokeObjectURL(url);
|
|
2449
|
+
}, 100);
|
|
2450
|
+
})["catch"](function (err) {
|
|
2451
|
+
console.error('Failed to download image:', err);
|
|
2452
|
+
});
|
|
2453
|
+
});
|