@crestapps/ai-chat-ui 2.0.0-preview.191 → 2.0.0-preview.197
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat.css +65 -0
- package/dist/ai-chat.css.map +1 -1
- package/dist/ai-chat.js +387 -147
- package/dist/ai-chat.js.map +1 -1
- package/dist/ai-chat.min.css +1 -1
- package/dist/ai-chat.min.js +1 -1
- package/dist/ai-chat.min.js.map +1 -1
- package/dist/chat-interaction.js +337 -134
- package/dist/chat-interaction.js.map +1 -1
- package/dist/chat-interaction.min.js +1 -1
- package/dist/chat-interaction.min.js.map +1 -1
- package/dist/chat-markers.js +439 -0
- package/dist/chat-markers.js.map +1 -0
- package/dist/chat-markers.min.js +2 -0
- package/dist/chat-markers.min.js.map +1 -0
- package/dist/chat-widget.css +50 -0
- package/dist/chat-widget.css.map +1 -1
- package/dist/chat-widget.min.css +1 -1
- package/dist/realtime-audio.js +16 -2
- package/dist/realtime-audio.js.map +1 -1
- package/dist/realtime-audio.min.js +1 -1
- package/dist/realtime-audio.min.js.map +1 -1
- package/package.json +1 -1
package/dist/ai-chat.js
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
var _excluded = ["placeholder"];
|
|
7
7
|
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 }; })(); }
|
|
8
8
|
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); }
|
|
9
|
-
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); }
|
|
10
|
-
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); }); }; }
|
|
11
9
|
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
12
10
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
13
11
|
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
@@ -15,6 +13,8 @@ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(
|
|
|
15
13
|
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
|
|
16
14
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
17
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 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); }
|
|
17
|
+
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); }); }; }
|
|
18
18
|
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
19
19
|
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."); }
|
|
20
20
|
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; } }
|
|
@@ -22,7 +22,25 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
22
22
|
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; } }
|
|
23
23
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
24
24
|
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); }
|
|
25
|
-
|
|
25
|
+
/*
|
|
26
|
+
* Figure markers: the short labels the model writes, turned into pictures by the host.
|
|
27
|
+
*
|
|
28
|
+
* Retrieval hands the model a label -- [fig:1] -- and keeps the picture's real link in the message's reference
|
|
29
|
+
* map, the same way a text citation keeps its source behind "[doc:1]". A model asked to reproduce a long opaque
|
|
30
|
+
* identifier does not reproduce it: it copies the shape and substitutes ordinals, so links to figures that were
|
|
31
|
+
* never in the results came back as 404s. A label short enough to be written exactly, with the link put back
|
|
32
|
+
* here where the model cannot reach it, is what stops that.
|
|
33
|
+
*
|
|
34
|
+
* It sits on window rather than inside the manager below so the host can reach it, and so its rules can be
|
|
35
|
+
* exercised in a bare sandbox with no browser and no DOM -- see tests/realtime-client/figure-markers.test.js --
|
|
36
|
+
* exactly as window.CoreAIRealtime exposes the microphone gate's rules.
|
|
37
|
+
*
|
|
38
|
+
* The same block sits at the top of chat-interaction.js: the two chat clients are separate bundles that share no
|
|
39
|
+
* module of their own, and both render the same markdown. Change one, change the other.
|
|
40
|
+
*/
|
|
41
|
+
// CoreAIChatMarkers lives in chat-markers.js, which every chat surface loads before this file.
|
|
42
|
+
|
|
43
|
+
window.coreAIChatManager = function (_window$CoreAIChatMar, _window$CoreAIChatMar2, _window$CoreAIChatMar3, _window$CoreAIChatMar4, _window$CoreAIChatMar5, _window$CoreAIChatMar6, _window$CoreAIChatMar7, _window$CoreAIChatMar8, _window$CoreAIChatMar9, _window$CoreAIChatMar0) {
|
|
26
44
|
// Defaults (can be overridden by instanceConfig)
|
|
27
45
|
var defaultConfig = {
|
|
28
46
|
// UI defaults for generated media
|
|
@@ -127,7 +145,7 @@ window.coreAIChatManager = function () {
|
|
|
127
145
|
return Math.min(Math.max(value, min), max);
|
|
128
146
|
}
|
|
129
147
|
function normalizeReference(reference) {
|
|
130
|
-
var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$title, _ref4, _normalized$link, _ref5, _normalized$reference, _normalized$isGenerat;
|
|
148
|
+
var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$title, _ref4, _normalized$link, _ref5, _normalized$reference, _normalized$isGenerat, _normalized$isImage;
|
|
131
149
|
if (!reference || _typeof(reference) !== 'object') {
|
|
132
150
|
return null;
|
|
133
151
|
}
|
|
@@ -138,6 +156,7 @@ window.coreAIChatManager = function () {
|
|
|
138
156
|
normalized.link = sanitizeUrl((_ref4 = (_normalized$link = normalized.link) !== null && _normalized$link !== void 0 ? _normalized$link : normalized.Link) !== null && _ref4 !== void 0 ? _ref4 : null);
|
|
139
157
|
normalized.referenceType = (_ref5 = (_normalized$reference = normalized.referenceType) !== null && _normalized$reference !== void 0 ? _normalized$reference : normalized.ReferenceType) !== null && _ref5 !== void 0 ? _ref5 : null;
|
|
140
158
|
normalized.isGenerated = ((_normalized$isGenerat = normalized.isGenerated) !== null && _normalized$isGenerat !== void 0 ? _normalized$isGenerat : normalized.IsGenerated) === true;
|
|
159
|
+
normalized.isImage = ((_normalized$isImage = normalized.isImage) !== null && _normalized$isImage !== void 0 ? _normalized$isImage : normalized.IsImage) === true;
|
|
141
160
|
return normalized;
|
|
142
161
|
}
|
|
143
162
|
function isDownloadCitationReference(reference) {
|
|
@@ -166,11 +185,95 @@ window.coreAIChatManager = function () {
|
|
|
166
185
|
}
|
|
167
186
|
return normalized;
|
|
168
187
|
}
|
|
188
|
+
|
|
189
|
+
// Installed at the top of this file so it can be unit tested without a browser; see the comment there.
|
|
190
|
+
|
|
191
|
+
/*
|
|
192
|
+
* Reads a JSON response body, or explains why it is not JSON.
|
|
193
|
+
*
|
|
194
|
+
* fetch follows a redirect silently and reports the page it landed on as a success, so an endpoint that
|
|
195
|
+
* answers "forbidden" with a redirect to a sign-in page arrives here as response.ok with HTML in it.
|
|
196
|
+
* Calling response.json() on that throws "unexpected character at line 1 column 1", which is what the
|
|
197
|
+
* reader was shown instead of being told they are not allowed to upload.
|
|
198
|
+
*
|
|
199
|
+
* Returns { ok, data, message }. A caller shows message when ok is false; it is never a parser error.
|
|
200
|
+
*/
|
|
201
|
+
function readJsonResponse(_x, _x2) {
|
|
202
|
+
return _readJsonResponse.apply(this, arguments);
|
|
203
|
+
}
|
|
204
|
+
function _readJsonResponse() {
|
|
205
|
+
_readJsonResponse = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0(response, fallbackMessage) {
|
|
206
|
+
var contentType, message, _t6, _t7;
|
|
207
|
+
return _regenerator().w(function (_context0) {
|
|
208
|
+
while (1) switch (_context0.p = _context0.n) {
|
|
209
|
+
case 0:
|
|
210
|
+
contentType = response.headers && response.headers.get('content-type') || '';
|
|
211
|
+
if (!(contentType.toLowerCase().indexOf('json') < 0)) {
|
|
212
|
+
_context0.n = 1;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
// A redirect that landed somewhere else is the common case and worth naming, because the endpoint
|
|
216
|
+
// did not refuse -- something in front of it did.
|
|
217
|
+
message = response.redirected ? 'You may not be signed in, or you do not have permission for this. Please reload and try again.' : fallbackMessage;
|
|
218
|
+
return _context0.a(2, {
|
|
219
|
+
ok: false,
|
|
220
|
+
data: null,
|
|
221
|
+
message: message
|
|
222
|
+
});
|
|
223
|
+
case 1:
|
|
224
|
+
_context0.p = 1;
|
|
225
|
+
_context0.n = 2;
|
|
226
|
+
return response.json();
|
|
227
|
+
case 2:
|
|
228
|
+
_t6 = _context0.v;
|
|
229
|
+
return _context0.a(2, {
|
|
230
|
+
ok: true,
|
|
231
|
+
data: _t6,
|
|
232
|
+
message: null
|
|
233
|
+
});
|
|
234
|
+
case 3:
|
|
235
|
+
_context0.p = 3;
|
|
236
|
+
_t7 = _context0.v;
|
|
237
|
+
return _context0.a(2, {
|
|
238
|
+
ok: false,
|
|
239
|
+
data: null,
|
|
240
|
+
message: fallbackMessage
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}, _callee0, null, [[1, 3]]);
|
|
244
|
+
}));
|
|
245
|
+
return _readJsonResponse.apply(this, arguments);
|
|
246
|
+
}
|
|
247
|
+
var expandImageMarkers = window.CoreAIChatMarkers.expandImageMarkers;
|
|
169
248
|
function getCitationLabel(reference, key) {
|
|
170
249
|
return reference.title || reference.text || key;
|
|
171
250
|
}
|
|
251
|
+
|
|
252
|
+
// Which references are one citation, and how a repeated marker reads, are statements about strings and
|
|
253
|
+
// live in chat-markers.js so every chat surface numbers them the same way. Without the module each
|
|
254
|
+
// reference keeps an identity of its own and nothing merges, which is the numbering this script had
|
|
255
|
+
// before it.
|
|
256
|
+
var unmergedCitationCount = 0;
|
|
257
|
+
var citationIdentity = (_window$CoreAIChatMar = (_window$CoreAIChatMar2 = window.CoreAIChatMarkers) === null || _window$CoreAIChatMar2 === void 0 ? void 0 : _window$CoreAIChatMar2.citationIdentity) !== null && _window$CoreAIChatMar !== void 0 ? _window$CoreAIChatMar : function () {
|
|
258
|
+
return "unmerged-".concat(++unmergedCitationCount);
|
|
259
|
+
};
|
|
260
|
+
var splitCombinedCitations = (_window$CoreAIChatMar3 = (_window$CoreAIChatMar4 = window.CoreAIChatMarkers) === null || _window$CoreAIChatMar4 === void 0 ? void 0 : _window$CoreAIChatMar4.splitCombinedCitations) !== null && _window$CoreAIChatMar3 !== void 0 ? _window$CoreAIChatMar3 : function (content) {
|
|
261
|
+
return content;
|
|
262
|
+
};
|
|
263
|
+
var collapseRepeatedCitations = (_window$CoreAIChatMar5 = (_window$CoreAIChatMar6 = window.CoreAIChatMarkers) === null || _window$CoreAIChatMar6 === void 0 ? void 0 : _window$CoreAIChatMar6.collapseRepeatedCitations) !== null && _window$CoreAIChatMar5 !== void 0 ? _window$CoreAIChatMar5 : function (html) {
|
|
264
|
+
return html;
|
|
265
|
+
};
|
|
266
|
+
var separateAdjacentCitations = (_window$CoreAIChatMar7 = (_window$CoreAIChatMar8 = window.CoreAIChatMarkers) === null || _window$CoreAIChatMar8 === void 0 ? void 0 : _window$CoreAIChatMar8.separateAdjacentCitations) !== null && _window$CoreAIChatMar7 !== void 0 ? _window$CoreAIChatMar7 : function (html) {
|
|
267
|
+
return html;
|
|
268
|
+
};
|
|
269
|
+
var citationMarkerHtml = (_window$CoreAIChatMar9 = (_window$CoreAIChatMar0 = window.CoreAIChatMarkers) === null || _window$CoreAIChatMar0 === void 0 ? void 0 : _window$CoreAIChatMar0.citationMarkerHtml) !== null && _window$CoreAIChatMar9 !== void 0 ? _window$CoreAIChatMar9 : function (displayIndex) {
|
|
270
|
+
return "<sup>".concat(displayIndex, "</sup>");
|
|
271
|
+
};
|
|
172
272
|
function buildCitationDisplay(content, references) {
|
|
173
|
-
|
|
273
|
+
// Before anything looks for a reference key, the combined form the model tends to write is split into
|
|
274
|
+
// the keys it means. A key that is not found is not replaced, and an unreplaced key reaches the reader
|
|
275
|
+
// as a raw marker mid-sentence.
|
|
276
|
+
var processedContent = splitCombinedCitations((content || '').trim());
|
|
174
277
|
var messageReferences = normalizeReferences(references);
|
|
175
278
|
var referenceEntries = Object.entries(messageReferences);
|
|
176
279
|
if (!referenceEntries.length) {
|
|
@@ -211,6 +314,8 @@ window.coreAIChatManager = function () {
|
|
|
211
314
|
return a.index - b.index;
|
|
212
315
|
});
|
|
213
316
|
var citations = [];
|
|
317
|
+
var citationsByIdentity = new Map();
|
|
318
|
+
var placeholders = [];
|
|
214
319
|
var displayIndex = 1;
|
|
215
320
|
var _iterator = _createForOfIteratorHelper(citedRefs),
|
|
216
321
|
_step;
|
|
@@ -219,28 +324,48 @@ window.coreAIChatManager = function () {
|
|
|
219
324
|
var _step$value = _slicedToArray(_step.value, 2),
|
|
220
325
|
key = _step$value[0],
|
|
221
326
|
value = _step$value[1];
|
|
222
|
-
var
|
|
327
|
+
var label = getCitationLabel(value, key);
|
|
328
|
+
var link = value.link || null;
|
|
329
|
+
var identity = citationIdentity(label, link);
|
|
330
|
+
var citation = citationsByIdentity.get(identity);
|
|
331
|
+
|
|
332
|
+
// Every reference still has its key replaced, so the text never keeps a raw key; what the merge
|
|
333
|
+
// changes is only how many numbers the reader is given for them.
|
|
334
|
+
if (!citation) {
|
|
335
|
+
citation = {
|
|
336
|
+
referenceKey: key,
|
|
337
|
+
referenceKeys: [],
|
|
338
|
+
displayIndex: displayIndex++,
|
|
339
|
+
label: label,
|
|
340
|
+
link: link,
|
|
341
|
+
isDownload: isDownloadCitationReference(value)
|
|
342
|
+
};
|
|
343
|
+
citationsByIdentity.set(identity, citation);
|
|
344
|
+
citations.push(citation);
|
|
345
|
+
}
|
|
346
|
+
citation.referenceKeys.push(key);
|
|
347
|
+
var placeholder = "__CITE_".concat(citation.displayIndex, "_").concat(value.index || citation.displayIndex, "__");
|
|
223
348
|
processedContent = processedContent.replaceAll(key, placeholder);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
displayIndex: displayIndex,
|
|
227
|
-
label:
|
|
228
|
-
link: value.link || null,
|
|
229
|
-
isDownload: isDownloadCitationReference(value),
|
|
230
|
-
placeholder: placeholder
|
|
349
|
+
placeholders.push({
|
|
350
|
+
placeholder: placeholder,
|
|
351
|
+
displayIndex: citation.displayIndex,
|
|
352
|
+
label: citation.label
|
|
231
353
|
});
|
|
232
|
-
displayIndex++;
|
|
233
354
|
}
|
|
234
355
|
} catch (err) {
|
|
235
356
|
_iterator.e(err);
|
|
236
357
|
} finally {
|
|
237
358
|
_iterator.f();
|
|
238
359
|
}
|
|
239
|
-
for (var _i2 = 0,
|
|
240
|
-
var
|
|
241
|
-
processedContent = processedContent.replaceAll(
|
|
360
|
+
for (var _i2 = 0, _placeholders = placeholders; _i2 < _placeholders.length; _i2++) {
|
|
361
|
+
var entry = _placeholders[_i2];
|
|
362
|
+
processedContent = processedContent.replaceAll(entry.placeholder, citationMarkerHtml(entry.displayIndex, entry.label));
|
|
242
363
|
}
|
|
243
|
-
|
|
364
|
+
|
|
365
|
+
// Merged citations can leave the same number twice over a sentence; that is collapsed before the
|
|
366
|
+
// commas go in, or the reader is given "1,1".
|
|
367
|
+
processedContent = collapseRepeatedCitations(processedContent);
|
|
368
|
+
processedContent = separateAdjacentCitations(processedContent);
|
|
244
369
|
|
|
245
370
|
// Generated files (such as exported tabular data) are always offered as a download even when
|
|
246
371
|
// the model does not cite them inline, so the user never loses access to the produced file.
|
|
@@ -253,6 +378,7 @@ window.coreAIChatManager = function () {
|
|
|
253
378
|
_value = _step2$value[1];
|
|
254
379
|
citations.push({
|
|
255
380
|
referenceKey: _key,
|
|
381
|
+
referenceKeys: [_key],
|
|
256
382
|
displayIndex: displayIndex,
|
|
257
383
|
label: getCitationLabel(_value, _key),
|
|
258
384
|
link: _value.link || null,
|
|
@@ -280,12 +406,27 @@ window.coreAIChatManager = function () {
|
|
|
280
406
|
if (!copyContent || !Array.isArray(citations) || citations.length === 0) {
|
|
281
407
|
return copyContent;
|
|
282
408
|
}
|
|
409
|
+
|
|
410
|
+
// Every key the citation absorbed is replaced, not just the first: a merged key left behind would
|
|
411
|
+
// reach the clipboard as the raw reference token the reader never saw on the page.
|
|
283
412
|
var _iterator3 = _createForOfIteratorHelper(citations),
|
|
284
413
|
_step3;
|
|
285
414
|
try {
|
|
286
415
|
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
416
|
+
var _citation$referenceKe;
|
|
287
417
|
var citation = _step3.value;
|
|
288
|
-
|
|
418
|
+
var _iterator5 = _createForOfIteratorHelper((_citation$referenceKe = citation.referenceKeys) !== null && _citation$referenceKe !== void 0 && _citation$referenceKe.length ? citation.referenceKeys : [citation.referenceKey]),
|
|
419
|
+
_step5;
|
|
420
|
+
try {
|
|
421
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
422
|
+
var key = _step5.value;
|
|
423
|
+
copyContent = copyContent.replaceAll(key, "[".concat(citation.displayIndex, "]"));
|
|
424
|
+
}
|
|
425
|
+
} catch (err) {
|
|
426
|
+
_iterator5.e(err);
|
|
427
|
+
} finally {
|
|
428
|
+
_iterator5.f();
|
|
429
|
+
}
|
|
289
430
|
}
|
|
290
431
|
} catch (err) {
|
|
291
432
|
_iterator3.e(err);
|
|
@@ -314,13 +455,20 @@ window.coreAIChatManager = function () {
|
|
|
314
455
|
function updateMessagePresentation(message, references) {
|
|
315
456
|
var messageReferences = normalizeReferences(references !== null && references !== void 0 ? references : message.references);
|
|
316
457
|
var rawContent = typeof message.rawContent === 'string' ? message.rawContent : typeof message.content === 'string' ? message.content : '';
|
|
317
|
-
|
|
458
|
+
// Figure markers become images before anything else reads the content: the citation pass looks for its
|
|
459
|
+
// own keys in what is left, and the markdown parser then sees an ordinary image.
|
|
460
|
+
var displayContent = expandImageMarkers(rawContent, messageReferences);
|
|
461
|
+
var citationDisplay = buildCitationDisplay(displayContent, messageReferences);
|
|
318
462
|
message.rawContent = rawContent;
|
|
319
463
|
message.content = rawContent;
|
|
320
464
|
message.displayContent = citationDisplay.content;
|
|
321
465
|
message.references = messageReferences;
|
|
322
466
|
message.citationReferences = citationDisplay.citations;
|
|
323
|
-
|
|
467
|
+
// Copied from the expanded content, so a copied answer carries the picture's link rather than a label
|
|
468
|
+
// that means nothing outside this page.
|
|
469
|
+
// Split here too: the copy is built from the expanded content rather than the display path's output,
|
|
470
|
+
// so a combined marker would otherwise survive into the clipboard as a raw key.
|
|
471
|
+
message.copyContent = buildCopyContent(splitCombinedCitations(displayContent), citationDisplay.citations);
|
|
324
472
|
message.htmlContent = parseMarkdownContent(citationDisplay.content, message);
|
|
325
473
|
return message;
|
|
326
474
|
}
|
|
@@ -364,7 +512,12 @@ window.coreAIChatManager = function () {
|
|
|
364
512
|
if (!src) return '';
|
|
365
513
|
var alt = data.text || defaultConfig.generatedImageAltText;
|
|
366
514
|
var maxWidth = defaultConfig.generatedImageMaxWidth;
|
|
367
|
-
|
|
515
|
+
// Spans, not divs. A figure marker is often written mid-sentence -- "Figures [fig:1], [fig:2] and
|
|
516
|
+
// [fig:3] show..." -- and a block element cannot sit inside a paragraph: the browser closes the <p>
|
|
517
|
+
// at the first one, so the sentence is torn into fragments and the commas between the markers are
|
|
518
|
+
// left stranded on their own lines. Inline-block keeps the figure looking exactly the same while
|
|
519
|
+
// remaining something a paragraph can legally contain.
|
|
520
|
+
return "<span class=\"generated-image-container\">\n <img src=\"".concat(src, "\" alt=\"").concat(alt, "\" class=\"img-thumbnail\" style=\"max-width: ").concat(maxWidth, "px; height: auto;\" />\n <span class=\"mt-2 d-block\">\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 </span>\n </span>");
|
|
368
521
|
};
|
|
369
522
|
|
|
370
523
|
// Chart counter for unique IDs
|
|
@@ -381,6 +534,11 @@ window.coreAIChatManager = function () {
|
|
|
381
534
|
return "<div class=\"chart-container\" style=\"position: relative; width: 100%; max-width: 560px; min-height: 420px;\">" + "<canvas id=\"".concat(chartId, "\"></canvas>") + "</div>" + "<div class=\"mt-2\">" + "<button type=\"button\" class=\"btn btn-sm btn-outline-secondary download-chart-btn\" data-chart-id=\"".concat(chartId, "\" title=\"").concat(defaultConfig.downloadChartTitle, "\">") + "<i class=\"fa-solid fa-download\"></i> ".concat(defaultConfig.downloadChartButtonText) + "</button>" + "</div>";
|
|
382
535
|
}
|
|
383
536
|
|
|
537
|
+
// The marker itself is read by chat-markers.js, shared with every other chat surface, so the three of them
|
|
538
|
+
// cannot disagree about what a marker is. Everything below -- the container, the id scheme, the Chart.js
|
|
539
|
+
// call -- is this surface's own and legitimately differs from the others.
|
|
540
|
+
var findChartMarker = window.CoreAIChatMarkers.findChartMarker;
|
|
541
|
+
|
|
384
542
|
// Register [chart:{...json...}] as a native marked block extension so the
|
|
385
543
|
// markdown parser handles chart markers inline with surrounding text.
|
|
386
544
|
marked.use({
|
|
@@ -392,7 +550,7 @@ window.coreAIChatManager = function () {
|
|
|
392
550
|
return idx >= 0 ? idx : undefined;
|
|
393
551
|
},
|
|
394
552
|
tokenizer: function tokenizer(src) {
|
|
395
|
-
var extracted =
|
|
553
|
+
var extracted = findChartMarker(src);
|
|
396
554
|
if (!extracted || extracted.startIndex !== 0) {
|
|
397
555
|
return undefined;
|
|
398
556
|
}
|
|
@@ -415,69 +573,60 @@ window.coreAIChatManager = function () {
|
|
|
415
573
|
}]
|
|
416
574
|
});
|
|
417
575
|
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
576
|
+
// An image the assistant named but the server will not serve. The address is written by a language
|
|
577
|
+
// model from what retrieval handed it, so a mistyped or invented one is possible, and the browser's
|
|
578
|
+
// default for that is a broken-image icon captioned with the alt text -- which reads as a real picture
|
|
579
|
+
// that failed to arrive. Saying plainly that it could not be loaded is both truer and quieter.
|
|
580
|
+
//
|
|
581
|
+
// Wired here rather than as an inline onerror attribute because message bodies go through DOMPurify,
|
|
582
|
+
// which strips event-handler attributes.
|
|
583
|
+
function markBrokenImagesInMessage() {
|
|
584
|
+
// Scanning the document rather than one message, because the value threaded through the render
|
|
585
|
+
// path is the message DATA object -- the thing carrying content, references and pending charts --
|
|
586
|
+
// and not the element it was rendered into. Wiring is idempotent via the data-broken-wired mark,
|
|
587
|
+
// so re-scanning after each render costs a query over the handful of images already handled.
|
|
588
|
+
if (typeof document === 'undefined' || typeof requestAnimationFrame !== 'function') {
|
|
589
|
+
return;
|
|
424
590
|
}
|
|
425
591
|
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
escape = false;
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
if (ch === '\\') {
|
|
446
|
-
escape = true;
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
if (ch === '"') {
|
|
450
|
-
inString = false;
|
|
451
|
-
}
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (ch === '"') {
|
|
455
|
-
inString = true;
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
if (ch === '{') {
|
|
459
|
-
depth++;
|
|
460
|
-
} else if (ch === '}') {
|
|
461
|
-
depth--;
|
|
462
|
-
if (depth === 0) {
|
|
463
|
-
var jsonEnd = i;
|
|
464
|
-
// Expect closing bracket after JSON
|
|
465
|
-
var closeBracketIndex = text.indexOf(']', jsonEnd + 1);
|
|
466
|
-
if (closeBracketIndex < 0) {
|
|
467
|
-
return null;
|
|
592
|
+
// Deferred for the same reason the charts below are: the render that produced these elements has
|
|
593
|
+
// not necessarily been flushed to the DOM when this runs.
|
|
594
|
+
requestAnimationFrame(wireBrokenImageHandlers);
|
|
595
|
+
}
|
|
596
|
+
function wireBrokenImageHandlers() {
|
|
597
|
+
var images = document.querySelectorAll('.generated-image-container img:not([data-broken-wired])');
|
|
598
|
+
var _iterator6 = _createForOfIteratorHelper(images),
|
|
599
|
+
_step6;
|
|
600
|
+
try {
|
|
601
|
+
var _loop = function _loop() {
|
|
602
|
+
var image = _step6.value;
|
|
603
|
+
image.setAttribute('data-broken-wired', 'true');
|
|
604
|
+
image.addEventListener('error', function () {
|
|
605
|
+
var container = image.closest('.generated-image-container');
|
|
606
|
+
if (!container || container.dataset.brokenHandled) {
|
|
607
|
+
return;
|
|
468
608
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
609
|
+
container.dataset.brokenHandled = 'true';
|
|
610
|
+
container.hidden = true;
|
|
611
|
+
var note = document.createElement('p');
|
|
612
|
+
note.className = 'text-muted small fst-italic mb-2';
|
|
613
|
+
note.textContent = image.alt ? "(the picture \"".concat(image.alt, "\" could not be loaded)") : '(that picture could not be loaded)';
|
|
614
|
+
container.insertAdjacentElement('afterend', note);
|
|
615
|
+
}, {
|
|
616
|
+
once: true
|
|
617
|
+
});
|
|
618
|
+
};
|
|
619
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
620
|
+
_loop();
|
|
476
621
|
}
|
|
622
|
+
} catch (err) {
|
|
623
|
+
_iterator6.e(err);
|
|
624
|
+
} finally {
|
|
625
|
+
_iterator6.f();
|
|
477
626
|
}
|
|
478
|
-
return null;
|
|
479
627
|
}
|
|
480
|
-
function
|
|
628
|
+
function renderMessageMedia(message) {
|
|
629
|
+
markBrokenImagesInMessage();
|
|
481
630
|
if (!message || !message._pendingCharts || !message._pendingCharts.length) {
|
|
482
631
|
return;
|
|
483
632
|
}
|
|
@@ -489,17 +638,17 @@ window.coreAIChatManager = function () {
|
|
|
489
638
|
// Defer to requestAnimationFrame so the browser has fully laid out the
|
|
490
639
|
// canvas elements before Chart.js reads their dimensions.
|
|
491
640
|
requestAnimationFrame(function () {
|
|
492
|
-
var
|
|
493
|
-
|
|
641
|
+
var _iterator7 = _createForOfIteratorHelper(charts),
|
|
642
|
+
_step7;
|
|
494
643
|
try {
|
|
495
|
-
for (
|
|
496
|
-
var c =
|
|
644
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
645
|
+
var c = _step7.value;
|
|
497
646
|
renderChartOnCanvas(c.chartId, c.config);
|
|
498
647
|
}
|
|
499
648
|
} catch (err) {
|
|
500
|
-
|
|
649
|
+
_iterator7.e(err);
|
|
501
650
|
} finally {
|
|
502
|
-
|
|
651
|
+
_iterator7.f();
|
|
503
652
|
}
|
|
504
653
|
});
|
|
505
654
|
}
|
|
@@ -662,8 +811,10 @@ window.coreAIChatManager = function () {
|
|
|
662
811
|
mediaRecorder: null,
|
|
663
812
|
preRecordingPrompt: '',
|
|
664
813
|
micButton: null,
|
|
665
|
-
|
|
666
|
-
|
|
814
|
+
// Conversation mode is carried either by a realtime session or by the speech-to-text plus
|
|
815
|
+
// text-to-speech cascade, never both. Dictation and spoken playback belong to the cascade.
|
|
816
|
+
speechToTextEnabled: !config.realtimeEnabled && (config.chatMode === 'AudioInput' || config.chatMode === 'Conversation'),
|
|
817
|
+
textToSpeechEnabled: !config.realtimeEnabled && config.chatMode === 'Conversation' || !!config.textToSpeechEnabled,
|
|
667
818
|
ttsVoiceName: config.ttsVoiceName || null,
|
|
668
819
|
audioChunks: [],
|
|
669
820
|
audioPlayQueue: [],
|
|
@@ -686,6 +837,7 @@ window.coreAIChatManager = function () {
|
|
|
686
837
|
realtimeWebRtcIceServers: Array.isArray(config.realtimeWebRtcIceServers) && config.realtimeWebRtcIceServers.length ? config.realtimeWebRtcIceServers : null,
|
|
687
838
|
realtimeVoiceName: config.realtimeVoiceName || null,
|
|
688
839
|
conversationButton: null,
|
|
840
|
+
realtimeHintElement: config.realtimeHintElementSelector ? document.querySelector(config.realtimeHintElementSelector) : null,
|
|
689
841
|
isConversationMode: false,
|
|
690
842
|
notificationDismissTimers: {},
|
|
691
843
|
pendingSessionPromise: null,
|
|
@@ -800,7 +952,7 @@ window.coreAIChatManager = function () {
|
|
|
800
952
|
return _context3.a(2);
|
|
801
953
|
case 2:
|
|
802
954
|
return _context3.a(2, _this.queueDocumentOperation(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
803
|
-
var sessionId, profileId, formData, i, response, errorText, uploadError, result, _t, _t2;
|
|
955
|
+
var sessionId, profileId, formData, i, response, errorText, uploadError, parsed, result, _t, _t2;
|
|
804
956
|
return _regenerator().w(function (_context2) {
|
|
805
957
|
while (1) switch (_context2.p = _context2.n) {
|
|
806
958
|
case 0:
|
|
@@ -873,9 +1025,24 @@ window.coreAIChatManager = function () {
|
|
|
873
1025
|
return _context2.a(2);
|
|
874
1026
|
case 9:
|
|
875
1027
|
_context2.n = 10;
|
|
876
|
-
return response.
|
|
1028
|
+
return readJsonResponse(response, 'Upload failed. Please try again.');
|
|
877
1029
|
case 10:
|
|
878
|
-
|
|
1030
|
+
parsed = _context2.v;
|
|
1031
|
+
if (parsed.ok) {
|
|
1032
|
+
_context2.n = 11;
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
console.error('Upload failed: response was not JSON.', {
|
|
1036
|
+
status: response.status,
|
|
1037
|
+
redirected: response.redirected
|
|
1038
|
+
});
|
|
1039
|
+
_this.uploadErrors = [{
|
|
1040
|
+
fileName: '',
|
|
1041
|
+
error: parsed.message
|
|
1042
|
+
}];
|
|
1043
|
+
return _context2.a(2);
|
|
1044
|
+
case 11:
|
|
1045
|
+
result = parsed.data;
|
|
879
1046
|
if (result.sessionId && result.sessionId !== _this.getSessionId()) {
|
|
880
1047
|
_this.initializeSession(result.sessionId);
|
|
881
1048
|
}
|
|
@@ -887,10 +1054,10 @@ window.coreAIChatManager = function () {
|
|
|
887
1054
|
if (result.failed && result.failed.length > 0) {
|
|
888
1055
|
_this.uploadErrors = result.failed;
|
|
889
1056
|
}
|
|
890
|
-
_context2.n =
|
|
1057
|
+
_context2.n = 13;
|
|
891
1058
|
break;
|
|
892
|
-
case
|
|
893
|
-
_context2.p =
|
|
1059
|
+
case 12:
|
|
1060
|
+
_context2.p = 12;
|
|
894
1061
|
_t2 = _context2.v;
|
|
895
1062
|
console.error('Upload error:', _t2);
|
|
896
1063
|
_this.uploadErrors = [{
|
|
@@ -900,15 +1067,15 @@ window.coreAIChatManager = function () {
|
|
|
900
1067
|
if (_this.getSessionId()) {
|
|
901
1068
|
_this.reloadCurrentSession();
|
|
902
1069
|
}
|
|
903
|
-
case
|
|
904
|
-
_context2.p =
|
|
1070
|
+
case 13:
|
|
1071
|
+
_context2.p = 13;
|
|
905
1072
|
_this.isUploading = false;
|
|
906
1073
|
_this.renderDocumentBar();
|
|
907
|
-
return _context2.f(
|
|
908
|
-
case
|
|
1074
|
+
return _context2.f(13);
|
|
1075
|
+
case 14:
|
|
909
1076
|
return _context2.a(2);
|
|
910
1077
|
}
|
|
911
|
-
}, _callee2, null, [[6,
|
|
1078
|
+
}, _callee2, null, [[6, 12, 13, 14], [1, 3]]);
|
|
912
1079
|
}))));
|
|
913
1080
|
}
|
|
914
1081
|
}, _callee3);
|
|
@@ -927,7 +1094,7 @@ window.coreAIChatManager = function () {
|
|
|
927
1094
|
return _context5.a(2);
|
|
928
1095
|
case 1:
|
|
929
1096
|
return _context5.a(2, _this2.queueDocumentOperation(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
|
|
930
|
-
var sessionId, response, result, idx, errorText, removeError, _t3;
|
|
1097
|
+
var sessionId, response, secondParsed, result, idx, errorText, removeError, _t3;
|
|
931
1098
|
return _regenerator().w(function (_context4) {
|
|
932
1099
|
while (1) switch (_context4.p = _context4.n) {
|
|
933
1100
|
case 0:
|
|
@@ -947,13 +1114,24 @@ window.coreAIChatManager = function () {
|
|
|
947
1114
|
case 1:
|
|
948
1115
|
response = _context4.v;
|
|
949
1116
|
if (!response.ok) {
|
|
950
|
-
_context4.n =
|
|
1117
|
+
_context4.n = 4;
|
|
951
1118
|
break;
|
|
952
1119
|
}
|
|
953
1120
|
_context4.n = 2;
|
|
954
|
-
return response.
|
|
1121
|
+
return readJsonResponse(response, 'The request could not be completed.');
|
|
955
1122
|
case 2:
|
|
956
|
-
|
|
1123
|
+
secondParsed = _context4.v;
|
|
1124
|
+
if (secondParsed.ok) {
|
|
1125
|
+
_context4.n = 3;
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
console.error('Response was not JSON.', {
|
|
1129
|
+
status: response.status,
|
|
1130
|
+
redirected: response.redirected
|
|
1131
|
+
});
|
|
1132
|
+
return _context4.a(2);
|
|
1133
|
+
case 3:
|
|
1134
|
+
result = secondParsed.data;
|
|
957
1135
|
if (Array.isArray(result.documents)) {
|
|
958
1136
|
_this2.documents = result.documents;
|
|
959
1137
|
} else {
|
|
@@ -962,12 +1140,12 @@ window.coreAIChatManager = function () {
|
|
|
962
1140
|
_this2.documents.splice(idx, 1);
|
|
963
1141
|
}
|
|
964
1142
|
}
|
|
965
|
-
_context4.n =
|
|
1143
|
+
_context4.n = 6;
|
|
966
1144
|
break;
|
|
967
|
-
case 3:
|
|
968
|
-
_context4.n = 4;
|
|
969
|
-
return response.text();
|
|
970
1145
|
case 4:
|
|
1146
|
+
_context4.n = 5;
|
|
1147
|
+
return response.text();
|
|
1148
|
+
case 5:
|
|
971
1149
|
errorText = _context4.v;
|
|
972
1150
|
removeError = _this2.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
|
|
973
1151
|
console.error('Failed to remove document:', response.status, errorText);
|
|
@@ -979,11 +1157,11 @@ window.coreAIChatManager = function () {
|
|
|
979
1157
|
_this2.reloadCurrentSession();
|
|
980
1158
|
}
|
|
981
1159
|
_this2.renderDocumentBar();
|
|
982
|
-
case 5:
|
|
983
|
-
_context4.n = 7;
|
|
984
|
-
break;
|
|
985
1160
|
case 6:
|
|
986
|
-
_context4.
|
|
1161
|
+
_context4.n = 8;
|
|
1162
|
+
break;
|
|
1163
|
+
case 7:
|
|
1164
|
+
_context4.p = 7;
|
|
987
1165
|
_t3 = _context4.v;
|
|
988
1166
|
console.error('Remove document error:', _t3);
|
|
989
1167
|
_this2.uploadErrors = [{
|
|
@@ -994,10 +1172,10 @@ window.coreAIChatManager = function () {
|
|
|
994
1172
|
_this2.reloadCurrentSession();
|
|
995
1173
|
}
|
|
996
1174
|
_this2.renderDocumentBar();
|
|
997
|
-
case
|
|
1175
|
+
case 8:
|
|
998
1176
|
return _context4.a(2);
|
|
999
1177
|
}
|
|
1000
|
-
}, _callee4, null, [[0,
|
|
1178
|
+
}, _callee4, null, [[0, 7]]);
|
|
1001
1179
|
}))));
|
|
1002
1180
|
}
|
|
1003
1181
|
}, _callee5);
|
|
@@ -1031,11 +1209,19 @@ window.coreAIChatManager = function () {
|
|
|
1031
1209
|
var failedItem = this.uploadErrors[m];
|
|
1032
1210
|
var failedName = failedItem.fileName || 'File';
|
|
1033
1211
|
var errorMsg = failedItem.error || 'Upload failed';
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
html += '
|
|
1212
|
+
// The reason is shown, not hidden behind a tooltip. A pill reading "quarterly-report-2..."
|
|
1213
|
+
// tells a reader their upload failed and nothing about why, and a title attribute is
|
|
1214
|
+
// unreachable on a touch device -- so the one sentence that tells them what to do next
|
|
1215
|
+
// was the one thing the widget would not display.
|
|
1216
|
+
html += '<span class="alert alert-danger d-flex align-items-start gap-2 w-100 mb-1 py-1 px-2" role="alert" style="font-size: 0.8rem;">';
|
|
1217
|
+
html += '<i class="fa-solid fa-circle-exclamation mt-1" style="font-size: 0.7rem;"></i>';
|
|
1218
|
+
html += '<span class="flex-grow-1">';
|
|
1219
|
+
if (failedItem.fileName) {
|
|
1220
|
+
html += '<strong>' + this.escapeHtml(failedItem.fileName) + '</strong>: ';
|
|
1221
|
+
}
|
|
1222
|
+
html += this.escapeHtml(errorMsg);
|
|
1223
|
+
html += '</span>';
|
|
1224
|
+
html += '<button type="button" class="btn-close btn-close-sm" style="font-size: 0.5rem;" data-error-index="' + m + '" aria-label="Dismiss"></button>';
|
|
1039
1225
|
html += '</span>';
|
|
1040
1226
|
}
|
|
1041
1227
|
if (this.isUploading) {
|
|
@@ -1195,7 +1381,7 @@ window.coreAIChatManager = function () {
|
|
|
1195
1381
|
((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
|
|
1196
1382
|
_this3.addMessage(msg);
|
|
1197
1383
|
_this3.$nextTick(function () {
|
|
1198
|
-
|
|
1384
|
+
renderMessageMedia(msg);
|
|
1199
1385
|
});
|
|
1200
1386
|
});
|
|
1201
1387
|
|
|
@@ -1388,7 +1574,7 @@ window.coreAIChatManager = function () {
|
|
|
1388
1574
|
msg.references = normalizeReferences(Object.assign({}, msg.references || {}, references || {}));
|
|
1389
1575
|
updateMessagePresentation(msg, msg.references);
|
|
1390
1576
|
_this3.$nextTick(function () {
|
|
1391
|
-
|
|
1577
|
+
renderMessageMedia(msg);
|
|
1392
1578
|
_this3.scrollToBottom();
|
|
1393
1579
|
});
|
|
1394
1580
|
}
|
|
@@ -1577,7 +1763,7 @@ window.coreAIChatManager = function () {
|
|
|
1577
1763
|
this.hidePlaceholder();
|
|
1578
1764
|
this.$nextTick(function () {
|
|
1579
1765
|
// Render any pending charts once the DOM is updated
|
|
1580
|
-
|
|
1766
|
+
renderMessageMedia(message);
|
|
1581
1767
|
_this6.scrollToBottom();
|
|
1582
1768
|
});
|
|
1583
1769
|
},
|
|
@@ -1644,6 +1830,11 @@ window.coreAIChatManager = function () {
|
|
|
1644
1830
|
return;
|
|
1645
1831
|
}
|
|
1646
1832
|
|
|
1833
|
+
// A live voice session and a typed turn are two writers into one chat session, and they
|
|
1834
|
+
// would interleave. End the session first; the turns it already produced are persisted, so
|
|
1835
|
+
// the typed message simply continues the same thread.
|
|
1836
|
+
this.forceStopActiveVoice();
|
|
1837
|
+
|
|
1647
1838
|
// Stop any active recording before sending.
|
|
1648
1839
|
if (this.isRecording) {
|
|
1649
1840
|
this.stopRecording();
|
|
@@ -1903,7 +2094,7 @@ window.coreAIChatManager = function () {
|
|
|
1903
2094
|
updateMessagePresentation(message, references);
|
|
1904
2095
|
_this9.messages[messageIndex] = message;
|
|
1905
2096
|
_this9.$nextTick(function () {
|
|
1906
|
-
|
|
2097
|
+
renderMessageMedia(message);
|
|
1907
2098
|
_this9.scrollToBottom();
|
|
1908
2099
|
});
|
|
1909
2100
|
},
|
|
@@ -1971,7 +2162,7 @@ window.coreAIChatManager = function () {
|
|
|
1971
2162
|
updateMessagePresentation(message, references);
|
|
1972
2163
|
this.messages[messageIndex] = message;
|
|
1973
2164
|
this.$nextTick(function () {
|
|
1974
|
-
|
|
2165
|
+
renderMessageMedia(message);
|
|
1975
2166
|
_this0.scrollToBottom();
|
|
1976
2167
|
});
|
|
1977
2168
|
}
|
|
@@ -2122,18 +2313,18 @@ window.coreAIChatManager = function () {
|
|
|
2122
2313
|
}, 0);
|
|
2123
2314
|
var combined = new Uint8Array(totalLength);
|
|
2124
2315
|
var offset = 0;
|
|
2125
|
-
var
|
|
2126
|
-
|
|
2316
|
+
var _iterator8 = _createForOfIteratorHelper(this.audioChunks),
|
|
2317
|
+
_step8;
|
|
2127
2318
|
try {
|
|
2128
|
-
for (
|
|
2129
|
-
var chunk =
|
|
2319
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
2320
|
+
var chunk = _step8.value;
|
|
2130
2321
|
combined.set(chunk, offset);
|
|
2131
2322
|
offset += chunk.length;
|
|
2132
2323
|
}
|
|
2133
2324
|
} catch (err) {
|
|
2134
|
-
|
|
2325
|
+
_iterator8.e(err);
|
|
2135
2326
|
} finally {
|
|
2136
|
-
|
|
2327
|
+
_iterator8.f();
|
|
2137
2328
|
}
|
|
2138
2329
|
this.audioChunks = [];
|
|
2139
2330
|
var blob = new Blob([combined], {
|
|
@@ -2216,6 +2407,45 @@ window.coreAIChatManager = function () {
|
|
|
2216
2407
|
return _this16.updateTtsPlaybackButtons();
|
|
2217
2408
|
});
|
|
2218
2409
|
},
|
|
2410
|
+
// A live voice session takes the message box away and puts the audio settings in its place; ending
|
|
2411
|
+
// the session gives it back. Sending a typed message already force-stopped the session, so the box
|
|
2412
|
+
// was never a way to say something *during* one -- hiding it only makes that honest, and frees the
|
|
2413
|
+
// row for the gear. Both transports land here: realtime through the module's onActivate, the
|
|
2414
|
+
// speech-to-text cascade through startConversationMode.
|
|
2415
|
+
// The realtime module inserts its settings popover directly after the button it was handed, and
|
|
2416
|
+
// hard-codes the element id. A page can host two chat clients at once -- this one and the admin
|
|
2417
|
+
// widget -- so that id is not unique and getElementById would hand the widget the page's gear.
|
|
2418
|
+
// Each client finds its own next to its own button.
|
|
2419
|
+
voiceSettingsElement: function voiceSettingsElement() {
|
|
2420
|
+
var next = this.conversationButton && this.conversationButton.nextElementSibling;
|
|
2421
|
+
return next && next.id === 'realtime-audio-settings' ? next : null;
|
|
2422
|
+
},
|
|
2423
|
+
setVoiceSessionLive: function setVoiceSessionLive(live) {
|
|
2424
|
+
if (this.inputElement) {
|
|
2425
|
+
this.inputElement.hidden = live;
|
|
2426
|
+
}
|
|
2427
|
+
if (this.buttonElement) {
|
|
2428
|
+
this.buttonElement.hidden = live;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// With the message box gone the button would otherwise sit at its natural width in an empty
|
|
2432
|
+
// row, so it takes the space the box left behind and reads as the thing now in charge. The
|
|
2433
|
+
// settings popover does not grow, so it stays at the right-hand end.
|
|
2434
|
+
if (this.conversationButton) {
|
|
2435
|
+
this.conversationButton.classList.toggle('flex-grow-1', live);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// The gear is built by the realtime module next to its button, and only in realtime mode, so
|
|
2439
|
+
// it is absent on the cascade. It belongs to a live session rather than to the surface.
|
|
2440
|
+
var audioSettings = this.voiceSettingsElement();
|
|
2441
|
+
if (audioSettings) {
|
|
2442
|
+
audioSettings.hidden = !live;
|
|
2443
|
+
}
|
|
2444
|
+
var hint = this.realtimeHintElement;
|
|
2445
|
+
if (hint) {
|
|
2446
|
+
hint.classList.toggle('d-none', !live);
|
|
2447
|
+
}
|
|
2448
|
+
},
|
|
2219
2449
|
toggleConversationMode: function toggleConversationMode() {
|
|
2220
2450
|
if (this.realtimeEnabled) {
|
|
2221
2451
|
// Realtime is driven by the shared module, which owns the button; this only exists for
|
|
@@ -2291,19 +2521,25 @@ window.coreAIChatManager = function () {
|
|
|
2291
2521
|
},
|
|
2292
2522
|
realtimeEnabled: true,
|
|
2293
2523
|
// The conversation button doubles as the realtime button on this host; the module owns its
|
|
2294
|
-
// click, label and state from here on.
|
|
2295
|
-
//
|
|
2296
|
-
//
|
|
2524
|
+
// click, label and state from here on.
|
|
2525
|
+
//
|
|
2526
|
+
// input and sendButton are deliberately not handed over. The module hides every control it
|
|
2527
|
+
// is given when realtime takes over, and typing has to stay available during a voice
|
|
2528
|
+
// conversation -- that is the whole feature.
|
|
2297
2529
|
//
|
|
2298
|
-
// conversationButton is
|
|
2530
|
+
// conversationButton is not handed over either. It is the same element as the realtime
|
|
2299
2531
|
// button here, and the module hides the conversation button before showing the realtime
|
|
2300
2532
|
// one -- naming it twice would ask it to hide the control it then has to show.
|
|
2301
2533
|
selectors: {
|
|
2302
2534
|
realtimeButton: config.conversationButtonElementSelector,
|
|
2303
|
-
input: config.inputElementSelector,
|
|
2304
|
-
sendButton: config.sendButtonElementSelector,
|
|
2305
2535
|
micButton: config.micButtonElementSelector
|
|
2306
2536
|
},
|
|
2537
|
+
// The module owns whether a session is live, so the message box follows its flag rather
|
|
2538
|
+
// than this app's copy of it. Losing one of these events used to leave the box hidden
|
|
2539
|
+
// under a button that had already gone back to "Start speaking".
|
|
2540
|
+
onSessionStateChanged: function onSessionStateChanged(active) {
|
|
2541
|
+
self.setVoiceSessionLive(active);
|
|
2542
|
+
},
|
|
2307
2543
|
onActivate: function onActivate() {
|
|
2308
2544
|
self.isConversationMode = true;
|
|
2309
2545
|
self._conversationAssistantMessage = null;
|
|
@@ -2946,6 +3182,11 @@ window.coreAIChatManager = function () {
|
|
|
2946
3182
|
// The shared realtime module owns the button (click, label, state) and adds the
|
|
2947
3183
|
// voice settings popover next to it.
|
|
2948
3184
|
this.setupRealtimeController(config);
|
|
3185
|
+
|
|
3186
|
+
// The module reveals the gear as soon as the surface enters realtime mode, but it
|
|
3187
|
+
// belongs to a live session. The watcher that would hide it only runs on a change,
|
|
3188
|
+
// so the idle state has to be stated once here.
|
|
3189
|
+
this.setVoiceSessionLive(false);
|
|
2949
3190
|
} else {
|
|
2950
3191
|
this.conversationButton.addEventListener('click', function () {
|
|
2951
3192
|
_this23.toggleConversationMode();
|
|
@@ -3139,22 +3380,20 @@ window.coreAIChatManager = function () {
|
|
|
3139
3380
|
});
|
|
3140
3381
|
},
|
|
3141
3382
|
isConversationMode: function isConversationMode(active) {
|
|
3142
|
-
//
|
|
3383
|
+
// The dictation microphone is hidden while a voice conversation runs: the conversation
|
|
3384
|
+
// already owns the microphone, and a second one would mean something different.
|
|
3143
3385
|
if (this.micButton) {
|
|
3144
3386
|
this.micButton.style.display = active ? 'none' : this.speechToTextEnabled ? '' : 'none';
|
|
3145
3387
|
}
|
|
3146
3388
|
|
|
3147
|
-
//
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
//
|
|
3153
|
-
if (this.
|
|
3154
|
-
this.
|
|
3155
|
-
if (active) {
|
|
3156
|
-
this.inputElement.placeholder = '';
|
|
3157
|
-
}
|
|
3389
|
+
// The message box and the send button give way to the voice settings while the conversation
|
|
3390
|
+
// runs, and come back when it ends. Both kinds of turn still land in the same thread; what
|
|
3391
|
+
// they cannot do is overlap.
|
|
3392
|
+
//
|
|
3393
|
+
// Only the cascade is driven from here. Realtime follows the module's own session flag
|
|
3394
|
+
// (onSessionStateChanged), so the two never disagree about whether a session is live.
|
|
3395
|
+
if (!this.realtimeEnabled) {
|
|
3396
|
+
this.setVoiceSessionLive(active);
|
|
3158
3397
|
}
|
|
3159
3398
|
}
|
|
3160
3399
|
},
|
|
@@ -3267,6 +3506,7 @@ window.coreAIChatManager = function () {
|
|
|
3267
3506
|
chatMode: getAttributeValue(element, 'data-coreai-chat-mode'),
|
|
3268
3507
|
micButtonElementSelector: getAttributeValue(element, 'data-coreai-chat-mic-button-element-selector'),
|
|
3269
3508
|
conversationButtonElementSelector: getAttributeValue(element, 'data-coreai-chat-conversation-button-element-selector'),
|
|
3509
|
+
realtimeHintElementSelector: getAttributeValue(element, 'data-coreai-chat-realtime-hint-element-selector'),
|
|
3270
3510
|
ttsVoiceName: getAttributeValue(element, 'data-coreai-chat-tts-voice-name'),
|
|
3271
3511
|
realtimeVoiceName: getAttributeValue(element, 'data-coreai-chat-realtime-voice-name'),
|
|
3272
3512
|
documentBarSelector: getAttributeValue(element, 'data-coreai-chat-document-bar-selector'),
|