@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.
@@ -0,0 +1,439 @@
1
+ /*
2
+ ** NOTE: This file is generated by Gulp and should not be edited directly!
3
+ ** Any changes made directly to this file will be overwritten next time its asset group is processed by Gulp.
4
+ */
5
+
6
+ function _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); }
7
+ /*
8
+ * Markers the host expands on the model's behalf.
9
+ *
10
+ * Retrieval hands the model a short label such as [fig:1] rather than a figure's address, because a model
11
+ * asked to reproduce a long opaque identifier reproduces its shape and varies the digits instead --
12
+ * producing addresses that look right and resolve to nothing. The model therefore never types an address,
13
+ * and this turns the label it does type back into the picture.
14
+ *
15
+ * The chart tool works the same way from the other end: it hands back a [chart:{...}] marker and asks for it
16
+ * verbatim, and the host turns that marker into a canvas. Reading a marker is a statement about a string, so
17
+ * it lives here; drawing one is not, so each surface still does its own drawing.
18
+ *
19
+ * Loaded on its own so every chat surface shares one implementation: the two shared chat scripts and the
20
+ * MVC chat-interaction view, which renders its markdown with its own inline marked setup.
21
+ */
22
+ window.CoreAIChatMarkers = window.CoreAIChatMarkers || function () {
23
+ 'use strict';
24
+
25
+ // Used when a figure has no caption, so the image is still announced as something.
26
+ var defaultImageAltText = 'Figure';
27
+
28
+ /*
29
+ * Makes a caption safe to sit inside the alt text of ![alt](link).
30
+ *
31
+ * Captions really do contain brackets, and an unescaped one closes the alt text early: a caption of
32
+ * "Figure 2 (revised) [draft]" would end the image after "Figure 2 (revised) " and leave the rest of the
33
+ * syntax on the page as markup. Parentheses are harmless there, only the brackets are. A caption that spans
34
+ * lines would end the paragraph the image lives in, so its whitespace is collapsed to single spaces.
35
+ */
36
+ function escapeImageAltText(title) {
37
+ var text = typeof title === 'string' ? title.replace(/\s+/g, ' ').trim() : '';
38
+ if (!text) {
39
+ return defaultImageAltText;
40
+ }
41
+
42
+ // The backslash is escaped as well, or a caption ending in one would escape the bracket that closes
43
+ // the alt text and the image would swallow the rest of the line.
44
+ return text.replace(/[\\\[\]]/g, '\\$&');
45
+ }
46
+
47
+ /*
48
+ * Makes a link safe to sit inside the destination of ![alt](link): the few characters that would end the
49
+ * destination early are percent-encoded, which leaves the URL addressing the same resource. The link is
50
+ * still run through sanitizeUrl by the image renderer, so this is about parsing, not about safety.
51
+ */
52
+ function encodeImageLink(link) {
53
+ return link.replace(/[\s()<>"\\]/g, function (character) {
54
+ return '%' + character.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0');
55
+ });
56
+ }
57
+
58
+ /*
59
+ * Returns the content with every marker the reference map describes as a servable image replaced by
60
+ * markdown image syntax, so the ordinary image renderer draws it with its thumbnail, its download button
61
+ * and its size cap. Pure: nothing here touches the DOM or the message.
62
+ *
63
+ * A marker is replaced only when its reference is an image and carries a link. Anything else is left
64
+ * exactly as written -- a marker the model invented for a figure that was never in the results, or one
65
+ * whose picture the host cannot serve, reaches the reader as the few characters the model typed rather
66
+ * than as a broken image.
67
+ */
68
+ function expandImageMarkers(content, references) {
69
+ if (typeof content !== 'string' || !content) {
70
+ return typeof content === 'string' ? content : '';
71
+ }
72
+ if (!references || _typeof(references) !== 'object') {
73
+ return content;
74
+ }
75
+ var expanded = content;
76
+ var _loop = function _loop() {
77
+ var _reference$isImage, _reference$link, _reference$title;
78
+ var marker = _Object$keys[_i];
79
+ var reference = references[marker];
80
+ if (!marker || !reference || _typeof(reference) !== 'object') {
81
+ return 0; // continue
82
+ }
83
+ if (((_reference$isImage = reference.isImage) !== null && _reference$isImage !== void 0 ? _reference$isImage : reference.IsImage) !== true) {
84
+ return 0; // continue
85
+ }
86
+ var rawLink = (_reference$link = reference.link) !== null && _reference$link !== void 0 ? _reference$link : reference.Link;
87
+ var link = typeof rawLink === 'string' ? rawLink.trim() : '';
88
+ if (!link) {
89
+ return 0; // continue
90
+ }
91
+ var image = "![".concat(escapeImageAltText((_reference$title = reference.title) !== null && _reference$title !== void 0 ? _reference$title : reference.Title), "](").concat(encodeImageLink(link), ")");
92
+
93
+ // Every occurrence, and through a replacer function so a '$' in a caption or a link is not read as
94
+ // a replacement pattern.
95
+ expanded = expanded.replaceAll(marker, function () {
96
+ return image;
97
+ });
98
+ },
99
+ _ret;
100
+ for (var _i = 0, _Object$keys = Object.keys(references); _i < _Object$keys.length; _i++) {
101
+ _ret = _loop();
102
+ if (_ret === 0) continue;
103
+ }
104
+ return expanded;
105
+ }
106
+
107
+ // The marker the chart tool emits and asks the model to repeat verbatim, braces and all.
108
+ var chartMarkerPrefix = '[chart:';
109
+
110
+ // What a citation the model typed opens with. The reference map is keyed by the whole marker, so this is
111
+ // also how a rewritten one is put back together.
112
+ var citationMarkerPrefix = '[doc:';
113
+
114
+ /*
115
+ * How much text may sit between "[doc:" and its "]" before it stops being a citation.
116
+ *
117
+ * A list of reference numbers is short: even ten of them, written the long way, is under a hundred
118
+ * characters. The limit is what keeps the work per marker fixed, and so the whole pass linear -- without
119
+ * it, an opening bracket whose "]" is thousands of characters away is examined in full, and a paragraph
120
+ * full of such openings is examined once per opening.
121
+ */
122
+ var maxCitationMarkerLength = 512;
123
+ function isMarkerWhitespace(character) {
124
+ return character === ' ' || character === '\n' || character === '\r' || character === '\t';
125
+ }
126
+
127
+ /*
128
+ * Reads the marker that begins at startIndex, or returns null when what begins there is not one.
129
+ *
130
+ * The configuration is JSON and JSON nests, so the end of the object is found by counting braces rather
131
+ * than by a regular expression: an expression that stopped at the first '}' would truncate every chart
132
+ * whose options carry an object of their own, which is all of them. The count has to know when it is
133
+ * inside a string as well -- an axis label really does read "Q1 { Q2", and a counter blind to strings
134
+ * would end the object in the middle of the label and hand the renderer a fragment. Escapes are tracked
135
+ * for the same reason, so a \" inside a label does not look like the end of it.
136
+ */
137
+ function readChartMarkerAt(text, startIndex) {
138
+ var index = startIndex + chartMarkerPrefix.length;
139
+ while (index < text.length && isMarkerWhitespace(text[index])) {
140
+ index++;
141
+ }
142
+ if (index >= text.length || text[index] !== '{') {
143
+ return null;
144
+ }
145
+ var jsonStart = index;
146
+ var depth = 0;
147
+ var inString = false;
148
+ var escaped = false;
149
+ for (; index < text.length; index++) {
150
+ var character = text[index];
151
+ if (inString) {
152
+ if (escaped) {
153
+ escaped = false;
154
+ } else if (character === '\\') {
155
+ escaped = true;
156
+ } else if (character === '"') {
157
+ inString = false;
158
+ }
159
+ continue;
160
+ }
161
+ if (character === '"') {
162
+ inString = true;
163
+ continue;
164
+ }
165
+ if (character === '{') {
166
+ depth++;
167
+ continue;
168
+ }
169
+ if (character !== '}') {
170
+ continue;
171
+ }
172
+ depth--;
173
+ if (depth > 0) {
174
+ continue;
175
+ }
176
+ var jsonEnd = index + 1;
177
+ var closeIndex = jsonEnd;
178
+ while (closeIndex < text.length && isMarkerWhitespace(text[closeIndex])) {
179
+ closeIndex++;
180
+ }
181
+
182
+ // Only whitespace may sit between the object and the bracket that closes the marker. Hunting
183
+ // further ahead for a ']' would let a marker whose bracket the model dropped reach the one
184
+ // belonging to something else, and everything in between -- a paragraph of the answer, the next
185
+ // marker -- would be swallowed into the span the caller removes from the text.
186
+ if (closeIndex >= text.length || text[closeIndex] !== ']') {
187
+ return null;
188
+ }
189
+ return {
190
+ startIndex: startIndex,
191
+ endIndex: closeIndex + 1,
192
+ json: text.substring(jsonStart, jsonEnd)
193
+ };
194
+ }
195
+ return null;
196
+ }
197
+
198
+ /*
199
+ * Finds the first [chart:{...}] marker in the content and returns where it starts, where it ends and the
200
+ * JSON between, or null when there is no marker. Pure: it knows nothing of canvases, element ids or
201
+ * Chart.js, because the three chat surfaces draw a chart differently and agree only on what a marker is.
202
+ *
203
+ * Text that merely looks like a marker is not one, and none of these return a span: "[chart:" with no
204
+ * object after it, an object whose braces never balance, an object the model never closed with ']'. Each
205
+ * reaches the reader as the text the model typed, which is what the caller does with a null.
206
+ */
207
+ function findChartMarker(text) {
208
+ if (typeof text !== 'string' || !text) {
209
+ return null;
210
+ }
211
+ var searchFrom = 0;
212
+ for (;;) {
213
+ var start = text.indexOf(chartMarkerPrefix, searchFrom);
214
+ if (start < 0) {
215
+ return null;
216
+ }
217
+ var marker = readChartMarkerAt(text, start);
218
+ if (marker) {
219
+ return marker;
220
+ }
221
+
222
+ // Something that opened like a marker and was not one must not hide a real one further on, so the
223
+ // search carries on past it rather than giving up at the first disappointment.
224
+ searchFrom = start + 1;
225
+ }
226
+ }
227
+
228
+ /*
229
+ * Splits a citation marker the model wrote as one bracket into the separate markers it means.
230
+ *
231
+ * A reference is keyed by the literal string the model is asked to type -- "[doc:1]" -- and the renderer
232
+ * replaces exactly that. Models routinely gather several into one bracket instead: "[doc:1, doc:2]". No
233
+ * key matches that, so nothing is replaced and the reader is shown the raw marker in the middle of a
234
+ * sentence, while the citations the model happened to write singly render correctly beside it.
235
+ *
236
+ * Rewriting the combined form into the separate markers is what makes the rest of the pipeline see them.
237
+ * Both spellings are accepted, since a model that writes "[doc:1, doc:2]" will also write "[doc:1, 2]".
238
+ * A lone "[doc:1]" is left exactly as it is.
239
+ */
240
+ function splitCombinedCitations(content) {
241
+ if (typeof content !== 'string' || !content) {
242
+ return '';
243
+ }
244
+ var result = '';
245
+ var from = 0;
246
+
247
+ // The closing bracket last searched for. A ']' found from an earlier position is still the next one
248
+ // from here, because there was none in between -- so text like "[doc:x[doc:x[doc:x...]" is walked once
249
+ // rather than rescanned from every false start, which would make the whole pass quadratic.
250
+ var knownClose = -1;
251
+ for (;;) {
252
+ var start = content.indexOf(citationMarkerPrefix, from);
253
+ if (start < 0) {
254
+ return result + content.slice(from);
255
+ }
256
+ var end = knownClose >= start ? knownClose : content.indexOf(']', start);
257
+ knownClose = end;
258
+ if (end < 0) {
259
+ // Nothing closes it, so nothing here is a marker. The rest is returned as it was written.
260
+ return result + content.slice(from);
261
+ }
262
+ var numbers = end - start <= maxCitationMarkerLength ? readCitationNumbers(content.slice(start + citationMarkerPrefix.length, end)) : null;
263
+ if (!numbers) {
264
+ // Whatever this opened, it was not a list of references. The search carries on from just
265
+ // after the prefix rather than past the bracket, because the ']' that was found may belong to
266
+ // a real marker further along -- "[doc:1, and later [doc:3, doc:4]" closes the second one, and
267
+ // skipping to it would hide it.
268
+ result += content.slice(from, start + citationMarkerPrefix.length);
269
+ from = start + citationMarkerPrefix.length;
270
+ continue;
271
+ }
272
+
273
+ // Rewritten whenever the bracket does not already read as the canonical marker: several numbers
274
+ // in one bracket, or a single number carrying a locator the model appended. A bare "[doc:1]"
275
+ // rewrites to itself, so it is passed through untouched rather than rebuilt.
276
+ var canonical = numbers.map(function (number) {
277
+ return citationMarkerPrefix + number + ']';
278
+ }).join('');
279
+ var original = content.slice(start, end + 1);
280
+ result += content.slice(from, start);
281
+ result += canonical === original ? original : canonical;
282
+ from = end + 1;
283
+ }
284
+ }
285
+
286
+ /*
287
+ * Reads the numbers out of the inside of a citation bracket, or returns null when it does not hold a list
288
+ * of them.
289
+ *
290
+ * Deliberately not a regular expression over the whole bracket. The pattern that reads naturally --
291
+ * optional spaces on both sides of each comma, repeated -- gives the engine several ways to match the same
292
+ * text, and on a bracket that never closes it explores all of them: CodeQL flagged it as exponential, and
293
+ * it was. A model writing "[doc:9, 9, 9, 9..." and forgetting the bracket is not a hostile act, and it is
294
+ * a reader's own tab that stops responding.
295
+ *
296
+ * Splitting on the comma and checking each part on its own is linear, and each pattern below is anchored
297
+ * over a single character class, so neither can backtrack at all.
298
+ */
299
+ /*
300
+ * A locator a model appends to a citation instead of writing the bare marker it was asked for: "pages
301
+ * 13-15", "page 4", "pp. 3-5", "pg 7". It is not a citation number and the reference map holds nothing
302
+ * under it, so before this it made the whole marker unreadable and "[doc:1, pages 13-15]" reached the
303
+ * reader as those characters.
304
+ *
305
+ * Dropped rather than rendered. The page a citation points at is already carried by the reference the
306
+ * number resolves to, which is the one the host looked up rather than the one the model recalled.
307
+ */
308
+ var citationLocatorPattern = /^(?:pages?|pp?g?|pp)\.?\s*[\d\s‐-―,&+-]*$/i;
309
+ function readCitationNumbers(interior) {
310
+ var parts = interior.split(',');
311
+ var numbers = [];
312
+ for (var index = 0; index < parts.length; index++) {
313
+ // Only the parts after the first may repeat the prefix: "[doc:1, doc:2]" and "[doc:1, 2]" are both
314
+ // written by models, "[doc:doc:1]" is not.
315
+ var part = index === 0 ? parts[index].trim() : parts[index].trim().replace(/^doc:\s*/, '');
316
+ if (/^\d+$/.test(part)) {
317
+ numbers.push(part);
318
+ continue;
319
+ }
320
+
321
+ // A locator is allowed only after a number has been read, so "[doc:pages 3]" is still not a
322
+ // citation -- something that names no document is not one whatever follows it.
323
+ if (numbers.length > 0 && citationLocatorPattern.test(part)) {
324
+ continue;
325
+ }
326
+ return null;
327
+ }
328
+ return numbers.length > 0 ? numbers : null;
329
+ }
330
+
331
+ /*
332
+ * The identity of a citation as a reader meets it: the line it prints, and where it points.
333
+ *
334
+ * Retrieval returns one reference per chunk, so a single article that answered a question through three of
335
+ * its chunks arrives as three references. Numbering those separately prints "1,2,3" over the sentence and
336
+ * then lists the same title three times, which tells the reader there are three sources corroborating the
337
+ * claim when there is one. Two references that would print the same line and lead to the same place are
338
+ * one citation, and are numbered once.
339
+ *
340
+ * Case and surrounding whitespace are not part of the identity, because they are not part of what the
341
+ * reader sees as different. Anything else -- a different title, a different page, a link to one figure
342
+ * rather than another -- keeps the citations apart, since that is a distinction the reader can act on.
343
+ */
344
+ function citationIdentity(label, link) {
345
+ var text = typeof label === 'string' ? label.replace(/\s+/g, ' ').trim().toLowerCase() : '';
346
+ var target = typeof link === 'string' ? link.trim().toLowerCase() : '';
347
+
348
+ // A null byte cannot occur in either part, so no pair of values can collide across the boundary:
349
+ // without it, label "a" + link "bc" and label "ab" + link "c" would be the same identity.
350
+ return text + '' + target;
351
+ }
352
+
353
+ /*
354
+ * Collapses a run of identical citation markers into one.
355
+ *
356
+ * Once two references share a number, a sentence that cited both ends with that number twice over --
357
+ * "1,1" -- which reads as a typo. Only an immediately repeated marker is removed: the same source cited
358
+ * again later in the paragraph is a separate citation of it and keeps its marker.
359
+ *
360
+ * The marker is matched with whatever attributes it carries, because the number is what makes two of them
361
+ * the same citation; a pattern written for a bare <sup> would silently stop collapsing the moment a
362
+ * marker gained a tooltip.
363
+ *
364
+ * What separates the two markers is swallowed with them, because the model writes its own punctuation
365
+ * between references -- "[ref1], [ref2]" -- and once both carry the same number that comma is left
366
+ * between a number and itself, printing "1,1". Only a separator that was joining the two citations is
367
+ * eaten: whitespace, a comma, or the marker the comma rule inserts. Anything else means the second
368
+ * marker is a fresh citation later in the sentence, and it is left alone.
369
+ */
370
+ var citationSeparator = '(?:\\s*(?:,|<sup(?:\\s[^>]*)?>,<\\/sup>)?\\s*)';
371
+ var repeatedCitationPattern = new RegExp('(<sup(?:\\s[^>]*)?>(\\d+)<\\/sup>)(?:' + citationSeparator + '<sup(?:\\s[^>]*)?>\\2<\\/sup>)+', 'g');
372
+ function collapseRepeatedCitations(html) {
373
+ if (typeof html !== 'string' || !html) {
374
+ return '';
375
+ }
376
+ return html.replace(repeatedCitationPattern, '$1');
377
+ }
378
+
379
+ /*
380
+ * Puts a comma between two citation markers that ended up side by side, so "12" reads as two sources
381
+ * rather than as the twelfth.
382
+ *
383
+ * Only the boundary between the markers is matched, so whatever attributes either one carries are left
384
+ * untouched. The inserted marker is not itself rescanned, since a replace walks the string it was given.
385
+ */
386
+ function separateAdjacentCitations(html) {
387
+ if (typeof html !== 'string' || !html) {
388
+ return '';
389
+ }
390
+ return html.replace(/<\/sup><sup/g, '</sup><sup>,</sup><sup');
391
+ }
392
+
393
+ // Escapes the few characters that would end an HTML attribute early, so a caption containing a quote
394
+ // cannot introduce markup of its own.
395
+ function escapeAttributeValue(text) {
396
+ return text.replace(/[&<>"']/g, function (character) {
397
+ switch (character) {
398
+ case '&':
399
+ return '&amp;';
400
+ case '<':
401
+ return '&lt;';
402
+ case '>':
403
+ return '&gt;';
404
+ case '"':
405
+ return '&quot;';
406
+ default:
407
+ return '&#39;';
408
+ }
409
+ });
410
+ }
411
+
412
+ /*
413
+ * The inline marker for a citation: its number, and the source it stands for as a tooltip.
414
+ *
415
+ * A bare number tells the reader only that something was cited, and answers which source it was by
416
+ * making them look away from the sentence and match a number against a list underneath. Carrying the
417
+ * label means the answer arrives where the question is asked. The list stays as it is, since a tooltip
418
+ * is not reachable by keyboard and cannot be the only place the source appears.
419
+ */
420
+ function citationMarkerHtml(displayIndex, label) {
421
+ var text = typeof label === 'string' ? label.replace(/\s+/g, ' ').trim() : '';
422
+ if (!text) {
423
+ return '<sup>' + displayIndex + '</sup>';
424
+ }
425
+ return '<sup title="' + escapeAttributeValue(text) + '">' + displayIndex + '</sup>';
426
+ }
427
+
428
+ // Deliberately small: pure functions over strings, for the hosts below and for the tests.
429
+ return {
430
+ expandImageMarkers: expandImageMarkers,
431
+ findChartMarker: findChartMarker,
432
+ citationIdentity: citationIdentity,
433
+ splitCombinedCitations: splitCombinedCitations,
434
+ collapseRepeatedCitations: collapseRepeatedCitations,
435
+ separateAdjacentCitations: separateAdjacentCitations,
436
+ citationMarkerHtml: citationMarkerHtml
437
+ };
438
+ }();
439
+ //# sourceMappingURL=chat-markers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["chat-markers.js"],"names":["window","CoreAIChatMarkers","defaultImageAltText","escapeImageAltText","title","text","replace","trim","encodeImageLink","link","character","charCodeAt","toString","toUpperCase","padStart","expandImageMarkers","content","references","_typeof","expanded","_loop","_reference$isImage","_reference$link","_reference$title","marker","_Object$keys","_i","reference","isImage","IsImage","rawLink","Link","image","concat","Title","replaceAll","_ret","Object","keys","length","chartMarkerPrefix","citationMarkerPrefix","maxCitationMarkerLength","isMarkerWhitespace","readChartMarkerAt","startIndex","index","jsonStart","depth","inString","escaped","jsonEnd","closeIndex","endIndex","json","substring","findChartMarker","searchFrom","start","indexOf","splitCombinedCitations","result","from","knownClose","slice","end","numbers","readCitationNumbers","canonical","map","number","join","original","citationLocatorPattern","interior","parts","split","part","test","push","citationIdentity","label","toLowerCase","target","citationSeparator","repeatedCitationPattern","RegExp","collapseRepeatedCitations","html","separateAdjacentCitations","escapeAttributeValue","citationMarkerHtml","displayIndex"],"mappings":"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","file":"chat-markers.js","sourcesContent":["/*\n * Markers the host expands on the model's behalf.\n *\n * Retrieval hands the model a short label such as [fig:1] rather than a figure's address, because a model\n * asked to reproduce a long opaque identifier reproduces its shape and varies the digits instead --\n * producing addresses that look right and resolve to nothing. The model therefore never types an address,\n * and this turns the label it does type back into the picture.\n *\n * The chart tool works the same way from the other end: it hands back a [chart:{...}] marker and asks for it\n * verbatim, and the host turns that marker into a canvas. Reading a marker is a statement about a string, so\n * it lives here; drawing one is not, so each surface still does its own drawing.\n *\n * Loaded on its own so every chat surface shares one implementation: the two shared chat scripts and the\n * MVC chat-interaction view, which renders its markdown with its own inline marked setup.\n */\nwindow.CoreAIChatMarkers = window.CoreAIChatMarkers || (function () {\n 'use strict';\n\n // Used when a figure has no caption, so the image is still announced as something.\n const defaultImageAltText = 'Figure';\n\n /*\n * Makes a caption safe to sit inside the alt text of ![alt](link).\n *\n * Captions really do contain brackets, and an unescaped one closes the alt text early: a caption of\n * \"Figure 2 (revised) [draft]\" would end the image after \"Figure 2 (revised) \" and leave the rest of the\n * syntax on the page as markup. Parentheses are harmless there, only the brackets are. A caption that spans\n * lines would end the paragraph the image lives in, so its whitespace is collapsed to single spaces.\n */\n function escapeImageAltText(title) {\n const text = typeof title === 'string' ? title.replace(/\\s+/g, ' ').trim() : '';\n\n if (!text) {\n return defaultImageAltText;\n }\n\n // The backslash is escaped as well, or a caption ending in one would escape the bracket that closes\n // the alt text and the image would swallow the rest of the line.\n return text.replace(/[\\\\\\[\\]]/g, '\\\\$&');\n }\n\n /*\n * Makes a link safe to sit inside the destination of ![alt](link): the few characters that would end the\n * destination early are percent-encoded, which leaves the URL addressing the same resource. The link is\n * still run through sanitizeUrl by the image renderer, so this is about parsing, not about safety.\n */\n function encodeImageLink(link) {\n return link.replace(/[\\s()<>\"\\\\]/g, function (character) {\n return '%' + character.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0');\n });\n }\n\n /*\n * Returns the content with every marker the reference map describes as a servable image replaced by\n * markdown image syntax, so the ordinary image renderer draws it with its thumbnail, its download button\n * and its size cap. Pure: nothing here touches the DOM or the message.\n *\n * A marker is replaced only when its reference is an image and carries a link. Anything else is left\n * exactly as written -- a marker the model invented for a figure that was never in the results, or one\n * whose picture the host cannot serve, reaches the reader as the few characters the model typed rather\n * than as a broken image.\n */\n function expandImageMarkers(content, references) {\n if (typeof content !== 'string' || !content) {\n return typeof content === 'string' ? content : '';\n }\n\n if (!references || typeof references !== 'object') {\n return content;\n }\n\n let expanded = content;\n\n for (const marker of Object.keys(references)) {\n const reference = references[marker];\n\n if (!marker || !reference || typeof reference !== 'object') {\n continue;\n }\n\n if ((reference.isImage ?? reference.IsImage) !== true) {\n continue;\n }\n\n const rawLink = reference.link ?? reference.Link;\n const link = typeof rawLink === 'string' ? rawLink.trim() : '';\n\n if (!link) {\n continue;\n }\n\n const image = `![${escapeImageAltText(reference.title ?? reference.Title)}](${encodeImageLink(link)})`;\n\n // Every occurrence, and through a replacer function so a '$' in a caption or a link is not read as\n // a replacement pattern.\n expanded = expanded.replaceAll(marker, function () { return image; });\n }\n\n return expanded;\n }\n\n // The marker the chart tool emits and asks the model to repeat verbatim, braces and all.\n const chartMarkerPrefix = '[chart:';\n\n // What a citation the model typed opens with. The reference map is keyed by the whole marker, so this is\n // also how a rewritten one is put back together.\n const citationMarkerPrefix = '[doc:';\n\n /*\n * How much text may sit between \"[doc:\" and its \"]\" before it stops being a citation.\n *\n * A list of reference numbers is short: even ten of them, written the long way, is under a hundred\n * characters. The limit is what keeps the work per marker fixed, and so the whole pass linear -- without\n * it, an opening bracket whose \"]\" is thousands of characters away is examined in full, and a paragraph\n * full of such openings is examined once per opening.\n */\n const maxCitationMarkerLength = 512;\n\n function isMarkerWhitespace(character) {\n return character === ' ' || character === '\\n' || character === '\\r' || character === '\\t';\n }\n\n /*\n * Reads the marker that begins at startIndex, or returns null when what begins there is not one.\n *\n * The configuration is JSON and JSON nests, so the end of the object is found by counting braces rather\n * than by a regular expression: an expression that stopped at the first '}' would truncate every chart\n * whose options carry an object of their own, which is all of them. The count has to know when it is\n * inside a string as well -- an axis label really does read \"Q1 { Q2\", and a counter blind to strings\n * would end the object in the middle of the label and hand the renderer a fragment. Escapes are tracked\n * for the same reason, so a \\\" inside a label does not look like the end of it.\n */\n function readChartMarkerAt(text, startIndex) {\n let index = startIndex + chartMarkerPrefix.length;\n\n while (index < text.length && isMarkerWhitespace(text[index])) {\n index++;\n }\n\n if (index >= text.length || text[index] !== '{') {\n return null;\n }\n\n const jsonStart = index;\n let depth = 0;\n let inString = false;\n let escaped = false;\n\n for (; index < text.length; index++) {\n const character = text[index];\n\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (character === '\\\\') {\n escaped = true;\n } else if (character === '\"') {\n inString = false;\n }\n\n continue;\n }\n\n if (character === '\"') {\n inString = true;\n continue;\n }\n\n if (character === '{') {\n depth++;\n continue;\n }\n\n if (character !== '}') {\n continue;\n }\n\n depth--;\n\n if (depth > 0) {\n continue;\n }\n\n const jsonEnd = index + 1;\n let closeIndex = jsonEnd;\n\n while (closeIndex < text.length && isMarkerWhitespace(text[closeIndex])) {\n closeIndex++;\n }\n\n // Only whitespace may sit between the object and the bracket that closes the marker. Hunting\n // further ahead for a ']' would let a marker whose bracket the model dropped reach the one\n // belonging to something else, and everything in between -- a paragraph of the answer, the next\n // marker -- would be swallowed into the span the caller removes from the text.\n if (closeIndex >= text.length || text[closeIndex] !== ']') {\n return null;\n }\n\n return {\n startIndex: startIndex,\n endIndex: closeIndex + 1,\n json: text.substring(jsonStart, jsonEnd)\n };\n }\n\n return null;\n }\n\n /*\n * Finds the first [chart:{...}] marker in the content and returns where it starts, where it ends and the\n * JSON between, or null when there is no marker. Pure: it knows nothing of canvases, element ids or\n * Chart.js, because the three chat surfaces draw a chart differently and agree only on what a marker is.\n *\n * Text that merely looks like a marker is not one, and none of these return a span: \"[chart:\" with no\n * object after it, an object whose braces never balance, an object the model never closed with ']'. Each\n * reaches the reader as the text the model typed, which is what the caller does with a null.\n */\n function findChartMarker(text) {\n if (typeof text !== 'string' || !text) {\n return null;\n }\n\n let searchFrom = 0;\n\n for (;;) {\n const start = text.indexOf(chartMarkerPrefix, searchFrom);\n\n if (start < 0) {\n return null;\n }\n\n const marker = readChartMarkerAt(text, start);\n\n if (marker) {\n return marker;\n }\n\n // Something that opened like a marker and was not one must not hide a real one further on, so the\n // search carries on past it rather than giving up at the first disappointment.\n searchFrom = start + 1;\n }\n }\n\n /*\n * Splits a citation marker the model wrote as one bracket into the separate markers it means.\n *\n * A reference is keyed by the literal string the model is asked to type -- \"[doc:1]\" -- and the renderer\n * replaces exactly that. Models routinely gather several into one bracket instead: \"[doc:1, doc:2]\". No\n * key matches that, so nothing is replaced and the reader is shown the raw marker in the middle of a\n * sentence, while the citations the model happened to write singly render correctly beside it.\n *\n * Rewriting the combined form into the separate markers is what makes the rest of the pipeline see them.\n * Both spellings are accepted, since a model that writes \"[doc:1, doc:2]\" will also write \"[doc:1, 2]\".\n * A lone \"[doc:1]\" is left exactly as it is.\n */\n function splitCombinedCitations(content) {\n if (typeof content !== 'string' || !content) {\n return '';\n }\n\n let result = '';\n let from = 0;\n\n // The closing bracket last searched for. A ']' found from an earlier position is still the next one\n // from here, because there was none in between -- so text like \"[doc:x[doc:x[doc:x...]\" is walked once\n // rather than rescanned from every false start, which would make the whole pass quadratic.\n let knownClose = -1;\n\n for (;;) {\n const start = content.indexOf(citationMarkerPrefix, from);\n\n if (start < 0) {\n return result + content.slice(from);\n }\n\n const end = knownClose >= start ? knownClose : content.indexOf(']', start);\n\n knownClose = end;\n\n if (end < 0) {\n // Nothing closes it, so nothing here is a marker. The rest is returned as it was written.\n return result + content.slice(from);\n }\n\n const numbers = end - start <= maxCitationMarkerLength\n ? readCitationNumbers(content.slice(start + citationMarkerPrefix.length, end))\n : null;\n\n if (!numbers) {\n // Whatever this opened, it was not a list of references. The search carries on from just\n // after the prefix rather than past the bracket, because the ']' that was found may belong to\n // a real marker further along -- \"[doc:1, and later [doc:3, doc:4]\" closes the second one, and\n // skipping to it would hide it.\n result += content.slice(from, start + citationMarkerPrefix.length);\n from = start + citationMarkerPrefix.length;\n\n continue;\n }\n\n // Rewritten whenever the bracket does not already read as the canonical marker: several numbers\n // in one bracket, or a single number carrying a locator the model appended. A bare \"[doc:1]\"\n // rewrites to itself, so it is passed through untouched rather than rebuilt.\n const canonical = numbers.map(function (number) { return citationMarkerPrefix + number + ']'; }).join('');\n const original = content.slice(start, end + 1);\n\n result += content.slice(from, start);\n result += canonical === original ? original : canonical;\n\n from = end + 1;\n }\n }\n\n /*\n * Reads the numbers out of the inside of a citation bracket, or returns null when it does not hold a list\n * of them.\n *\n * Deliberately not a regular expression over the whole bracket. The pattern that reads naturally --\n * optional spaces on both sides of each comma, repeated -- gives the engine several ways to match the same\n * text, and on a bracket that never closes it explores all of them: CodeQL flagged it as exponential, and\n * it was. A model writing \"[doc:9, 9, 9, 9...\" and forgetting the bracket is not a hostile act, and it is\n * a reader's own tab that stops responding.\n *\n * Splitting on the comma and checking each part on its own is linear, and each pattern below is anchored\n * over a single character class, so neither can backtrack at all.\n */\n /*\n * A locator a model appends to a citation instead of writing the bare marker it was asked for: \"pages\n * 13-15\", \"page 4\", \"pp. 3-5\", \"pg 7\". It is not a citation number and the reference map holds nothing\n * under it, so before this it made the whole marker unreadable and \"[doc:1, pages 13-15]\" reached the\n * reader as those characters.\n *\n * Dropped rather than rendered. The page a citation points at is already carried by the reference the\n * number resolves to, which is the one the host looked up rather than the one the model recalled.\n */\n const citationLocatorPattern = /^(?:pages?|pp?g?|pp)\\.?\\s*[\\d\\s‐-―,&+-]*$/i;\n\n function readCitationNumbers(interior) {\n const parts = interior.split(',');\n const numbers = [];\n\n for (let index = 0; index < parts.length; index++) {\n // Only the parts after the first may repeat the prefix: \"[doc:1, doc:2]\" and \"[doc:1, 2]\" are both\n // written by models, \"[doc:doc:1]\" is not.\n const part = index === 0 ? parts[index].trim() : parts[index].trim().replace(/^doc:\\s*/, '');\n\n if (/^\\d+$/.test(part)) {\n numbers.push(part);\n\n continue;\n }\n\n // A locator is allowed only after a number has been read, so \"[doc:pages 3]\" is still not a\n // citation -- something that names no document is not one whatever follows it.\n if (numbers.length > 0 && citationLocatorPattern.test(part)) {\n continue;\n }\n\n return null;\n }\n\n return numbers.length > 0 ? numbers : null;\n }\n\n /*\n * The identity of a citation as a reader meets it: the line it prints, and where it points.\n *\n * Retrieval returns one reference per chunk, so a single article that answered a question through three of\n * its chunks arrives as three references. Numbering those separately prints \"1,2,3\" over the sentence and\n * then lists the same title three times, which tells the reader there are three sources corroborating the\n * claim when there is one. Two references that would print the same line and lead to the same place are\n * one citation, and are numbered once.\n *\n * Case and surrounding whitespace are not part of the identity, because they are not part of what the\n * reader sees as different. Anything else -- a different title, a different page, a link to one figure\n * rather than another -- keeps the citations apart, since that is a distinction the reader can act on.\n */\n function citationIdentity(label, link) {\n const text = typeof label === 'string' ? label.replace(/\\s+/g, ' ').trim().toLowerCase() : '';\n const target = typeof link === 'string' ? link.trim().toLowerCase() : '';\n\n // A null byte cannot occur in either part, so no pair of values can collide across the boundary:\n // without it, label \"a\" + link \"bc\" and label \"ab\" + link \"c\" would be the same identity.\n return text + '\u0000' + target;\n }\n\n /*\n * Collapses a run of identical citation markers into one.\n *\n * Once two references share a number, a sentence that cited both ends with that number twice over --\n * \"1,1\" -- which reads as a typo. Only an immediately repeated marker is removed: the same source cited\n * again later in the paragraph is a separate citation of it and keeps its marker.\n *\n * The marker is matched with whatever attributes it carries, because the number is what makes two of them\n * the same citation; a pattern written for a bare <sup> would silently stop collapsing the moment a\n * marker gained a tooltip.\n *\n * What separates the two markers is swallowed with them, because the model writes its own punctuation\n * between references -- \"[ref1], [ref2]\" -- and once both carry the same number that comma is left\n * between a number and itself, printing \"1,1\". Only a separator that was joining the two citations is\n * eaten: whitespace, a comma, or the marker the comma rule inserts. Anything else means the second\n * marker is a fresh citation later in the sentence, and it is left alone.\n */\n const citationSeparator = '(?:\\\\s*(?:,|<sup(?:\\\\s[^>]*)?>,<\\\\/sup>)?\\\\s*)';\n const repeatedCitationPattern = new RegExp(\n '(<sup(?:\\\\s[^>]*)?>(\\\\d+)<\\\\/sup>)(?:' + citationSeparator + '<sup(?:\\\\s[^>]*)?>\\\\2<\\\\/sup>)+', 'g');\n\n function collapseRepeatedCitations(html) {\n if (typeof html !== 'string' || !html) {\n return '';\n }\n\n return html.replace(repeatedCitationPattern, '$1');\n }\n\n /*\n * Puts a comma between two citation markers that ended up side by side, so \"12\" reads as two sources\n * rather than as the twelfth.\n *\n * Only the boundary between the markers is matched, so whatever attributes either one carries are left\n * untouched. The inserted marker is not itself rescanned, since a replace walks the string it was given.\n */\n function separateAdjacentCitations(html) {\n if (typeof html !== 'string' || !html) {\n return '';\n }\n\n return html.replace(/<\\/sup><sup/g, '</sup><sup>,</sup><sup');\n }\n\n // Escapes the few characters that would end an HTML attribute early, so a caption containing a quote\n // cannot introduce markup of its own.\n function escapeAttributeValue(text) {\n return text.replace(/[&<>\"']/g, function (character) {\n switch (character) {\n case '&': return '&amp;';\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '\"': return '&quot;';\n default: return '&#39;';\n }\n });\n }\n\n /*\n * The inline marker for a citation: its number, and the source it stands for as a tooltip.\n *\n * A bare number tells the reader only that something was cited, and answers which source it was by\n * making them look away from the sentence and match a number against a list underneath. Carrying the\n * label means the answer arrives where the question is asked. The list stays as it is, since a tooltip\n * is not reachable by keyboard and cannot be the only place the source appears.\n */\n function citationMarkerHtml(displayIndex, label) {\n const text = typeof label === 'string' ? label.replace(/\\s+/g, ' ').trim() : '';\n\n if (!text) {\n return '<sup>' + displayIndex + '</sup>';\n }\n\n return '<sup title=\"' + escapeAttributeValue(text) + '\">' + displayIndex + '</sup>';\n }\n\n // Deliberately small: pure functions over strings, for the hosts below and for the tests.\n return {\n expandImageMarkers: expandImageMarkers,\n findChartMarker: findChartMarker,\n citationIdentity: citationIdentity,\n splitCombinedCitations: splitCombinedCitations,\n collapseRepeatedCitations: collapseRepeatedCitations,\n separateAdjacentCitations: separateAdjacentCitations,\n citationMarkerHtml: citationMarkerHtml\n };\n})();\n"]}
@@ -0,0 +1,2 @@
1
+ function _typeof(t){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof(t)}window.CoreAIChatMarkers=window.CoreAIChatMarkers||function(){"use strict";var t="[chart:",r="[doc:";function e(t){return" "===t||"\n"===t||"\r"===t||"\t"===t}function n(t,r){for(var n=r+7;n<t.length&&e(t[n]);)n++;if(n>=t.length||"{"!==t[n])return null;for(var i=n,o=0,u=!1,s=!1;n<t.length;n++){var f=t[n];if(u)s?s=!1:"\\"===f?s=!0:'"'===f&&(u=!1);else if('"'!==f)if("{"!==f){if("}"===f&&!(--o>0)){for(var a=n+1,l=a;l<t.length&&e(t[l]);)l++;return l>=t.length||"]"!==t[l]?null:{startIndex:r,endIndex:l+1,json:t.substring(i,a)}}}else o++;else u=!0}return null}var i=/^(?:pages?|pp?g?|pp)\.?\s*[\d\s‐-―,&+-]*$/i;function o(t){for(var r=t.split(","),e=[],n=0;n<r.length;n++){var o=0===n?r[n].trim():r[n].trim().replace(/^doc:\s*/,"");if(/^\d+$/.test(o))e.push(o);else if(!(e.length>0&&i.test(o)))return null}return e.length>0?e:null}var u=new RegExp("(<sup(?:\\s[^>]*)?>(\\d+)<\\/sup>)(?:(?:\\s*(?:,|<sup(?:\\s[^>]*)?>,<\\/sup>)?\\s*)<sup(?:\\s[^>]*)?>\\2<\\/sup>)+","g");return{expandImageMarkers:function(t,r){if("string"!=typeof t||!t)return"string"==typeof t?t:"";if(!r||"object"!==_typeof(r))return t;for(var e=t,n=function(){var t,n,u,s=o[i],f=r[s];if(!s||!f||"object"!==_typeof(f))return 0;if(!0!==(null!==(t=f.isImage)&&void 0!==t?t:f.IsImage))return 0;var a=null!==(n=f.link)&&void 0!==n?n:f.Link,l="string"==typeof a?a.trim():"";if(!l)return 0;var p,c,g="![".concat((p=null!==(u=f.title)&&void 0!==u?u:f.Title,c="string"==typeof p?p.replace(/\s+/g," ").trim():"",c?c.replace(/[\\\[\]]/g,"\\$&"):"Figure"),"](").concat(function(t){return t.replace(/[\s()<>"\\]/g,function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase().padStart(2,"0")})}(l),")");e=e.replaceAll(s,function(){return g})},i=0,o=Object.keys(r);i<o.length;i++)n();return e},findChartMarker:function(r){if("string"!=typeof r||!r)return null;for(var e=0;;){var i=r.indexOf(t,e);if(i<0)return null;var o=n(r,i);if(o)return o;e=i+1}},citationIdentity:function(t,r){return("string"==typeof t?t.replace(/\s+/g," ").trim().toLowerCase():"")+"\0"+("string"==typeof r?r.trim().toLowerCase():"")},splitCombinedCitations:function(t){if("string"!=typeof t||!t)return"";for(var e="",n=0,i=-1;;){var u=t.indexOf(r,n);if(u<0)return e+t.slice(n);var s=i>=u?i:t.indexOf("]",u);if(i=s,s<0)return e+t.slice(n);var f=s-u<=512?o(t.slice(u+5,s)):null;if(f){var a=f.map(function(t){return r+t+"]"}).join(""),l=t.slice(u,s+1);e+=t.slice(n,u),e+=a===l?l:a,n=s+1}else e+=t.slice(n,u+5),n=u+5}},collapseRepeatedCitations:function(t){return"string"==typeof t&&t?t.replace(u,"$1"):""},separateAdjacentCitations:function(t){return"string"==typeof t&&t?t.replace(/<\/sup><sup/g,"</sup><sup>,</sup><sup"):""},citationMarkerHtml:function(t,r){var e="string"==typeof r?r.replace(/\s+/g," ").trim():"";return e?'<sup title="'+function(t){return t.replace(/[&<>"']/g,function(t){switch(t){case"&":return"&amp;";case"<":return"&lt;";case">":return"&gt;";case'"':return"&quot;";default:return"&#39;"}})}(e)+'">'+t+"</sup>":"<sup>"+t+"</sup>"}}}();
2
+ //# sourceMappingURL=chat-markers.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["chat-markers.js"],"names":["_typeof","o","Symbol","iterator","constructor","prototype","window","CoreAIChatMarkers","chartMarkerPrefix","citationMarkerPrefix","isMarkerWhitespace","character","readChartMarkerAt","text","startIndex","index","length","jsonStart","depth","inString","escaped","jsonEnd","closeIndex","endIndex","json","substring","citationLocatorPattern","readCitationNumbers","interior","parts","split","numbers","part","trim","replace","test","push","repeatedCitationPattern","RegExp","expandImageMarkers","content","references","expanded","_loop","_reference$isImage","_reference$link","_reference$title","marker","_Object$keys","_i","reference","isImage","IsImage","rawLink","link","Link","title","image","concat","Title","charCodeAt","toString","toUpperCase","padStart","encodeImageLink","replaceAll","Object","keys","findChartMarker","searchFrom","start","indexOf","citationIdentity","label","toLowerCase","splitCombinedCitations","result","from","knownClose","slice","end","canonical","map","number","join","original","collapseRepeatedCitations","html","separateAdjacentCitations","citationMarkerHtml","displayIndex","escapeAttributeValue"],"mappings":"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","file":"chat-markers.min.js","sourcesContent":["/*\n * Markers the host expands on the model's behalf.\n *\n * Retrieval hands the model a short label such as [fig:1] rather than a figure's address, because a model\n * asked to reproduce a long opaque identifier reproduces its shape and varies the digits instead --\n * producing addresses that look right and resolve to nothing. The model therefore never types an address,\n * and this turns the label it does type back into the picture.\n *\n * The chart tool works the same way from the other end: it hands back a [chart:{...}] marker and asks for it\n * verbatim, and the host turns that marker into a canvas. Reading a marker is a statement about a string, so\n * it lives here; drawing one is not, so each surface still does its own drawing.\n *\n * Loaded on its own so every chat surface shares one implementation: the two shared chat scripts and the\n * MVC chat-interaction view, which renders its markdown with its own inline marked setup.\n */\nwindow.CoreAIChatMarkers = window.CoreAIChatMarkers || (function () {\n 'use strict';\n\n // Used when a figure has no caption, so the image is still announced as something.\n const defaultImageAltText = 'Figure';\n\n /*\n * Makes a caption safe to sit inside the alt text of ![alt](link).\n *\n * Captions really do contain brackets, and an unescaped one closes the alt text early: a caption of\n * \"Figure 2 (revised) [draft]\" would end the image after \"Figure 2 (revised) \" and leave the rest of the\n * syntax on the page as markup. Parentheses are harmless there, only the brackets are. A caption that spans\n * lines would end the paragraph the image lives in, so its whitespace is collapsed to single spaces.\n */\n function escapeImageAltText(title) {\n const text = typeof title === 'string' ? title.replace(/\\s+/g, ' ').trim() : '';\n\n if (!text) {\n return defaultImageAltText;\n }\n\n // The backslash is escaped as well, or a caption ending in one would escape the bracket that closes\n // the alt text and the image would swallow the rest of the line.\n return text.replace(/[\\\\\\[\\]]/g, '\\\\$&');\n }\n\n /*\n * Makes a link safe to sit inside the destination of ![alt](link): the few characters that would end the\n * destination early are percent-encoded, which leaves the URL addressing the same resource. The link is\n * still run through sanitizeUrl by the image renderer, so this is about parsing, not about safety.\n */\n function encodeImageLink(link) {\n return link.replace(/[\\s()<>\"\\\\]/g, function (character) {\n return '%' + character.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0');\n });\n }\n\n /*\n * Returns the content with every marker the reference map describes as a servable image replaced by\n * markdown image syntax, so the ordinary image renderer draws it with its thumbnail, its download button\n * and its size cap. Pure: nothing here touches the DOM or the message.\n *\n * A marker is replaced only when its reference is an image and carries a link. Anything else is left\n * exactly as written -- a marker the model invented for a figure that was never in the results, or one\n * whose picture the host cannot serve, reaches the reader as the few characters the model typed rather\n * than as a broken image.\n */\n function expandImageMarkers(content, references) {\n if (typeof content !== 'string' || !content) {\n return typeof content === 'string' ? content : '';\n }\n\n if (!references || typeof references !== 'object') {\n return content;\n }\n\n let expanded = content;\n\n for (const marker of Object.keys(references)) {\n const reference = references[marker];\n\n if (!marker || !reference || typeof reference !== 'object') {\n continue;\n }\n\n if ((reference.isImage ?? reference.IsImage) !== true) {\n continue;\n }\n\n const rawLink = reference.link ?? reference.Link;\n const link = typeof rawLink === 'string' ? rawLink.trim() : '';\n\n if (!link) {\n continue;\n }\n\n const image = `![${escapeImageAltText(reference.title ?? reference.Title)}](${encodeImageLink(link)})`;\n\n // Every occurrence, and through a replacer function so a '$' in a caption or a link is not read as\n // a replacement pattern.\n expanded = expanded.replaceAll(marker, function () { return image; });\n }\n\n return expanded;\n }\n\n // The marker the chart tool emits and asks the model to repeat verbatim, braces and all.\n const chartMarkerPrefix = '[chart:';\n\n // What a citation the model typed opens with. The reference map is keyed by the whole marker, so this is\n // also how a rewritten one is put back together.\n const citationMarkerPrefix = '[doc:';\n\n /*\n * How much text may sit between \"[doc:\" and its \"]\" before it stops being a citation.\n *\n * A list of reference numbers is short: even ten of them, written the long way, is under a hundred\n * characters. The limit is what keeps the work per marker fixed, and so the whole pass linear -- without\n * it, an opening bracket whose \"]\" is thousands of characters away is examined in full, and a paragraph\n * full of such openings is examined once per opening.\n */\n const maxCitationMarkerLength = 512;\n\n function isMarkerWhitespace(character) {\n return character === ' ' || character === '\\n' || character === '\\r' || character === '\\t';\n }\n\n /*\n * Reads the marker that begins at startIndex, or returns null when what begins there is not one.\n *\n * The configuration is JSON and JSON nests, so the end of the object is found by counting braces rather\n * than by a regular expression: an expression that stopped at the first '}' would truncate every chart\n * whose options carry an object of their own, which is all of them. The count has to know when it is\n * inside a string as well -- an axis label really does read \"Q1 { Q2\", and a counter blind to strings\n * would end the object in the middle of the label and hand the renderer a fragment. Escapes are tracked\n * for the same reason, so a \\\" inside a label does not look like the end of it.\n */\n function readChartMarkerAt(text, startIndex) {\n let index = startIndex + chartMarkerPrefix.length;\n\n while (index < text.length && isMarkerWhitespace(text[index])) {\n index++;\n }\n\n if (index >= text.length || text[index] !== '{') {\n return null;\n }\n\n const jsonStart = index;\n let depth = 0;\n let inString = false;\n let escaped = false;\n\n for (; index < text.length; index++) {\n const character = text[index];\n\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (character === '\\\\') {\n escaped = true;\n } else if (character === '\"') {\n inString = false;\n }\n\n continue;\n }\n\n if (character === '\"') {\n inString = true;\n continue;\n }\n\n if (character === '{') {\n depth++;\n continue;\n }\n\n if (character !== '}') {\n continue;\n }\n\n depth--;\n\n if (depth > 0) {\n continue;\n }\n\n const jsonEnd = index + 1;\n let closeIndex = jsonEnd;\n\n while (closeIndex < text.length && isMarkerWhitespace(text[closeIndex])) {\n closeIndex++;\n }\n\n // Only whitespace may sit between the object and the bracket that closes the marker. Hunting\n // further ahead for a ']' would let a marker whose bracket the model dropped reach the one\n // belonging to something else, and everything in between -- a paragraph of the answer, the next\n // marker -- would be swallowed into the span the caller removes from the text.\n if (closeIndex >= text.length || text[closeIndex] !== ']') {\n return null;\n }\n\n return {\n startIndex: startIndex,\n endIndex: closeIndex + 1,\n json: text.substring(jsonStart, jsonEnd)\n };\n }\n\n return null;\n }\n\n /*\n * Finds the first [chart:{...}] marker in the content and returns where it starts, where it ends and the\n * JSON between, or null when there is no marker. Pure: it knows nothing of canvases, element ids or\n * Chart.js, because the three chat surfaces draw a chart differently and agree only on what a marker is.\n *\n * Text that merely looks like a marker is not one, and none of these return a span: \"[chart:\" with no\n * object after it, an object whose braces never balance, an object the model never closed with ']'. Each\n * reaches the reader as the text the model typed, which is what the caller does with a null.\n */\n function findChartMarker(text) {\n if (typeof text !== 'string' || !text) {\n return null;\n }\n\n let searchFrom = 0;\n\n for (;;) {\n const start = text.indexOf(chartMarkerPrefix, searchFrom);\n\n if (start < 0) {\n return null;\n }\n\n const marker = readChartMarkerAt(text, start);\n\n if (marker) {\n return marker;\n }\n\n // Something that opened like a marker and was not one must not hide a real one further on, so the\n // search carries on past it rather than giving up at the first disappointment.\n searchFrom = start + 1;\n }\n }\n\n /*\n * Splits a citation marker the model wrote as one bracket into the separate markers it means.\n *\n * A reference is keyed by the literal string the model is asked to type -- \"[doc:1]\" -- and the renderer\n * replaces exactly that. Models routinely gather several into one bracket instead: \"[doc:1, doc:2]\". No\n * key matches that, so nothing is replaced and the reader is shown the raw marker in the middle of a\n * sentence, while the citations the model happened to write singly render correctly beside it.\n *\n * Rewriting the combined form into the separate markers is what makes the rest of the pipeline see them.\n * Both spellings are accepted, since a model that writes \"[doc:1, doc:2]\" will also write \"[doc:1, 2]\".\n * A lone \"[doc:1]\" is left exactly as it is.\n */\n function splitCombinedCitations(content) {\n if (typeof content !== 'string' || !content) {\n return '';\n }\n\n let result = '';\n let from = 0;\n\n // The closing bracket last searched for. A ']' found from an earlier position is still the next one\n // from here, because there was none in between -- so text like \"[doc:x[doc:x[doc:x...]\" is walked once\n // rather than rescanned from every false start, which would make the whole pass quadratic.\n let knownClose = -1;\n\n for (;;) {\n const start = content.indexOf(citationMarkerPrefix, from);\n\n if (start < 0) {\n return result + content.slice(from);\n }\n\n const end = knownClose >= start ? knownClose : content.indexOf(']', start);\n\n knownClose = end;\n\n if (end < 0) {\n // Nothing closes it, so nothing here is a marker. The rest is returned as it was written.\n return result + content.slice(from);\n }\n\n const numbers = end - start <= maxCitationMarkerLength\n ? readCitationNumbers(content.slice(start + citationMarkerPrefix.length, end))\n : null;\n\n if (!numbers) {\n // Whatever this opened, it was not a list of references. The search carries on from just\n // after the prefix rather than past the bracket, because the ']' that was found may belong to\n // a real marker further along -- \"[doc:1, and later [doc:3, doc:4]\" closes the second one, and\n // skipping to it would hide it.\n result += content.slice(from, start + citationMarkerPrefix.length);\n from = start + citationMarkerPrefix.length;\n\n continue;\n }\n\n // Rewritten whenever the bracket does not already read as the canonical marker: several numbers\n // in one bracket, or a single number carrying a locator the model appended. A bare \"[doc:1]\"\n // rewrites to itself, so it is passed through untouched rather than rebuilt.\n const canonical = numbers.map(function (number) { return citationMarkerPrefix + number + ']'; }).join('');\n const original = content.slice(start, end + 1);\n\n result += content.slice(from, start);\n result += canonical === original ? original : canonical;\n\n from = end + 1;\n }\n }\n\n /*\n * Reads the numbers out of the inside of a citation bracket, or returns null when it does not hold a list\n * of them.\n *\n * Deliberately not a regular expression over the whole bracket. The pattern that reads naturally --\n * optional spaces on both sides of each comma, repeated -- gives the engine several ways to match the same\n * text, and on a bracket that never closes it explores all of them: CodeQL flagged it as exponential, and\n * it was. A model writing \"[doc:9, 9, 9, 9...\" and forgetting the bracket is not a hostile act, and it is\n * a reader's own tab that stops responding.\n *\n * Splitting on the comma and checking each part on its own is linear, and each pattern below is anchored\n * over a single character class, so neither can backtrack at all.\n */\n /*\n * A locator a model appends to a citation instead of writing the bare marker it was asked for: \"pages\n * 13-15\", \"page 4\", \"pp. 3-5\", \"pg 7\". It is not a citation number and the reference map holds nothing\n * under it, so before this it made the whole marker unreadable and \"[doc:1, pages 13-15]\" reached the\n * reader as those characters.\n *\n * Dropped rather than rendered. The page a citation points at is already carried by the reference the\n * number resolves to, which is the one the host looked up rather than the one the model recalled.\n */\n const citationLocatorPattern = /^(?:pages?|pp?g?|pp)\\.?\\s*[\\d\\s‐-―,&+-]*$/i;\n\n function readCitationNumbers(interior) {\n const parts = interior.split(',');\n const numbers = [];\n\n for (let index = 0; index < parts.length; index++) {\n // Only the parts after the first may repeat the prefix: \"[doc:1, doc:2]\" and \"[doc:1, 2]\" are both\n // written by models, \"[doc:doc:1]\" is not.\n const part = index === 0 ? parts[index].trim() : parts[index].trim().replace(/^doc:\\s*/, '');\n\n if (/^\\d+$/.test(part)) {\n numbers.push(part);\n\n continue;\n }\n\n // A locator is allowed only after a number has been read, so \"[doc:pages 3]\" is still not a\n // citation -- something that names no document is not one whatever follows it.\n if (numbers.length > 0 && citationLocatorPattern.test(part)) {\n continue;\n }\n\n return null;\n }\n\n return numbers.length > 0 ? numbers : null;\n }\n\n /*\n * The identity of a citation as a reader meets it: the line it prints, and where it points.\n *\n * Retrieval returns one reference per chunk, so a single article that answered a question through three of\n * its chunks arrives as three references. Numbering those separately prints \"1,2,3\" over the sentence and\n * then lists the same title three times, which tells the reader there are three sources corroborating the\n * claim when there is one. Two references that would print the same line and lead to the same place are\n * one citation, and are numbered once.\n *\n * Case and surrounding whitespace are not part of the identity, because they are not part of what the\n * reader sees as different. Anything else -- a different title, a different page, a link to one figure\n * rather than another -- keeps the citations apart, since that is a distinction the reader can act on.\n */\n function citationIdentity(label, link) {\n const text = typeof label === 'string' ? label.replace(/\\s+/g, ' ').trim().toLowerCase() : '';\n const target = typeof link === 'string' ? link.trim().toLowerCase() : '';\n\n // A null byte cannot occur in either part, so no pair of values can collide across the boundary:\n // without it, label \"a\" + link \"bc\" and label \"ab\" + link \"c\" would be the same identity.\n return text + '\u0000' + target;\n }\n\n /*\n * Collapses a run of identical citation markers into one.\n *\n * Once two references share a number, a sentence that cited both ends with that number twice over --\n * \"1,1\" -- which reads as a typo. Only an immediately repeated marker is removed: the same source cited\n * again later in the paragraph is a separate citation of it and keeps its marker.\n *\n * The marker is matched with whatever attributes it carries, because the number is what makes two of them\n * the same citation; a pattern written for a bare <sup> would silently stop collapsing the moment a\n * marker gained a tooltip.\n *\n * What separates the two markers is swallowed with them, because the model writes its own punctuation\n * between references -- \"[ref1], [ref2]\" -- and once both carry the same number that comma is left\n * between a number and itself, printing \"1,1\". Only a separator that was joining the two citations is\n * eaten: whitespace, a comma, or the marker the comma rule inserts. Anything else means the second\n * marker is a fresh citation later in the sentence, and it is left alone.\n */\n const citationSeparator = '(?:\\\\s*(?:,|<sup(?:\\\\s[^>]*)?>,<\\\\/sup>)?\\\\s*)';\n const repeatedCitationPattern = new RegExp(\n '(<sup(?:\\\\s[^>]*)?>(\\\\d+)<\\\\/sup>)(?:' + citationSeparator + '<sup(?:\\\\s[^>]*)?>\\\\2<\\\\/sup>)+', 'g');\n\n function collapseRepeatedCitations(html) {\n if (typeof html !== 'string' || !html) {\n return '';\n }\n\n return html.replace(repeatedCitationPattern, '$1');\n }\n\n /*\n * Puts a comma between two citation markers that ended up side by side, so \"12\" reads as two sources\n * rather than as the twelfth.\n *\n * Only the boundary between the markers is matched, so whatever attributes either one carries are left\n * untouched. The inserted marker is not itself rescanned, since a replace walks the string it was given.\n */\n function separateAdjacentCitations(html) {\n if (typeof html !== 'string' || !html) {\n return '';\n }\n\n return html.replace(/<\\/sup><sup/g, '</sup><sup>,</sup><sup');\n }\n\n // Escapes the few characters that would end an HTML attribute early, so a caption containing a quote\n // cannot introduce markup of its own.\n function escapeAttributeValue(text) {\n return text.replace(/[&<>\"']/g, function (character) {\n switch (character) {\n case '&': return '&amp;';\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '\"': return '&quot;';\n default: return '&#39;';\n }\n });\n }\n\n /*\n * The inline marker for a citation: its number, and the source it stands for as a tooltip.\n *\n * A bare number tells the reader only that something was cited, and answers which source it was by\n * making them look away from the sentence and match a number against a list underneath. Carrying the\n * label means the answer arrives where the question is asked. The list stays as it is, since a tooltip\n * is not reachable by keyboard and cannot be the only place the source appears.\n */\n function citationMarkerHtml(displayIndex, label) {\n const text = typeof label === 'string' ? label.replace(/\\s+/g, ' ').trim() : '';\n\n if (!text) {\n return '<sup>' + displayIndex + '</sup>';\n }\n\n return '<sup title=\"' + escapeAttributeValue(text) + '\">' + displayIndex + '</sup>';\n }\n\n // Deliberately small: pure functions over strings, for the hosts below and for the tests.\n return {\n expandImageMarkers: expandImageMarkers,\n findChartMarker: findChartMarker,\n citationIdentity: citationIdentity,\n splitCombinedCitations: splitCombinedCitations,\n collapseRepeatedCitations: collapseRepeatedCitations,\n separateAdjacentCitations: separateAdjacentCitations,\n citationMarkerHtml: citationMarkerHtml\n };\n})();\n"]}