@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.
@@ -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.chatInteractionManager = 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 ai-chat.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.chatInteractionManager = 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
@@ -99,7 +117,7 @@ window.chatInteractionManager = function () {
99
117
  return observer;
100
118
  }
101
119
  function normalizeReference(reference) {
102
- var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$title, _ref4, _normalized$link, _ref5, _normalized$reference, _normalized$isGenerat;
120
+ var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$title, _ref4, _normalized$link, _ref5, _normalized$reference, _normalized$isGenerat, _normalized$isImage;
103
121
  if (!reference || _typeof(reference) !== 'object') {
104
122
  return null;
105
123
  }
@@ -110,6 +128,7 @@ window.chatInteractionManager = function () {
110
128
  normalized.link = sanitizeUrl((_ref4 = (_normalized$link = normalized.link) !== null && _normalized$link !== void 0 ? _normalized$link : normalized.Link) !== null && _ref4 !== void 0 ? _ref4 : null);
111
129
  normalized.referenceType = (_ref5 = (_normalized$reference = normalized.referenceType) !== null && _normalized$reference !== void 0 ? _normalized$reference : normalized.ReferenceType) !== null && _ref5 !== void 0 ? _ref5 : null;
112
130
  normalized.isGenerated = ((_normalized$isGenerat = normalized.isGenerated) !== null && _normalized$isGenerat !== void 0 ? _normalized$isGenerat : normalized.IsGenerated) === true;
131
+ normalized.isImage = ((_normalized$isImage = normalized.isImage) !== null && _normalized$isImage !== void 0 ? _normalized$isImage : normalized.IsImage) === true;
113
132
  return normalized;
114
133
  }
115
134
  function isDownloadCitationReference(reference) {
@@ -138,11 +157,38 @@ window.chatInteractionManager = function () {
138
157
  }
139
158
  return normalized;
140
159
  }
160
+
161
+ // Installed at the top of this file so it can be unit tested without a browser; see the comment there.
162
+ var expandImageMarkers = window.CoreAIChatMarkers.expandImageMarkers;
141
163
  function getCitationLabel(reference, key) {
142
164
  return reference.title || reference.text || key;
143
165
  }
166
+
167
+ // Which references are one citation, and how a repeated marker reads, are statements about strings and
168
+ // live in chat-markers.js so every chat surface numbers them the same way. Without the module each
169
+ // reference keeps an identity of its own and nothing merges, which is the numbering this script had
170
+ // before it.
171
+ var unmergedCitationCount = 0;
172
+ 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 () {
173
+ return "unmerged-".concat(++unmergedCitationCount);
174
+ };
175
+ 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) {
176
+ return content;
177
+ };
178
+ 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) {
179
+ return html;
180
+ };
181
+ 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) {
182
+ return html;
183
+ };
184
+ 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) {
185
+ return "<sup>".concat(displayIndex, "</sup>");
186
+ };
144
187
  function buildCitationDisplay(content, references) {
145
- var processedContent = (content || '').trim();
188
+ // Before anything looks for a reference key, the combined form the model tends to write is split into
189
+ // the keys it means. A key that is not found is not replaced, and an unreplaced key reaches the reader
190
+ // as a raw marker mid-sentence.
191
+ var processedContent = splitCombinedCitations((content || '').trim());
146
192
  var messageReferences = normalizeReferences(references);
147
193
  var referenceEntries = Object.entries(messageReferences);
148
194
  if (!referenceEntries.length) {
@@ -183,6 +229,8 @@ window.chatInteractionManager = function () {
183
229
  return a.index - b.index;
184
230
  });
185
231
  var citations = [];
232
+ var citationsByIdentity = new Map();
233
+ var placeholders = [];
186
234
  var displayIndex = 1;
187
235
  var _iterator = _createForOfIteratorHelper(citedRefs),
188
236
  _step;
@@ -191,28 +239,48 @@ window.chatInteractionManager = function () {
191
239
  var _step$value = _slicedToArray(_step.value, 2),
192
240
  key = _step$value[0],
193
241
  value = _step$value[1];
194
- var placeholder = "__CITE_".concat(displayIndex, "_").concat(value.index || displayIndex, "__");
242
+ var label = getCitationLabel(value, key);
243
+ var link = value.link || null;
244
+ var identity = citationIdentity(label, link);
245
+ var citation = citationsByIdentity.get(identity);
246
+
247
+ // Every reference still has its key replaced, so the text never keeps a raw key; what the merge
248
+ // changes is only how many numbers the reader is given for them.
249
+ if (!citation) {
250
+ citation = {
251
+ referenceKey: key,
252
+ referenceKeys: [],
253
+ displayIndex: displayIndex++,
254
+ label: label,
255
+ link: link,
256
+ isDownload: isDownloadCitationReference(value)
257
+ };
258
+ citationsByIdentity.set(identity, citation);
259
+ citations.push(citation);
260
+ }
261
+ citation.referenceKeys.push(key);
262
+ var placeholder = "__CITE_".concat(citation.displayIndex, "_").concat(value.index || citation.displayIndex, "__");
195
263
  processedContent = processedContent.replaceAll(key, placeholder);
196
- citations.push({
197
- referenceKey: key,
198
- displayIndex: displayIndex,
199
- label: getCitationLabel(value, key),
200
- link: value.link || null,
201
- isDownload: isDownloadCitationReference(value),
202
- placeholder: placeholder
264
+ placeholders.push({
265
+ placeholder: placeholder,
266
+ displayIndex: citation.displayIndex,
267
+ label: citation.label
203
268
  });
204
- displayIndex++;
205
269
  }
206
270
  } catch (err) {
207
271
  _iterator.e(err);
208
272
  } finally {
209
273
  _iterator.f();
210
274
  }
211
- for (var _i2 = 0, _citations = citations; _i2 < _citations.length; _i2++) {
212
- var citation = _citations[_i2];
213
- processedContent = processedContent.replaceAll(citation.placeholder, "<sup>".concat(citation.displayIndex, "</sup>"));
275
+ for (var _i2 = 0, _placeholders = placeholders; _i2 < _placeholders.length; _i2++) {
276
+ var entry = _placeholders[_i2];
277
+ processedContent = processedContent.replaceAll(entry.placeholder, citationMarkerHtml(entry.displayIndex, entry.label));
214
278
  }
215
- processedContent = processedContent.replaceAll('</sup><sup>', '</sup><sup>,</sup><sup>');
279
+
280
+ // Merged citations can leave the same number twice over a sentence; that is collapsed before the
281
+ // commas go in, or the reader is given "1,1".
282
+ processedContent = collapseRepeatedCitations(processedContent);
283
+ processedContent = separateAdjacentCitations(processedContent);
216
284
 
217
285
  // Generated files (such as exported tabular data) are always offered as a download even when
218
286
  // the model does not cite them inline, so the user never loses access to the produced file.
@@ -225,6 +293,7 @@ window.chatInteractionManager = function () {
225
293
  _value = _step2$value[1];
226
294
  citations.push({
227
295
  referenceKey: _key,
296
+ referenceKeys: [_key],
228
297
  displayIndex: displayIndex,
229
298
  label: getCitationLabel(_value, _key),
230
299
  link: _value.link || null,
@@ -252,12 +321,27 @@ window.chatInteractionManager = function () {
252
321
  if (!copyContent || !Array.isArray(citations) || citations.length === 0) {
253
322
  return copyContent;
254
323
  }
324
+
325
+ // Every key the citation absorbed is replaced, not just the first: a merged key left behind would
326
+ // reach the clipboard as the raw reference token the reader never saw on the page.
255
327
  var _iterator3 = _createForOfIteratorHelper(citations),
256
328
  _step3;
257
329
  try {
258
330
  for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
331
+ var _citation$referenceKe;
259
332
  var citation = _step3.value;
260
- copyContent = copyContent.replaceAll(citation.referenceKey, "[".concat(citation.displayIndex, "]"));
333
+ var _iterator5 = _createForOfIteratorHelper((_citation$referenceKe = citation.referenceKeys) !== null && _citation$referenceKe !== void 0 && _citation$referenceKe.length ? citation.referenceKeys : [citation.referenceKey]),
334
+ _step5;
335
+ try {
336
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
337
+ var key = _step5.value;
338
+ copyContent = copyContent.replaceAll(key, "[".concat(citation.displayIndex, "]"));
339
+ }
340
+ } catch (err) {
341
+ _iterator5.e(err);
342
+ } finally {
343
+ _iterator5.f();
344
+ }
261
345
  }
262
346
  } catch (err) {
263
347
  _iterator3.e(err);
@@ -286,13 +370,20 @@ window.chatInteractionManager = function () {
286
370
  function updateMessagePresentation(message, references) {
287
371
  var messageReferences = normalizeReferences(references !== null && references !== void 0 ? references : message.references);
288
372
  var rawContent = typeof message.rawContent === 'string' ? message.rawContent : typeof message.content === 'string' ? message.content : '';
289
- var citationDisplay = buildCitationDisplay(rawContent, messageReferences);
373
+ // Figure markers become images before anything else reads the content: the citation pass looks for its
374
+ // own keys in what is left, and the markdown parser then sees an ordinary image.
375
+ var displayContent = expandImageMarkers(rawContent, messageReferences);
376
+ var citationDisplay = buildCitationDisplay(displayContent, messageReferences);
290
377
  message.rawContent = rawContent;
291
378
  message.content = rawContent;
292
379
  message.displayContent = citationDisplay.content;
293
380
  message.references = messageReferences;
294
381
  message.citationReferences = citationDisplay.citations;
295
- message.copyContent = buildCopyContent(rawContent, citationDisplay.citations);
382
+ // Copied from the expanded content, so a copied answer carries the picture's link rather than a label
383
+ // that means nothing outside this page.
384
+ // Split here too: the copy is built from the expanded content rather than the display path's output,
385
+ // so a combined marker would otherwise survive into the clipboard as a raw key.
386
+ message.copyContent = buildCopyContent(splitCombinedCitations(displayContent), citationDisplay.citations);
296
387
  message.htmlContent = parseMarkdownContent(citationDisplay.content, message);
297
388
  return message;
298
389
  }
@@ -336,7 +427,12 @@ window.chatInteractionManager = function () {
336
427
  if (!src) return '';
337
428
  var alt = data.text || defaultConfig.generatedImageAltText;
338
429
  var maxWidth = defaultConfig.generatedImageMaxWidth;
339
- 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>");
430
+ // Spans, not divs. A figure marker is often written mid-sentence -- "Figures [fig:1], [fig:2] and
431
+ // [fig:3] show..." -- and a block element cannot sit inside a paragraph: the browser closes the <p>
432
+ // at the first one, so the sentence is torn into fragments and the commas between the markers are
433
+ // left stranded on their own lines. Inline-block keeps the figure looking exactly the same while
434
+ // remaining something a paragraph can legally contain.
435
+ 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>");
340
436
  };
341
437
 
342
438
  // Chart counter for unique IDs
@@ -351,6 +447,11 @@ window.chatInteractionManager = function () {
351
447
  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>";
352
448
  }
353
449
 
450
+ // The marker itself is read by chat-markers.js, shared with every other chat surface, so the three of them
451
+ // cannot disagree about what a marker is. Everything below -- the container, the id scheme, the Chart.js
452
+ // call -- is this surface's own and legitimately differs from the others.
453
+ var findChartMarker = window.CoreAIChatMarkers.findChartMarker;
454
+
354
455
  // Register [chart:{...json...}] as a native marked block extension so the
355
456
  // markdown parser handles chart markers inline with surrounding text.
356
457
  marked.use({
@@ -362,7 +463,7 @@ window.chatInteractionManager = function () {
362
463
  return idx >= 0 ? idx : undefined;
363
464
  },
364
465
  tokenizer: function tokenizer(src) {
365
- var extracted = tryExtractChartMarker(src);
466
+ var extracted = findChartMarker(src);
366
467
  if (!extracted || extracted.startIndex !== 0) {
367
468
  return undefined;
368
469
  }
@@ -385,69 +486,60 @@ window.chatInteractionManager = function () {
385
486
  }]
386
487
  });
387
488
 
388
- // Extract a [chart:{...json...}] marker. This avoids regex issues with nested brackets.
389
- function tryExtractChartMarker(text) {
390
- var token = '[chart:';
391
- var start = text.indexOf(token);
392
- if (start < 0) {
393
- return null;
489
+ // An image the assistant named but the server will not serve. The address is written by a language
490
+ // model from what retrieval handed it, so a mistyped or invented one is possible, and the browser's
491
+ // default for that is a broken-image icon captioned with the alt text -- which reads as a real picture
492
+ // that failed to arrive. Saying plainly that it could not be loaded is both truer and quieter.
493
+ //
494
+ // Wired here rather than as an inline onerror attribute because message bodies go through DOMPurify,
495
+ // which strips event-handler attributes.
496
+ function markBrokenImagesInMessage() {
497
+ // Scanning the document rather than one message, because the value threaded through the render
498
+ // path is the message DATA object -- the thing carrying content, references and pending charts --
499
+ // and not the element it was rendered into. Wiring is idempotent via the data-broken-wired mark,
500
+ // so re-scanning after each render costs a query over the handful of images already handled.
501
+ if (typeof document === 'undefined' || typeof requestAnimationFrame !== 'function') {
502
+ return;
394
503
  }
395
504
 
396
- // Find JSON object boundary by balancing braces
397
- var jsonStart = start + token.length;
398
- var i = jsonStart;
399
- while (i < text.length && (text[i] === ' ' || text[i] === '\n' || text[i] === '\r' || text[i] === '\t')) {
400
- i++;
401
- }
402
- if (i >= text.length || text[i] !== '{') {
403
- return null;
404
- }
405
- var depth = 0;
406
- var inString = false;
407
- var escape = false;
408
- for (; i < text.length; i++) {
409
- var ch = text[i];
410
- if (inString) {
411
- if (escape) {
412
- escape = false;
413
- continue;
414
- }
415
- if (ch === '\\') {
416
- escape = true;
417
- continue;
418
- }
419
- if (ch === '"') {
420
- inString = false;
421
- }
422
- continue;
423
- }
424
- if (ch === '"') {
425
- inString = true;
426
- continue;
427
- }
428
- if (ch === '{') {
429
- depth++;
430
- } else if (ch === '}') {
431
- depth--;
432
- if (depth === 0) {
433
- var jsonEnd = i;
434
- // Expect closing bracket after JSON
435
- var closeBracketIndex = text.indexOf(']', jsonEnd + 1);
436
- if (closeBracketIndex < 0) {
437
- return null;
505
+ // Deferred for the same reason the charts below are: the render that produced these elements has
506
+ // not necessarily been flushed to the DOM when this runs.
507
+ requestAnimationFrame(wireBrokenImageHandlers);
508
+ }
509
+ function wireBrokenImageHandlers() {
510
+ var images = document.querySelectorAll('.generated-image-container img:not([data-broken-wired])');
511
+ var _iterator6 = _createForOfIteratorHelper(images),
512
+ _step6;
513
+ try {
514
+ var _loop = function _loop() {
515
+ var image = _step6.value;
516
+ image.setAttribute('data-broken-wired', 'true');
517
+ image.addEventListener('error', function () {
518
+ var container = image.closest('.generated-image-container');
519
+ if (!container || container.dataset.brokenHandled) {
520
+ return;
438
521
  }
439
- var json = text.substring(jsonStart, jsonEnd + 1).trim();
440
- return {
441
- startIndex: start,
442
- endIndex: closeBracketIndex + 1,
443
- json: json
444
- };
445
- }
522
+ container.dataset.brokenHandled = 'true';
523
+ container.hidden = true;
524
+ var note = document.createElement('p');
525
+ note.className = 'text-muted small fst-italic mb-2';
526
+ note.textContent = image.alt ? "(the picture \"".concat(image.alt, "\" could not be loaded)") : '(that picture could not be loaded)';
527
+ container.insertAdjacentElement('afterend', note);
528
+ }, {
529
+ once: true
530
+ });
531
+ };
532
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
533
+ _loop();
446
534
  }
535
+ } catch (err) {
536
+ _iterator6.e(err);
537
+ } finally {
538
+ _iterator6.f();
447
539
  }
448
- return null;
449
540
  }
450
- function renderChartsInMessage(message) {
541
+ function renderMessageMedia(message) {
542
+ markBrokenImagesInMessage();
451
543
  if (!message || !message._pendingCharts || !message._pendingCharts.length) {
452
544
  return;
453
545
  }
@@ -459,11 +551,11 @@ window.chatInteractionManager = function () {
459
551
  // Defer to requestAnimationFrame so the browser has fully laid out the
460
552
  // canvas elements before Chart.js reads their dimensions.
461
553
  requestAnimationFrame(function () {
462
- var _iterator5 = _createForOfIteratorHelper(charts),
463
- _step5;
554
+ var _iterator7 = _createForOfIteratorHelper(charts),
555
+ _step7;
464
556
  try {
465
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
466
- var c = _step5.value;
557
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
558
+ var c = _step7.value;
467
559
  var canvas = document.getElementById(c.chartId);
468
560
  if (!canvas) {
469
561
  continue;
@@ -496,9 +588,9 @@ window.chatInteractionManager = function () {
496
588
  }
497
589
  }
498
590
  } catch (err) {
499
- _iterator5.e(err);
591
+ _iterator7.e(err);
500
592
  } finally {
501
- _iterator5.f();
593
+ _iterator7.f();
502
594
  }
503
595
  });
504
596
  }
@@ -506,11 +598,20 @@ window.chatInteractionManager = function () {
506
598
  // Parse markdown content via marked (which natively handles [chart:...] markers
507
599
  // through the registered extension) and collect pending chart configs for later
508
600
  // Chart.js rendering.
601
+ // A markdown table becomes a bare <table>, which Bootstrap leaves completely unstyled: it renders
602
+ // as cramped, borderless text that reads as plain output rather than a table. Adding the framework's
603
+ // own classes styles it wherever Bootstrap is loaded, and the wrapper lets a wide table scroll on
604
+ // its own instead of stretching the conversation. Markdown tables cannot nest, so the plain
605
+ // replacement is safe. This page renders markdown through its own pipeline, so the same treatment
606
+ // is applied here as in the shared chat script.
607
+ function styleMarkdownTables(html) {
608
+ 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>');
609
+ }
509
610
  function parseMarkdownContent(content, message) {
510
611
  _pendingCharts = [];
511
- var html = marked.parse(content, {
612
+ var html = styleMarkdownTables(marked.parse(content, {
512
613
  renderer: renderer
513
- });
614
+ }));
514
615
  message._pendingCharts = _pendingCharts.length > 0 ? _toConsumableArray(_pendingCharts) : [];
515
616
  return DOMPurify.sanitize(html, {
516
617
  ADD_TAGS: ['canvas'],
@@ -637,7 +738,7 @@ window.chatInteractionManager = function () {
637
738
  ((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
638
739
  _this.addMessage(msg);
639
740
  _this.$nextTick(function () {
640
- renderChartsInMessage(msg);
741
+ renderMessageMedia(msg);
641
742
  });
642
743
  });
643
744
  });
@@ -795,7 +896,7 @@ window.chatInteractionManager = function () {
795
896
  msg.references = normalizeReferences(Object.assign({}, msg.references || {}, references || {}));
796
897
  updateMessagePresentation(msg, msg.references);
797
898
  _this.$nextTick(function () {
798
- renderChartsInMessage(msg);
899
+ renderMessageMedia(msg);
799
900
  _this.scrollToBottom();
800
901
  });
801
902
  }
@@ -914,7 +1015,7 @@ window.chatInteractionManager = function () {
914
1015
  this.hidePlaceholder();
915
1016
  this.$nextTick(function () {
916
1017
  // Render any pending charts once the DOM is updated
917
- renderChartsInMessage(message);
1018
+ renderMessageMedia(message);
918
1019
  _this3.scrollToBottom();
919
1020
  });
920
1021
  },
@@ -1082,7 +1183,7 @@ window.chatInteractionManager = function () {
1082
1183
  updateMessagePresentation(message, references);
1083
1184
  _this5.messages[messageIndex] = message;
1084
1185
  _this5.$nextTick(function () {
1085
- renderChartsInMessage(message);
1186
+ renderMessageMedia(message);
1086
1187
  _this5.scrollToBottom();
1087
1188
  });
1088
1189
  },
@@ -1141,7 +1242,7 @@ window.chatInteractionManager = function () {
1141
1242
  updateMessagePresentation(message, references);
1142
1243
  this.messages[messageIndex] = message;
1143
1244
  this.$nextTick(function () {
1144
- renderChartsInMessage(message);
1245
+ renderMessageMedia(message);
1145
1246
  _this6.scrollToBottom();
1146
1247
  });
1147
1248
  }
@@ -1294,18 +1395,18 @@ window.chatInteractionManager = function () {
1294
1395
  }, 0);
1295
1396
  var combined = new Uint8Array(totalLength);
1296
1397
  var offset = 0;
1297
- var _iterator6 = _createForOfIteratorHelper(this.audioChunks),
1298
- _step6;
1398
+ var _iterator8 = _createForOfIteratorHelper(this.audioChunks),
1399
+ _step8;
1299
1400
  try {
1300
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
1301
- var chunk = _step6.value;
1401
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
1402
+ var chunk = _step8.value;
1302
1403
  combined.set(chunk, offset);
1303
1404
  offset += chunk.length;
1304
1405
  }
1305
1406
  } catch (err) {
1306
- _iterator6.e(err);
1407
+ _iterator8.e(err);
1307
1408
  } finally {
1308
- _iterator6.f();
1409
+ _iterator8.f();
1309
1410
  }
1310
1411
  this.audioChunks = [];
1311
1412
  var blob = new Blob([combined], {
@@ -2144,7 +2245,7 @@ window.chatInteractionManager = function () {
2144
2245
  }
2145
2246
  function _loadVoices() {
2146
2247
  _loadVoices = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
2147
- var deploymentName, realtime, response, result, grouped, _iterator7, _step7, voice, groupName, _t2;
2248
+ var deploymentName, realtime, response, contentType, result, grouped, _iterator9, _step9, voice, groupName, _t2;
2148
2249
  return _regenerator().w(function (_context4) {
2149
2250
  while (1) switch (_context4.p = _context4.n) {
2150
2251
  case 0:
@@ -2167,21 +2268,29 @@ window.chatInteractionManager = function () {
2167
2268
  });
2168
2269
  case 3:
2169
2270
  response = _context4.v;
2170
- _context4.n = 4;
2171
- return response.json();
2271
+ contentType = (response.headers.get('content-type') || '').toLowerCase(); // A sign-in redirect answers with HTML and response.ok, so this has to be checked
2272
+ // rather than parsed: the default voice is a fine outcome, a parser error is not.
2273
+ if (!(!response.ok || contentType.indexOf('json') < 0)) {
2274
+ _context4.n = 4;
2275
+ break;
2276
+ }
2277
+ return _context4.a(2);
2172
2278
  case 4:
2279
+ _context4.n = 5;
2280
+ return response.json();
2281
+ case 5:
2173
2282
  result = _context4.v;
2174
2283
  if (!(!result || !Array.isArray(result.voices))) {
2175
- _context4.n = 5;
2284
+ _context4.n = 6;
2176
2285
  break;
2177
2286
  }
2178
2287
  return _context4.a(2);
2179
- case 5:
2288
+ case 6:
2180
2289
  grouped = new Map();
2181
- _iterator7 = _createForOfIteratorHelper(result.voices);
2290
+ _iterator9 = _createForOfIteratorHelper(result.voices);
2182
2291
  try {
2183
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
2184
- voice = _step7.value;
2292
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
2293
+ voice = _step9.value;
2185
2294
  groupName = voice.gender || 'Voices';
2186
2295
  if (!grouped.has(groupName)) {
2187
2296
  grouped.set(groupName, []);
@@ -2189,9 +2298,9 @@ window.chatInteractionManager = function () {
2189
2298
  grouped.get(groupName).push(voice);
2190
2299
  }
2191
2300
  } catch (err) {
2192
- _iterator7.e(err);
2301
+ _iterator9.e(err);
2193
2302
  } finally {
2194
- _iterator7.f();
2303
+ _iterator9.f();
2195
2304
  }
2196
2305
  Array.from(grouped.keys()).sort(function (a, b) {
2197
2306
  return a.localeCompare(b);
@@ -2207,15 +2316,15 @@ window.chatInteractionManager = function () {
2207
2316
  });
2208
2317
  voiceSelect.appendChild(optgroup);
2209
2318
  });
2210
- _context4.n = 7;
2319
+ _context4.n = 8;
2211
2320
  break;
2212
- case 6:
2213
- _context4.p = 6;
2214
- _t2 = _context4.v;
2215
2321
  case 7:
2322
+ _context4.p = 7;
2323
+ _t2 = _context4.v;
2324
+ case 8:
2216
2325
  return _context4.a(2);
2217
2326
  }
2218
- }, _callee4, null, [[2, 6]]);
2327
+ }, _callee4, null, [[2, 7]]);
2219
2328
  }));
2220
2329
  return _loadVoices.apply(this, arguments);
2221
2330
  }
@@ -3141,9 +3250,20 @@ window.chatInteractionDocumentManager = function () {
3141
3250
  showUploadStatus('Failed to remove document.', 'text-danger');
3142
3251
  return _context8.a(2);
3143
3252
  case 4:
3144
- _context8.n = 5;
3145
- return response.json();
3253
+ if (!((response.headers.get('content-type') || '').toLowerCase().indexOf('json') < 0)) {
3254
+ _context8.n = 5;
3255
+ break;
3256
+ }
3257
+ console.error('Remove document: response was not JSON.', {
3258
+ status: response.status,
3259
+ redirected: response.redirected
3260
+ });
3261
+ showUploadStatus(response.redirected ? 'You may not be signed in, or you do not have permission for this. Please reload and try again.' : 'Failed to remove document.', 'text-danger');
3262
+ return _context8.a(2);
3146
3263
  case 5:
3264
+ _context8.n = 6;
3265
+ return response.json();
3266
+ case 6:
3147
3267
  result = _context8.v;
3148
3268
  serverDocuments = (Array.isArray(result.documents) ? result.documents : []).map(normalizeDocumentInfo).filter(function (document) {
3149
3269
  return document && document.documentId;
@@ -3153,17 +3273,17 @@ window.chatInteractionDocumentManager = function () {
3153
3273
  });
3154
3274
  renderDocuments();
3155
3275
  showUploadStatus('Document removed.', 'text-success');
3156
- _context8.n = 7;
3276
+ _context8.n = 8;
3157
3277
  break;
3158
- case 6:
3159
- _context8.p = 6;
3278
+ case 7:
3279
+ _context8.p = 7;
3160
3280
  _t6 = _context8.v;
3161
3281
  console.error('Remove failed:', _t6);
3162
3282
  showUploadStatus('Failed to remove document.', 'text-danger');
3163
- case 7:
3283
+ case 8:
3164
3284
  return _context8.a(2);
3165
3285
  }
3166
- }, _callee8, null, [[1, 6]]);
3286
+ }, _callee8, null, [[1, 7]]);
3167
3287
  }));
3168
3288
  return _removeDocument.apply(this, arguments);
3169
3289
  }