@crestapps/ai-chat-ui 2.0.0-preview.191 → 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
  }
@@ -800,7 +949,7 @@ window.coreAIChatManager = function () {
800
949
  return _context3.a(2);
801
950
  case 2:
802
951
  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;
952
+ var sessionId, profileId, formData, i, response, errorText, uploadError, parsed, result, _t, _t2;
804
953
  return _regenerator().w(function (_context2) {
805
954
  while (1) switch (_context2.p = _context2.n) {
806
955
  case 0:
@@ -873,9 +1022,24 @@ window.coreAIChatManager = function () {
873
1022
  return _context2.a(2);
874
1023
  case 9:
875
1024
  _context2.n = 10;
876
- return response.json();
1025
+ return readJsonResponse(response, 'Upload failed. Please try again.');
877
1026
  case 10:
878
- 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;
879
1043
  if (result.sessionId && result.sessionId !== _this.getSessionId()) {
880
1044
  _this.initializeSession(result.sessionId);
881
1045
  }
@@ -887,10 +1051,10 @@ window.coreAIChatManager = function () {
887
1051
  if (result.failed && result.failed.length > 0) {
888
1052
  _this.uploadErrors = result.failed;
889
1053
  }
890
- _context2.n = 12;
1054
+ _context2.n = 13;
891
1055
  break;
892
- case 11:
893
- _context2.p = 11;
1056
+ case 12:
1057
+ _context2.p = 12;
894
1058
  _t2 = _context2.v;
895
1059
  console.error('Upload error:', _t2);
896
1060
  _this.uploadErrors = [{
@@ -900,15 +1064,15 @@ window.coreAIChatManager = function () {
900
1064
  if (_this.getSessionId()) {
901
1065
  _this.reloadCurrentSession();
902
1066
  }
903
- case 12:
904
- _context2.p = 12;
1067
+ case 13:
1068
+ _context2.p = 13;
905
1069
  _this.isUploading = false;
906
1070
  _this.renderDocumentBar();
907
- return _context2.f(12);
908
- case 13:
1071
+ return _context2.f(13);
1072
+ case 14:
909
1073
  return _context2.a(2);
910
1074
  }
911
- }, _callee2, null, [[6, 11, 12, 13], [1, 3]]);
1075
+ }, _callee2, null, [[6, 12, 13, 14], [1, 3]]);
912
1076
  }))));
913
1077
  }
914
1078
  }, _callee3);
@@ -927,7 +1091,7 @@ window.coreAIChatManager = function () {
927
1091
  return _context5.a(2);
928
1092
  case 1:
929
1093
  return _context5.a(2, _this2.queueDocumentOperation(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
930
- var sessionId, response, result, idx, errorText, removeError, _t3;
1094
+ var sessionId, response, secondParsed, result, idx, errorText, removeError, _t3;
931
1095
  return _regenerator().w(function (_context4) {
932
1096
  while (1) switch (_context4.p = _context4.n) {
933
1097
  case 0:
@@ -947,13 +1111,24 @@ window.coreAIChatManager = function () {
947
1111
  case 1:
948
1112
  response = _context4.v;
949
1113
  if (!response.ok) {
950
- _context4.n = 3;
1114
+ _context4.n = 4;
951
1115
  break;
952
1116
  }
953
1117
  _context4.n = 2;
954
- return response.json();
1118
+ return readJsonResponse(response, 'The request could not be completed.');
955
1119
  case 2:
956
- 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;
957
1132
  if (Array.isArray(result.documents)) {
958
1133
  _this2.documents = result.documents;
959
1134
  } else {
@@ -962,12 +1137,12 @@ window.coreAIChatManager = function () {
962
1137
  _this2.documents.splice(idx, 1);
963
1138
  }
964
1139
  }
965
- _context4.n = 5;
1140
+ _context4.n = 6;
966
1141
  break;
967
- case 3:
968
- _context4.n = 4;
969
- return response.text();
970
1142
  case 4:
1143
+ _context4.n = 5;
1144
+ return response.text();
1145
+ case 5:
971
1146
  errorText = _context4.v;
972
1147
  removeError = _this2.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
973
1148
  console.error('Failed to remove document:', response.status, errorText);
@@ -979,11 +1154,11 @@ window.coreAIChatManager = function () {
979
1154
  _this2.reloadCurrentSession();
980
1155
  }
981
1156
  _this2.renderDocumentBar();
982
- case 5:
983
- _context4.n = 7;
984
- break;
985
1157
  case 6:
986
- _context4.p = 6;
1158
+ _context4.n = 8;
1159
+ break;
1160
+ case 7:
1161
+ _context4.p = 7;
987
1162
  _t3 = _context4.v;
988
1163
  console.error('Remove document error:', _t3);
989
1164
  _this2.uploadErrors = [{
@@ -994,10 +1169,10 @@ window.coreAIChatManager = function () {
994
1169
  _this2.reloadCurrentSession();
995
1170
  }
996
1171
  _this2.renderDocumentBar();
997
- case 7:
1172
+ case 8:
998
1173
  return _context4.a(2);
999
1174
  }
1000
- }, _callee4, null, [[0, 6]]);
1175
+ }, _callee4, null, [[0, 7]]);
1001
1176
  }))));
