@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
|
@@ -0,0 +1,2088 @@
|
|
|
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 _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
7
|
+
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."); }
|
|
8
|
+
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; } }
|
|
9
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
10
|
+
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); }
|
|
11
|
+
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 }; })(); }
|
|
12
|
+
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); }
|
|
13
|
+
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); }
|
|
14
|
+
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); }); }; }
|
|
15
|
+
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; } } }; }
|
|
16
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
17
|
+
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."); }
|
|
18
|
+
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; } }
|
|
19
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
20
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
21
|
+
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; }
|
|
22
|
+
window.chatInteractionManager = 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
|
+
codeCopiedText: 'Copied!',
|
|
33
|
+
assistantLabel: 'Assistant',
|
|
34
|
+
messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"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\">You</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=\"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 <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" title=\"Click here to copy response to clipboard.\">\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 ",
|
|
35
|
+
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 ",
|
|
36
|
+
// Localizable strings
|
|
37
|
+
untitledText: 'Untitled',
|
|
38
|
+
clearHistoryTitle: 'Clear History',
|
|
39
|
+
clearHistoryMessage: 'Are you sure you want to clear the chat history? This action cannot be undone. Your documents, parameters, and tools will be preserved.',
|
|
40
|
+
clearHistoryOkText: 'Yes',
|
|
41
|
+
clearHistoryCancelText: 'Cancel'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Sanitize URLs to prevent javascript: protocol injection.
|
|
45
|
+
function sanitizeUrl(url) {
|
|
46
|
+
if (!url) return '';
|
|
47
|
+
var trimmed = url.trim();
|
|
48
|
+
if (/^javascript:/i.test(trimmed) || /^vbscript:/i.test(trimmed) || /^data:text\/html/i.test(trimmed)) {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
return url;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Safely HTML-encode a string using the DOM (avoids regex-based HTML filtering).
|
|
55
|
+
function escapeHtmlEntities(text) {
|
|
56
|
+
var span = document.createElement('span');
|
|
57
|
+
span.textContent = text;
|
|
58
|
+
return span.innerHTML;
|
|
59
|
+
}
|
|
60
|
+
var renderer = new marked.Renderer();
|
|
61
|
+
|
|
62
|
+
// Modify the link rendering to open in a new tab
|
|
63
|
+
renderer.link = function (data) {
|
|
64
|
+
var href = sanitizeUrl(data.href);
|
|
65
|
+
if (!href) return data.text || '';
|
|
66
|
+
return "<a href=\"".concat(href, "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(data.text, "</a>");
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Custom code block renderer with highlight.js integration and copy button.
|
|
70
|
+
renderer.code = function (data) {
|
|
71
|
+
var code = data.text || '';
|
|
72
|
+
var lang = (data.lang || '').trim();
|
|
73
|
+
var highlighted = code;
|
|
74
|
+
if (typeof hljs !== 'undefined') {
|
|
75
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
76
|
+
try {
|
|
77
|
+
highlighted = hljs.highlight(code, {
|
|
78
|
+
language: lang
|
|
79
|
+
}).value;
|
|
80
|
+
} catch (_) {}
|
|
81
|
+
} else {
|
|
82
|
+
try {
|
|
83
|
+
highlighted = hljs.highlightAuto(code).value;
|
|
84
|
+
} catch (_) {}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
highlighted = escapeHtmlEntities(code);
|
|
88
|
+
}
|
|
89
|
+
var langDisplay = lang ? escapeHtmlEntities(lang) : 'code';
|
|
90
|
+
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>");
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Custom image renderer for generated images with thumbnail styling and download button.
|
|
94
|
+
// Handles both URL and data-URI sources (data URIs are converted to blobs for download).
|
|
95
|
+
renderer.image = function (data) {
|
|
96
|
+
var src = sanitizeUrl(data.href);
|
|
97
|
+
if (!src) return '';
|
|
98
|
+
var alt = data.text || defaultConfig.generatedImageAltText;
|
|
99
|
+
var maxWidth = defaultConfig.generatedImageMaxWidth;
|
|
100
|
+
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>");
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Chart counter for unique IDs
|
|
104
|
+
var chartCounter = 0;
|
|
105
|
+
|
|
106
|
+
// Collector for charts discovered during marked parsing.
|
|
107
|
+
var _pendingCharts = [];
|
|
108
|
+
|
|
109
|
+
// Global chart config map shared with ai-chat.js
|
|
110
|
+
window.__chartConfigs = window.__chartConfigs || {};
|
|
111
|
+
function createChartHtml(chartId) {
|
|
112
|
+
var chartMaxWidth = defaultConfig.generatedChartMaxWidth;
|
|
113
|
+
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>";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Register [chart:{...json...}] as a native marked block extension so the
|
|
117
|
+
// markdown parser handles chart markers inline with surrounding text.
|
|
118
|
+
marked.use({
|
|
119
|
+
extensions: [{
|
|
120
|
+
name: 'chart',
|
|
121
|
+
level: 'block',
|
|
122
|
+
start: function start(src) {
|
|
123
|
+
var idx = src.indexOf('[chart:');
|
|
124
|
+
return idx >= 0 ? idx : undefined;
|
|
125
|
+
},
|
|
126
|
+
tokenizer: function tokenizer(src) {
|
|
127
|
+
var extracted = tryExtractChartMarker(src);
|
|
128
|
+
if (!extracted || extracted.startIndex !== 0) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
var chartId = "chat_chart_".concat(++chartCounter);
|
|
132
|
+
return {
|
|
133
|
+
type: 'chart',
|
|
134
|
+
raw: src.substring(0, extracted.endIndex),
|
|
135
|
+
chartId: chartId,
|
|
136
|
+
json: extracted.json
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
renderer: function renderer(token) {
|
|
140
|
+
_pendingCharts.push({
|
|
141
|
+
chartId: token.chartId,
|
|
142
|
+
config: token.json
|
|
143
|
+
});
|
|
144
|
+
window.__chartConfigs[token.chartId] = token.json;
|
|
145
|
+
return createChartHtml(token.chartId);
|
|
146
|
+
}
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Extract a [chart:{...json...}] marker. This avoids regex issues with nested brackets.
|
|
151
|
+
function tryExtractChartMarker(text) {
|
|
152
|
+
var token = '[chart:';
|
|
153
|
+
var start = text.indexOf(token);
|
|
154
|
+
if (start < 0) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Find JSON object boundary by balancing braces
|
|
159
|
+
var jsonStart = start + token.length;
|
|
160
|
+
var i = jsonStart;
|
|
161
|
+
while (i < text.length && (text[i] === ' ' || text[i] === '\n' || text[i] === '\r' || text[i] === '\t')) {
|
|
162
|
+
i++;
|
|
163
|
+
}
|
|
164
|
+
if (i >= text.length || text[i] !== '{') {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
var depth = 0;
|
|
168
|
+
var inString = false;
|
|
169
|
+
var escape = false;
|
|
170
|
+
for (; i < text.length; i++) {
|
|
171
|
+
var ch = text[i];
|
|
172
|
+
if (inString) {
|
|
173
|
+
if (escape) {
|
|
174
|
+
escape = false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (ch === '\\') {
|
|
178
|
+
escape = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (ch === '"') {
|
|
182
|
+
inString = false;
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (ch === '"') {
|
|
187
|
+
inString = true;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (ch === '{') {
|
|
191
|
+
depth++;
|
|
192
|
+
} else if (ch === '}') {
|
|
193
|
+
depth--;
|
|
194
|
+
if (depth === 0) {
|
|
195
|
+
var jsonEnd = i;
|
|
196
|
+
// Expect closing bracket after JSON
|
|
197
|
+
var closeBracketIndex = text.indexOf(']', jsonEnd + 1);
|
|
198
|
+
if (closeBracketIndex < 0) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
var json = text.substring(jsonStart, jsonEnd + 1).trim();
|
|
202
|
+
return {
|
|
203
|
+
startIndex: start,
|
|
204
|
+
endIndex: closeBracketIndex + 1,
|
|
205
|
+
json: json
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
function renderChartsInMessage(message) {
|
|
213
|
+
if (!message || !message._pendingCharts || !message._pendingCharts.length) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Copy and clear pending charts immediately to prevent duplicate renders.
|
|
218
|
+
var charts = _toConsumableArray(message._pendingCharts);
|
|
219
|
+
message._pendingCharts = [];
|
|
220
|
+
|
|
221
|
+
// Defer to requestAnimationFrame so the browser has fully laid out the
|
|
222
|
+
// canvas elements before Chart.js reads their dimensions.
|
|
223
|
+
requestAnimationFrame(function () {
|
|
224
|
+
var _iterator = _createForOfIteratorHelper(charts),
|
|
225
|
+
_step;
|
|
226
|
+
try {
|
|
227
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
228
|
+
var c = _step.value;
|
|
229
|
+
var canvas = document.getElementById(c.chartId);
|
|
230
|
+
if (!canvas) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (typeof Chart === 'undefined') {
|
|
234
|
+
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>).');
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
var _cfg$options;
|
|
239
|
+
// Destroy existing chart instance if re-rendering
|
|
240
|
+
if (canvas._chartInstance) {
|
|
241
|
+
canvas._chartInstance.destroy();
|
|
242
|
+
}
|
|
243
|
+
var cfg = typeof c.config === 'string' ? JSON.parse(c.config) : c.config;
|
|
244
|
+
(_cfg$options = cfg.options) !== null && _cfg$options !== void 0 ? _cfg$options : cfg.options = {};
|
|
245
|
+
cfg.options.responsive = true;
|
|
246
|
+
cfg.options.maintainAspectRatio = false;
|
|
247
|
+
canvas._chartInstance = new Chart(canvas, cfg);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error('Error creating chart:', e);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
_iterator.e(err);
|
|
254
|
+
} finally {
|
|
255
|
+
_iterator.f();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Parse markdown content via marked (which natively handles [chart:...] markers
|
|
261
|
+
// through the registered extension) and collect pending chart configs for later
|
|
262
|
+
// Chart.js rendering.
|
|
263
|
+
function parseMarkdownContent(content, message) {
|
|
264
|
+
_pendingCharts = [];
|
|
265
|
+
var html = marked.parse(content, {
|
|
266
|
+
renderer: renderer
|
|
267
|
+
});
|
|
268
|
+
message._pendingCharts = _pendingCharts.length > 0 ? _toConsumableArray(_pendingCharts) : [];
|
|
269
|
+
return DOMPurify.sanitize(html, {
|
|
270
|
+
ADD_ATTR: ['target']
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
var initialize = function initialize(instanceConfig) {
|
|
274
|
+
var config = Object.assign({}, defaultConfig, instanceConfig);
|
|
275
|
+
// Keep defaultConfig in sync so renderers use overridden values
|
|
276
|
+
defaultConfig = config;
|
|
277
|
+
if (!config.signalRHubUrl) {
|
|
278
|
+
console.error('The signalRHubUrl is required.');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!config.appElementSelector) {
|
|
282
|
+
console.error('The appElementSelector is required.');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (!config.chatContainerElementSelector) {
|
|
286
|
+
console.error('The chatContainerElementSelector is required.');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!config.inputElementSelector) {
|
|
290
|
+
console.error('The inputElementSelector is required.');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!config.sendButtonElementSelector) {
|
|
294
|
+
console.error('The sendButtonElementSelector is required.');
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
var app = Vue.createApp({
|
|
298
|
+
data: function data() {
|
|
299
|
+
return {
|
|
300
|
+
inputElement: null,
|
|
301
|
+
buttonElement: null,
|
|
302
|
+
chatContainer: null,
|
|
303
|
+
placeholder: null,
|
|
304
|
+
isInteractionStarted: false,
|
|
305
|
+
isPlaceholderVisible: true,
|
|
306
|
+
isStreaming: false,
|
|
307
|
+
isNavigatingAway: false,
|
|
308
|
+
autoScroll: true,
|
|
309
|
+
stream: null,
|
|
310
|
+
messages: [],
|
|
311
|
+
prompt: '',
|
|
312
|
+
initialFieldValues: new Map(),
|
|
313
|
+
settingsDirty: false,
|
|
314
|
+
saveSettingsTimeout: null,
|
|
315
|
+
saveIndicatorTimeout: null,
|
|
316
|
+
isRecording: false,
|
|
317
|
+
mediaRecorder: null,
|
|
318
|
+
preRecordingPrompt: '',
|
|
319
|
+
micButton: null,
|
|
320
|
+
speechToTextEnabled: config.chatMode === 'AudioInput' || config.chatMode === 'Conversation',
|
|
321
|
+
textToSpeechEnabled: config.chatMode === 'Conversation',
|
|
322
|
+
ttsVoiceName: config.ttsVoiceName || null,
|
|
323
|
+
audioChunks: [],
|
|
324
|
+
audioPlayQueue: [],
|
|
325
|
+
isPlayingAudio: false,
|
|
326
|
+
currentAudioElement: null,
|
|
327
|
+
conversationModeEnabled: config.chatMode === 'Conversation',
|
|
328
|
+
conversationButton: null,
|
|
329
|
+
isConversationMode: false,
|
|
330
|
+
notifications: [],
|
|
331
|
+
notificationDismissTimers: {}
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
computed: {
|
|
335
|
+
lastAssistantIndex: function lastAssistantIndex() {
|
|
336
|
+
for (var i = this.messages.length - 1; i >= 0; i--) {
|
|
337
|
+
if (this.messages[i].role === 'assistant') {
|
|
338
|
+
return i;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return -1;
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
methods: {
|
|
345
|
+
handleBeforeUnload: function handleBeforeUnload() {
|
|
346
|
+
this.isNavigatingAway = true;
|
|
347
|
+
},
|
|
348
|
+
startConnection: function startConnection() {
|
|
349
|
+
var _this = this;
|
|
350
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
351
|
+
var _t;
|
|
352
|
+
return _regenerator().w(function (_context) {
|
|
353
|
+
while (1) switch (_context.p = _context.n) {
|
|
354
|
+
case 0:
|
|
355
|
+
_this.connection = new signalR.HubConnectionBuilder().withUrl(config.signalRHubUrl).withAutomaticReconnect().build();
|
|
356
|
+
|
|
357
|
+
// Allow long-running operations (e.g., multi-step MCP tool calls)
|
|
358
|
+
// without the client disconnecting prematurely.
|
|
359
|
+
_this.connection.serverTimeoutInMilliseconds = 600000;
|
|
360
|
+
_this.connection.keepAliveIntervalInMilliseconds = 15000;
|
|
361
|
+
_this.connection.on("LoadInteraction", function (data) {
|
|
362
|
+
var _data$messages;
|
|
363
|
+
_this.initializeInteraction(data.itemId, true);
|
|
364
|
+
_this.messages = []; // Update the title field if it exists
|
|
365
|
+
var titleInput = document.querySelector('[data-chat-interaction-title], .setting-input[data-setting="title"], input[name="ChatInteraction.Title"], input[name="Title"]');
|
|
366
|
+
if (titleInput && data.title) {
|
|
367
|
+
titleInput.value = data.title;
|
|
368
|
+
}
|
|
369
|
+
((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
|
|
370
|
+
_this.addMessage(msg);
|
|
371
|
+
_this.$nextTick(function () {
|
|
372
|
+
renderChartsInMessage(msg);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
_this.connection.on("SettingsSaved", function (itemId, title) {
|
|
377
|
+
// Update the history list item if it exists
|
|
378
|
+
// Use a more specific selector to only target history list items, not other elements like the Clear History button
|
|
379
|
+
var historyItem = document.querySelector(".chat-interaction-history-item[data-interaction-id=\"".concat(itemId, "\"]"));
|
|
380
|
+
if (historyItem) {
|
|
381
|
+
historyItem.textContent = title || config.untitledText;
|
|
382
|
+
}
|
|
383
|
+
var titleInput = document.querySelector('[data-chat-interaction-title], .setting-input[data-setting="title"], input[name="ChatInteraction.Title"], input[name="Title"]');
|
|
384
|
+
if (titleInput && title) {
|
|
385
|
+
titleInput.value = title;
|
|
386
|
+
}
|
|
387
|
+
_this.showSaveIndicator('Saved', 'text-success');
|
|
388
|
+
});
|
|
389
|
+
_this.connection.on("ReceiveError", function (error) {
|
|
390
|
+
console.error("SignalR Error: ", error);
|
|
391
|
+
_this.showSaveIndicator('Save failed', 'text-danger');
|
|
392
|
+
if (_this.isRecording) {
|
|
393
|
+
_this.stopRecording();
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
_this.connection.on("ReceiveTranscript", function (itemId, text, isFinal) {
|
|
397
|
+
if (_this.isConversationMode) {
|
|
398
|
+
if (!isFinal && text) {
|
|
399
|
+
_this._conversationPartialTranscript = text;
|
|
400
|
+
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
401
|
+
var html = '<p class="ai-partial-transcript">' + escaped + '</p>';
|
|
402
|
+
|
|
403
|
+
// Show partial transcript as a live user message.
|
|
404
|
+
if (!_this._conversationPartialMessage) {
|
|
405
|
+
_this.hidePlaceholder();
|
|
406
|
+
_this._conversationPartialMessage = {
|
|
407
|
+
role: 'user',
|
|
408
|
+
content: text,
|
|
409
|
+
htmlContent: html,
|
|
410
|
+
isPartial: true
|
|
411
|
+
};
|
|
412
|
+
_this.messages.push(_this._conversationPartialMessage);
|
|
413
|
+
} else {
|
|
414
|
+
_this._conversationPartialMessage.content = text;
|
|
415
|
+
_this._conversationPartialMessage.htmlContent = html;
|
|
416
|
+
}
|
|
417
|
+
_this.scrollToBottom();
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (text && !_this._audioInputSent) {
|
|
422
|
+
_this.prompt = _this.preRecordingPrompt + text;
|
|
423
|
+
if (_this.inputElement) {
|
|
424
|
+
_this.inputElement.value = _this.prompt;
|
|
425
|
+
_this.inputElement.dispatchEvent(new Event('input'));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
_this.connection.on("ReceiveConversationUserMessage", function (itemId, text) {
|
|
430
|
+
if (text) {
|
|
431
|
+
_this.stopAudio();
|
|
432
|
+
|
|
433
|
+
// If there's an interrupted assistant message still streaming,
|
|
434
|
+
// mark it as done to stop the spinner animation.
|
|
435
|
+
if (_this._conversationAssistantMessage) {
|
|
436
|
+
var oldMsg = _this.messages[_this._conversationAssistantMessage.index];
|
|
437
|
+
if (oldMsg) {
|
|
438
|
+
oldMsg.isStreaming = false;
|
|
439
|
+
}
|
|
440
|
+
_this._conversationAssistantMessage = null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Replace the partial transcript message with the final one.
|
|
444
|
+
if (_this._conversationPartialMessage) {
|
|
445
|
+
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
446
|
+
_this._conversationPartialMessage.content = text;
|
|
447
|
+
_this._conversationPartialMessage.htmlContent = '<p>' + escaped + '</p>';
|
|
448
|
+
_this._conversationPartialMessage.isPartial = false;
|
|
449
|
+
_this._conversationPartialMessage = null;
|
|
450
|
+
} else {
|
|
451
|
+
_this.addMessage({
|
|
452
|
+
role: 'user',
|
|
453
|
+
content: text
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
_this.scrollToBottom();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
_this.connection.on("ReceiveConversationAssistantToken", function (itemId, messageId, token, responseId, appearance) {
|
|
460
|
+
if (!_this._conversationAssistantMessage) {
|
|
461
|
+
_this.stopAudio();
|
|
462
|
+
_this.hideTypingIndicator();
|
|
463
|
+
|
|
464
|
+
// Ensure no stale streaming indicators remain from prior messages.
|
|
465
|
+
for (var j = 0; j < _this.messages.length; j++) {
|
|
466
|
+
if (_this.messages[j].isStreaming) {
|
|
467
|
+
_this.messages[j].isStreaming = false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
var msgIndex = _this.messages.length;
|
|
471
|
+
var newMessage = {
|
|
472
|
+
id: messageId,
|
|
473
|
+
role: "assistant",
|
|
474
|
+
content: "",
|
|
475
|
+
htmlContent: "",
|
|
476
|
+
isStreaming: true,
|
|
477
|
+
appearance: _this.normalizeAssistantAppearance(appearance)
|
|
478
|
+
};
|
|
479
|
+
_this.messages.push(newMessage);
|
|
480
|
+
_this._conversationAssistantMessage = {
|
|
481
|
+
index: msgIndex,
|
|
482
|
+
content: ''
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
_this._conversationAssistantMessage.content += token;
|
|
486
|
+
var msg = _this.messages[_this._conversationAssistantMessage.index];
|
|
487
|
+
if (msg) {
|
|
488
|
+
if (!msg.appearance) {
|
|
489
|
+
msg.appearance = _this.normalizeAssistantAppearance(appearance);
|
|
490
|
+
}
|
|
491
|
+
msg.content = _this._conversationAssistantMessage.content;
|
|
492
|
+
msg.htmlContent = parseMarkdownContent(msg.content, msg);
|
|
493
|
+
_this.$nextTick(function () {
|
|
494
|
+
renderChartsInMessage(msg);
|
|
495
|
+
_this.scrollToBottom();
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
_this.connection.on("ReceiveConversationAssistantComplete", function (itemId, messageId) {
|
|
500
|
+
if (_this._conversationAssistantMessage) {
|
|
501
|
+
var msg = _this.messages[_this._conversationAssistantMessage.index];
|
|
502
|
+
if (msg) {
|
|
503
|
+
msg.isStreaming = false;
|
|
504
|
+
}
|
|
505
|
+
_this._conversationAssistantMessage = null;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
_this.connection.on("ReceiveAudioChunk", function (itemId, base64Audio, contentType) {
|
|
509
|
+
if (base64Audio) {
|
|
510
|
+
var binaryString = atob(base64Audio);
|
|
511
|
+
var bytes = new Uint8Array(binaryString.length);
|
|
512
|
+
for (var i = 0; i < binaryString.length; i++) {
|
|
513
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
514
|
+
}
|
|
515
|
+
_this.audioChunks.push(bytes);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
_this.connection.on("ReceiveAudioComplete", function (itemId) {
|
|
519
|
+
_this.playCollectedAudio();
|
|
520
|
+
});
|
|
521
|
+
_this.connection.on("HistoryCleared", function (itemId) {
|
|
522
|
+
// Clear messages and show placeholder
|
|
523
|
+
_this.messages = [];
|
|
524
|
+
_this.showPlaceholder();
|
|
525
|
+
|
|
526
|
+
// Hide the clear history button since there's no history now
|
|
527
|
+
var clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
|
528
|
+
if (clearHistoryBtn) {
|
|
529
|
+
clearHistoryBtn.classList.add('d-none');
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
_this.connection.on("ReceiveNotification", function (notification) {
|
|
533
|
+
_this.receiveNotification(notification);
|
|
534
|
+
});
|
|
535
|
+
_this.connection.on("UpdateNotification", function (notification) {
|
|
536
|
+
_this.updateNotification(notification);
|
|
537
|
+
});
|
|
538
|
+
_this.connection.on("RemoveNotification", function (notificationType) {
|
|
539
|
+
_this.removeNotification(notificationType);
|
|
540
|
+
});
|
|
541
|
+
_this.connection.onreconnecting(function () {
|
|
542
|
+
console.warn("SignalR: reconnecting...");
|
|
543
|
+
});
|
|
544
|
+
_this.connection.onreconnected(function () {
|
|
545
|
+
console.info("SignalR: reconnected.");
|
|
546
|
+
_this.reloadCurrentInteraction();
|
|
547
|
+
});
|
|
548
|
+
_this.connection.onclose(function (error) {
|
|
549
|
+
if (_this.isNavigatingAway) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (error) {
|
|
553
|
+
console.warn("SignalR connection closed with error:", error.message || error);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
_context.p = 1;
|
|
557
|
+
_context.n = 2;
|
|
558
|
+
return _this.connection.start();
|
|
559
|
+
case 2:
|
|
560
|
+
_context.n = 4;
|
|
561
|
+
break;
|
|
562
|
+
case 3:
|
|
563
|
+
_context.p = 3;
|
|
564
|
+
_t = _context.v;
|
|
565
|
+
console.error("SignalR Connection Error: ", _t);
|
|
566
|
+
case 4:
|
|
567
|
+
return _context.a(2);
|
|
568
|
+
}
|
|
569
|
+
}, _callee, null, [[1, 3]]);
|
|
570
|
+
}))();
|
|
571
|
+
},
|
|
572
|
+
addMessageInternal: function addMessageInternal(message) {
|
|
573
|
+
var _this2 = this;
|
|
574
|
+
if (message.role === 'assistant') {
|
|
575
|
+
message.appearance = this.normalizeAssistantAppearance(message.appearance);
|
|
576
|
+
}
|
|
577
|
+
if (message.content && !message.htmlContent) {
|
|
578
|
+
message.htmlContent = parseMarkdownContent(message.content, message);
|
|
579
|
+
}
|
|
580
|
+
this.fireEvent(new CustomEvent("addingChatInteractionMessage", {
|
|
581
|
+
detail: {
|
|
582
|
+
message: message
|
|
583
|
+
}
|
|
584
|
+
}));
|
|
585
|
+
this.messages.push(message);
|
|
586
|
+
this.$nextTick(function () {
|
|
587
|
+
_this2.fireEvent(new CustomEvent("addedChatInteractionMessage", {
|
|
588
|
+
detail: {
|
|
589
|
+
message: message
|
|
590
|
+
}
|
|
591
|
+
}));
|
|
592
|
+
});
|
|
593
|
+
},
|
|
594
|
+
addMessage: function addMessage(message) {
|
|
595
|
+
var _this3 = this;
|
|
596
|
+
if (message.content) {
|
|
597
|
+
var processedContent = message.content.trim();
|
|
598
|
+
if (message.references && _typeof(message.references) === "object" && Object.keys(message.references).length) {
|
|
599
|
+
// Only include references that were actually cited in the response.
|
|
600
|
+
var citedRefs = Object.entries(message.references).filter(function (_ref) {
|
|
601
|
+
var _ref2 = _slicedToArray(_ref, 1),
|
|
602
|
+
key = _ref2[0];
|
|
603
|
+
return processedContent.includes(key);
|
|
604
|
+
});
|
|
605
|
+
if (citedRefs.length) {
|
|
606
|
+
// Sort by original index so display indices follow a natural order.
|
|
607
|
+
citedRefs.sort(function (_ref3, _ref4) {
|
|
608
|
+
var _ref5 = _slicedToArray(_ref3, 2),
|
|
609
|
+
a = _ref5[1];
|
|
610
|
+
var _ref6 = _slicedToArray(_ref4, 2),
|
|
611
|
+
b = _ref6[1];
|
|
612
|
+
return a.index - b.index;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Phase 1: Replace all markers with unique placeholders.
|
|
616
|
+
var displayIndex = 1;
|
|
617
|
+
var _iterator2 = _createForOfIteratorHelper(citedRefs),
|
|
618
|
+
_step2;
|
|
619
|
+
try {
|
|
620
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
621
|
+
var _step2$value = _slicedToArray(_step2.value, 2),
|
|
622
|
+
key = _step2$value[0],
|
|
623
|
+
value = _step2$value[1];
|
|
624
|
+
var placeholder = "__CITE_".concat(value.index, "__");
|
|
625
|
+
processedContent = processedContent.replaceAll(key, placeholder);
|
|
626
|
+
value._displayIndex = displayIndex++;
|
|
627
|
+
value._placeholder = placeholder;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Phase 2: Replace placeholders with sequential display indices.
|
|
631
|
+
} catch (err) {
|
|
632
|
+
_iterator2.e(err);
|
|
633
|
+
} finally {
|
|
634
|
+
_iterator2.f();
|
|
635
|
+
}
|
|
636
|
+
var _iterator3 = _createForOfIteratorHelper(citedRefs),
|
|
637
|
+
_step3;
|
|
638
|
+
try {
|
|
639
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
640
|
+
var _step3$value = _slicedToArray(_step3.value, 2),
|
|
641
|
+
_value = _step3$value[1];
|
|
642
|
+
processedContent = processedContent.replaceAll(_value._placeholder, "<sup><strong>".concat(_value._displayIndex, "</strong></sup>"));
|
|
643
|
+
}
|
|
644
|
+
} catch (err) {
|
|
645
|
+
_iterator3.e(err);
|
|
646
|
+
} finally {
|
|
647
|
+
_iterator3.f();
|
|
648
|
+
}
|
|
649
|
+
processedContent = processedContent.replaceAll('</strong></sup><sup>', '</strong></sup><sup>,</sup><sup>');
|
|
650
|
+
processedContent += '<br><br>';
|
|
651
|
+
var _iterator4 = _createForOfIteratorHelper(citedRefs),
|
|
652
|
+
_step4;
|
|
653
|
+
try {
|
|
654
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
655
|
+
var _step4$value = _slicedToArray(_step4.value, 2),
|
|
656
|
+
_key = _step4$value[0],
|
|
657
|
+
_value2 = _step4$value[1];
|
|
658
|
+
var label = _value2.text || "[doc:".concat(_value2.index, "]");
|
|
659
|
+
processedContent += _value2.link ? "**".concat(_value2._displayIndex, "**. [").concat(label, "](").concat(_value2.link, ")<br>") : "**".concat(_value2._displayIndex, "**. ").concat(label, "<br>");
|
|
660
|
+
}
|
|
661
|
+
} catch (err) {
|
|
662
|
+
_iterator4.e(err);
|
|
663
|
+
} finally {
|
|
664
|
+
_iterator4.f();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
message.content = processedContent;
|
|
669
|
+
message.htmlContent = parseMarkdownContent(processedContent, message);
|
|
670
|
+
}
|
|
671
|
+
this.addMessageInternal(message);
|
|
672
|
+
this.hidePlaceholder();
|
|
673
|
+
this.$nextTick(function () {
|
|
674
|
+
// Render any pending charts once the DOM is updated
|
|
675
|
+
renderChartsInMessage(message);
|
|
676
|
+
_this3.scrollToBottom();
|
|
677
|
+
});
|
|
678
|
+
},
|
|
679
|
+
hidePlaceholder: function hidePlaceholder() {
|
|
680
|
+
if (this.placeholder) {
|
|
681
|
+
this.placeholder.classList.add('d-none');
|
|
682
|
+
}
|
|
683
|
+
this.isPlaceholderVisible = false;
|
|
684
|
+
},
|
|
685
|
+
showPlaceholder: function showPlaceholder() {
|
|
686
|
+
if (this.placeholder) {
|
|
687
|
+
this.placeholder.classList.remove('d-none');
|
|
688
|
+
}
|
|
689
|
+
this.isPlaceholderVisible = true;
|
|
690
|
+
},
|
|
691
|
+
fireEvent: function fireEvent(event) {
|
|
692
|
+
document.dispatchEvent(event);
|
|
693
|
+
},
|
|
694
|
+
normalizeAssistantAppearance: function normalizeAssistantAppearance(appearance) {
|
|
695
|
+
if (!appearance) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
var label = typeof appearance.label === 'string' ? appearance.label.trim() : '';
|
|
699
|
+
var icon = typeof appearance.icon === 'string' ? appearance.icon.trim() : '';
|
|
700
|
+
var cssClass = typeof appearance.cssClass === 'string' ? appearance.cssClass.trim() : '';
|
|
701
|
+
var disableStreamingAnimation = !!appearance.disableStreamingAnimation;
|
|
702
|
+
if (!label && !icon && !cssClass && !disableStreamingAnimation) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
label: label,
|
|
707
|
+
icon: icon,
|
|
708
|
+
cssClass: cssClass,
|
|
709
|
+
disableStreamingAnimation: disableStreamingAnimation
|
|
710
|
+
};
|
|
711
|
+
},
|
|
712
|
+
getAssistantLabel: function getAssistantLabel(message) {
|
|
713
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
714
|
+
return appearance && appearance.label ? appearance.label : defaultConfig.assistantLabel;
|
|
715
|
+
},
|
|
716
|
+
getAssistantRoleClasses: function getAssistantRoleClasses(message) {
|
|
717
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
718
|
+
var classes = ['ai-chat-msg-role'];
|
|
719
|
+
if (appearance && appearance.cssClass) {
|
|
720
|
+
classes.push(appearance.cssClass);
|
|
721
|
+
} else {
|
|
722
|
+
classes.push('ai-chat-msg-role-assistant');
|
|
723
|
+
}
|
|
724
|
+
return classes;
|
|
725
|
+
},
|
|
726
|
+
getAssistantIconClasses: function getAssistantIconClasses(message, index) {
|
|
727
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
728
|
+
return [this.shouldAnimateAssistantIcon(message, index) ? 'ai-streaming-icon' : 'ai-bot-icon', appearance && appearance.cssClass ? appearance.cssClass : ''];
|
|
729
|
+
},
|
|
730
|
+
getAssistantIcon: function getAssistantIcon(message) {
|
|
731
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
732
|
+
return appearance && appearance.icon ? appearance.icon : 'fa fa-robot';
|
|
733
|
+
},
|
|
734
|
+
shouldAnimateAssistantIcon: function shouldAnimateAssistantIcon(message, index) {
|
|
735
|
+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
|
|
736
|
+
return !!message && message.isStreaming && index === this.lastAssistantIndex && !(appearance && appearance.disableStreamingAnimation);
|
|
737
|
+
},
|
|
738
|
+
isIndicator: function isIndicator(message) {
|
|
739
|
+
return message.role === 'indicator';
|
|
740
|
+
},
|
|
741
|
+
sendMessage: function sendMessage() {
|
|
742
|
+
var _this4 = this;
|
|
743
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
744
|
+
var trimmedPrompt, clearHistoryBtn;
|
|
745
|
+
return _regenerator().w(function (_context2) {
|
|
746
|
+
while (1) switch (_context2.n) {
|
|
747
|
+
case 0:
|
|
748
|
+
trimmedPrompt = _this4.prompt.trim();
|
|
749
|
+
if (trimmedPrompt) {
|
|
750
|
+
_context2.n = 1;
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
return _context2.a(2);
|
|
754
|
+
case 1:
|
|
755
|
+
// Stop any active recording before sending.
|
|
756
|
+
if (_this4.isRecording) {
|
|
757
|
+
_this4.stopRecording();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Prevent stale ReceiveTranscript events from repopulating the prompt.
|
|
761
|
+
_this4._audioInputSent = true;
|
|
762
|
+
|
|
763
|
+
// Flush any pending settings save before sending a message
|
|
764
|
+
// to prevent concurrent hub calls that can cause database deadlocks.
|
|
765
|
+
_context2.n = 2;
|
|
766
|
+
return _this4.flushPendingSave();
|
|
767
|
+
case 2:
|
|
768
|
+
_this4.addMessage({
|
|
769
|
+
role: 'user',
|
|
770
|
+
content: trimmedPrompt
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// Show the clear history button since we now have prompts
|
|
774
|
+
clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
|
775
|
+
if (clearHistoryBtn) {
|
|
776
|
+
clearHistoryBtn.classList.remove('d-none');
|
|
777
|
+
}
|
|
778
|
+
_this4.streamMessage(trimmedPrompt);
|
|
779
|
+
_this4.inputElement.value = '';
|
|
780
|
+
_this4.prompt = '';
|
|
781
|
+
case 3:
|
|
782
|
+
return _context2.a(2);
|
|
783
|
+
}
|
|
784
|
+
}, _callee2);
|
|
785
|
+
}))();
|
|
786
|
+
},
|
|
787
|
+
streamMessage: function streamMessage(trimmedPrompt) {
|
|
788
|
+
var _this5 = this;
|
|
789
|
+
if (this.stream) {
|
|
790
|
+
this.stream.dispose();
|
|
791
|
+
this.stream = null;
|
|
792
|
+
}
|
|
793
|
+
this.streamingStarted();
|
|
794
|
+
this.showTypingIndicator();
|
|
795
|
+
this.autoScroll = true;
|
|
796
|
+
var content = '';
|
|
797
|
+
var references = {};
|
|
798
|
+
var lastResponseId = null;
|
|
799
|
+
var messageIndex = this.messages.length;
|
|
800
|
+
var currentItemId = this.getItemId();
|
|
801
|
+
this.stream = this.connection.stream("SendMessage", currentItemId, trimmedPrompt).subscribe({
|
|
802
|
+
next: function next(chunk) {
|
|
803
|
+
var message = _this5.messages[messageIndex];
|
|
804
|
+
if (!message) {
|
|
805
|
+
if (chunk.sessionId && !currentItemId) {
|
|
806
|
+
_this5.setItemId(chunk.sessionId);
|
|
807
|
+
}
|
|
808
|
+
_this5.hideTypingIndicator();
|
|
809
|
+
messageIndex = _this5.messages.length;
|
|
810
|
+
var newMessage = {
|
|
811
|
+
role: "assistant",
|
|
812
|
+
content: "",
|
|
813
|
+
htmlContent: "",
|
|
814
|
+
isStreaming: true
|
|
815
|
+
};
|
|
816
|
+
_this5.messages.push(newMessage);
|
|
817
|
+
message = newMessage;
|
|
818
|
+
}
|
|
819
|
+
if (chunk.references && _typeof(chunk.references) === "object" && Object.keys(chunk.references).length) {
|
|
820
|
+
for (var _i = 0, _Object$entries = Object.entries(chunk.references); _i < _Object$entries.length; _i++) {
|
|
821
|
+
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
|
|
822
|
+
key = _Object$entries$_i[0],
|
|
823
|
+
value = _Object$entries$_i[1];
|
|
824
|
+
references[key] = value;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (chunk.content) {
|
|
828
|
+
// When the responseId changes (e.g., after an internal tool call),
|
|
829
|
+
// insert a line break to visually separate response segments.
|
|
830
|
+
if (chunk.responseId && lastResponseId && chunk.responseId !== lastResponseId) {
|
|
831
|
+
content += '\n\n';
|
|
832
|
+
}
|
|
833
|
+
if (chunk.responseId) {
|
|
834
|
+
lastResponseId = chunk.responseId;
|
|
835
|
+
}
|
|
836
|
+
var processedContent = chunk.content;
|
|
837
|
+
for (var _i2 = 0, _Object$entries2 = Object.entries(references); _i2 < _Object$entries2.length; _i2++) {
|
|
838
|
+
var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
|
|
839
|
+
_key2 = _Object$entries2$_i[0],
|
|
840
|
+
_value3 = _Object$entries2$_i[1];
|
|
841
|
+
processedContent = processedContent.replaceAll(_key2, "<sup><strong>".concat(_value3.index, "</strong></sup>"));
|
|
842
|
+
}
|
|
843
|
+
content += processedContent.replaceAll('</strong></sup><sup>', '</strong></sup><sup>,</sup><sup>');
|
|
844
|
+
}
|
|
845
|
+
message.content = content;
|
|
846
|
+
message.htmlContent = parseMarkdownContent(content, message);
|
|
847
|
+
_this5.messages[messageIndex] = message;
|
|
848
|
+
_this5.$nextTick(function () {
|
|
849
|
+
renderChartsInMessage(message);
|
|
850
|
+
_this5.scrollToBottom();
|
|
851
|
+
});
|
|
852
|
+
},
|
|
853
|
+
complete: function complete() {
|
|
854
|
+
var _this5$stream;
|
|
855
|
+
_this5.processReferences(references, messageIndex);
|
|
856
|
+
_this5.streamingFinished();
|
|
857
|
+
var msg = _this5.messages[messageIndex];
|
|
858
|
+
if (msg) {
|
|
859
|
+
msg.isStreaming = false;
|
|
860
|
+
}
|
|
861
|
+
if (!msg || !msg.content) {
|
|
862
|
+
// No content received at all.
|
|
863
|
+
_this5.hideTypingIndicator();
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Trigger text-to-speech only in conversation mode.
|
|
867
|
+
if (_this5.isConversationMode && _this5.textToSpeechEnabled && msg && msg.content) {
|
|
868
|
+
_this5.synthesizeSpeech(msg.content);
|
|
869
|
+
}
|
|
870
|
+
(_this5$stream = _this5.stream) === null || _this5$stream === void 0 || _this5$stream.dispose();
|
|
871
|
+
_this5.stream = null;
|
|
872
|
+
},
|
|
873
|
+
error: function error(err) {
|
|
874
|
+
var _this5$stream2;
|
|
875
|
+
_this5.processReferences(references, messageIndex);
|
|
876
|
+
_this5.streamingFinished();
|
|
877
|
+
var msg = _this5.messages[messageIndex];
|
|
878
|
+
if (msg) {
|
|
879
|
+
msg.isStreaming = false;
|
|
880
|
+
}
|
|
881
|
+
_this5.hideTypingIndicator();
|
|
882
|
+
if (!_this5.isNavigatingAway) {
|
|
883
|
+
_this5.addMessage(_this5.getServiceDownMessage());
|
|
884
|
+
}
|
|
885
|
+
(_this5$stream2 = _this5.stream) === null || _this5$stream2 === void 0 || _this5$stream2.dispose();
|
|
886
|
+
_this5.stream = null;
|
|
887
|
+
console.error("Stream error:", err);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
},
|
|
891
|
+
getServiceDownMessage: function getServiceDownMessage() {
|
|
892
|
+
return {
|
|
893
|
+
role: "assistant",
|
|
894
|
+
content: "Our service is currently unavailable. Please try again later. We apologize for the inconvenience.",
|
|
895
|
+
htmlContent: ""
|
|
896
|
+
};
|
|
897
|
+
},
|
|
898
|
+
processReferences: function processReferences(references, messageIndex) {
|
|
899
|
+
var _this6 = this;
|
|
900
|
+
if (Object.keys(references).length) {
|
|
901
|
+
var message = this.messages[messageIndex];
|
|
902
|
+
var content = message.content || '';
|
|
903
|
+
|
|
904
|
+
// Only include references that were actually cited in the response.
|
|
905
|
+
// Check both raw [doc:N] markers and already-rendered <sup> tags from streaming.
|
|
906
|
+
var citedRefs = Object.entries(references).filter(function (_ref7) {
|
|
907
|
+
var _ref8 = _slicedToArray(_ref7, 2),
|
|
908
|
+
key = _ref8[0],
|
|
909
|
+
value = _ref8[1];
|
|
910
|
+
return content.includes(key) || content.includes("<sup><strong>".concat(value.index, "</strong></sup>"));
|
|
911
|
+
});
|
|
912
|
+
if (!citedRefs.length) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Sort by original index so display indices follow a natural order.
|
|
917
|
+
citedRefs.sort(function (_ref9, _ref0) {
|
|
918
|
+
var _ref1 = _slicedToArray(_ref9, 2),
|
|
919
|
+
a = _ref1[1];
|
|
920
|
+
var _ref10 = _slicedToArray(_ref0, 2),
|
|
921
|
+
b = _ref10[1];
|
|
922
|
+
return a.index - b.index;
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// Phase 1: Replace all markers with unique placeholders to avoid collisions during remapping.
|
|
926
|
+
var processed = content.trim();
|
|
927
|
+
var displayIndex = 1;
|
|
928
|
+
var _iterator5 = _createForOfIteratorHelper(citedRefs),
|
|
929
|
+
_step5;
|
|
930
|
+
try {
|
|
931
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
932
|
+
var _step5$value = _slicedToArray(_step5.value, 2),
|
|
933
|
+
key = _step5$value[0],
|
|
934
|
+
value = _step5$value[1];
|
|
935
|
+
var placeholder = "__CITE_".concat(value.index, "__");
|
|
936
|
+
processed = processed.replaceAll(key, placeholder);
|
|
937
|
+
processed = processed.replaceAll("<sup><strong>".concat(value.index, "</strong></sup>"), placeholder);
|
|
938
|
+
value._displayIndex = displayIndex++;
|
|
939
|
+
value._placeholder = placeholder;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Phase 2: Replace placeholders with sequential display indices.
|
|
943
|
+
} catch (err) {
|
|
944
|
+
_iterator5.e(err);
|
|
945
|
+
} finally {
|
|
946
|
+
_iterator5.f();
|
|
947
|
+
}
|
|
948
|
+
var _iterator6 = _createForOfIteratorHelper(citedRefs),
|
|
949
|
+
_step6;
|
|
950
|
+
try {
|
|
951
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
952
|
+
var _step6$value = _slicedToArray(_step6.value, 2),
|
|
953
|
+
_value4 = _step6$value[1];
|
|
954
|
+
processed = processed.replaceAll(_value4._placeholder, "<sup><strong>".concat(_value4._displayIndex, "</strong></sup>"));
|
|
955
|
+
}
|
|
956
|
+
} catch (err) {
|
|
957
|
+
_iterator6.e(err);
|
|
958
|
+
} finally {
|
|
959
|
+
_iterator6.f();
|
|
960
|
+
}
|
|
961
|
+
processed = processed.replaceAll('</strong></sup><sup>', '</strong></sup><sup>,</sup><sup>');
|
|
962
|
+
processed += '<br><br>';
|
|
963
|
+
var _iterator7 = _createForOfIteratorHelper(citedRefs),
|
|
964
|
+
_step7;
|
|
965
|
+
try {
|
|
966
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
967
|
+
var _step7$value = _slicedToArray(_step7.value, 2),
|
|
968
|
+
_key3 = _step7$value[0],
|
|
969
|
+
_value5 = _step7$value[1];
|
|
970
|
+
var label = _value5.text || "[doc:".concat(_value5.index, "]");
|
|
971
|
+
processed += _value5.link ? "**".concat(_value5._displayIndex, "**. [").concat(label, "](").concat(_value5.link, ")<br>") : "**".concat(_value5._displayIndex, "**. ").concat(label, "<br>");
|
|
972
|
+
}
|
|
973
|
+
} catch (err) {
|
|
974
|
+
_iterator7.e(err);
|
|
975
|
+
} finally {
|
|
976
|
+
_iterator7.f();
|
|
977
|
+
}
|
|
978
|
+
message.content = processed;
|
|
979
|
+
message.htmlContent = parseMarkdownContent(processed, message);
|
|
980
|
+
this.messages[messageIndex] = message;
|
|
981
|
+
this.$nextTick(function () {
|
|
982
|
+
renderChartsInMessage(message);
|
|
983
|
+
_this6.scrollToBottom();
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
streamingStarted: function streamingStarted() {
|
|
988
|
+
var stopIcon = this.buttonElement.getAttribute('data-stop-icon');
|
|
989
|
+
if (stopIcon) {
|
|
990
|
+
this.buttonElement.replaceChildren(DOMPurify.sanitize(stopIcon, {
|
|
991
|
+
RETURN_DOM_FRAGMENT: true
|
|
992
|
+
}));
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
streamingFinished: function streamingFinished() {
|
|
996
|
+
var startIcon = this.buttonElement.getAttribute('data-start-icon');
|
|
997
|
+
if (startIcon) {
|
|
998
|
+
this.buttonElement.replaceChildren(DOMPurify.sanitize(startIcon, {
|
|
999
|
+
RETURN_DOM_FRAGMENT: true
|
|
1000
|
+
}));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Directly manipulate the DOM to stop all streaming animations.
|
|
1004
|
+
if (this.chatContainer) {
|
|
1005
|
+
var icons = this.chatContainer.querySelectorAll('.ai-streaming-icon');
|
|
1006
|
+
for (var i = 0; i < icons.length; i++) {
|
|
1007
|
+
icons[i].classList.remove('ai-streaming-icon');
|
|
1008
|
+
icons[i].classList.add('ai-bot-icon');
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Also update Vue data for consistency.
|
|
1013
|
+
for (var i = 0; i < this.messages.length; i++) {
|
|
1014
|
+
if (this.messages[i].isStreaming) {
|
|
1015
|
+
this.messages[i].isStreaming = false;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Save any settings that were deferred during streaming.
|
|
1020
|
+
if (this.settingsDirty) {
|
|
1021
|
+
this.debouncedSaveSettings();
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
synthesizeSpeech: function synthesizeSpeech(text) {
|
|
1025
|
+
var _this7 = this;
|
|
1026
|
+
if (!this.textToSpeechEnabled || !text || !this.connection) {
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
this.audioChunks = [];
|
|
1030
|
+
this.isPlayingAudio = true;
|
|
1031
|
+
this.connection.invoke("SynthesizeSpeech", this.getItemId(), text, this.ttsVoiceName)["catch"](function (err) {
|
|
1032
|
+
console.error("TTS synthesis error:", err);
|
|
1033
|
+
_this7.isPlayingAudio = false;
|
|
1034
|
+
});
|
|
1035
|
+
},
|
|
1036
|
+
playCollectedAudio: function playCollectedAudio() {
|
|
1037
|
+
if (this.audioChunks.length === 0) {
|
|
1038
|
+
if (!this.isPlayingAudio && this.audioPlayQueue.length === 0) {
|
|
1039
|
+
this.isPlayingAudio = false;
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
var totalLength = this.audioChunks.reduce(function (sum, chunk) {
|
|
1044
|
+
return sum + chunk.length;
|
|
1045
|
+
}, 0);
|
|
1046
|
+
var combined = new Uint8Array(totalLength);
|
|
1047
|
+
var offset = 0;
|
|
1048
|
+
var _iterator8 = _createForOfIteratorHelper(this.audioChunks),
|
|
1049
|
+
_step8;
|
|
1050
|
+
try {
|
|
1051
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
1052
|
+
var chunk = _step8.value;
|
|
1053
|
+
combined.set(chunk, offset);
|
|
1054
|
+
offset += chunk.length;
|
|
1055
|
+
}
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
_iterator8.e(err);
|
|
1058
|
+
} finally {
|
|
1059
|
+
_iterator8.f();
|
|
1060
|
+
}
|
|
1061
|
+
this.audioChunks = [];
|
|
1062
|
+
var blob = new Blob([combined], {
|
|
1063
|
+
type: 'audio/mp3'
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// If audio is currently playing, queue this blob for later.
|
|
1067
|
+
if (this.isPlayingAudio && this.currentAudioElement) {
|
|
1068
|
+
this.audioPlayQueue.push(blob);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
this.playAudioBlob(blob);
|
|
1072
|
+
},
|
|
1073
|
+
playAudioBlob: function playAudioBlob(blob) {
|
|
1074
|
+
var _this8 = this;
|
|
1075
|
+
var url = URL.createObjectURL(blob);
|
|
1076
|
+
var audio = new Audio(url);
|
|
1077
|
+
this.currentAudioElement = audio;
|
|
1078
|
+
this.isPlayingAudio = true;
|
|
1079
|
+
audio.addEventListener('ended', function () {
|
|
1080
|
+
URL.revokeObjectURL(url);
|
|
1081
|
+
_this8.currentAudioElement = null;
|
|
1082
|
+
_this8.playNextInQueue();
|
|
1083
|
+
});
|
|
1084
|
+
audio.addEventListener('error', function () {
|
|
1085
|
+
URL.revokeObjectURL(url);
|
|
1086
|
+
_this8.currentAudioElement = null;
|
|
1087
|
+
_this8.playNextInQueue();
|
|
1088
|
+
});
|
|
1089
|
+
audio.play()["catch"](function (err) {
|
|
1090
|
+
console.error("Audio playback error:", err);
|
|
1091
|
+
URL.revokeObjectURL(url);
|
|
1092
|
+
_this8.currentAudioElement = null;
|
|
1093
|
+
_this8.isPlayingAudio = false;
|
|
1094
|
+
});
|
|
1095
|
+
},
|
|
1096
|
+
playNextInQueue: function playNextInQueue() {
|
|
1097
|
+
if (this.audioPlayQueue.length > 0) {
|
|
1098
|
+
var nextBlob = this.audioPlayQueue.shift();
|
|
1099
|
+
this.playAudioBlob(nextBlob);
|
|
1100
|
+
} else {
|
|
1101
|
+
this.isPlayingAudio = false;
|
|
1102
|
+
this.conversationModeOnAudioEnded();
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
stopAudio: function stopAudio() {
|
|
1106
|
+
if (this.currentAudioElement) {
|
|
1107
|
+
this.currentAudioElement.pause();
|
|
1108
|
+
this.currentAudioElement.currentTime = 0;
|
|
1109
|
+
this.currentAudioElement = null;
|
|
1110
|
+
}
|
|
1111
|
+
this.audioChunks = [];
|
|
1112
|
+
this.audioPlayQueue = [];
|
|
1113
|
+
this.isPlayingAudio = false;
|
|
1114
|
+
},
|
|
1115
|
+
toggleConversationMode: function toggleConversationMode() {
|
|
1116
|
+
if (this.isConversationMode) {
|
|
1117
|
+
this.stopConversationMode();
|
|
1118
|
+
} else {
|
|
1119
|
+
this.startConversationMode();
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
startConversationMode: function startConversationMode() {
|
|
1123
|
+
var _this9 = this;
|
|
1124
|
+
if (!this.conversationModeEnabled || this.isConversationMode || !this.connection) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
this.isConversationMode = true;
|
|
1128
|
+
this.updateConversationButton();
|
|
1129
|
+
this._conversationPartialTranscript = '';
|
|
1130
|
+
this._conversationAssistantMessage = null;
|
|
1131
|
+
this._conversationPartialMessage = null;
|
|
1132
|
+
|
|
1133
|
+
// Remove any previous conversation ended notification.
|
|
1134
|
+
this.removeNotification('conversation-ended');
|
|
1135
|
+
navigator.mediaDevices.getUserMedia({
|
|
1136
|
+
audio: {
|
|
1137
|
+
echoCancellation: true,
|
|
1138
|
+
noiseSuppression: true,
|
|
1139
|
+
autoGainControl: true
|
|
1140
|
+
}
|
|
1141
|
+
}).then(function (stream) {
|
|
1142
|
+
var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
|
|
1143
|
+
_this9.mediaRecorder = new MediaRecorder(stream, {
|
|
1144
|
+
mimeType: mimeType,
|
|
1145
|
+
audioBitsPerSecond: 128000
|
|
1146
|
+
});
|
|
1147
|
+
_this9._conversationSubject = new signalR.Subject();
|
|
1148
|
+
_this9._conversationStream = stream;
|
|
1149
|
+
|
|
1150
|
+
// Create an AnalyserNode for volume-based interrupt detection.
|
|
1151
|
+
var AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
1152
|
+
if (AudioCtx) {
|
|
1153
|
+
_this9._conversationAudioCtx = new AudioCtx();
|
|
1154
|
+
_this9._conversationAnalyser = _this9._conversationAudioCtx.createAnalyser();
|
|
1155
|
+
_this9._conversationAnalyser.fftSize = 256;
|
|
1156
|
+
var micSource = _this9._conversationAudioCtx.createMediaStreamSource(stream);
|
|
1157
|
+
micSource.connect(_this9._conversationAnalyser);
|
|
1158
|
+
}
|
|
1159
|
+
var pendingChunk = Promise.resolve();
|
|
1160
|
+
var analyser = _this9._conversationAnalyser;
|
|
1161
|
+
var interruptVolumeThreshold = 30;
|
|
1162
|
+
_this9.mediaRecorder.addEventListener('dataavailable', function (e) {
|
|
1163
|
+
if (e.data && e.data.size > 0) {
|
|
1164
|
+
// During TTS playback, check mic volume to detect
|
|
1165
|
+
// user interruption (speaking above threshold).
|
|
1166
|
+
if (_this9.isPlayingAudio && analyser) {
|
|
1167
|
+
var freqData = new Uint8Array(analyser.frequencyBinCount);
|
|
1168
|
+
analyser.getByteFrequencyData(freqData);
|
|
1169
|
+
var sum = 0;
|
|
1170
|
+
for (var k = 0; k < freqData.length; k++) {
|
|
1171
|
+
sum += freqData[k];
|
|
1172
|
+
}
|
|
1173
|
+
var avg = sum / freqData.length;
|
|
1174
|
+
if (avg >= interruptVolumeThreshold) {
|
|
1175
|
+
// User is speaking — interrupt TTS playback.
|
|
1176
|
+
_this9.stopAudio();
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Always send audio to STT — browser echo cancellation
|
|
1181
|
+
// handles speaker echo; continuous audio avoids gaps
|
|
1182
|
+
// that increase recognition latency.
|
|
1183
|
+
pendingChunk = pendingChunk.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
|
|
1184
|
+
var data, uint8Array, binaryString, base64;
|
|
1185
|
+
return _regenerator().w(function (_context3) {
|
|
1186
|
+
while (1) switch (_context3.n) {
|
|
1187
|
+
case 0:
|
|
1188
|
+
_context3.n = 1;
|
|
1189
|
+
return e.data.arrayBuffer();
|
|
1190
|
+
case 1:
|
|
1191
|
+
data = _context3.v;
|
|
1192
|
+
uint8Array = new Uint8Array(data);
|
|
1193
|
+
binaryString = uint8Array.reduce(function (str, _byte) {
|
|
1194
|
+
return str + String.fromCharCode(_byte);
|
|
1195
|
+
}, '');
|
|
1196
|
+
base64 = btoa(binaryString);
|
|
1197
|
+
try {
|
|
1198
|
+
_this9._conversationSubject.next(base64);
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
// Subject may have been completed already.
|
|
1201
|
+
}
|
|
1202
|
+
case 2:
|
|
1203
|
+
return _context3.a(2);
|
|
1204
|
+
}
|
|
1205
|
+
}, _callee3);
|
|
1206
|
+
})));
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
_this9.mediaRecorder.addEventListener('stop', function () {
|
|
1210
|
+
stream.getTracks().forEach(function (track) {
|
|
1211
|
+
return track.stop();
|
|
1212
|
+
});
|
|
1213
|
+
pendingChunk.then(function () {
|
|
1214
|
+
try {
|
|
1215
|
+
_this9._conversationSubject.complete();
|
|
1216
|
+
} catch (err) {
|
|
1217
|
+
// Already completed.
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
});
|
|
1221
|
+
var itemId = _this9.getItemId();
|
|
1222
|
+
var language = navigator.language || document.documentElement.lang || 'en-US';
|
|
1223
|
+
_this9.connection.send("StartConversation", itemId, _this9._conversationSubject, mimeType, language);
|
|
1224
|
+
_this9.mediaRecorder.start(250);
|
|
1225
|
+
_this9.isRecording = true;
|
|
1226
|
+
})["catch"](function (err) {
|
|
1227
|
+
console.error('Microphone access denied:', err);
|
|
1228
|
+
_this9.isConversationMode = false;
|
|
1229
|
+
_this9.updateConversationButton();
|
|
1230
|
+
});
|
|
1231
|
+
},
|
|
1232
|
+
stopConversationMode: function stopConversationMode() {
|
|
1233
|
+
if (!this.isConversationMode) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
this.isConversationMode = false;
|
|
1237
|
+
this.updateConversationButton();
|
|
1238
|
+
|
|
1239
|
+
// Signal the server to cancel all in-progress STT/TTS streams immediately.
|
|
1240
|
+
if (this.connection) {
|
|
1241
|
+
this.connection.invoke("StopConversation")["catch"](function () {});
|
|
1242
|
+
}
|
|
1243
|
+
if (this.isRecording && this.mediaRecorder) {
|
|
1244
|
+
this.mediaRecorder.stop();
|
|
1245
|
+
this.isRecording = false;
|
|
1246
|
+
}
|
|
1247
|
+
this.stopAudio();
|
|
1248
|
+
this._conversationPartialTranscript = '';
|
|
1249
|
+
this._conversationPartialMessage = null;
|
|
1250
|
+
|
|
1251
|
+
// Clean up the AudioContext used for volume monitoring.
|
|
1252
|
+
if (this._conversationAudioCtx) {
|
|
1253
|
+
this._conversationAudioCtx.close()["catch"](function () {});
|
|
1254
|
+
this._conversationAudioCtx = null;
|
|
1255
|
+
this._conversationAnalyser = null;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Mark any in-flight assistant message as done to stop the spinner.
|
|
1259
|
+
if (this._conversationAssistantMessage) {
|
|
1260
|
+
var msg = this.messages[this._conversationAssistantMessage.index];
|
|
1261
|
+
if (msg) {
|
|
1262
|
+
msg.isStreaming = false;
|
|
1263
|
+
}
|
|
1264
|
+
this._conversationAssistantMessage = null;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// Safety net: clear all lingering streaming indicators.
|
|
1268
|
+
for (var i = 0; i < this.messages.length; i++) {
|
|
1269
|
+
if (this.messages[i].isStreaming) {
|
|
1270
|
+
this.messages[i].isStreaming = false;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Show a "conversation ended" notification system message.
|
|
1275
|
+
this.receiveNotification({
|
|
1276
|
+
type: 'conversation-ended',
|
|
1277
|
+
content: 'Conversation ended.',
|
|
1278
|
+
icon: 'fa-solid fa-circle-check',
|
|
1279
|
+
dismissible: true,
|
|
1280
|
+
autoDismissMs: 5000
|
|
1281
|
+
});
|
|
1282
|
+
},
|
|
1283
|
+
updateConversationButton: function updateConversationButton() {
|
|
1284
|
+
if (!this.conversationButton) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (this.isConversationMode) {
|
|
1288
|
+
this.conversationButton.classList.add('active', 'btn-primary');
|
|
1289
|
+
this.conversationButton.classList.remove('btn-dark', 'btn-outline-dark', 'btn-outline-secondary');
|
|
1290
|
+
this.conversationButton.title = this.conversationButton.getAttribute('data-end-title') || 'End Conversation';
|
|
1291
|
+
var endHtml = this.conversationButton.getAttribute('data-end-html');
|
|
1292
|
+
if (endHtml) {
|
|
1293
|
+
this.conversationButton.replaceChildren(DOMPurify.sanitize(endHtml, {
|
|
1294
|
+
RETURN_DOM_FRAGMENT: true
|
|
1295
|
+
}));
|
|
1296
|
+
}
|
|
1297
|
+
} else {
|
|
1298
|
+
this.conversationButton.classList.remove('active', 'btn-primary');
|
|
1299
|
+
this.conversationButton.classList.remove('btn-dark', 'btn-outline-secondary');
|
|
1300
|
+
this.conversationButton.classList.add('btn-outline-dark');
|
|
1301
|
+
this.conversationButton.blur();
|
|
1302
|
+
this.conversationButton.title = this.conversationButton.getAttribute('data-start-title') || 'Start Conversation';
|
|
1303
|
+
var startHtml = this.conversationButton.getAttribute('data-start-html');
|
|
1304
|
+
if (startHtml) {
|
|
1305
|
+
this.conversationButton.replaceChildren(DOMPurify.sanitize(startHtml, {
|
|
1306
|
+
RETURN_DOM_FRAGMENT: true
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
conversationModeSendPrompt: function conversationModeSendPrompt() {
|
|
1312
|
+
// Legacy: only used by AudioInput mode's ReceiveTranscript.
|
|
1313
|
+
},
|
|
1314
|
+
conversationModeOnAudioEnded: function conversationModeOnAudioEnded() {
|
|
1315
|
+
// Legacy: in conversation mode, continuation is handled
|
|
1316
|
+
// by the persistent stream.
|
|
1317
|
+
},
|
|
1318
|
+
showTypingIndicator: function showTypingIndicator() {
|
|
1319
|
+
this.addMessage({
|
|
1320
|
+
role: 'indicator',
|
|
1321
|
+
htmlContent: config.indicatorTemplate
|
|
1322
|
+
});
|
|
1323
|
+
},
|
|
1324
|
+
hideTypingIndicator: function hideTypingIndicator() {
|
|
1325
|
+
var originalLength = this.messages.length;
|
|
1326
|
+
this.messages = this.messages.filter(function (msg) {
|
|
1327
|
+
return msg.role !== 'indicator';
|
|
1328
|
+
});
|
|
1329
|
+
var removedCount = originalLength - this.messages.length;
|
|
1330
|
+
return removedCount;
|
|
1331
|
+
},
|
|
1332
|
+
scrollToBottom: function scrollToBottom() {
|
|
1333
|
+
var _this0 = this;
|
|
1334
|
+
if (!this.autoScroll) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
setTimeout(function () {
|
|
1338
|
+
_this0.chatContainer.scrollTop = _this0.chatContainer.scrollHeight - _this0.chatContainer.clientHeight;
|
|
1339
|
+
}, 50);
|
|
1340
|
+
},
|
|
1341
|
+
handleUserInput: function handleUserInput(event) {
|
|
1342
|
+
this.prompt = event.target.value;
|
|
1343
|
+
},
|
|
1344
|
+
getItemId: function getItemId() {
|
|
1345
|
+
return this.inputElement.getAttribute('data-interaction-id') || this.inputElement.getAttribute('data-item-id');
|
|
1346
|
+
},
|
|
1347
|
+
getSaveIndicatorElement: function getSaveIndicatorElement() {
|
|
1348
|
+
if (config.saveIndicatorElementSelector) {
|
|
1349
|
+
return document.querySelector(config.saveIndicatorElementSelector);
|
|
1350
|
+
}
|
|
1351
|
+
return document.querySelector('[data-chat-interaction-save-indicator]');
|
|
1352
|
+
},
|
|
1353
|
+
showSaveIndicator: function showSaveIndicator(text, className) {
|
|
1354
|
+
var _this1 = this;
|
|
1355
|
+
var indicator = this.getSaveIndicatorElement();
|
|
1356
|
+
if (!indicator) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
indicator.textContent = text || '';
|
|
1360
|
+
indicator.className = 'settings-save-indicator ' + (className || 'text-muted');
|
|
1361
|
+
if (this.saveIndicatorTimeout) {
|
|
1362
|
+
clearTimeout(this.saveIndicatorTimeout);
|
|
1363
|
+
this.saveIndicatorTimeout = null;
|
|
1364
|
+
}
|
|
1365
|
+
if (!text) {
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
this.saveIndicatorTimeout = setTimeout(function () {
|
|
1369
|
+
indicator.textContent = '';
|
|
1370
|
+
_this1.saveIndicatorTimeout = null;
|
|
1371
|
+
}, 3000);
|
|
1372
|
+
},
|
|
1373
|
+
clearPendingSettingsSave: function clearPendingSettingsSave() {
|
|
1374
|
+
if (this.saveSettingsTimeout) {
|
|
1375
|
+
clearTimeout(this.saveSettingsTimeout);
|
|
1376
|
+
this.saveSettingsTimeout = null;
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
getFieldLabel: function getFieldLabel(input) {
|
|
1380
|
+
var _input$closest;
|
|
1381
|
+
var label = (_input$closest = input.closest('.mb-3, .col-6, .col, .form-group, .form-floating')) === null || _input$closest === void 0 ? void 0 : _input$closest.querySelector('label');
|
|
1382
|
+
if (!label) {
|
|
1383
|
+
return input.dataset.setting || 'This field';
|
|
1384
|
+
}
|
|
1385
|
+
return (label.textContent || '').replace(/\s+/g, ' ').trim() || input.dataset.setting || 'This field';
|
|
1386
|
+
},
|
|
1387
|
+
getValidationFeedbackElement: function getValidationFeedbackElement(input) {
|
|
1388
|
+
var feedback = input.nextElementSibling;
|
|
1389
|
+
if (feedback && feedback.classList.contains('invalid-feedback')) {
|
|
1390
|
+
return feedback;
|
|
1391
|
+
}
|
|
1392
|
+
feedback = document.createElement('div');
|
|
1393
|
+
feedback.className = 'invalid-feedback';
|
|
1394
|
+
input.insertAdjacentElement('afterend', feedback);
|
|
1395
|
+
return feedback;
|
|
1396
|
+
},
|
|
1397
|
+
clearSettingValidationError: function clearSettingValidationError(input) {
|
|
1398
|
+
input.classList.remove('is-invalid');
|
|
1399
|
+
input.removeAttribute('aria-invalid');
|
|
1400
|
+
var feedback = input.nextElementSibling;
|
|
1401
|
+
if (feedback && feedback.classList.contains('invalid-feedback')) {
|
|
1402
|
+
feedback.textContent = '';
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
setSettingValidationError: function setSettingValidationError(input, message) {
|
|
1406
|
+
var feedback = this.getValidationFeedbackElement(input);
|
|
1407
|
+
input.classList.add('is-invalid');
|
|
1408
|
+
input.setAttribute('aria-invalid', 'true');
|
|
1409
|
+
feedback.textContent = message;
|
|
1410
|
+
},
|
|
1411
|
+
getSettingValidationMessage: function getSettingValidationMessage(input) {
|
|
1412
|
+
if (!input || input.disabled || input.type !== 'number') {
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
var value = (input.value || '').trim();
|
|
1416
|
+
if (!value) {
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
var number = Number(value);
|
|
1420
|
+
var fieldLabel = this.getFieldLabel(input);
|
|
1421
|
+
if (Number.isNaN(number)) {
|
|
1422
|
+
return "".concat(fieldLabel, " must be a valid number.");
|
|
1423
|
+
}
|
|
1424
|
+
var min = input.getAttribute('min');
|
|
1425
|
+
if (min !== null && number < Number(min)) {
|
|
1426
|
+
return "".concat(fieldLabel, " must be ").concat(min, " or greater.");
|
|
1427
|
+
}
|
|
1428
|
+
var max = input.getAttribute('max');
|
|
1429
|
+
if (max !== null && number > Number(max)) {
|
|
1430
|
+
return "".concat(fieldLabel, " must be ").concat(max, " or less.");
|
|
1431
|
+
}
|
|
1432
|
+
return null;
|
|
1433
|
+
},
|
|
1434
|
+
validateSettingInput: function validateSettingInput(input) {
|
|
1435
|
+
var message = this.getSettingValidationMessage(input);
|
|
1436
|
+
if (!message) {
|
|
1437
|
+
this.clearSettingValidationError(input);
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
this.setSettingValidationError(input, message);
|
|
1441
|
+
return false;
|
|
1442
|
+
},
|
|
1443
|
+
validateSettings: function validateSettings() {
|
|
1444
|
+
var _this10 = this;
|
|
1445
|
+
var isValid = true;
|
|
1446
|
+
this.getSettingInputs().forEach(function (input) {
|
|
1447
|
+
isValid = _this10.validateSettingInput(input) && isValid;
|
|
1448
|
+
});
|
|
1449
|
+
return isValid;
|
|
1450
|
+
},
|
|
1451
|
+
queueSettingsSave: function queueSettingsSave() {
|
|
1452
|
+
if (!this.validateSettings()) {
|
|
1453
|
+
this.clearPendingSettingsSave();
|
|
1454
|
+
this.settingsDirty = false;
|
|
1455
|
+
this.showSaveIndicator('Fix errors', 'text-danger');
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
this.settingsDirty = true;
|
|
1459
|
+
this.showSaveIndicator('Saving...', 'text-warning');
|
|
1460
|
+
this.debouncedSaveSettings();
|
|
1461
|
+
},
|
|
1462
|
+
getSettingInputs: function getSettingInputs() {
|
|
1463
|
+
var explicitInputs = document.querySelectorAll('.setting-input[data-setting]');
|
|
1464
|
+
if (explicitInputs.length > 0) {
|
|
1465
|
+
return explicitInputs;
|
|
1466
|
+
}
|
|
1467
|
+
return document.querySelectorAll('input[name^="ChatInteraction."]:not([name*=".Tools["]):not([name*=".Connections["]), ' + 'select[name^="ChatInteraction."]:not([name*=".Tools["]):not([name*=".Connections["]), ' + 'textarea[name^="ChatInteraction."]:not([name*=".Tools["]):not([name*=".Connections["])');
|
|
1468
|
+
},
|
|
1469
|
+
getSelectedGroupValues: function getSelectedGroupValues(groupName, fallbackSelector) {
|
|
1470
|
+
var explicitSelections = document.querySelectorAll(".capability-checkbox[data-save-group=\"".concat(groupName, "\"]:checked"));
|
|
1471
|
+
if (explicitSelections.length > 0) {
|
|
1472
|
+
var _values = [];
|
|
1473
|
+
explicitSelections.forEach(function (checkbox) {
|
|
1474
|
+
var value = checkbox.getAttribute('data-item-id') || checkbox.value;
|
|
1475
|
+
if (value) {
|
|
1476
|
+
_values.push(value);
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
return _values;
|
|
1480
|
+
}
|
|
1481
|
+
if (!fallbackSelector) {
|
|
1482
|
+
return [];
|
|
1483
|
+
}
|
|
1484
|
+
var values = [];
|
|
1485
|
+
var checkboxes = document.querySelectorAll(fallbackSelector);
|
|
1486
|
+
checkboxes.forEach(function (checkbox) {
|
|
1487
|
+
var baseName = checkbox.name.replace('.IsSelected', '.ItemId');
|
|
1488
|
+
var hiddenInput = document.querySelector("input[type=\"hidden\"][name=\"".concat(baseName, "\"]"));
|
|
1489
|
+
if (hiddenInput && hiddenInput.value) {
|
|
1490
|
+
values.push(hiddenInput.value);
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
return values;
|
|
1494
|
+
},
|
|
1495
|
+
getPromptTemplateSelections: function getPromptTemplateSelections() {
|
|
1496
|
+
var promptTemplates = [];
|
|
1497
|
+
document.querySelectorAll('.prompt-template-card').forEach(function (card) {
|
|
1498
|
+
var templateIdInput = card.querySelector('.prompt-template-id-input');
|
|
1499
|
+
var promptParametersInput = card.querySelector('.prompt-template-parameters-input');
|
|
1500
|
+
var templateId = templateIdInput ? templateIdInput.value : card.getAttribute('data-template-id');
|
|
1501
|
+
if (!templateId) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
promptTemplates.push({
|
|
1505
|
+
templateId: templateId,
|
|
1506
|
+
promptParameters: promptParametersInput ? (promptParametersInput.value || '').trim() : ''
|
|
1507
|
+
});
|
|
1508
|
+
});
|
|
1509
|
+
return promptTemplates;
|
|
1510
|
+
},
|
|
1511
|
+
receiveNotification: function receiveNotification(notification) {
|
|
1512
|
+
if (!notification || !notification.type) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
this.clearNotificationDismiss(notification.type);
|
|
1516
|
+
// Replace existing notification with same type, or add new one.
|
|
1517
|
+
var idx = this.notifications.findIndex(function (n) {
|
|
1518
|
+
return n.type === notification.type;
|
|
1519
|
+
});
|
|
1520
|
+
if (idx >= 0) {
|
|
1521
|
+
this.notifications.splice(idx, 1, notification);
|
|
1522
|
+
} else {
|
|
1523
|
+
this.notifications.push(notification);
|
|
1524
|
+
}
|
|
1525
|
+
this.scheduleNotificationDismiss(notification);
|
|
1526
|
+
this.scrollToBottom();
|
|
1527
|
+
},
|
|
1528
|
+
updateNotification: function updateNotification(notification) {
|
|
1529
|
+
if (!notification || !notification.type) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
this.clearNotificationDismiss(notification.type);
|
|
1533
|
+
var idx = this.notifications.findIndex(function (n) {
|
|
1534
|
+
return n.type === notification.type;
|
|
1535
|
+
});
|
|
1536
|
+
if (idx >= 0) {
|
|
1537
|
+
this.notifications.splice(idx, 1, notification);
|
|
1538
|
+
this.scheduleNotificationDismiss(notification);
|
|
1539
|
+
this.scrollToBottom();
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
scheduleNotificationDismiss: function scheduleNotificationDismiss(notification) {
|
|
1543
|
+
var _this11 = this;
|
|
1544
|
+
if (!notification || !notification.type || !notification.autoDismissMs || notification.autoDismissMs <= 0) {
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
this.notificationDismissTimers[notification.type] = setTimeout(function () {
|
|
1548
|
+
_this11.removeNotification(notification.type);
|
|
1549
|
+
}, notification.autoDismissMs);
|
|
1550
|
+
},
|
|
1551
|
+
clearNotificationDismiss: function clearNotificationDismiss(notificationType) {
|
|
1552
|
+
var timerId = this.notificationDismissTimers[notificationType];
|
|
1553
|
+
if (!timerId) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
clearTimeout(timerId);
|
|
1557
|
+
delete this.notificationDismissTimers[notificationType];
|
|
1558
|
+
},
|
|
1559
|
+
removeNotification: function removeNotification(notificationType) {
|
|
1560
|
+
this.clearNotificationDismiss(notificationType);
|
|
1561
|
+
this.notifications = this.notifications.filter(function (n) {
|
|
1562
|
+
return n.type !== notificationType;
|
|
1563
|
+
});
|
|
1564
|
+
},
|
|
1565
|
+
dismissNotification: function dismissNotification(notificationType) {
|
|
1566
|
+
this.removeNotification(notificationType);
|
|
1567
|
+
},
|
|
1568
|
+
handleNotificationAction: function handleNotificationAction(notificationType, actionName) {
|
|
1569
|
+
if (!this.connection) {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
var itemId = this.getItemId();
|
|
1573
|
+
this.connection.invoke('HandleNotificationAction', itemId, notificationType, actionName)["catch"](function (err) {
|
|
1574
|
+
return console.error('Failed to handle notification action:', err);
|
|
1575
|
+
});
|
|
1576
|
+
},
|
|
1577
|
+
setItemId: function setItemId(itemId) {
|
|
1578
|
+
this.inputElement.setAttribute('data-interaction-id', itemId || '');
|
|
1579
|
+
this.inputElement.setAttribute('data-item-id', itemId || '');
|
|
1580
|
+
},
|
|
1581
|
+
resetInteraction: function resetInteraction() {
|
|
1582
|
+
this.setItemId('');
|
|
1583
|
+
this.isInteractionStarted = false;
|
|
1584
|
+
this.messages = [];
|
|
1585
|
+
this.showPlaceholder();
|
|
1586
|
+
},
|
|
1587
|
+
initializeApp: function initializeApp() {
|
|
1588
|
+
var _this12 = this;
|
|
1589
|
+
this.inputElement = document.querySelector(config.inputElementSelector);
|
|
1590
|
+
this.buttonElement = document.querySelector(config.sendButtonElementSelector);
|
|
1591
|
+
this.chatContainer = document.querySelector(config.chatContainerElementSelector);
|
|
1592
|
+
this.placeholder = document.querySelector(config.placeholderElementSelector);
|
|
1593
|
+
var itemId = this.getItemId();
|
|
1594
|
+
if (itemId) {
|
|
1595
|
+
this.loadInteraction(itemId);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Pause auto-scroll when the user manually scrolls up during streaming.
|
|
1599
|
+
this.chatContainer.addEventListener('scroll', function () {
|
|
1600
|
+
if (!_this12.stream) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
var threshold = 30;
|
|
1604
|
+
var atBottom = _this12.chatContainer.scrollHeight - _this12.chatContainer.clientHeight - _this12.chatContainer.scrollTop <= threshold;
|
|
1605
|
+
_this12.autoScroll = atBottom;
|
|
1606
|
+
});
|
|
1607
|
+
this.inputElement.addEventListener('keyup', function (event) {
|
|
1608
|
+
if (_this12.stream != null) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
1612
|
+
_this12.buttonElement.click();
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
this.inputElement.addEventListener('input', function (e) {
|
|
1616
|
+
_this12.handleUserInput(e);
|
|
1617
|
+
if (e.target.value.trim()) {
|
|
1618
|
+
_this12.buttonElement.removeAttribute('disabled');
|
|
1619
|
+
} else {
|
|
1620
|
+
_this12.buttonElement.setAttribute('disabled', true);
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
this.inputElement.addEventListener('paste', function (e) {
|
|
1624
|
+
// Use setTimeout to allow the paste to complete before checking the value
|
|
1625
|
+
setTimeout(function () {
|
|
1626
|
+
_this12.prompt = _this12.inputElement.value;
|
|
1627
|
+
if (_this12.inputElement.value.trim()) {
|
|
1628
|
+
_this12.buttonElement.removeAttribute('disabled');
|
|
1629
|
+
} else {
|
|
1630
|
+
_this12.buttonElement.setAttribute('disabled', true);
|
|
1631
|
+
}
|
|
1632
|
+
}, 0);
|
|
1633
|
+
});
|
|
1634
|
+
this.buttonElement.addEventListener('click', function () {
|
|
1635
|
+
if (_this12.stream != null) {
|
|
1636
|
+
_this12.stream.dispose();
|
|
1637
|
+
_this12.stream = null;
|
|
1638
|
+
_this12.streamingFinished();
|
|
1639
|
+
_this12.hideTypingIndicator();
|
|
1640
|
+
|
|
1641
|
+
// Clean up: remove empty assistant message or stop streaming animation.
|
|
1642
|
+
if (_this12.messages.length > 0) {
|
|
1643
|
+
var lastMsg = _this12.messages[_this12.messages.length - 1];
|
|
1644
|
+
if (lastMsg.role === 'assistant' && !lastMsg.content) {
|
|
1645
|
+
_this12.messages.pop();
|
|
1646
|
+
} else if (lastMsg.isStreaming) {
|
|
1647
|
+
lastMsg.isStreaming = false;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
_this12.sendMessage();
|
|
1653
|
+
});
|
|
1654
|
+
var chatInteractionItems = document.getElementsByClassName('chat-interaction-history-item');
|
|
1655
|
+
for (var i = 0; i < chatInteractionItems.length; i++) {
|
|
1656
|
+
chatInteractionItems[i].addEventListener('click', function (e) {
|
|
1657
|
+
e.preventDefault();
|
|
1658
|
+
var itemId = e.target.getAttribute('data-interaction-id');
|
|
1659
|
+
if (!itemId) {
|
|
1660
|
+
console.error('An element with the class chat-interaction-history-item with no data-interaction-id set.');
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
_this12.loadInteraction(itemId);
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
for (var _i3 = 0; _i3 < config.messages.length; _i3++) {
|
|
1667
|
+
this.addMessage(config.messages[_i3]);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// Delegate click for code block copy buttons.
|
|
1671
|
+
if (this.chatContainer) {
|
|
1672
|
+
this.chatContainer.addEventListener('click', function (e) {
|
|
1673
|
+
var btn = e.target.closest('.ai-code-copy-btn');
|
|
1674
|
+
if (!btn) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
var block = btn.closest('.ai-code-block') || btn.closest('pre');
|
|
1678
|
+
if (!block) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
var codeEl = block.querySelector('code');
|
|
1682
|
+
if (codeEl) {
|
|
1683
|
+
navigator.clipboard.writeText(codeEl.textContent);
|
|
1684
|
+
var copiedText = config.codeCopiedText || 'Copied!';
|
|
1685
|
+
btn.innerHTML = '<i class="fa-solid fa-check"></i> ' + copiedText;
|
|
1686
|
+
setTimeout(function () {
|
|
1687
|
+
btn.innerHTML = '<i class="fa-regular fa-copy"></i>';
|
|
1688
|
+
}, 2000);
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
document.addEventListener('input', function (event) {
|
|
1693
|
+
if (event.target.matches('.setting-input[data-setting]')) {
|
|
1694
|
+
_this12.validateSettingInput(event.target);
|
|
1695
|
+
_this12.queueSettingsSave();
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
document.addEventListener('change', function (event) {
|
|
1699
|
+
if (event.target.matches('.setting-input[data-setting], .capability-checkbox[data-save-group], .group-toggle, .ci-agent-global-toggle')) {
|
|
1700
|
+
if (event.target.matches('.setting-input[data-setting]')) {
|
|
1701
|
+
_this12.validateSettingInput(event.target);
|
|
1702
|
+
}
|
|
1703
|
+
_this12.queueSettingsSave();
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (event.target.closest('.prompt-template-parameters-input, .prompt-template-id-input')) {
|
|
1707
|
+
_this12.queueSettingsSave();
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
document.addEventListener('click', function (event) {
|
|
1711
|
+
if (!event.target.closest('.prompt-template-add-btn, .remove-prompt-template-btn')) {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
setTimeout(function () {
|
|
1715
|
+
_this12.queueSettingsSave();
|
|
1716
|
+
}, 0);
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
// Add event listener for clear history button
|
|
1720
|
+
var clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
|
1721
|
+
if (clearHistoryBtn) {
|
|
1722
|
+
clearHistoryBtn.addEventListener('click', function () {
|
|
1723
|
+
var itemId = clearHistoryBtn.getAttribute('data-interaction-id');
|
|
1724
|
+
if (itemId) {
|
|
1725
|
+
_this12.clearHistory(itemId);
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// Initialize speech-to-text microphone button.
|
|
1731
|
+
if (this.speechToTextEnabled && config.micButtonElementSelector) {
|
|
1732
|
+
this.micButton = document.querySelector(config.micButtonElementSelector);
|
|
1733
|
+
if (this.micButton) {
|
|
1734
|
+
this.micButton.style.display = '';
|
|
1735
|
+
this.micButton.addEventListener('click', function () {
|
|
1736
|
+
_this12.toggleRecording();
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// Initialize conversation mode button.
|
|
1742
|
+
if (this.conversationModeEnabled && config.conversationButtonElementSelector) {
|
|
1743
|
+
this.conversationButton = document.querySelector(config.conversationButtonElementSelector);
|
|
1744
|
+
if (this.conversationButton) {
|
|
1745
|
+
this.conversationButton.addEventListener('click', function () {
|
|
1746
|
+
_this12.toggleConversationMode();
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
},
|
|
1751
|
+
loadInteraction: function loadInteraction(itemId) {
|
|
1752
|
+
this.connection.invoke("LoadInteraction", itemId)["catch"](function (err) {
|
|
1753
|
+
return console.error(err);
|
|
1754
|
+
});
|
|
1755
|
+
},
|
|
1756
|
+
reloadCurrentInteraction: function reloadCurrentInteraction() {
|
|
1757
|
+
var itemId = this.getItemId();
|
|
1758
|
+
if (itemId) {
|
|
1759
|
+
this.loadInteraction(itemId);
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
clearHistory: function clearHistory(itemId) {
|
|
1763
|
+
var self = this;
|
|
1764
|
+
confirmDialog({
|
|
1765
|
+
title: config.clearHistoryTitle,
|
|
1766
|
+
message: config.clearHistoryMessage,
|
|
1767
|
+
okText: config.clearHistoryOkText,
|
|
1768
|
+
cancelText: config.clearHistoryCancelText,
|
|
1769
|
+
callback: function callback(confirmed) {
|
|
1770
|
+
if (confirmed) {
|
|
1771
|
+
// Cancel any active stream before clearing history.
|
|
1772
|
+
if (self.stream) {
|
|
1773
|
+
self.stream.dispose();
|
|
1774
|
+
self.stream = null;
|
|
1775
|
+
self.hideTypingIndicator();
|
|
1776
|
+
self.streamingFinished();
|
|
1777
|
+
}
|
|
1778
|
+
self.connection.invoke("ClearHistory", itemId)["catch"](function (err) {
|
|
1779
|
+
return console.error('Error clearing history:', err);
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
},
|
|
1785
|
+
debouncedSaveSettings: function debouncedSaveSettings() {
|
|
1786
|
+
var _this13 = this;
|
|
1787
|
+
// Clear any existing timeout to reset the debounce timer
|
|
1788
|
+
this.clearPendingSettingsSave();
|
|
1789
|
+
|
|
1790
|
+
// Don't save while streaming — it will be saved when streaming completes.
|
|
1791
|
+
if (this.stream) {
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// Set a new timeout to save after 850ms of no changes
|
|
1796
|
+
this.saveSettingsTimeout = setTimeout(function () {
|
|
1797
|
+
if (_this13.settingsDirty) {
|
|
1798
|
+
_this13.saveSettings();
|
|
1799
|
+
_this13.settingsDirty = false;
|
|
1800
|
+
}
|
|
1801
|
+
_this13.saveSettingsTimeout = null;
|
|
1802
|
+
}, 850);
|
|
1803
|
+
},
|
|
1804
|
+
getSelectedToolNames: function getSelectedToolNames() {
|
|
1805
|
+
return this.getSelectedGroupValues('toolNames', 'input[type="checkbox"][name$="].IsSelected"][name^="ChatInteraction.Tools["]:checked');
|
|
1806
|
+
},
|
|
1807
|
+
getSelectedMcpConnectionIds: function getSelectedMcpConnectionIds() {
|
|
1808
|
+
return this.getSelectedGroupValues('mcpConnectionIds', 'input[type="checkbox"][name$="].IsSelected"][name^="ChatInteraction.Connections["]:checked');
|
|
1809
|
+
},
|
|
1810
|
+
getSelectedA2AConnectionIds: function getSelectedA2AConnectionIds() {
|
|
1811
|
+
return this.getSelectedGroupValues('a2aConnectionIds');
|
|
1812
|
+
},
|
|
1813
|
+
getSelectedAgentNames: function getSelectedAgentNames() {
|
|
1814
|
+
return this.getSelectedGroupValues('agentNames', 'input[type="checkbox"][name$="].IsSelected"][name^="ChatInteraction.Agents["]:checked');
|
|
1815
|
+
},
|
|
1816
|
+
saveSettings: function saveSettings() {
|
|
1817
|
+
var _this14 = this;
|
|
1818
|
+
var itemId = this.getItemId();
|
|
1819
|
+
if (!itemId) {
|
|
1820
|
+
return Promise.resolve();
|
|
1821
|
+
}
|
|
1822
|
+
if (!this.validateSettings()) {
|
|
1823
|
+
this.showSaveIndicator('Fix errors', 'text-danger');
|
|
1824
|
+
return Promise.resolve();
|
|
1825
|
+
}
|
|
1826
|
+
var settings = {};
|
|
1827
|
+
|
|
1828
|
+
// Collect all form inputs with the "ChatInteraction." prefix generically.
|
|
1829
|
+
// This avoids coupling the JS to specific field names — new fields added by
|
|
1830
|
+
// any module are automatically included.
|
|
1831
|
+
var inputs = this.getSettingInputs();
|
|
1832
|
+
inputs.forEach(function (input) {
|
|
1833
|
+
var key = input.dataset.setting || (input.name || '').replace('ChatInteraction.', '').replace(/^[A-Z]/, function (match) {
|
|
1834
|
+
return match.toLowerCase();
|
|
1835
|
+
});
|
|
1836
|
+
if (!key) {
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (input.type === 'checkbox') {
|
|
1840
|
+
settings[key] = input.checked;
|
|
1841
|
+
} else if (input.type === 'number') {
|
|
1842
|
+
settings[key] = input.value ? parseFloat(input.value) : null;
|
|
1843
|
+
} else {
|
|
1844
|
+
settings[key] = input.value || null;
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
// Add tool, MCP connection, and agent collections (special handling).
|
|
1849
|
+
settings.toolNames = this.getSelectedToolNames();
|
|
1850
|
+
settings.mcpConnectionIds = this.getSelectedMcpConnectionIds();
|
|
1851
|
+
settings.a2aConnectionIds = this.getSelectedA2AConnectionIds();
|
|
1852
|
+
settings.agentNames = this.getSelectedAgentNames();
|
|
1853
|
+
var promptTemplates = this.getPromptTemplateSelections();
|
|
1854
|
+
if (promptTemplates.length > 0) {
|
|
1855
|
+
settings.promptTemplates = promptTemplates;
|
|
1856
|
+
settings.promptTemplateIds = promptTemplates.map(function (template) {
|
|
1857
|
+
return template.templateId;
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
return this.connection.invoke("SaveSettings", itemId, settings)["catch"](function (err) {
|
|
1861
|
+
console.error('Error saving settings:', err);
|
|
1862
|
+
_this14.showSaveIndicator('Save failed', 'text-danger');
|
|
1863
|
+
});
|
|
1864
|
+
},
|
|
1865
|
+
flushPendingSave: function flushPendingSave() {
|
|
1866
|
+
if (this.saveSettingsTimeout) {
|
|
1867
|
+
clearTimeout(this.saveSettingsTimeout);
|
|
1868
|
+
this.saveSettingsTimeout = null;
|
|
1869
|
+
}
|
|
1870
|
+
if (this.settingsDirty) {
|
|
1871
|
+
this.settingsDirty = false;
|
|
1872
|
+
return this.saveSettings();
|
|
1873
|
+
}
|
|
1874
|
+
return Promise.resolve();
|
|
1875
|
+
},
|
|
1876
|
+
initializeInteraction: function initializeInteraction(itemId, force) {
|
|
1877
|
+
if (this.isInteractionStarted && !force) {
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
this.fireEvent(new CustomEvent("initializingChatInteraction", {
|
|
1881
|
+
detail: {
|
|
1882
|
+
itemId: itemId
|
|
1883
|
+
}
|
|
1884
|
+
}));
|
|
1885
|
+
this.setItemId(itemId);
|
|
1886
|
+
this.isInteractionStarted = true;
|
|
1887
|
+
},
|
|
1888
|
+
copyResponse: function copyResponse(message) {
|
|
1889
|
+
navigator.clipboard.writeText(message);
|
|
1890
|
+
},
|
|
1891
|
+
startRecording: function startRecording() {
|
|
1892
|
+
var _this15 = this;
|
|
1893
|
+
if (this.isRecording || !this.connection) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
navigator.mediaDevices.getUserMedia({
|
|
1897
|
+
audio: {
|
|
1898
|
+
echoCancellation: true,
|
|
1899
|
+
noiseSuppression: true,
|
|
1900
|
+
autoGainControl: true
|
|
1901
|
+
}
|
|
1902
|
+
}).then(function (stream) {
|
|
1903
|
+
var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
|
|
1904
|
+
_this15.mediaRecorder = new MediaRecorder(stream, {
|
|
1905
|
+
mimeType: mimeType,
|
|
1906
|
+
audioBitsPerSecond: 128000
|
|
1907
|
+
});
|
|
1908
|
+
_this15.preRecordingPrompt = _this15.prompt;
|
|
1909
|
+
_this15._audioInputSent = false;
|
|
1910
|
+
var subject = new signalR.Subject();
|
|
1911
|
+
var itemId = _this15.getItemId();
|
|
1912
|
+
var pendingChunk = Promise.resolve();
|
|
1913
|
+
_this15.mediaRecorder.addEventListener('dataavailable', function (e) {
|
|
1914
|
+
if (e.data && e.data.size > 0) {
|
|
1915
|
+
pendingChunk = pendingChunk.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
|
|
1916
|
+
var data, uint8Array, binaryString, base64;
|
|
1917
|
+
return _regenerator().w(function (_context4) {
|
|
1918
|
+
while (1) switch (_context4.n) {
|
|
1919
|
+
case 0:
|
|
1920
|
+
_context4.n = 1;
|
|
1921
|
+
return e.data.arrayBuffer();
|
|
1922
|
+
case 1:
|
|
1923
|
+
data = _context4.v;
|
|
1924
|
+
uint8Array = new Uint8Array(data);
|
|
1925
|
+
binaryString = uint8Array.reduce(function (str, _byte2) {
|
|
1926
|
+
return str + String.fromCharCode(_byte2);
|
|
1927
|
+
}, '');
|
|
1928
|
+
base64 = btoa(binaryString);
|
|
1929
|
+
subject.next(base64);
|
|
1930
|
+
case 2:
|
|
1931
|
+
return _context4.a(2);
|
|
1932
|
+
}
|
|
1933
|
+
}, _callee4);
|
|
1934
|
+
})));
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
_this15.mediaRecorder.addEventListener('stop', function () {
|
|
1938
|
+
stream.getTracks().forEach(function (track) {
|
|
1939
|
+
return track.stop();
|
|
1940
|
+
});
|
|
1941
|
+
pendingChunk.then(function () {
|
|
1942
|
+
return subject.complete();
|
|
1943
|
+
});
|
|
1944
|
+
});
|
|
1945
|
+
var language = navigator.language || document.documentElement.lang || 'en-US';
|
|
1946
|
+
_this15.connection.send("SendAudioStream", itemId, subject, mimeType, language);
|
|
1947
|
+
_this15.mediaRecorder.start(250);
|
|
1948
|
+
_this15.isRecording = true;
|
|
1949
|
+
_this15.updateMicButton();
|
|
1950
|
+
})["catch"](function (err) {
|
|
1951
|
+
console.error('Microphone access denied:', err);
|
|
1952
|
+
});
|
|
1953
|
+
},
|
|
1954
|
+
stopRecording: function stopRecording() {
|
|
1955
|
+
if (!this.isRecording || !this.mediaRecorder) {
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
this.mediaRecorder.stop();
|
|
1959
|
+
this.isRecording = false;
|
|
1960
|
+
this.updateMicButton();
|
|
1961
|
+
},
|
|
1962
|
+
toggleRecording: function toggleRecording() {
|
|
1963
|
+
if (this.isRecording) {
|
|
1964
|
+
this.stopRecording();
|
|
1965
|
+
} else {
|
|
1966
|
+
this.startRecording();
|
|
1967
|
+
}
|
|
1968
|
+
},
|
|
1969
|
+
updateMicButton: function updateMicButton() {
|
|
1970
|
+
if (!this.micButton) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
if (this.isRecording) {
|
|
1974
|
+
this.micButton.classList.add('stt-recording');
|
|
1975
|
+
this.micButton.innerHTML = '<i class="fa-solid fa-stop"></i>';
|
|
1976
|
+
} else {
|
|
1977
|
+
this.micButton.classList.remove('stt-recording');
|
|
1978
|
+
this.micButton.innerHTML = '<i class="fa-solid fa-microphone"></i>';
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
},
|
|
1982
|
+
watch: {
|
|
1983
|
+
isPlayingAudio: function isPlayingAudio() {
|
|
1984
|
+
// Reserved for future use — volume-based interrupt detection
|
|
1985
|
+
// no longer mutes tracks; browser echo cancellation handles echo.
|
|
1986
|
+
},
|
|
1987
|
+
isConversationMode: function isConversationMode(active) {
|
|
1988
|
+
if (this.micButton) {
|
|
1989
|
+
this.micButton.style.display = active ? 'none' : this.speechToTextEnabled ? '' : 'none';
|
|
1990
|
+
}
|
|
1991
|
+
if (this.buttonElement) {
|
|
1992
|
+
this.buttonElement.style.display = active ? 'none' : '';
|
|
1993
|
+
}
|
|
1994
|
+
if (this.inputElement) {
|
|
1995
|
+
this.inputElement.disabled = active;
|
|
1996
|
+
if (active) {
|
|
1997
|
+
this.inputElement.placeholder = '';
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
},
|
|
2002
|
+
mounted: function mounted() {
|
|
2003
|
+
var _this16 = this;
|
|
2004
|
+
_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
|
|
2005
|
+
return _regenerator().w(function (_context5) {
|
|
2006
|
+
while (1) switch (_context5.n) {
|
|
2007
|
+
case 0:
|
|
2008
|
+
_context5.n = 1;
|
|
2009
|
+
return _this16.startConnection();
|
|
2010
|
+
case 1:
|
|
2011
|
+
_this16.initializeApp();
|
|
2012
|
+
case 2:
|
|
2013
|
+
return _context5.a(2);
|
|
2014
|
+
}
|
|
2015
|
+
}, _callee5);
|
|
2016
|
+
}))();
|
|
2017
|
+
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
2018
|
+
},
|
|
2019
|
+
beforeUnmount: function beforeUnmount() {
|
|
2020
|
+
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
2021
|
+
if (this.stream) {
|
|
2022
|
+
this.stream.dispose();
|
|
2023
|
+
this.stream = null;
|
|
2024
|
+
}
|
|
2025
|
+
if (this.connection) {
|
|
2026
|
+
this.connection.stop();
|
|
2027
|
+
}
|
|
2028
|
+
},
|
|
2029
|
+
template: config.messageTemplate
|
|
2030
|
+
}).mount(config.appElementSelector);
|
|
2031
|
+
return app;
|
|
2032
|
+
};
|
|
2033
|
+
return {
|
|
2034
|
+
initialize: initialize
|
|
2035
|
+
};
|
|
2036
|
+
}();
|
|
2037
|
+
|
|
2038
|
+
// Download chart as image via event delegation (DOMPurify strips inline onclick).
|
|
2039
|
+
document.addEventListener('click', function (e) {
|
|
2040
|
+
var btn = e.target.closest('.download-chart-btn');
|
|
2041
|
+
if (!btn) {
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
var chartId = btn.getAttribute('data-chart-id');
|
|
2045
|
+
var canvas = chartId ? document.getElementById(chartId) : null;
|
|
2046
|
+
if (!canvas) {
|
|
2047
|
+
console.error('Chart canvas not found:', chartId);
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
var link = document.createElement('a');
|
|
2051
|
+
link.download = 'chart-' + chartId + '.png';
|
|
2052
|
+
link.href = canvas.toDataURL('image/png');
|
|
2053
|
+
link.click();
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
// Intercept download clicks for data-URI images and convert to blob downloads.
|
|
2057
|
+
document.addEventListener('click', function (e) {
|
|
2058
|
+
var link = e.target.closest('.ai-download-image');
|
|
2059
|
+
if (!link) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
var container = link.closest('.generated-image-container');
|
|
2063
|
+
var img = container === null || container === void 0 ? void 0 : container.querySelector('img');
|
|
2064
|
+
if (!img) {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
var src = img.src;
|
|
2068
|
+
if (!src || !src.startsWith('data:')) {
|
|
2069
|
+
return; // Normal URL – let the default <a> behaviour handle it.
|
|
2070
|
+
}
|
|
2071
|
+
e.preventDefault();
|
|
2072
|
+
fetch(src).then(function (res) {
|
|
2073
|
+
return res.blob();
|
|
2074
|
+
}).then(function (blob) {
|
|
2075
|
+
var url = URL.createObjectURL(blob);
|
|
2076
|
+
var a = document.createElement('a');
|
|
2077
|
+
a.href = url;
|
|
2078
|
+
a.download = link.getAttribute('download') || 'generated-image.png';
|
|
2079
|
+
document.body.appendChild(a);
|
|
2080
|
+
a.click();
|
|
2081
|
+
document.body.removeChild(a);
|
|
2082
|
+
setTimeout(function () {
|
|
2083
|
+
URL.revokeObjectURL(url);
|
|
2084
|
+
}, 100);
|
|
2085
|
+
})["catch"](function (err) {
|
|
2086
|
+
console.error('Failed to download image:', err);
|
|
2087
|
+
});
|
|
2088
|
+
});
|