@crestapps/ai-chat-ui 2.0.0-preview.190 → 2.0.0-preview.193

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ai-chat.js CHANGED
@@ -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
- window.coreAIChatManager = function () {
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
- var processedContent = (content || '').trim();
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 placeholder = "__CITE_".concat(displayIndex, "_").concat(value.index || displayIndex, "__");
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
- citations.push({
225
- referenceKey: key,
226
- displayIndex: displayIndex,
227
- label: getCitationLabel(value, key),
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, _citations = citations; _i2 < _citations.length; _i2++) {
240
- var citation = _citations[_i2];
241
- processedContent = processedContent.replaceAll(citation.placeholder, "<sup>".concat(citation.displayIndex, "</sup>"));
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
- processedContent = processedContent.replaceAll('</sup><sup>', '</sup><sup>,</sup><sup>');
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
- copyContent = copyContent.replaceAll(citation.referenceKey, "[".concat(citation.displayIndex, "]"));
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
- var citationDisplay = buildCitationDisplay(rawContent, messageReferences);
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
- message.copyContent = buildCopyContent(rawContent, citationDisplay.citations);
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
- 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>");
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 = tryExtractChartMarker(src);
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
- // Extract a [chart:{...json...}] marker. This avoids regex issues with nested brackets.
419
- function tryExtractChartMarker(text) {
420
- var token = '[chart:';
421
- var start = text.indexOf(token);
422
- if (start < 0) {
423
- return null;
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
- // Find JSON object boundary by balancing braces
427
- var jsonStart = start + token.length;
428
- var i = jsonStart;
429
- while (i < text.length && (text[i] === ' ' || text[i] === '\n' || text[i] === '\r' || text[i] === '\t')) {
430
- i++;
431
- }
432
- if (i >= text.length || text[i] !== '{') {
433
- return null;
434
- }
435
- var depth = 0;
436
- var inString = false;
437
- var escape = false;
438
- for (; i < text.length; i++) {
439
- var ch = text[i];
440
- if (inString) {
441
- if (escape) {
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
- var json = text.substring(jsonStart, jsonEnd + 1).trim();
470
- return {
471
- startIndex: start,
472
- endIndex: closeBracketIndex + 1,
473
- json: json
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 renderChartsInMessage(message) {
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 _iterator5 = _createForOfIteratorHelper(charts),
493
- _step5;
641
+ var _iterator7 = _createForOfIteratorHelper(charts),
642
+ _step7;
494
643
  try {
495
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
496
- var c = _step5.value;
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
- _iterator5.e(err);
649
+ _iterator7.e(err);
501
650
  } finally {
502
- _iterator5.f();
651
+ _iterator7.f();
503
652
  }
504
653
  });
505
654
  }
@@ -562,11 +711,19 @@ window.coreAIChatManager = function () {
562
711
  // Parse markdown content via marked (which natively handles [chart:...] markers
563
712
  // through the registered extension) and collect pending chart configs for later
564
713
  // Chart.js rendering.
714
+ // A markdown table becomes a bare <table>, which Bootstrap leaves completely unstyled: it renders
715
+ // as cramped, borderless text that reads as plain output rather than a table. Adding the framework's
716
+ // own classes styles it wherever Bootstrap is loaded, and the wrapper lets a wide table scroll on
717
+ // its own instead of stretching the conversation. Markdown tables cannot nest, so the plain
718
+ // replacement is safe.
719
+ function styleMarkdownTables(html) {
720
+ return html.replace(/<table>/g, '<div class="table-responsive"><table class="table table-sm table-bordered table-striped align-middle">').replace(/<\/table>/g, '</table></div>');
721
+ }
565
722
  function parseMarkdownContent(content, message) {
566
723
  _pendingCharts = [];
567
- var html = marked.parse(content, {
724
+ var html = styleMarkdownTables(marked.parse(content, {
568
725
  renderer: renderer
569
- });
726
+ }));
570
727
  message._pendingCharts = _pendingCharts.length > 0 ? _toConsumableArray(_pendingCharts) : [];
571
728
  return DOMPurify.sanitize(html, {
572
729
  ADD_TAGS: ['canvas'],
@@ -792,7 +949,7 @@ window.coreAIChatManager = function () {
792
949
  return _context3.a(2);
793
950
  case 2:
794
951
  return _context3.a(2, _this.queueDocumentOperation(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
795
- var sessionId, profileId, formData, i, response, errorText, uploadError, result, _t, _t2;
952
+ var sessionId, profileId, formData, i, response, errorText, uploadError, parsed, result, _t, _t2;
796
953
  return _regenerator().w(function (_context2) {
797
954
  while (1) switch (_context2.p = _context2.n) {
798
955
  case 0:
@@ -865,9 +1022,24 @@ window.coreAIChatManager = function () {
865
1022
  return _context2.a(2);
866
1023
  case 9:
867
1024
  _context2.n = 10;
868
- return response.json();
1025
+ return readJsonResponse(response, 'Upload failed. Please try again.');
869
1026
  case 10:
870
- result = _context2.v;
1027
+ parsed = _context2.v;
1028
+ if (parsed.ok) {
1029
+ _context2.n = 11;
1030
+ break;
1031
+ }
1032
+ console.error('Upload failed: response was not JSON.', {
1033
+ status: response.status,
1034
+ redirected: response.redirected
1035
+ });
1036
+ _this.uploadErrors = [{
1037
+ fileName: '',
1038
+ error: parsed.message
1039
+ }];
1040
+ return _context2.a(2);
1041
+ case 11:
1042
+ result = parsed.data;
871
1043
  if (result.sessionId && result.sessionId !== _this.getSessionId()) {
872
1044
  _this.initializeSession(result.sessionId);
873
1045
  }
@@ -879,10 +1051,10 @@ window.coreAIChatManager = function () {
879
1051
  if (result.failed && result.failed.length > 0) {
880
1052
  _this.uploadErrors = result.failed;
881
1053
  }
882
- _context2.n = 12;
1054
+ _context2.n = 13;
883
1055
  break;
884
- case 11:
885
- _context2.p = 11;
1056
+ case 12:
1057
+ _context2.p = 12;
886
1058
  _t2 = _context2.v;
887
1059
  console.error('Upload error:', _t2);
888
1060
  _this.uploadErrors = [{
@@ -892,15 +1064,15 @@ window.coreAIChatManager = function () {
892
1064
  if (_this.getSessionId()) {
893
1065
  _this.reloadCurrentSession();
894
1066
  }
895
- case 12:
896
- _context2.p = 12;
1067
+ case 13:
1068
+ _context2.p = 13;
897
1069
  _this.isUploading = false;
898
1070
  _this.renderDocumentBar();
899
- return _context2.f(12);
900
- case 13:
1071
+ return _context2.f(13);
1072
+ case 14:
901
1073
  return _context2.a(2);
902
1074
  }
903
- }, _callee2, null, [[6, 11, 12, 13], [1, 3]]);
1075
+ }, _callee2, null, [[6, 12, 13, 14], [1, 3]]);
904
1076
  }))));
905
1077
  }
906
1078
  }, _callee3);
@@ -919,7 +1091,7 @@ window.coreAIChatManager = function () {
919
1091
  return _context5.a(2);
920
1092
  case 1:
921
1093
  return _context5.a(2, _this2.queueDocumentOperation(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
922
- var sessionId, response, result, idx, errorText, removeError, _t3;
1094
+ var sessionId, response, secondParsed, result, idx, errorText, removeError, _t3;
923
1095
  return _regenerator().w(function (_context4) {
924
1096
  while (1) switch (_context4.p = _context4.n) {
925
1097
  case 0:
@@ -939,13 +1111,24 @@ window.coreAIChatManager = function () {
939
1111
  case 1:
940
1112
  response = _context4.v;
941
1113
  if (!response.ok) {
942
- _context4.n = 3;
1114
+ _context4.n = 4;
943
1115
  break;
944
1116
  }
945
1117
  _context4.n = 2;
946
- return response.json();
1118
+ return readJsonResponse(response, 'The request could not be completed.');
947
1119
  case 2:
948
- result = _context4.v;
1120
+ secondParsed = _context4.v;
1121
+ if (secondParsed.ok) {
1122
+ _context4.n = 3;
1123
+ break;
1124
+ }
1125
+ console.error('Response was not JSON.', {
1126
+ status: response.status,
1127
+ redirected: response.redirected
1128
+ });
1129
+ return _context4.a(2);
1130
+ case 3:
1131
+ result = secondParsed.data;
949
1132
  if (Array.isArray(result.documents)) {
950
1133
  _this2.documents = result.documents;
951
1134
  } else {
@@ -954,12 +1137,12 @@ window.coreAIChatManager = function () {
954
1137
  _this2.documents.splice(idx, 1);
955
1138
  }
956
1139
  }
957
- _context4.n = 5;
1140
+ _context4.n = 6;
958
1141
  break;
959
- case 3:
960
- _context4.n = 4;
961
- return response.text();
962
1142
  case 4:
1143
+ _context4.n = 5;
1144
+ return response.text();
1145
+ case 5:
963
1146
  errorText = _context4.v;
964
1147
  removeError = _this2.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
965
1148
  console.error('Failed to remove document:', response.status, errorText);
@@ -971,11 +1154,11 @@ window.coreAIChatManager = function () {
971
1154
  _this2.reloadCurrentSession();
972
1155
  }
973
1156
  _this2.renderDocumentBar();
974
- case 5:
975
- _context4.n = 7;
976
- break;
977
1157
  case 6:
978
- _context4.p = 6;
1158
+ _context4.n = 8;
1159
+ break;
1160
+ case 7:
1161
+ _context4.p = 7;
979
1162
  _t3 = _context4.v;
980
1163
  console.error('Remove document error:', _t3);
981
1164
  _this2.uploadErrors = [{
@@ -986,10 +1169,10 @@ window.coreAIChatManager = function () {
986
1169
  _this2.reloadCurrentSession();
987
1170
  }
988
1171
  _this2.renderDocumentBar();
989
- case 7:
1172
+ case 8:
990
1173
  return _context4.a(2);
991
1174
  }
992
- }, _callee4, null, [[0, 6]]);
1175
+ }, _callee4, null, [[0, 7]]);
993
1176
  }))));
994
1177
  }
995
1178
  }, _callee5);
@@ -1023,11 +1206,19 @@ window.coreAIChatManager = function () {
1023
1206
  var failedItem = this.uploadErrors[m];
1024
1207
  var failedName = failedItem.fileName || 'File';
1025
1208
  var errorMsg = failedItem.error || 'Upload failed';
1026
- if (failedName.length > 15) failedName = failedName.substring(0, 12) + '...';
1027
- html += '<span class="badge bg-danger bg-opacity-25 text-danger d-inline-flex align-items-center gap-1 px-2 py-1" style="font-size: 0.8rem;" title="' + this.escapeHtml((failedItem.fileName || '') + ': ' + errorMsg) + '">';
1028
- html += '<i class="fa-solid fa-circle-exclamation" style="font-size: 0.7rem;"></i> ';
1029
- html += this.escapeHtml(failedName);
1030
- html += ' <button type="button" class="btn-close btn-close-sm ms-1" style="font-size: 0.5rem;" data-error-index="' + m + '" aria-label="Dismiss"></button>';
1209
+ // The reason is shown, not hidden behind a tooltip. A pill reading "quarterly-report-2..."
1210
+ // tells a reader their upload failed and nothing about why, and a title attribute is
1211
+ // unreachable on a touch device -- so the one sentence that tells them what to do next
1212
+ // was the one thing the widget would not display.
1213
+ 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;">';
1214
+ html += '<i class="fa-solid fa-circle-exclamation mt-1" style="font-size: 0.7rem;"></i>';
1215
+ html += '<span class="flex-grow-1">';
1216
+ if (failedItem.fileName) {
1217
+ html += '<strong>' + this.escapeHtml(failedItem.fileName) + '</strong>: ';
1218
+ }
1219
+ html += this.escapeHtml(errorMsg);
1220
+ html += '</span>';
1221
+ html += '<button type="button" class="btn-close btn-close-sm" style="font-size: 0.5rem;" data-error-index="' + m + '" aria-label="Dismiss"></button>';
1031
1222
  html += '</span>';
1032
1223
  }
1033
1224
  if (this.isUploading) {
@@ -1187,7 +1378,7 @@ window.coreAIChatManager = function () {
1187
1378
  ((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
1188
1379
  _this3.addMessage(msg);
1189
1380
  _this3.$nextTick(function () {
1190
- renderChartsInMessage(msg);
1381
+ renderMessageMedia(msg);
1191
1382
  });
1192
1383
  });
1193
1384
 
@@ -1380,7 +1571,7 @@ window.coreAIChatManager = function () {
1380
1571
  msg.references = normalizeReferences(Object.assign({}, msg.references || {}, references || {}));
1381
1572
  updateMessagePresentation(msg, msg.references);
1382
1573
  _this3.$nextTick(function () {
1383
- renderChartsInMessage(msg);
1574
+ renderMessageMedia(msg);
1384
1575
  _this3.scrollToBottom();
1385
1576
  });
1386
1577
  }
@@ -1569,7 +1760,7 @@ window.coreAIChatManager = function () {
1569
1760
  this.hidePlaceholder();
1570
1761
  this.$nextTick(function () {
1571
1762
  // Render any pending charts once the DOM is updated
1572
- renderChartsInMessage(message);
1763
+ renderMessageMedia(message);
1573
1764
  _this6.scrollToBottom();
1574
1765
  });
1575
1766
  },
@@ -1895,7 +2086,7 @@ window.coreAIChatManager = function () {
1895
2086
  updateMessagePresentation(message, references);
1896
2087
  _this9.messages[messageIndex] = message;
1897
2088
  _this9.$nextTick(function () {
1898
- renderChartsInMessage(message);
2089
+ renderMessageMedia(message);
1899
2090
  _this9.scrollToBottom();
1900
2091
  });
1901
2092
  },
@@ -1963,7 +2154,7 @@ window.coreAIChatManager = function () {
1963
2154
  updateMessagePresentation(message, references);
1964
2155
  this.messages[messageIndex] = message;
1965
2156
  this.$nextTick(function () {
1966
- renderChartsInMessage(message);
2157
+ renderMessageMedia(message);
1967
2158
  _this0.scrollToBottom();
1968
2159
  });
1969
2160
  }
@@ -2114,18 +2305,18 @@ window.coreAIChatManager = function () {
2114
2305
  }, 0);
2115
2306
  var combined = new Uint8Array(totalLength);
2116
2307
  var offset = 0;
2117
- var _iterator6 = _createForOfIteratorHelper(this.audioChunks),
2118
- _step6;
2308
+ var _iterator8 = _createForOfIteratorHelper(this.audioChunks),
2309
+ _step8;
2119
2310
  try {
2120
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
2121
- var chunk = _step6.value;
2311
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
2312
+ var chunk = _step8.value;
2122
2313
  combined.set(chunk, offset);
2123
2314
  offset += chunk.length;
2124
2315
  }
2125
2316
  } catch (err) {
2126
- _iterator6.e(err);
2317
+ _iterator8.e(err);
2127
2318
  } finally {
2128
- _iterator6.f();
2319
+ _iterator8.f();
2129
2320
  }
2130
2321
  this.audioChunks = [];
2131
2322
  var blob = new Blob([combined], {