1002
1177
  }
1003
1178
  }, _callee5);
@@ -1031,11 +1206,19 @@ window.coreAIChatManager = function () {
1031
1206
  var failedItem = this.uploadErrors[m];
1032
1207
  var failedName = failedItem.fileName || 'File';
1033
1208
  var errorMsg = failedItem.error || 'Upload failed';
1034
- if (failedName.length > 15) failedName = failedName.substring(0, 12) + '...';
1035
- 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) + '">';
1036
- html += '<i class="fa-solid fa-circle-exclamation" style="font-size: 0.7rem;"></i> ';
1037
- html += this.escapeHtml(failedName);
1038
- 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>';
1039
1222
  html += '</span>';
1040
1223
  }
1041
1224
  if (this.isUploading) {
@@ -1195,7 +1378,7 @@ window.coreAIChatManager = function () {
1195
1378
  ((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
1196
1379
  _this3.addMessage(msg);
1197
1380
  _this3.$nextTick(function () {
1198
- renderChartsInMessage(msg);
1381
+ renderMessageMedia(msg);
1199
1382
  });
1200
1383
  });
1201
1384
 
@@ -1388,7 +1571,7 @@ window.coreAIChatManager = function () {
1388
1571
  msg.references = normalizeReferences(Object.assign({}, msg.references || {}, references || {}));
1389
1572
  updateMessagePresentation(msg, msg.references);
1390
1573
  _this3.$nextTick(function () {
1391
- renderChartsInMessage(msg);
1574
+ renderMessageMedia(msg);
1392
1575
  _this3.scrollToBottom();
1393
1576
  });
1394
1577
  }
@@ -1577,7 +1760,7 @@ window.coreAIChatManager = function () {
1577
1760
  this.hidePlaceholder();
1578
1761
  this.$nextTick(function () {
1579
1762
  // Render any pending charts once the DOM is updated
1580
- renderChartsInMessage(message);
1763
+ renderMessageMedia(message);
1581
1764
  _this6.scrollToBottom();
1582
1765
  });
1583
1766
  },
@@ -1903,7 +2086,7 @@ window.coreAIChatManager = function () {
1903
2086
  updateMessagePresentation(message, references);
1904
2087
  _this9.messages[messageIndex] = message;
1905
2088
  _this9.$nextTick(function () {
1906
- renderChartsInMessage(message);
2089
+ renderMessageMedia(message);
1907
2090
  _this9.scrollToBottom();
1908
2091
  });
1909
2092
  },
@@ -1971,7 +2154,7 @@ window.coreAIChatManager = function () {
1971
2154
  updateMessagePresentation(message, references);
1972
2155
  this.messages[messageIndex] = message;
1973
2156
  this.$nextTick(function () {
1974
- renderChartsInMessage(message);
2157
+ renderMessageMedia(message);
1975
2158
  _this0.scrollToBottom();
1976
2159
  });
1977
2160
  }
@@ -2122,18 +2305,18 @@ window.coreAIChatManager = function () {
2122
2305
  }, 0);
2123
2306
  var combined = new Uint8Array(totalLength);
2124
2307
  var offset = 0;
2125
- var _iterator6 = _createForOfIteratorHelper(this.audioChunks),
2126
- _step6;
2308
+ var _iterator8 = _createForOfIteratorHelper(this.audioChunks),
2309
+ _step8;
2127
2310
  try {
2128
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
2129
- var chunk = _step6.value;
2311
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
2312
+ var chunk = _step8.value;
2130
2313
  combined.set(chunk, offset);
2131
2314
  offset += chunk.length;
2132
2315
  }
2133
2316
  } catch (err) {
2134
- _iterator6.e(err);
2317
+ _iterator8.e(err);
2135
2318
  } finally {
2136
- _iterator6.f();
2319
+ _iterator8.f();
2137
2320
  }
2138
2321
  this.audioChunks = [];
2139
2322
  var blob = new Blob([combined], {