@crestapps/ai-chat-ui 2.0.0-preview.223 → 2.0.0-preview.225

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.
@@ -36,8 +36,7 @@
36
36
  let suppressNextClick = false;
37
37
  function stackAdjacentImages(html) {
38
38
  if (typeof document === "undefined" || !html) return html;
39
- const firstContainerIndex = html.indexOf(imageContainerClass);
40
- if (firstContainerIndex < 0 || html.indexOf(imageContainerClass, firstContainerIndex + 25) < 0) return html;
39
+ if (html.indexOf(imageContainerClass) < 0) return html;
41
40
  const template = document.createElement("template");
42
41
  template.innerHTML = html;
43
42
  const runs = findImageRuns(template.content);
@@ -61,7 +60,19 @@
61
60
  run.push(container);
62
61
  }
63
62
  runs.push(run);
64
- return runs.filter((candidate) => candidate.length > 1);
63
+ return runs.filter((candidate) => candidate.length > 1 || candidate.length === 1 && isOnLineOfItsOwn(candidate[0]));
64
+ }
65
+ function isOnLineOfItsOwn(container) {
66
+ return isOnlySpaceTo(container, "previousSibling") && isOnlySpaceTo(container, "nextSibling");
67
+ }
68
+ function isOnlySpaceTo(container, direction) {
69
+ for (let sibling = container[direction]; sibling && sibling.nodeName !== "BR"; sibling = sibling[direction]) {
70
+ if (sibling.nodeType !== Node.TEXT_NODE) return false;
71
+ const lines = sibling.textContent.split("\n");
72
+ if ((direction === "previousSibling" ? lines[lines.length - 1] : lines[0]).trim() !== "") return false;
73
+ if (lines.length > 1) return true;
74
+ }
75
+ return true;
65
76
  }
66
77
  function isOnlyWhitespaceBetween(previous, next) {
67
78
  const range = previous.ownerDocument.createRange();
@@ -78,7 +89,7 @@
78
89
  carousel.tabIndex = 0;
79
90
  carousel.setAttribute("role", "region");
80
91
  carousel.setAttribute("aria-roledescription", "carousel");
81
- carousel.setAttribute("aria-label", `${containers.length} images`);
92
+ carousel.setAttribute("aria-label", containers.length === 1 ? "1 image" : `${containers.length} images`);
82
93
  carousel.dataset.carouselKey = getCarouselKey(containers);
83
94
  carousel.dataset.carouselSize = getPreferredSize();
84
95
  const stage = createElement(ownerDocument, "span", "ai-image-carousel-stage");
@@ -104,6 +115,7 @@
104
115
  navigation.append(createStepButton(ownerDocument, -1, "Previous image", previousIcon), dots, createStepButton(ownerDocument, 1, "Next image", nextIcon));
105
116
  controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);
106
117
  carousel.append(stage, controls);
118
+ removeSeparatorsAfter(carousel);
107
119
  for (const paragraph of vacatedParagraphs) if (paragraph.textContent.trim() === "" && !paragraph.querySelector(contentBetweenSelector)) paragraph.remove();
108
120
  showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);
109
121
  }
@@ -115,6 +127,11 @@
115
127
  sibling = previousSibling;
116
128
  }
117
129
  }
130
+ function removeSeparatorsAfter(carousel) {
131
+ let sibling = carousel.nextSibling;
132
+ while (sibling && sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === "") sibling = sibling.nextSibling;
133
+ if (sibling && sibling.nodeName === "BR") sibling.remove();
134
+ }
118
135
  function getCarouselKey(containers) {
119
136
  const firstImage = containers[0].querySelector("img");
120
137
  const firstSource = firstImage ? firstImage.getAttribute("src") || "" : "";
@@ -1 +1 @@
1
- {"version":3,"file":"chat-image-carousel.js","sources":["../../Assets/js/chat-image-carousel.js"],"sourcesContent":["/*\n * Stacks the pictures a single answer shows one after another into one carousel.\n *\n * A tool that previews a document -- slides, sheets, pages -- hands back one figure marker per picture, all on a\n * line of their own, and each one renders at full thumbnail size: eight slides is eight thumbnails running down\n * the conversation. Here a run of two or more pictures with nothing but whitespace between them becomes a stack\n * of cards instead: the current picture in front, its neighbours fanned out behind it, moved through by swiping,\n * clicking a neighbour, the arrows, the dots or the arrow keys. A picture written mid-sentence keeps its place.\n *\n * It works on the sanitized HTML every chat surface already produces, so the surfaces share one implementation\n * without sharing a renderer, and the carousel is written in the same pass as the message -- there is no frame\n * of full-size pictures before they are gathered up. HTML with no such run is handed back exactly as it came.\n *\n * Loaded on its own, like chat-markers.js, and optional: a surface without it renders the pictures as before.\n */\nwindow.CoreAIChatImageCarousel = window.CoreAIChatImageCarousel || (function () {\n 'use strict';\n\n const imageContainerClass = 'generated-image-container';\n\n // How far a drag has to travel before it counts as a swipe rather than a click.\n const swipeThreshold = 40;\n\n // How far a drag has to travel before the card starts following the pointer.\n const dragStartThreshold = 8;\n\n // Cards further than this from the current one are stacked out of sight behind the last visible one.\n const furthestVisibleOffset = 2;\n\n // Anything that is content in its own right, so pictures either side of it are not one run.\n const contentBetweenSelector = 'img, svg, canvas, video, audio, iframe, table, hr, input';\n\n const previousIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 3 5 8l5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n const nextIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m6 3 5 5-5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n\n // How large the reader wants previews drawn. It is the reader's taste rather than the answer's, so one choice\n // applies to every carousel on the page and is kept in the browser for the next visit.\n const sizes = [\n { name: 'small', text: 'S', label: 'Small previews' },\n { name: 'medium', text: 'M', label: 'Medium previews' },\n { name: 'large', text: 'L', label: 'Large previews' },\n ];\n const defaultSize = 'medium';\n const sizeStorageKey = 'coreai-image-carousel-size';\n\n // A streamed answer is re-rendered on every chunk, and every render builds the carousel afresh. The picture\n // the reader moved to is remembered here so the next render opens on it rather than back on the first.\n const activeIndexByCarouselKey = new Map();\n\n let drag = null;\n let suppressNextClick = false;\n\n\n function stackAdjacentImages(html) {\n if (typeof document === 'undefined' || !html) {\n return html;\n }\n\n const firstContainerIndex = html.indexOf(imageContainerClass);\n\n if (firstContainerIndex < 0 || html.indexOf(imageContainerClass, firstContainerIndex + imageContainerClass.length) < 0) {\n return html;\n }\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n const runs = findImageRuns(template.content);\n\n if (runs.length === 0) {\n return html;\n }\n\n for (const run of runs) {\n buildCarousel(run);\n }\n\n return template.innerHTML;\n }\n\n\n function findImageRuns(root) {\n const runs = [];\n let run = [];\n\n for (const container of root.querySelectorAll('.' + imageContainerClass)) {\n // Only a picture standing in a paragraph is gathered. One in a list, a table cell or a link belongs\n // to that structure, and lifting it out would change what the answer says.\n if (!container.parentElement || container.parentElement.tagName !== 'P') {\n runs.push(run);\n run = [];\n continue;\n }\n\n if (run.length > 0 && !isOnlyWhitespaceBetween(run[run.length - 1], container)) {\n runs.push(run);\n run = [];\n }\n\n run.push(container);\n }\n\n runs.push(run);\n\n return runs.filter(candidate => candidate.length > 1);\n }\n\n\n // Two pictures belong to one run when nothing a reader would read sits between them: spaces and line breaks\n // within a paragraph, or the boundary between two paragraphs that hold nothing else.\n function isOnlyWhitespaceBetween(previous, next) {\n const range = previous.ownerDocument.createRange();\n range.setStartAfter(previous);\n range.setEndBefore(next);\n\n const between = range.cloneContents();\n\n return between.textContent.trim() === '' && !between.querySelector(contentBetweenSelector);\n }\n\n\n function buildCarousel(containers) {\n const ownerDocument = containers[0].ownerDocument;\n const firstParagraph = containers[0].parentElement;\n const vacatedParagraphs = new Set();\n\n // Spans throughout, for the same reason the picture's own wrapper is one: the carousel sits inside the\n // paragraph the first picture was in, and a block element there would close that paragraph.\n const carousel = createElement(ownerDocument, 'span', 'ai-image-carousel');\n carousel.tabIndex = 0;\n carousel.setAttribute('role', 'region');\n carousel.setAttribute('aria-roledescription', 'carousel');\n carousel.setAttribute('aria-label', `${containers.length} images`);\n carousel.dataset.carouselKey = getCarouselKey(containers);\n carousel.dataset.carouselSize = getPreferredSize();\n\n const stage = createElement(ownerDocument, 'span', 'ai-image-carousel-stage');\n const controls = createElement(ownerDocument, 'span', 'ai-image-carousel-controls');\n const caption = createElement(ownerDocument, 'span', 'ai-image-carousel-caption');\n const navigation = createElement(ownerDocument, 'span', 'ai-image-carousel-navigation');\n const dots = createElement(ownerDocument, 'span', 'ai-image-carousel-dots');\n\n caption.setAttribute('aria-live', 'polite');\n containers[0].before(carousel);\n\n containers.forEach((container, index) => {\n removeSeparatorsBefore(container);\n\n if (container.parentElement !== firstParagraph) {\n vacatedParagraphs.add(container.parentElement);\n }\n\n container.classList.add('ai-image-carousel-item');\n\n const image = container.querySelector('img');\n\n // The browser's own image drag would otherwise take over the pointer the moment a swipe starts.\n if (image) {\n image.setAttribute('draggable', 'false');\n }\n\n const dot = createElement(ownerDocument, 'button', 'ai-image-carousel-dot');\n dot.type = 'button';\n dot.dataset.carouselTarget = index;\n dot.setAttribute('aria-label', `Show image ${index + 1}`);\n dots.appendChild(dot);\n\n stage.appendChild(container);\n });\n\n navigation.append(\n createStepButton(ownerDocument, -1, 'Previous image', previousIcon),\n dots,\n createStepButton(ownerDocument, 1, 'Next image', nextIcon));\n\n controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);\n carousel.append(stage, controls);\n\n for (const paragraph of vacatedParagraphs) {\n if (paragraph.textContent.trim() === '' && !paragraph.querySelector(contentBetweenSelector)) {\n paragraph.remove();\n }\n }\n\n showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);\n }\n\n\n // Whatever separated this picture from the one before it -- a space, a line break -- is all that is left once\n // the pictures have moved into the carousel, and it would otherwise trail the carousel as a blank line.\n function removeSeparatorsBefore(container) {\n let sibling = container.previousSibling;\n\n while (sibling && ((sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') || sibling.nodeName === 'BR')) {\n const previousSibling = sibling.previousSibling;\n sibling.remove();\n sibling = previousSibling;\n }\n }\n\n\n // Cheap rather than exact: a generated image can be a data URI hundreds of kilobytes long, so only the tail\n // of the first address -- where a served document's identifier sits -- and the number of pictures are used.\n function getCarouselKey(containers) {\n const firstImage = containers[0].querySelector('img');\n const firstSource = firstImage ? firstImage.getAttribute('src') || '' : '';\n\n return `${containers.length}:${firstSource.slice(-120)}`;\n }\n\n\n function createElement(ownerDocument, tagName, className) {\n const element = ownerDocument.createElement(tagName);\n element.className = className;\n\n return element;\n }\n\n\n function createStepButton(ownerDocument, step, label, icon) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-button');\n button.type = 'button';\n button.dataset.carouselStep = step;\n button.setAttribute('aria-label', label);\n button.innerHTML = icon;\n\n return button;\n }\n\n\n // A segmented S / M / L control.\n function createSizePicker(ownerDocument, selectedSize) {\n const picker = createElement(ownerDocument, 'span', 'ai-image-carousel-sizes');\n picker.setAttribute('role', 'group');\n picker.setAttribute('aria-label', 'Preview size');\n\n for (const size of sizes) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-size');\n button.type = 'button';\n button.title = size.label;\n button.textContent = size.text;\n button.dataset.carouselSizeOption = size.name;\n button.setAttribute('aria-label', size.label);\n button.setAttribute('aria-pressed', size.name === selectedSize ? 'true' : 'false');\n picker.appendChild(button);\n }\n\n return picker;\n }\n\n\n // Storage can be missing or refuse access -- a private window, blocked site data -- and the default is a fine\n // answer then.\n function getPreferredSize() {\n try {\n const storedSize = localStorage.getItem(sizeStorageKey);\n\n return sizes.some(size => size.name === storedSize) ? storedSize : defaultSize;\n } catch (_) {\n return defaultSize;\n }\n }\n\n\n function setPreferredSize(selectedSize) {\n try {\n localStorage.setItem(sizeStorageKey, selectedSize);\n } catch (_) {\n // Still applied to the page; only remembering it for the next visit is lost.\n }\n\n for (const carousel of document.querySelectorAll('.ai-image-carousel')) {\n carousel.dataset.carouselSize = selectedSize;\n }\n\n for (const button of document.querySelectorAll('[data-carousel-size-option]')) {\n button.setAttribute('aria-pressed', button.dataset.carouselSizeOption === selectedSize ? 'true' : 'false');\n }\n }\n\n\n function getItems(carousel) {\n return carousel.querySelectorAll('.ai-image-carousel-stage > .ai-image-carousel-item');\n }\n\n\n function getActiveIndex(carousel) {\n return Number(carousel.dataset.activeIndex) || 0;\n }\n\n\n function showImage(carousel, requestedIndex) {\n const items = getItems(carousel);\n\n if (items.length === 0) {\n carousel.remove();\n\n return;\n }\n\n const activeIndex = Math.max(0, Math.min(items.length - 1, requestedIndex));\n const dots = carousel.querySelector('.ai-image-carousel-dots');\n\n while (dots.children.length > items.length) {\n dots.lastElementChild.remove();\n }\n\n items.forEach((item, index) => {\n const offset = index - activeIndex;\n const isActive = offset === 0;\n const downloadLink = item.querySelector('a');\n\n item.dataset.stackOffset = Math.max(-furthestVisibleOffset - 1, Math.min(furthestVisibleOffset + 1, offset));\n item.classList.toggle('is-active', isActive);\n item.setAttribute('aria-hidden', isActive ? 'false' : 'true');\n\n // A card behind the current one is hidden from assistive technology, so nothing in it may take focus.\n if (downloadLink) {\n downloadLink.tabIndex = isActive ? 0 : -1;\n }\n\n dots.children[index].classList.toggle('is-active', isActive);\n dots.children[index].setAttribute('aria-current', isActive ? 'true' : 'false');\n });\n\n const activeImage = items[activeIndex].querySelector('img');\n const altText = activeImage ? activeImage.getAttribute('alt') : '';\n\n carousel.dataset.activeIndex = activeIndex;\n carousel.dataset.imageCount = items.length;\n carousel.querySelector('[data-carousel-step=\"-1\"]').disabled = activeIndex === 0;\n carousel.querySelector('[data-carousel-step=\"1\"]').disabled = activeIndex === items.length - 1;\n carousel.querySelector('.ai-image-carousel-caption').textContent = altText\n ? `${activeIndex + 1} / ${items.length} · ${altText}`\n : `${activeIndex + 1} / ${items.length}`;\n\n activeIndexByCarouselKey.set(carousel.dataset.carouselKey, activeIndex);\n }\n\n\n function stopEvent(event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n\n // Captured, so it runs before anything bound to the picture itself: a card behind the current one is brought\n // to the front, and is not zoomed or downloaded on the way.\n function onClick(event) {\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n // A click raised from the keyboard has no pointer behind it, so a swipe that ended without one -- a touch\n // swipe, or a drag released outside the carousel -- must not swallow it.\n if (suppressNextClick && event.detail > 0) {\n suppressNextClick = false;\n stopEvent(event);\n\n return;\n }\n\n const stepButton = event.target.closest('[data-carousel-step]');\n\n if (stepButton) {\n stopEvent(event);\n showImage(carousel, getActiveIndex(carousel) + Number(stepButton.dataset.carouselStep));\n\n return;\n }\n\n const sizeButton = event.target.closest('[data-carousel-size-option]');\n\n if (sizeButton) {\n stopEvent(event);\n setPreferredSize(sizeButton.dataset.carouselSizeOption);\n\n return;\n }\n\n const dot = event.target.closest('[data-carousel-target]');\n\n if (dot) {\n stopEvent(event);\n showImage(carousel, Number(dot.dataset.carouselTarget));\n\n return;\n }\n\n const item = event.target.closest('.ai-image-carousel-item');\n\n if (item && !item.classList.contains('is-active')) {\n stopEvent(event);\n showImage(carousel, Array.prototype.indexOf.call(getItems(carousel), item));\n }\n }\n\n\n // Only while the carousel has focus, so the arrow keys still move the caret in the prompt.\n function onKeyDown(event) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {\n return;\n }\n\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n event.preventDefault();\n showImage(carousel, getActiveIndex(carousel) + (event.key === 'ArrowRight' ? 1 : -1));\n }\n\n\n function onPointerDown(event) {\n suppressNextClick = false;\n\n const stage = event.button === 0 && event.target.closest && event.target.closest('.ai-image-carousel-stage');\n\n if (!stage) {\n return;\n }\n\n drag = {\n stage: stage,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n distance: 0,\n isSwiping: false,\n };\n }\n\n\n // The pointer is not captured: a captured pointer retargets the click that follows to the stage, and the click\n // is what brings a card forward or opens the zoom. Listening on the document follows the pointer anyway.\n function onPointerMove(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const distance = event.clientX - drag.startX;\n\n if (!drag.isSwiping) {\n if (Math.abs(distance) < dragStartThreshold || Math.abs(distance) < Math.abs(event.clientY - drag.startY)) {\n return;\n }\n\n drag.isSwiping = true;\n drag.stage.classList.add('is-dragging');\n }\n\n drag.distance = distance;\n drag.stage.style.setProperty('--ai-image-carousel-drag', `${distance}px`);\n }\n\n\n function onPointerUp(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const { stage, distance, isSwiping } = drag;\n\n endDrag();\n\n if (!isSwiping) {\n return;\n }\n\n // The click the browser fires at the end of a drag is not a click on whatever the pointer ended over.\n suppressNextClick = true;\n\n if (Math.abs(distance) >= swipeThreshold) {\n const carousel = stage.closest('.ai-image-carousel');\n showImage(carousel, getActiveIndex(carousel) + (distance < 0 ? 1 : -1));\n }\n }\n\n\n function endDrag() {\n if (!drag) {\n return;\n }\n\n drag.stage.classList.remove('is-dragging');\n drag.stage.style.removeProperty('--ai-image-carousel-drag');\n drag = null;\n }\n\n\n // A picture the server will not serve is taken out of the stack and put back just after it, where the\n // surface's own handler hides it and says it could not be loaded, exactly as it does for a lone picture.\n // Captured on the document because an image's error event does not bubble.\n function onImageError(event) {\n const item = event.target.tagName === 'IMG' && event.target.closest('.ai-image-carousel-item');\n\n if (!item) {\n return;\n }\n\n const carousel = item.closest('.ai-image-carousel');\n\n item.classList.remove('ai-image-carousel-item', 'is-active');\n item.removeAttribute('data-stack-offset');\n item.removeAttribute('aria-hidden');\n carousel.after(item);\n showImage(carousel, getActiveIndex(carousel));\n }\n\n\n if (typeof document !== 'undefined') {\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('pointerdown', onPointerDown);\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', endDrag);\n document.addEventListener('error', onImageError, true);\n }\n\n return {\n stackAdjacentImages: stackAdjacentImages,\n };\n})();\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-image-carousel.js","sources":["../../Assets/js/chat-image-carousel.js"],"sourcesContent":["/*\n * Stacks the pictures a single answer shows one after another into one carousel.\n *\n * A tool that previews a document -- slides, sheets, pages -- hands back one figure marker per picture, all on a\n * line of their own, and each one renders at full thumbnail size: eight slides is eight thumbnails running down\n * the conversation. Here a run of two or more pictures with nothing but whitespace between them becomes a stack\n * of cards instead: the current picture in front, its neighbours fanned out behind it, moved through by swiping,\n * clicking a neighbour, the arrows, the dots or the arrow keys. A single picture on a line of its own -- a\n * one-page document, one sheet -- becomes a stack of one, so it gets the same caption and size control and the\n * reader can make it smaller. A picture written mid-sentence keeps its place.\n *\n * It works on the sanitized HTML every chat surface already produces, so the surfaces share one implementation\n * without sharing a renderer, and the carousel is written in the same pass as the message -- there is no frame\n * of full-size pictures before they are gathered up. HTML with no such run is handed back exactly as it came.\n *\n * Loaded on its own, like chat-markers.js, and optional: a surface without it renders the pictures as before.\n */\nwindow.CoreAIChatImageCarousel = window.CoreAIChatImageCarousel || (function () {\n 'use strict';\n\n const imageContainerClass = 'generated-image-container';\n\n // How far a drag has to travel before it counts as a swipe rather than a click.\n const swipeThreshold = 40;\n\n // How far a drag has to travel before the card starts following the pointer.\n const dragStartThreshold = 8;\n\n // Cards further than this from the current one are stacked out of sight behind the last visible one.\n const furthestVisibleOffset = 2;\n\n // Anything that is content in its own right, so pictures either side of it are not one run.\n const contentBetweenSelector = 'img, svg, canvas, video, audio, iframe, table, hr, input';\n\n const previousIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 3 5 8l5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n const nextIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m6 3 5 5-5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n\n // How large the reader wants previews drawn. It is the reader's taste rather than the answer's, so one choice\n // applies to every carousel on the page and is kept in the browser for the next visit.\n const sizes = [\n { name: 'small', text: 'S', label: 'Small previews' },\n { name: 'medium', text: 'M', label: 'Medium previews' },\n { name: 'large', text: 'L', label: 'Large previews' },\n ];\n const defaultSize = 'medium';\n const sizeStorageKey = 'coreai-image-carousel-size';\n\n // A streamed answer is re-rendered on every chunk, and every render builds the carousel afresh. The picture\n // the reader moved to is remembered here so the next render opens on it rather than back on the first.\n const activeIndexByCarouselKey = new Map();\n\n let drag = null;\n let suppressNextClick = false;\n\n\n function stackAdjacentImages(html) {\n if (typeof document === 'undefined' || !html) {\n return html;\n }\n\n if (html.indexOf(imageContainerClass) < 0) {\n return html;\n }\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n const runs = findImageRuns(template.content);\n\n if (runs.length === 0) {\n return html;\n }\n\n for (const run of runs) {\n buildCarousel(run);\n }\n\n return template.innerHTML;\n }\n\n\n function findImageRuns(root) {\n const runs = [];\n let run = [];\n\n for (const container of root.querySelectorAll('.' + imageContainerClass)) {\n // Only a picture standing in a paragraph is gathered. One in a list, a table cell or a link belongs\n // to that structure, and lifting it out would change what the answer says.\n if (!container.parentElement || container.parentElement.tagName !== 'P') {\n runs.push(run);\n run = [];\n continue;\n }\n\n if (run.length > 0 && !isOnlyWhitespaceBetween(run[run.length - 1], container)) {\n runs.push(run);\n run = [];\n }\n\n run.push(container);\n }\n\n runs.push(run);\n\n return runs.filter(candidate => candidate.length > 1 || (candidate.length === 1 && isOnLineOfItsOwn(candidate[0])));\n }\n\n\n // A picture with nothing but whitespace between it and the start of its line, and between it and the end of\n // its line. A preview tool writes its markers on a line of their own, often with the download on the next\n // line of the same paragraph, so the end of a line counts as much as the end of the paragraph.\n function isOnLineOfItsOwn(container) {\n return isOnlySpaceTo(container, 'previousSibling') && isOnlySpaceTo(container, 'nextSibling');\n }\n\n\n function isOnlySpaceTo(container, direction) {\n for (let sibling = container[direction]; sibling && sibling.nodeName !== 'BR'; sibling = sibling[direction]) {\n if (sibling.nodeType !== Node.TEXT_NODE) {\n return false;\n }\n\n // Only the part of the text on the picture's own line matters.\n const lines = sibling.textContent.split('\\n');\n const sameLine = direction === 'previousSibling' ? lines[lines.length - 1] : lines[0];\n\n if (sameLine.trim() !== '') {\n return false;\n }\n\n if (lines.length > 1) {\n return true;\n }\n }\n\n return true;\n }\n\n\n // Two pictures belong to one run when nothing a reader would read sits between them: spaces and line breaks\n // within a paragraph, or the boundary between two paragraphs that hold nothing else.\n function isOnlyWhitespaceBetween(previous, next) {\n const range = previous.ownerDocument.createRange();\n range.setStartAfter(previous);\n range.setEndBefore(next);\n\n const between = range.cloneContents();\n\n return between.textContent.trim() === '' && !between.querySelector(contentBetweenSelector);\n }\n\n\n function buildCarousel(containers) {\n const ownerDocument = containers[0].ownerDocument;\n const firstParagraph = containers[0].parentElement;\n const vacatedParagraphs = new Set();\n\n // Spans throughout, for the same reason the picture's own wrapper is one: the carousel sits inside the\n // paragraph the first picture was in, and a block element there would close that paragraph.\n const carousel = createElement(ownerDocument, 'span', 'ai-image-carousel');\n carousel.tabIndex = 0;\n carousel.setAttribute('role', 'region');\n carousel.setAttribute('aria-roledescription', 'carousel');\n carousel.setAttribute('aria-label', containers.length === 1 ? '1 image' : `${containers.length} images`);\n carousel.dataset.carouselKey = getCarouselKey(containers);\n carousel.dataset.carouselSize = getPreferredSize();\n\n const stage = createElement(ownerDocument, 'span', 'ai-image-carousel-stage');\n const controls = createElement(ownerDocument, 'span', 'ai-image-carousel-controls');\n const caption = createElement(ownerDocument, 'span', 'ai-image-carousel-caption');\n const navigation = createElement(ownerDocument, 'span', 'ai-image-carousel-navigation');\n const dots = createElement(ownerDocument, 'span', 'ai-image-carousel-dots');\n\n caption.setAttribute('aria-live', 'polite');\n containers[0].before(carousel);\n\n containers.forEach((container, index) => {\n removeSeparatorsBefore(container);\n\n if (container.parentElement !== firstParagraph) {\n vacatedParagraphs.add(container.parentElement);\n }\n\n container.classList.add('ai-image-carousel-item');\n\n const image = container.querySelector('img');\n\n // The browser's own image drag would otherwise take over the pointer the moment a swipe starts.\n if (image) {\n image.setAttribute('draggable', 'false');\n }\n\n const dot = createElement(ownerDocument, 'button', 'ai-image-carousel-dot');\n dot.type = 'button';\n dot.dataset.carouselTarget = index;\n dot.setAttribute('aria-label', `Show image ${index + 1}`);\n dots.appendChild(dot);\n\n stage.appendChild(container);\n });\n\n navigation.append(\n createStepButton(ownerDocument, -1, 'Previous image', previousIcon),\n dots,\n createStepButton(ownerDocument, 1, 'Next image', nextIcon));\n\n controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);\n carousel.append(stage, controls);\n\n removeSeparatorsAfter(carousel);\n\n for (const paragraph of vacatedParagraphs) {\n if (paragraph.textContent.trim() === '' && !paragraph.querySelector(contentBetweenSelector)) {\n paragraph.remove();\n }\n }\n\n showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);\n }\n\n\n // Whatever separated this picture from the one before it -- a space, a line break -- is all that is left once\n // the pictures have moved into the carousel, and it would otherwise trail the carousel as a blank line.\n function removeSeparatorsBefore(container) {\n let sibling = container.previousSibling;\n\n while (sibling && ((sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') || sibling.nodeName === 'BR')) {\n const previousSibling = sibling.previousSibling;\n sibling.remove();\n sibling = previousSibling;\n }\n }\n\n\n // The line break that ended the last picture's line now follows a carousel that already ends its own line, and\n // would leave a blank line between the carousel and whatever the answer says next -- often its download.\n function removeSeparatorsAfter(carousel) {\n let sibling = carousel.nextSibling;\n\n while (sibling && sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') {\n sibling = sibling.nextSibling;\n }\n\n if (sibling && sibling.nodeName === 'BR') {\n sibling.remove();\n }\n }\n\n\n // Cheap rather than exact: a generated image can be a data URI hundreds of kilobytes long, so only the tail\n // of the first address -- where a served document's identifier sits -- and the number of pictures are used.\n function getCarouselKey(containers) {\n const firstImage = containers[0].querySelector('img');\n const firstSource = firstImage ? firstImage.getAttribute('src') || '' : '';\n\n return `${containers.length}:${firstSource.slice(-120)}`;\n }\n\n\n function createElement(ownerDocument, tagName, className) {\n const element = ownerDocument.createElement(tagName);\n element.className = className;\n\n return element;\n }\n\n\n function createStepButton(ownerDocument, step, label, icon) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-button');\n button.type = 'button';\n button.dataset.carouselStep = step;\n button.setAttribute('aria-label', label);\n button.innerHTML = icon;\n\n return button;\n }\n\n\n // A segmented S / M / L control.\n function createSizePicker(ownerDocument, selectedSize) {\n const picker = createElement(ownerDocument, 'span', 'ai-image-carousel-sizes');\n picker.setAttribute('role', 'group');\n picker.setAttribute('aria-label', 'Preview size');\n\n for (const size of sizes) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-size');\n button.type = 'button';\n button.title = size.label;\n button.textContent = size.text;\n button.dataset.carouselSizeOption = size.name;\n button.setAttribute('aria-label', size.label);\n button.setAttribute('aria-pressed', size.name === selectedSize ? 'true' : 'false');\n picker.appendChild(button);\n }\n\n return picker;\n }\n\n\n // Storage can be missing or refuse access -- a private window, blocked site data -- and the default is a fine\n // answer then.\n function getPreferredSize() {\n try {\n const storedSize = localStorage.getItem(sizeStorageKey);\n\n return sizes.some(size => size.name === storedSize) ? storedSize : defaultSize;\n } catch (_) {\n return defaultSize;\n }\n }\n\n\n function setPreferredSize(selectedSize) {\n try {\n localStorage.setItem(sizeStorageKey, selectedSize);\n } catch (_) {\n // Still applied to the page; only remembering it for the next visit is lost.\n }\n\n for (const carousel of document.querySelectorAll('.ai-image-carousel')) {\n carousel.dataset.carouselSize = selectedSize;\n }\n\n for (const button of document.querySelectorAll('[data-carousel-size-option]')) {\n button.setAttribute('aria-pressed', button.dataset.carouselSizeOption === selectedSize ? 'true' : 'false');\n }\n }\n\n\n function getItems(carousel) {\n return carousel.querySelectorAll('.ai-image-carousel-stage > .ai-image-carousel-item');\n }\n\n\n function getActiveIndex(carousel) {\n return Number(carousel.dataset.activeIndex) || 0;\n }\n\n\n function showImage(carousel, requestedIndex) {\n const items = getItems(carousel);\n\n if (items.length === 0) {\n carousel.remove();\n\n return;\n }\n\n const activeIndex = Math.max(0, Math.min(items.length - 1, requestedIndex));\n const dots = carousel.querySelector('.ai-image-carousel-dots');\n\n while (dots.children.length > items.length) {\n dots.lastElementChild.remove();\n }\n\n items.forEach((item, index) => {\n const offset = index - activeIndex;\n const isActive = offset === 0;\n const downloadLink = item.querySelector('a');\n\n item.dataset.stackOffset = Math.max(-furthestVisibleOffset - 1, Math.min(furthestVisibleOffset + 1, offset));\n item.classList.toggle('is-active', isActive);\n item.setAttribute('aria-hidden', isActive ? 'false' : 'true');\n\n // A card behind the current one is hidden from assistive technology, so nothing in it may take focus.\n if (downloadLink) {\n downloadLink.tabIndex = isActive ? 0 : -1;\n }\n\n dots.children[index].classList.toggle('is-active', isActive);\n dots.children[index].setAttribute('aria-current', isActive ? 'true' : 'false');\n });\n\n const activeImage = items[activeIndex].querySelector('img');\n const altText = activeImage ? activeImage.getAttribute('alt') : '';\n\n carousel.dataset.activeIndex = activeIndex;\n carousel.dataset.imageCount = items.length;\n carousel.querySelector('[data-carousel-step=\"-1\"]').disabled = activeIndex === 0;\n carousel.querySelector('[data-carousel-step=\"1\"]').disabled = activeIndex === items.length - 1;\n carousel.querySelector('.ai-image-carousel-caption').textContent = altText\n ? `${activeIndex + 1} / ${items.length} · ${altText}`\n : `${activeIndex + 1} / ${items.length}`;\n\n activeIndexByCarouselKey.set(carousel.dataset.carouselKey, activeIndex);\n }\n\n\n function stopEvent(event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n\n // Captured, so it runs before anything bound to the picture itself: a card behind the current one is brought\n // to the front, and is not zoomed or downloaded on the way.\n function onClick(event) {\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n // A click raised from the keyboard has no pointer behind it, so a swipe that ended without one -- a touch\n // swipe, or a drag released outside the carousel -- must not swallow it.\n if (suppressNextClick && event.detail > 0) {\n suppressNextClick = false;\n stopEvent(event);\n\n return;\n }\n\n const stepButton = event.target.closest('[data-carousel-step]');\n\n if (stepButton) {\n stopEvent(event);\n showImage(carousel, getActiveIndex(carousel) + Number(stepButton.dataset.carouselStep));\n\n return;\n }\n\n const sizeButton = event.target.closest('[data-carousel-size-option]');\n\n if (sizeButton) {\n stopEvent(event);\n setPreferredSize(sizeButton.dataset.carouselSizeOption);\n\n return;\n }\n\n const dot = event.target.closest('[data-carousel-target]');\n\n if (dot) {\n stopEvent(event);\n showImage(carousel, Number(dot.dataset.carouselTarget));\n\n return;\n }\n\n const item = event.target.closest('.ai-image-carousel-item');\n\n if (item && !item.classList.contains('is-active')) {\n stopEvent(event);\n showImage(carousel, Array.prototype.indexOf.call(getItems(carousel), item));\n }\n }\n\n\n // Only while the carousel has focus, so the arrow keys still move the caret in the prompt.\n function onKeyDown(event) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {\n return;\n }\n\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n event.preventDefault();\n showImage(carousel, getActiveIndex(carousel) + (event.key === 'ArrowRight' ? 1 : -1));\n }\n\n\n function onPointerDown(event) {\n suppressNextClick = false;\n\n const stage = event.button === 0 && event.target.closest && event.target.closest('.ai-image-carousel-stage');\n\n if (!stage) {\n return;\n }\n\n drag = {\n stage: stage,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n distance: 0,\n isSwiping: false,\n };\n }\n\n\n // The pointer is not captured: a captured pointer retargets the click that follows to the stage, and the click\n // is what brings a card forward or opens the zoom. Listening on the document follows the pointer anyway.\n function onPointerMove(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const distance = event.clientX - drag.startX;\n\n if (!drag.isSwiping) {\n if (Math.abs(distance) < dragStartThreshold || Math.abs(distance) < Math.abs(event.clientY - drag.startY)) {\n return;\n }\n\n drag.isSwiping = true;\n drag.stage.classList.add('is-dragging');\n }\n\n drag.distance = distance;\n drag.stage.style.setProperty('--ai-image-carousel-drag', `${distance}px`);\n }\n\n\n function onPointerUp(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const { stage, distance, isSwiping } = drag;\n\n endDrag();\n\n if (!isSwiping) {\n return;\n }\n\n // The click the browser fires at the end of a drag is not a click on whatever the pointer ended over.\n suppressNextClick = true;\n\n if (Math.abs(distance) >= swipeThreshold) {\n const carousel = stage.closest('.ai-image-carousel');\n showImage(carousel, getActiveIndex(carousel) + (distance < 0 ? 1 : -1));\n }\n }\n\n\n function endDrag() {\n if (!drag) {\n return;\n }\n\n drag.stage.classList.remove('is-dragging');\n drag.stage.style.removeProperty('--ai-image-carousel-drag');\n drag = null;\n }\n\n\n // A picture the server will not serve is taken out of the stack and put back just after it, where the\n // surface's own handler hides it and says it could not be loaded, exactly as it does for a lone picture.\n // Captured on the document because an image's error event does not bubble.\n function onImageError(event) {\n const item = event.target.tagName === 'IMG' && event.target.closest('.ai-image-carousel-item');\n\n if (!item) {\n return;\n }\n\n const carousel = item.closest('.ai-image-carousel');\n\n item.classList.remove('ai-image-carousel-item', 'is-active');\n item.removeAttribute('data-stack-offset');\n item.removeAttribute('aria-hidden');\n carousel.after(item);\n showImage(carousel, getActiveIndex(carousel));\n }\n\n\n if (typeof document !== 'undefined') {\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('pointerdown', onPointerDown);\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', endDrag);\n document.addEventListener('error', onImageError, true);\n }\n\n return {\n stackAdjacentImages: stackAdjacentImages,\n };\n})();\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
- (function(){window.CoreAIChatImageCarousel=window.CoreAIChatImageCarousel||(function(){"use strict";let e=`generated-image-container`,t=`img, svg, canvas, video, audio, iframe, table, hr, input`,n=[{name:`small`,text:`S`,label:`Small previews`},{name:`medium`,text:`M`,label:`Medium previews`},{name:`large`,text:`L`,label:`Large previews`}],r=`medium`,i=`coreai-image-carousel-size`,a=new Map,o=null,s=!1;function c(t){if(typeof document>`u`||!t)return t;let n=t.indexOf(e);if(n<0||t.indexOf(e,n+25)<0)return t;let r=document.createElement(`template`);r.innerHTML=t;let i=l(r.content);if(i.length===0)return t;for(let e of i)d(e);return r.innerHTML}function l(t){let n=[],r=[];for(let i of t.querySelectorAll(`.`+e))!i.parentElement||i.parentElement.tagName!==`P`?(n.push(r),r=[]):(r.length>0&&!u(r[r.length-1],i)&&(n.push(r),r=[]),r.push(i));return n.push(r),n.filter(e=>e.length>1)}function u(e,n){let r=e.ownerDocument.createRange();r.setStartAfter(e),r.setEndBefore(n);let i=r.cloneContents();return i.textContent.trim()===``&&!i.querySelector(t)}function d(e){let n=e[0].ownerDocument,r=e[0].parentElement,i=new Set,o=m(n,`span`,`ai-image-carousel`);o.tabIndex=0,o.setAttribute(`role`,`region`),o.setAttribute(`aria-roledescription`,`carousel`),o.setAttribute(`aria-label`,`${e.length} images`),o.dataset.carouselKey=p(e),o.dataset.carouselSize=_();let s=m(n,`span`,`ai-image-carousel-stage`),c=m(n,`span`,`ai-image-carousel-controls`),l=m(n,`span`,`ai-image-carousel-caption`),u=m(n,`span`,`ai-image-carousel-navigation`),d=m(n,`span`,`ai-image-carousel-dots`);l.setAttribute(`aria-live`,`polite`),e[0].before(o),e.forEach((e,t)=>{f(e),e.parentElement!==r&&i.add(e.parentElement),e.classList.add(`ai-image-carousel-item`);let a=e.querySelector(`img`);a&&a.setAttribute(`draggable`,`false`);let o=m(n,`button`,`ai-image-carousel-dot`);o.type=`button`,o.dataset.carouselTarget=t,o.setAttribute(`aria-label`,`Show image ${t+1}`),d.appendChild(o),s.appendChild(e)}),u.append(h(n,-1,`Previous image`,`<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false"><path d="M10 3 5 8l5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`),d,h(n,1,`Next image`,`<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false"><path d="m6 3 5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`)),c.append(l,g(n,o.dataset.carouselSize),u),o.append(s,c);for(let e of i)e.textContent.trim()===``&&!e.querySelector(t)&&e.remove();x(o,a.get(o.dataset.carouselKey)||0)}function f(e){let t=e.previousSibling;for(;t&&(t.nodeType===Node.TEXT_NODE&&t.textContent.trim()===``||t.nodeName===`BR`);){let e=t.previousSibling;t.remove(),t=e}}function p(e){let t=e[0].querySelector(`img`),n=t&&t.getAttribute(`src`)||``;return`${e.length}:${n.slice(-120)}`}function m(e,t,n){let r=e.createElement(t);return r.className=n,r}function h(e,t,n,r){let i=m(e,`button`,`ai-image-carousel-button`);return i.type=`button`,i.dataset.carouselStep=t,i.setAttribute(`aria-label`,n),i.innerHTML=r,i}function g(e,t){let r=m(e,`span`,`ai-image-carousel-sizes`);r.setAttribute(`role`,`group`),r.setAttribute(`aria-label`,`Preview size`);for(let i of n){let n=m(e,`button`,`ai-image-carousel-size`);n.type=`button`,n.title=i.label,n.textContent=i.text,n.dataset.carouselSizeOption=i.name,n.setAttribute(`aria-label`,i.label),n.setAttribute(`aria-pressed`,i.name===t?`true`:`false`),r.appendChild(n)}return r}function _(){try{let e=localStorage.getItem(i);return n.some(t=>t.name===e)?e:r}catch{return r}}function v(e){try{localStorage.setItem(i,e)}catch{}for(let t of document.querySelectorAll(`.ai-image-carousel`))t.dataset.carouselSize=e;for(let t of document.querySelectorAll(`[data-carousel-size-option]`))t.setAttribute(`aria-pressed`,t.dataset.carouselSizeOption===e?`true`:`false`)}function y(e){return e.querySelectorAll(`.ai-image-carousel-stage > .ai-image-carousel-item`)}function b(e){return Number(e.dataset.activeIndex)||0}function x(e,t){let n=y(e);if(n.length===0){e.remove();return}let r=Math.max(0,Math.min(n.length-1,t)),i=e.querySelector(`.ai-image-carousel-dots`);for(;i.children.length>n.length;)i.lastElementChild.remove();n.forEach((e,t)=>{let n=t-r,a=n===0,o=e.querySelector(`a`);e.dataset.stackOffset=Math.max(-3,Math.min(3,n)),e.classList.toggle(`is-active`,a),e.setAttribute(`aria-hidden`,a?`false`:`true`),o&&(o.tabIndex=a?0:-1),i.children[t].classList.toggle(`is-active`,a),i.children[t].setAttribute(`aria-current`,a?`true`:`false`)});let o=n[r].querySelector(`img`),s=o?o.getAttribute(`alt`):``;e.dataset.activeIndex=r,e.dataset.imageCount=n.length,e.querySelector(`[data-carousel-step="-1"]`).disabled=r===0,e.querySelector(`[data-carousel-step="1"]`).disabled=r===n.length-1,e.querySelector(`.ai-image-carousel-caption`).textContent=s?`${r+1} / ${n.length} · ${s}`:`${r+1} / ${n.length}`,a.set(e.dataset.carouselKey,r)}function S(e){e.preventDefault(),e.stopPropagation()}function C(e){let t=e.target.closest&&e.target.closest(`.ai-image-carousel`);if(!t)return;if(s&&e.detail>0){s=!1,S(e);return}let n=e.target.closest(`[data-carousel-step]`);if(n){S(e),x(t,b(t)+Number(n.dataset.carouselStep));return}let r=e.target.closest(`[data-carousel-size-option]`);if(r){S(e),v(r.dataset.carouselSizeOption);return}let i=e.target.closest(`[data-carousel-target]`);if(i){S(e),x(t,Number(i.dataset.carouselTarget));return}let a=e.target.closest(`.ai-image-carousel-item`);a&&!a.classList.contains(`is-active`)&&(S(e),x(t,Array.prototype.indexOf.call(y(t),a)))}function w(e){if(e.key!==`ArrowLeft`&&e.key!==`ArrowRight`)return;let t=e.target.closest&&e.target.closest(`.ai-image-carousel`);t&&(e.preventDefault(),x(t,b(t)+(e.key===`ArrowRight`?1:-1)))}function T(e){s=!1;let t=e.button===0&&e.target.closest&&e.target.closest(`.ai-image-carousel-stage`);t&&(o={stage:t,pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,distance:0,isSwiping:!1})}function E(e){if(!o||e.pointerId!==o.pointerId)return;let t=e.clientX-o.startX;if(!o.isSwiping){if(Math.abs(t)<8||Math.abs(t)<Math.abs(e.clientY-o.startY))return;o.isSwiping=!0,o.stage.classList.add(`is-dragging`)}o.distance=t,o.stage.style.setProperty(`--ai-image-carousel-drag`,`${t}px`)}function D(e){if(!o||e.pointerId!==o.pointerId)return;let{stage:t,distance:n,isSwiping:r}=o;if(O(),r&&(s=!0,Math.abs(n)>=40)){let e=t.closest(`.ai-image-carousel`);x(e,b(e)+(n<0?1:-1))}}function O(){o&&=(o.stage.classList.remove(`is-dragging`),o.stage.style.removeProperty(`--ai-image-carousel-drag`),null)}function k(e){let t=e.target.tagName===`IMG`&&e.target.closest(`.ai-image-carousel-item`);if(!t)return;let n=t.closest(`.ai-image-carousel`);t.classList.remove(`ai-image-carousel-item`,`is-active`),t.removeAttribute(`data-stack-offset`),t.removeAttribute(`aria-hidden`),n.after(t),x(n,b(n))}return typeof document<`u`&&(document.addEventListener(`click`,C,!0),document.addEventListener(`keydown`,w),document.addEventListener(`pointerdown`,T),document.addEventListener(`pointermove`,E),document.addEventListener(`pointerup`,D),document.addEventListener(`pointercancel`,O),document.addEventListener(`error`,k,!0)),{stackAdjacentImages:c}})()})();
1
+ (function(){window.CoreAIChatImageCarousel=window.CoreAIChatImageCarousel||(function(){"use strict";let e=`generated-image-container`,t=`img, svg, canvas, video, audio, iframe, table, hr, input`,n=[{name:`small`,text:`S`,label:`Small previews`},{name:`medium`,text:`M`,label:`Medium previews`},{name:`large`,text:`L`,label:`Large previews`}],r=`medium`,i=`coreai-image-carousel-size`,a=new Map,o=null,s=!1;function c(t){if(typeof document>`u`||!t||t.indexOf(e)<0)return t;let n=document.createElement(`template`);n.innerHTML=t;let r=l(n.content);if(r.length===0)return t;for(let e of r)p(e);return n.innerHTML}function l(t){let n=[],r=[];for(let i of t.querySelectorAll(`.`+e))!i.parentElement||i.parentElement.tagName!==`P`?(n.push(r),r=[]):(r.length>0&&!f(r[r.length-1],i)&&(n.push(r),r=[]),r.push(i));return n.push(r),n.filter(e=>e.length>1||e.length===1&&u(e[0]))}function u(e){return d(e,`previousSibling`)&&d(e,`nextSibling`)}function d(e,t){for(let n=e[t];n&&n.nodeName!==`BR`;n=n[t]){if(n.nodeType!==Node.TEXT_NODE)return!1;let e=n.textContent.split(`
2
+ `);if((t===`previousSibling`?e[e.length-1]:e[0]).trim()!==``)return!1;if(e.length>1)return!0}return!0}function f(e,n){let r=e.ownerDocument.createRange();r.setStartAfter(e),r.setEndBefore(n);let i=r.cloneContents();return i.textContent.trim()===``&&!i.querySelector(t)}function p(e){let n=e[0].ownerDocument,r=e[0].parentElement,i=new Set,o=_(n,`span`,`ai-image-carousel`);o.tabIndex=0,o.setAttribute(`role`,`region`),o.setAttribute(`aria-roledescription`,`carousel`),o.setAttribute(`aria-label`,e.length===1?`1 image`:`${e.length} images`),o.dataset.carouselKey=g(e),o.dataset.carouselSize=b();let s=_(n,`span`,`ai-image-carousel-stage`),c=_(n,`span`,`ai-image-carousel-controls`),l=_(n,`span`,`ai-image-carousel-caption`),u=_(n,`span`,`ai-image-carousel-navigation`),d=_(n,`span`,`ai-image-carousel-dots`);l.setAttribute(`aria-live`,`polite`),e[0].before(o),e.forEach((e,t)=>{m(e),e.parentElement!==r&&i.add(e.parentElement),e.classList.add(`ai-image-carousel-item`);let a=e.querySelector(`img`);a&&a.setAttribute(`draggable`,`false`);let o=_(n,`button`,`ai-image-carousel-dot`);o.type=`button`,o.dataset.carouselTarget=t,o.setAttribute(`aria-label`,`Show image ${t+1}`),d.appendChild(o),s.appendChild(e)}),u.append(v(n,-1,`Previous image`,`<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false"><path d="M10 3 5 8l5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`),d,v(n,1,`Next image`,`<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false"><path d="m6 3 5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`)),c.append(l,y(n,o.dataset.carouselSize),u),o.append(s,c),h(o);for(let e of i)e.textContent.trim()===``&&!e.querySelector(t)&&e.remove();w(o,a.get(o.dataset.carouselKey)||0)}function m(e){let t=e.previousSibling;for(;t&&(t.nodeType===Node.TEXT_NODE&&t.textContent.trim()===``||t.nodeName===`BR`);){let e=t.previousSibling;t.remove(),t=e}}function h(e){let t=e.nextSibling;for(;t&&t.nodeType===Node.TEXT_NODE&&t.textContent.trim()===``;)t=t.nextSibling;t&&t.nodeName===`BR`&&t.remove()}function g(e){let t=e[0].querySelector(`img`),n=t&&t.getAttribute(`src`)||``;return`${e.length}:${n.slice(-120)}`}function _(e,t,n){let r=e.createElement(t);return r.className=n,r}function v(e,t,n,r){let i=_(e,`button`,`ai-image-carousel-button`);return i.type=`button`,i.dataset.carouselStep=t,i.setAttribute(`aria-label`,n),i.innerHTML=r,i}function y(e,t){let r=_(e,`span`,`ai-image-carousel-sizes`);r.setAttribute(`role`,`group`),r.setAttribute(`aria-label`,`Preview size`);for(let i of n){let n=_(e,`button`,`ai-image-carousel-size`);n.type=`button`,n.title=i.label,n.textContent=i.text,n.dataset.carouselSizeOption=i.name,n.setAttribute(`aria-label`,i.label),n.setAttribute(`aria-pressed`,i.name===t?`true`:`false`),r.appendChild(n)}return r}function b(){try{let e=localStorage.getItem(i);return n.some(t=>t.name===e)?e:r}catch{return r}}function x(e){try{localStorage.setItem(i,e)}catch{}for(let t of document.querySelectorAll(`.ai-image-carousel`))t.dataset.carouselSize=e;for(let t of document.querySelectorAll(`[data-carousel-size-option]`))t.setAttribute(`aria-pressed`,t.dataset.carouselSizeOption===e?`true`:`false`)}function S(e){return e.querySelectorAll(`.ai-image-carousel-stage > .ai-image-carousel-item`)}function C(e){return Number(e.dataset.activeIndex)||0}function w(e,t){let n=S(e);if(n.length===0){e.remove();return}let r=Math.max(0,Math.min(n.length-1,t)),i=e.querySelector(`.ai-image-carousel-dots`);for(;i.children.length>n.length;)i.lastElementChild.remove();n.forEach((e,t)=>{let n=t-r,a=n===0,o=e.querySelector(`a`);e.dataset.stackOffset=Math.max(-3,Math.min(3,n)),e.classList.toggle(`is-active`,a),e.setAttribute(`aria-hidden`,a?`false`:`true`),o&&(o.tabIndex=a?0:-1),i.children[t].classList.toggle(`is-active`,a),i.children[t].setAttribute(`aria-current`,a?`true`:`false`)});let o=n[r].querySelector(`img`),s=o?o.getAttribute(`alt`):``;e.dataset.activeIndex=r,e.dataset.imageCount=n.length,e.querySelector(`[data-carousel-step="-1"]`).disabled=r===0,e.querySelector(`[data-carousel-step="1"]`).disabled=r===n.length-1,e.querySelector(`.ai-image-carousel-caption`).textContent=s?`${r+1} / ${n.length} · ${s}`:`${r+1} / ${n.length}`,a.set(e.dataset.carouselKey,r)}function T(e){e.preventDefault(),e.stopPropagation()}function E(e){let t=e.target.closest&&e.target.closest(`.ai-image-carousel`);if(!t)return;if(s&&e.detail>0){s=!1,T(e);return}let n=e.target.closest(`[data-carousel-step]`);if(n){T(e),w(t,C(t)+Number(n.dataset.carouselStep));return}let r=e.target.closest(`[data-carousel-size-option]`);if(r){T(e),x(r.dataset.carouselSizeOption);return}let i=e.target.closest(`[data-carousel-target]`);if(i){T(e),w(t,Number(i.dataset.carouselTarget));return}let a=e.target.closest(`.ai-image-carousel-item`);a&&!a.classList.contains(`is-active`)&&(T(e),w(t,Array.prototype.indexOf.call(S(t),a)))}function D(e){if(e.key!==`ArrowLeft`&&e.key!==`ArrowRight`)return;let t=e.target.closest&&e.target.closest(`.ai-image-carousel`);t&&(e.preventDefault(),w(t,C(t)+(e.key===`ArrowRight`?1:-1)))}function O(e){s=!1;let t=e.button===0&&e.target.closest&&e.target.closest(`.ai-image-carousel-stage`);t&&(o={stage:t,pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,distance:0,isSwiping:!1})}function k(e){if(!o||e.pointerId!==o.pointerId)return;let t=e.clientX-o.startX;if(!o.isSwiping){if(Math.abs(t)<8||Math.abs(t)<Math.abs(e.clientY-o.startY))return;o.isSwiping=!0,o.stage.classList.add(`is-dragging`)}o.distance=t,o.stage.style.setProperty(`--ai-image-carousel-drag`,`${t}px`)}function A(e){if(!o||e.pointerId!==o.pointerId)return;let{stage:t,distance:n,isSwiping:r}=o;if(j(),r&&(s=!0,Math.abs(n)>=40)){let e=t.closest(`.ai-image-carousel`);w(e,C(e)+(n<0?1:-1))}}function j(){o&&=(o.stage.classList.remove(`is-dragging`),o.stage.style.removeProperty(`--ai-image-carousel-drag`),null)}function M(e){let t=e.target.tagName===`IMG`&&e.target.closest(`.ai-image-carousel-item`);if(!t)return;let n=t.closest(`.ai-image-carousel`);t.classList.remove(`ai-image-carousel-item`,`is-active`),t.removeAttribute(`data-stack-offset`),t.removeAttribute(`aria-hidden`),n.after(t),w(n,C(n))}return typeof document<`u`&&(document.addEventListener(`click`,E,!0),document.addEventListener(`keydown`,D),document.addEventListener(`pointerdown`,O),document.addEventListener(`pointermove`,k),document.addEventListener(`pointerup`,A),document.addEventListener(`pointercancel`,j),document.addEventListener(`error`,M,!0)),{stackAdjacentImages:c}})()})();
2
3
  //# sourceMappingURL=chat-image-carousel.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-image-carousel.min.js","sources":["../../Assets/js/chat-image-carousel.js"],"sourcesContent":["/*\n * Stacks the pictures a single answer shows one after another into one carousel.\n *\n * A tool that previews a document -- slides, sheets, pages -- hands back one figure marker per picture, all on a\n * line of their own, and each one renders at full thumbnail size: eight slides is eight thumbnails running down\n * the conversation. Here a run of two or more pictures with nothing but whitespace between them becomes a stack\n * of cards instead: the current picture in front, its neighbours fanned out behind it, moved through by swiping,\n * clicking a neighbour, the arrows, the dots or the arrow keys. A picture written mid-sentence keeps its place.\n *\n * It works on the sanitized HTML every chat surface already produces, so the surfaces share one implementation\n * without sharing a renderer, and the carousel is written in the same pass as the message -- there is no frame\n * of full-size pictures before they are gathered up. HTML with no such run is handed back exactly as it came.\n *\n * Loaded on its own, like chat-markers.js, and optional: a surface without it renders the pictures as before.\n */\nwindow.CoreAIChatImageCarousel = window.CoreAIChatImageCarousel || (function () {\n 'use strict';\n\n const imageContainerClass = 'generated-image-container';\n\n // How far a drag has to travel before it counts as a swipe rather than a click.\n const swipeThreshold = 40;\n\n // How far a drag has to travel before the card starts following the pointer.\n const dragStartThreshold = 8;\n\n // Cards further than this from the current one are stacked out of sight behind the last visible one.\n const furthestVisibleOffset = 2;\n\n // Anything that is content in its own right, so pictures either side of it are not one run.\n const contentBetweenSelector = 'img, svg, canvas, video, audio, iframe, table, hr, input';\n\n const previousIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 3 5 8l5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n const nextIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m6 3 5 5-5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n\n // How large the reader wants previews drawn. It is the reader's taste rather than the answer's, so one choice\n // applies to every carousel on the page and is kept in the browser for the next visit.\n const sizes = [\n { name: 'small', text: 'S', label: 'Small previews' },\n { name: 'medium', text: 'M', label: 'Medium previews' },\n { name: 'large', text: 'L', label: 'Large previews' },\n ];\n const defaultSize = 'medium';\n const sizeStorageKey = 'coreai-image-carousel-size';\n\n // A streamed answer is re-rendered on every chunk, and every render builds the carousel afresh. The picture\n // the reader moved to is remembered here so the next render opens on it rather than back on the first.\n const activeIndexByCarouselKey = new Map();\n\n let drag = null;\n let suppressNextClick = false;\n\n\n function stackAdjacentImages(html) {\n if (typeof document === 'undefined' || !html) {\n return html;\n }\n\n const firstContainerIndex = html.indexOf(imageContainerClass);\n\n if (firstContainerIndex < 0 || html.indexOf(imageContainerClass, firstContainerIndex + imageContainerClass.length) < 0) {\n return html;\n }\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n const runs = findImageRuns(template.content);\n\n if (runs.length === 0) {\n return html;\n }\n\n for (const run of runs) {\n buildCarousel(run);\n }\n\n return template.innerHTML;\n }\n\n\n function findImageRuns(root) {\n const runs = [];\n let run = [];\n\n for (const container of root.querySelectorAll('.' + imageContainerClass)) {\n // Only a picture standing in a paragraph is gathered. One in a list, a table cell or a link belongs\n // to that structure, and lifting it out would change what the answer says.\n if (!container.parentElement || container.parentElement.tagName !== 'P') {\n runs.push(run);\n run = [];\n continue;\n }\n\n if (run.length > 0 && !isOnlyWhitespaceBetween(run[run.length - 1], container)) {\n runs.push(run);\n run = [];\n }\n\n run.push(container);\n }\n\n runs.push(run);\n\n return runs.filter(candidate => candidate.length > 1);\n }\n\n\n // Two pictures belong to one run when nothing a reader would read sits between them: spaces and line breaks\n // within a paragraph, or the boundary between two paragraphs that hold nothing else.\n function isOnlyWhitespaceBetween(previous, next) {\n const range = previous.ownerDocument.createRange();\n range.setStartAfter(previous);\n range.setEndBefore(next);\n\n const between = range.cloneContents();\n\n return between.textContent.trim() === '' && !between.querySelector(contentBetweenSelector);\n }\n\n\n function buildCarousel(containers) {\n const ownerDocument = containers[0].ownerDocument;\n const firstParagraph = containers[0].parentElement;\n const vacatedParagraphs = new Set();\n\n // Spans throughout, for the same reason the picture's own wrapper is one: the carousel sits inside the\n // paragraph the first picture was in, and a block element there would close that paragraph.\n const carousel = createElement(ownerDocument, 'span', 'ai-image-carousel');\n carousel.tabIndex = 0;\n carousel.setAttribute('role', 'region');\n carousel.setAttribute('aria-roledescription', 'carousel');\n carousel.setAttribute('aria-label', `${containers.length} images`);\n carousel.dataset.carouselKey = getCarouselKey(containers);\n carousel.dataset.carouselSize = getPreferredSize();\n\n const stage = createElement(ownerDocument, 'span', 'ai-image-carousel-stage');\n const controls = createElement(ownerDocument, 'span', 'ai-image-carousel-controls');\n const caption = createElement(ownerDocument, 'span', 'ai-image-carousel-caption');\n const navigation = createElement(ownerDocument, 'span', 'ai-image-carousel-navigation');\n const dots = createElement(ownerDocument, 'span', 'ai-image-carousel-dots');\n\n caption.setAttribute('aria-live', 'polite');\n containers[0].before(carousel);\n\n containers.forEach((container, index) => {\n removeSeparatorsBefore(container);\n\n if (container.parentElement !== firstParagraph) {\n vacatedParagraphs.add(container.parentElement);\n }\n\n container.classList.add('ai-image-carousel-item');\n\n const image = container.querySelector('img');\n\n // The browser's own image drag would otherwise take over the pointer the moment a swipe starts.\n if (image) {\n image.setAttribute('draggable', 'false');\n }\n\n const dot = createElement(ownerDocument, 'button', 'ai-image-carousel-dot');\n dot.type = 'button';\n dot.dataset.carouselTarget = index;\n dot.setAttribute('aria-label', `Show image ${index + 1}`);\n dots.appendChild(dot);\n\n stage.appendChild(container);\n });\n\n navigation.append(\n createStepButton(ownerDocument, -1, 'Previous image', previousIcon),\n dots,\n createStepButton(ownerDocument, 1, 'Next image', nextIcon));\n\n controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);\n carousel.append(stage, controls);\n\n for (const paragraph of vacatedParagraphs) {\n if (paragraph.textContent.trim() === '' && !paragraph.querySelector(contentBetweenSelector)) {\n paragraph.remove();\n }\n }\n\n showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);\n }\n\n\n // Whatever separated this picture from the one before it -- a space, a line break -- is all that is left once\n // the pictures have moved into the carousel, and it would otherwise trail the carousel as a blank line.\n function removeSeparatorsBefore(container) {\n let sibling = container.previousSibling;\n\n while (sibling && ((sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') || sibling.nodeName === 'BR')) {\n const previousSibling = sibling.previousSibling;\n sibling.remove();\n sibling = previousSibling;\n }\n }\n\n\n // Cheap rather than exact: a generated image can be a data URI hundreds of kilobytes long, so only the tail\n // of the first address -- where a served document's identifier sits -- and the number of pictures are used.\n function getCarouselKey(containers) {\n const firstImage = containers[0].querySelector('img');\n const firstSource = firstImage ? firstImage.getAttribute('src') || '' : '';\n\n return `${containers.length}:${firstSource.slice(-120)}`;\n }\n\n\n function createElement(ownerDocument, tagName, className) {\n const element = ownerDocument.createElement(tagName);\n element.className = className;\n\n return element;\n }\n\n\n function createStepButton(ownerDocument, step, label, icon) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-button');\n button.type = 'button';\n button.dataset.carouselStep = step;\n button.setAttribute('aria-label', label);\n button.innerHTML = icon;\n\n return button;\n }\n\n\n // A segmented S / M / L control.\n function createSizePicker(ownerDocument, selectedSize) {\n const picker = createElement(ownerDocument, 'span', 'ai-image-carousel-sizes');\n picker.setAttribute('role', 'group');\n picker.setAttribute('aria-label', 'Preview size');\n\n for (const size of sizes) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-size');\n button.type = 'button';\n button.title = size.label;\n button.textContent = size.text;\n button.dataset.carouselSizeOption = size.name;\n button.setAttribute('aria-label', size.label);\n button.setAttribute('aria-pressed', size.name === selectedSize ? 'true' : 'false');\n picker.appendChild(button);\n }\n\n return picker;\n }\n\n\n // Storage can be missing or refuse access -- a private window, blocked site data -- and the default is a fine\n // answer then.\n function getPreferredSize() {\n try {\n const storedSize = localStorage.getItem(sizeStorageKey);\n\n return sizes.some(size => size.name === storedSize) ? storedSize : defaultSize;\n } catch (_) {\n return defaultSize;\n }\n }\n\n\n function setPreferredSize(selectedSize) {\n try {\n localStorage.setItem(sizeStorageKey, selectedSize);\n } catch (_) {\n // Still applied to the page; only remembering it for the next visit is lost.\n }\n\n for (const carousel of document.querySelectorAll('.ai-image-carousel')) {\n carousel.dataset.carouselSize = selectedSize;\n }\n\n for (const button of document.querySelectorAll('[data-carousel-size-option]')) {\n button.setAttribute('aria-pressed', button.dataset.carouselSizeOption === selectedSize ? 'true' : 'false');\n }\n }\n\n\n function getItems(carousel) {\n return carousel.querySelectorAll('.ai-image-carousel-stage > .ai-image-carousel-item');\n }\n\n\n function getActiveIndex(carousel) {\n return Number(carousel.dataset.activeIndex) || 0;\n }\n\n\n function showImage(carousel, requestedIndex) {\n const items = getItems(carousel);\n\n if (items.length === 0) {\n carousel.remove();\n\n return;\n }\n\n const activeIndex = Math.max(0, Math.min(items.length - 1, requestedIndex));\n const dots = carousel.querySelector('.ai-image-carousel-dots');\n\n while (dots.children.length > items.length) {\n dots.lastElementChild.remove();\n }\n\n items.forEach((item, index) => {\n const offset = index - activeIndex;\n const isActive = offset === 0;\n const downloadLink = item.querySelector('a');\n\n item.dataset.stackOffset = Math.max(-furthestVisibleOffset - 1, Math.min(furthestVisibleOffset + 1, offset));\n item.classList.toggle('is-active', isActive);\n item.setAttribute('aria-hidden', isActive ? 'false' : 'true');\n\n // A card behind the current one is hidden from assistive technology, so nothing in it may take focus.\n if (downloadLink) {\n downloadLink.tabIndex = isActive ? 0 : -1;\n }\n\n dots.children[index].classList.toggle('is-active', isActive);\n dots.children[index].setAttribute('aria-current', isActive ? 'true' : 'false');\n });\n\n const activeImage = items[activeIndex].querySelector('img');\n const altText = activeImage ? activeImage.getAttribute('alt') : '';\n\n carousel.dataset.activeIndex = activeIndex;\n carousel.dataset.imageCount = items.length;\n carousel.querySelector('[data-carousel-step=\"-1\"]').disabled = activeIndex === 0;\n carousel.querySelector('[data-carousel-step=\"1\"]').disabled = activeIndex === items.length - 1;\n carousel.querySelector('.ai-image-carousel-caption').textContent = altText\n ? `${activeIndex + 1} / ${items.length} · ${altText}`\n : `${activeIndex + 1} / ${items.length}`;\n\n activeIndexByCarouselKey.set(carousel.dataset.carouselKey, activeIndex);\n }\n\n\n function stopEvent(event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n\n // Captured, so it runs before anything bound to the picture itself: a card behind the current one is brought\n // to the front, and is not zoomed or downloaded on the way.\n function onClick(event) {\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n // A click raised from the keyboard has no pointer behind it, so a swipe that ended without one -- a touch\n // swipe, or a drag released outside the carousel -- must not swallow it.\n if (suppressNextClick && event.detail > 0) {\n suppressNextClick = false;\n stopEvent(event);\n\n return;\n }\n\n const stepButton = event.target.closest('[data-carousel-step]');\n\n if (stepButton) {\n stopEvent(event);\n showImage(carousel, getActiveIndex(carousel) + Number(stepButton.dataset.carouselStep));\n\n return;\n }\n\n const sizeButton = event.target.closest('[data-carousel-size-option]');\n\n if (sizeButton) {\n stopEvent(event);\n setPreferredSize(sizeButton.dataset.carouselSizeOption);\n\n return;\n }\n\n const dot = event.target.closest('[data-carousel-target]');\n\n if (dot) {\n stopEvent(event);\n showImage(carousel, Number(dot.dataset.carouselTarget));\n\n return;\n }\n\n const item = event.target.closest('.ai-image-carousel-item');\n\n if (item && !item.classList.contains('is-active')) {\n stopEvent(event);\n showImage(carousel, Array.prototype.indexOf.call(getItems(carousel), item));\n }\n }\n\n\n // Only while the carousel has focus, so the arrow keys still move the caret in the prompt.\n function onKeyDown(event) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {\n return;\n }\n\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n event.preventDefault();\n showImage(carousel, getActiveIndex(carousel) + (event.key === 'ArrowRight' ? 1 : -1));\n }\n\n\n function onPointerDown(event) {\n suppressNextClick = false;\n\n const stage = event.button === 0 && event.target.closest && event.target.closest('.ai-image-carousel-stage');\n\n if (!stage) {\n return;\n }\n\n drag = {\n stage: stage,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n distance: 0,\n isSwiping: false,\n };\n }\n\n\n // The pointer is not captured: a captured pointer retargets the click that follows to the stage, and the click\n // is what brings a card forward or opens the zoom. Listening on the document follows the pointer anyway.\n function onPointerMove(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const distance = event.clientX - drag.startX;\n\n if (!drag.isSwiping) {\n if (Math.abs(distance) < dragStartThreshold || Math.abs(distance) < Math.abs(event.clientY - drag.startY)) {\n return;\n }\n\n drag.isSwiping = true;\n drag.stage.classList.add('is-dragging');\n }\n\n drag.distance = distance;\n drag.stage.style.setProperty('--ai-image-carousel-drag', `${distance}px`);\n }\n\n\n function onPointerUp(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const { stage, distance, isSwiping } = drag;\n\n endDrag();\n\n if (!isSwiping) {\n return;\n }\n\n // The click the browser fires at the end of a drag is not a click on whatever the pointer ended over.\n suppressNextClick = true;\n\n if (Math.abs(distance) >= swipeThreshold) {\n const carousel = stage.closest('.ai-image-carousel');\n showImage(carousel, getActiveIndex(carousel) + (distance < 0 ? 1 : -1));\n }\n }\n\n\n function endDrag() {\n if (!drag) {\n return;\n }\n\n drag.stage.classList.remove('is-dragging');\n drag.stage.style.removeProperty('--ai-image-carousel-drag');\n drag = null;\n }\n\n\n // A picture the server will not serve is taken out of the stack and put back just after it, where the\n // surface's own handler hides it and says it could not be loaded, exactly as it does for a lone picture.\n // Captured on the document because an image's error event does not bubble.\n function onImageError(event) {\n const item = event.target.tagName === 'IMG' && event.target.closest('.ai-image-carousel-item');\n\n if (!item) {\n return;\n }\n\n const carousel = item.closest('.ai-image-carousel');\n\n item.classList.remove('ai-image-carousel-item', 'is-active');\n item.removeAttribute('data-stack-offset');\n item.removeAttribute('aria-hidden');\n carousel.after(item);\n showImage(carousel, getActiveIndex(carousel));\n }\n\n\n if (typeof document !== 'undefined') {\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('pointerdown', onPointerDown);\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', endDrag);\n document.addEventListener('error', onImageError, true);\n }\n\n return {\n stackAdjacentImages: stackAdjacentImages,\n };\n})();\n"],"names":["imageContainerClass","contentBetweenSelector","sizes","defaultSize","sizeStorageKey","activeIndexByCarouselKey","drag","suppressNextClick","stackAdjacentImages","html","firstContainerIndex","template","runs","findImageRuns","run","buildCarousel","container","root","isOnlyWhitespaceBetween","candidate","range","previous","next","between","ownerDocument","containers","firstParagraph","vacatedParagraphs","carousel","createElement","getCarouselKey","getPreferredSize","stage","controls","caption","navigation","dots","removeSeparatorsBefore","image","dot","index","createStepButton","createSizePicker","paragraph","showImage","sibling","previousSibling","firstImage","firstSource","element","tagName","className","button","step","label","icon","picker","size","selectedSize","storedSize","setPreferredSize","getItems","getActiveIndex","items","activeIndex","requestedIndex","offset","isActive","downloadLink","item","activeImage","altText","stopEvent","event","onClick","stepButton","sizeButton","onKeyDown","onPointerDown","onPointerMove","distance","onPointerUp","endDrag","isSwiping","onImageError"],"mappings":"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"}
1
+ {"version":3,"file":"chat-image-carousel.min.js","sources":["../../Assets/js/chat-image-carousel.js"],"sourcesContent":["/*\n * Stacks the pictures a single answer shows one after another into one carousel.\n *\n * A tool that previews a document -- slides, sheets, pages -- hands back one figure marker per picture, all on a\n * line of their own, and each one renders at full thumbnail size: eight slides is eight thumbnails running down\n * the conversation. Here a run of two or more pictures with nothing but whitespace between them becomes a stack\n * of cards instead: the current picture in front, its neighbours fanned out behind it, moved through by swiping,\n * clicking a neighbour, the arrows, the dots or the arrow keys. A single picture on a line of its own -- a\n * one-page document, one sheet -- becomes a stack of one, so it gets the same caption and size control and the\n * reader can make it smaller. A picture written mid-sentence keeps its place.\n *\n * It works on the sanitized HTML every chat surface already produces, so the surfaces share one implementation\n * without sharing a renderer, and the carousel is written in the same pass as the message -- there is no frame\n * of full-size pictures before they are gathered up. HTML with no such run is handed back exactly as it came.\n *\n * Loaded on its own, like chat-markers.js, and optional: a surface without it renders the pictures as before.\n */\nwindow.CoreAIChatImageCarousel = window.CoreAIChatImageCarousel || (function () {\n 'use strict';\n\n const imageContainerClass = 'generated-image-container';\n\n // How far a drag has to travel before it counts as a swipe rather than a click.\n const swipeThreshold = 40;\n\n // How far a drag has to travel before the card starts following the pointer.\n const dragStartThreshold = 8;\n\n // Cards further than this from the current one are stacked out of sight behind the last visible one.\n const furthestVisibleOffset = 2;\n\n // Anything that is content in its own right, so pictures either side of it are not one run.\n const contentBetweenSelector = 'img, svg, canvas, video, audio, iframe, table, hr, input';\n\n const previousIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 3 5 8l5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n const nextIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m6 3 5 5-5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n\n // How large the reader wants previews drawn. It is the reader's taste rather than the answer's, so one choice\n // applies to every carousel on the page and is kept in the browser for the next visit.\n const sizes = [\n { name: 'small', text: 'S', label: 'Small previews' },\n { name: 'medium', text: 'M', label: 'Medium previews' },\n { name: 'large', text: 'L', label: 'Large previews' },\n ];\n const defaultSize = 'medium';\n const sizeStorageKey = 'coreai-image-carousel-size';\n\n // A streamed answer is re-rendered on every chunk, and every render builds the carousel afresh. The picture\n // the reader moved to is remembered here so the next render opens on it rather than back on the first.\n const activeIndexByCarouselKey = new Map();\n\n let drag = null;\n let suppressNextClick = false;\n\n\n function stackAdjacentImages(html) {\n if (typeof document === 'undefined' || !html) {\n return html;\n }\n\n if (html.indexOf(imageContainerClass) < 0) {\n return html;\n }\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n const runs = findImageRuns(template.content);\n\n if (runs.length === 0) {\n return html;\n }\n\n for (const run of runs) {\n buildCarousel(run);\n }\n\n return template.innerHTML;\n }\n\n\n function findImageRuns(root) {\n const runs = [];\n let run = [];\n\n for (const container of root.querySelectorAll('.' + imageContainerClass)) {\n // Only a picture standing in a paragraph is gathered. One in a list, a table cell or a link belongs\n // to that structure, and lifting it out would change what the answer says.\n if (!container.parentElement || container.parentElement.tagName !== 'P') {\n runs.push(run);\n run = [];\n continue;\n }\n\n if (run.length > 0 && !isOnlyWhitespaceBetween(run[run.length - 1], container)) {\n runs.push(run);\n run = [];\n }\n\n run.push(container);\n }\n\n runs.push(run);\n\n return runs.filter(candidate => candidate.length > 1 || (candidate.length === 1 && isOnLineOfItsOwn(candidate[0])));\n }\n\n\n // A picture with nothing but whitespace between it and the start of its line, and between it and the end of\n // its line. A preview tool writes its markers on a line of their own, often with the download on the next\n // line of the same paragraph, so the end of a line counts as much as the end of the paragraph.\n function isOnLineOfItsOwn(container) {\n return isOnlySpaceTo(container, 'previousSibling') && isOnlySpaceTo(container, 'nextSibling');\n }\n\n\n function isOnlySpaceTo(container, direction) {\n for (let sibling = container[direction]; sibling && sibling.nodeName !== 'BR'; sibling = sibling[direction]) {\n if (sibling.nodeType !== Node.TEXT_NODE) {\n return false;\n }\n\n // Only the part of the text on the picture's own line matters.\n const lines = sibling.textContent.split('\\n');\n const sameLine = direction === 'previousSibling' ? lines[lines.length - 1] : lines[0];\n\n if (sameLine.trim() !== '') {\n return false;\n }\n\n if (lines.length > 1) {\n return true;\n }\n }\n\n return true;\n }\n\n\n // Two pictures belong to one run when nothing a reader would read sits between them: spaces and line breaks\n // within a paragraph, or the boundary between two paragraphs that hold nothing else.\n function isOnlyWhitespaceBetween(previous, next) {\n const range = previous.ownerDocument.createRange();\n range.setStartAfter(previous);\n range.setEndBefore(next);\n\n const between = range.cloneContents();\n\n return between.textContent.trim() === '' && !between.querySelector(contentBetweenSelector);\n }\n\n\n function buildCarousel(containers) {\n const ownerDocument = containers[0].ownerDocument;\n const firstParagraph = containers[0].parentElement;\n const vacatedParagraphs = new Set();\n\n // Spans throughout, for the same reason the picture's own wrapper is one: the carousel sits inside the\n // paragraph the first picture was in, and a block element there would close that paragraph.\n const carousel = createElement(ownerDocument, 'span', 'ai-image-carousel');\n carousel.tabIndex = 0;\n carousel.setAttribute('role', 'region');\n carousel.setAttribute('aria-roledescription', 'carousel');\n carousel.setAttribute('aria-label', containers.length === 1 ? '1 image' : `${containers.length} images`);\n carousel.dataset.carouselKey = getCarouselKey(containers);\n carousel.dataset.carouselSize = getPreferredSize();\n\n const stage = createElement(ownerDocument, 'span', 'ai-image-carousel-stage');\n const controls = createElement(ownerDocument, 'span', 'ai-image-carousel-controls');\n const caption = createElement(ownerDocument, 'span', 'ai-image-carousel-caption');\n const navigation = createElement(ownerDocument, 'span', 'ai-image-carousel-navigation');\n const dots = createElement(ownerDocument, 'span', 'ai-image-carousel-dots');\n\n caption.setAttribute('aria-live', 'polite');\n containers[0].before(carousel);\n\n containers.forEach((container, index) => {\n removeSeparatorsBefore(container);\n\n if (container.parentElement !== firstParagraph) {\n vacatedParagraphs.add(container.parentElement);\n }\n\n container.classList.add('ai-image-carousel-item');\n\n const image = container.querySelector('img');\n\n // The browser's own image drag would otherwise take over the pointer the moment a swipe starts.\n if (image) {\n image.setAttribute('draggable', 'false');\n }\n\n const dot = createElement(ownerDocument, 'button', 'ai-image-carousel-dot');\n dot.type = 'button';\n dot.dataset.carouselTarget = index;\n dot.setAttribute('aria-label', `Show image ${index + 1}`);\n dots.appendChild(dot);\n\n stage.appendChild(container);\n });\n\n navigation.append(\n createStepButton(ownerDocument, -1, 'Previous image', previousIcon),\n dots,\n createStepButton(ownerDocument, 1, 'Next image', nextIcon));\n\n controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);\n carousel.append(stage, controls);\n\n removeSeparatorsAfter(carousel);\n\n for (const paragraph of vacatedParagraphs) {\n if (paragraph.textContent.trim() === '' && !paragraph.querySelector(contentBetweenSelector)) {\n paragraph.remove();\n }\n }\n\n showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);\n }\n\n\n // Whatever separated this picture from the one before it -- a space, a line break -- is all that is left once\n // the pictures have moved into the carousel, and it would otherwise trail the carousel as a blank line.\n function removeSeparatorsBefore(container) {\n let sibling = container.previousSibling;\n\n while (sibling && ((sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') || sibling.nodeName === 'BR')) {\n const previousSibling = sibling.previousSibling;\n sibling.remove();\n sibling = previousSibling;\n }\n }\n\n\n // The line break that ended the last picture's line now follows a carousel that already ends its own line, and\n // would leave a blank line between the carousel and whatever the answer says next -- often its download.\n function removeSeparatorsAfter(carousel) {\n let sibling = carousel.nextSibling;\n\n while (sibling && sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') {\n sibling = sibling.nextSibling;\n }\n\n if (sibling && sibling.nodeName === 'BR') {\n sibling.remove();\n }\n }\n\n\n // Cheap rather than exact: a generated image can be a data URI hundreds of kilobytes long, so only the tail\n // of the first address -- where a served document's identifier sits -- and the number of pictures are used.\n function getCarouselKey(containers) {\n const firstImage = containers[0].querySelector('img');\n const firstSource = firstImage ? firstImage.getAttribute('src') || '' : '';\n\n return `${containers.length}:${firstSource.slice(-120)}`;\n }\n\n\n function createElement(ownerDocument, tagName, className) {\n const element = ownerDocument.createElement(tagName);\n element.className = className;\n\n return element;\n }\n\n\n function createStepButton(ownerDocument, step, label, icon) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-button');\n button.type = 'button';\n button.dataset.carouselStep = step;\n button.setAttribute('aria-label', label);\n button.innerHTML = icon;\n\n return button;\n }\n\n\n // A segmented S / M / L control.\n function createSizePicker(ownerDocument, selectedSize) {\n const picker = createElement(ownerDocument, 'span', 'ai-image-carousel-sizes');\n picker.setAttribute('role', 'group');\n picker.setAttribute('aria-label', 'Preview size');\n\n for (const size of sizes) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-size');\n button.type = 'button';\n button.title = size.label;\n button.textContent = size.text;\n button.dataset.carouselSizeOption = size.name;\n button.setAttribute('aria-label', size.label);\n button.setAttribute('aria-pressed', size.name === selectedSize ? 'true' : 'false');\n picker.appendChild(button);\n }\n\n return picker;\n }\n\n\n // Storage can be missing or refuse access -- a private window, blocked site data -- and the default is a fine\n // answer then.\n function getPreferredSize() {\n try {\n const storedSize = localStorage.getItem(sizeStorageKey);\n\n return sizes.some(size => size.name === storedSize) ? storedSize : defaultSize;\n } catch (_) {\n return defaultSize;\n }\n }\n\n\n function setPreferredSize(selectedSize) {\n try {\n localStorage.setItem(sizeStorageKey, selectedSize);\n } catch (_) {\n // Still applied to the page; only remembering it for the next visit is lost.\n }\n\n for (const carousel of document.querySelectorAll('.ai-image-carousel')) {\n carousel.dataset.carouselSize = selectedSize;\n }\n\n for (const button of document.querySelectorAll('[data-carousel-size-option]')) {\n button.setAttribute('aria-pressed', button.dataset.carouselSizeOption === selectedSize ? 'true' : 'false');\n }\n }\n\n\n function getItems(carousel) {\n return carousel.querySelectorAll('.ai-image-carousel-stage > .ai-image-carousel-item');\n }\n\n\n function getActiveIndex(carousel) {\n return Number(carousel.dataset.activeIndex) || 0;\n }\n\n\n function showImage(carousel, requestedIndex) {\n const items = getItems(carousel);\n\n if (items.length === 0) {\n carousel.remove();\n\n return;\n }\n\n const activeIndex = Math.max(0, Math.min(items.length - 1, requestedIndex));\n const dots = carousel.querySelector('.ai-image-carousel-dots');\n\n while (dots.children.length > items.length) {\n dots.lastElementChild.remove();\n }\n\n items.forEach((item, index) => {\n const offset = index - activeIndex;\n const isActive = offset === 0;\n const downloadLink = item.querySelector('a');\n\n item.dataset.stackOffset = Math.max(-furthestVisibleOffset - 1, Math.min(furthestVisibleOffset + 1, offset));\n item.classList.toggle('is-active', isActive);\n item.setAttribute('aria-hidden', isActive ? 'false' : 'true');\n\n // A card behind the current one is hidden from assistive technology, so nothing in it may take focus.\n if (downloadLink) {\n downloadLink.tabIndex = isActive ? 0 : -1;\n }\n\n dots.children[index].classList.toggle('is-active', isActive);\n dots.children[index].setAttribute('aria-current', isActive ? 'true' : 'false');\n });\n\n const activeImage = items[activeIndex].querySelector('img');\n const altText = activeImage ? activeImage.getAttribute('alt') : '';\n\n carousel.dataset.activeIndex = activeIndex;\n carousel.dataset.imageCount = items.length;\n carousel.querySelector('[data-carousel-step=\"-1\"]').disabled = activeIndex === 0;\n carousel.querySelector('[data-carousel-step=\"1\"]').disabled = activeIndex === items.length - 1;\n carousel.querySelector('.ai-image-carousel-caption').textContent = altText\n ? `${activeIndex + 1} / ${items.length} · ${altText}`\n : `${activeIndex + 1} / ${items.length}`;\n\n activeIndexByCarouselKey.set(carousel.dataset.carouselKey, activeIndex);\n }\n\n\n function stopEvent(event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n\n // Captured, so it runs before anything bound to the picture itself: a card behind the current one is brought\n // to the front, and is not zoomed or downloaded on the way.\n function onClick(event) {\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n // A click raised from the keyboard has no pointer behind it, so a swipe that ended without one -- a touch\n // swipe, or a drag released outside the carousel -- must not swallow it.\n if (suppressNextClick && event.detail > 0) {\n suppressNextClick = false;\n stopEvent(event);\n\n return;\n }\n\n const stepButton = event.target.closest('[data-carousel-step]');\n\n if (stepButton) {\n stopEvent(event);\n showImage(carousel, getActiveIndex(carousel) + Number(stepButton.dataset.carouselStep));\n\n return;\n }\n\n const sizeButton = event.target.closest('[data-carousel-size-option]');\n\n if (sizeButton) {\n stopEvent(event);\n setPreferredSize(sizeButton.dataset.carouselSizeOption);\n\n return;\n }\n\n const dot = event.target.closest('[data-carousel-target]');\n\n if (dot) {\n stopEvent(event);\n showImage(carousel, Number(dot.dataset.carouselTarget));\n\n return;\n }\n\n const item = event.target.closest('.ai-image-carousel-item');\n\n if (item && !item.classList.contains('is-active')) {\n stopEvent(event);\n showImage(carousel, Array.prototype.indexOf.call(getItems(carousel), item));\n }\n }\n\n\n // Only while the carousel has focus, so the arrow keys still move the caret in the prompt.\n function onKeyDown(event) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {\n return;\n }\n\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n event.preventDefault();\n showImage(carousel, getActiveIndex(carousel) + (event.key === 'ArrowRight' ? 1 : -1));\n }\n\n\n function onPointerDown(event) {\n suppressNextClick = false;\n\n const stage = event.button === 0 && event.target.closest && event.target.closest('.ai-image-carousel-stage');\n\n if (!stage) {\n return;\n }\n\n drag = {\n stage: stage,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n distance: 0,\n isSwiping: false,\n };\n }\n\n\n // The pointer is not captured: a captured pointer retargets the click that follows to the stage, and the click\n // is what brings a card forward or opens the zoom. Listening on the document follows the pointer anyway.\n function onPointerMove(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const distance = event.clientX - drag.startX;\n\n if (!drag.isSwiping) {\n if (Math.abs(distance) < dragStartThreshold || Math.abs(distance) < Math.abs(event.clientY - drag.startY)) {\n return;\n }\n\n drag.isSwiping = true;\n drag.stage.classList.add('is-dragging');\n }\n\n drag.distance = distance;\n drag.stage.style.setProperty('--ai-image-carousel-drag', `${distance}px`);\n }\n\n\n function onPointerUp(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const { stage, distance, isSwiping } = drag;\n\n endDrag();\n\n if (!isSwiping) {\n return;\n }\n\n // The click the browser fires at the end of a drag is not a click on whatever the pointer ended over.\n suppressNextClick = true;\n\n if (Math.abs(distance) >= swipeThreshold) {\n const carousel = stage.closest('.ai-image-carousel');\n showImage(carousel, getActiveIndex(carousel) + (distance < 0 ? 1 : -1));\n }\n }\n\n\n function endDrag() {\n if (!drag) {\n return;\n }\n\n drag.stage.classList.remove('is-dragging');\n drag.stage.style.removeProperty('--ai-image-carousel-drag');\n drag = null;\n }\n\n\n // A picture the server will not serve is taken out of the stack and put back just after it, where the\n // surface's own handler hides it and says it could not be loaded, exactly as it does for a lone picture.\n // Captured on the document because an image's error event does not bubble.\n function onImageError(event) {\n const item = event.target.tagName === 'IMG' && event.target.closest('.ai-image-carousel-item');\n\n if (!item) {\n return;\n }\n\n const carousel = item.closest('.ai-image-carousel');\n\n item.classList.remove('ai-image-carousel-item', 'is-active');\n item.removeAttribute('data-stack-offset');\n item.removeAttribute('aria-hidden');\n carousel.after(item);\n showImage(carousel, getActiveIndex(carousel));\n }\n\n\n if (typeof document !== 'undefined') {\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('pointerdown', onPointerDown);\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', endDrag);\n document.addEventListener('error', onImageError, true);\n }\n\n return {\n stackAdjacentImages: stackAdjacentImages,\n };\n})();\n"],"names":["imageContainerClass","contentBetweenSelector","sizes","defaultSize","sizeStorageKey","activeIndexByCarouselKey","drag","suppressNextClick","stackAdjacentImages","html","template","runs","findImageRuns","run","buildCarousel","container","root","isOnlyWhitespaceBetween","candidate","isOnLineOfItsOwn","isOnlySpaceTo","sibling","direction","lines","range","previous","next","between","ownerDocument","containers","firstParagraph","vacatedParagraphs","carousel","createElement","getCarouselKey","getPreferredSize","stage","controls","caption","navigation","dots","removeSeparatorsBefore","image","dot","index","createStepButton","createSizePicker","removeSeparatorsAfter","paragraph","showImage","previousSibling","firstImage","firstSource","element","tagName","className","button","step","label","icon","picker","size","selectedSize","storedSize","setPreferredSize","getItems","getActiveIndex","items","activeIndex","requestedIndex","offset","isActive","downloadLink","item","activeImage","altText","stopEvent","event","onClick","stepButton","sizeButton","onKeyDown","onPointerDown","onPointerMove","distance","onPointerUp","endDrag","isSwiping","onImageError"],"mappings":"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"}
@@ -16,13 +16,14 @@
16
16
  copiedTitle: "Response copied to clipboard.",
17
17
  copyResetDelayMs: 2e3,
18
18
  assistantLabel: "Assistant",
19
+ assistantAvatarUrl: null,
19
20
  messageTemplate: `
20
21
  <div class="ai-chat-messages">
21
22
  <div v-for="(message, index) in messages" :key="index" class="ai-chat-message-item">
22
23
  <div>
23
24
  <div v-if="message.role === 'user'" class="ai-chat-msg-role ai-chat-msg-role-user">You</div>
24
25
  <div v-else-if="message.role !== 'indicator'" :class="getAssistantRoleClasses(message)">
25
- <span :class="getAssistantIconClasses(message, index)"><i :class="getAssistantIcon(message)"></i></span>
26
+ <span :class="getAssistantIconClasses(message, index)"><img v-if="getAssistantAvatarUrl(message)" class="ai-chat-assistant-avatar" :src="getAssistantAvatarUrl(message)" alt="" /><i v-else :class="getAssistantIcon(message)"></i></span>
26
27
  {{ getAssistantLabel(message) }}
27
28
  </div>
28
29
  <div class="ai-chat-message-body lh-base">
@@ -64,8 +65,8 @@
64
65
  `,
65
66
  indicatorTemplate: `
66
67
  <div class="ai-chat-msg-role ai-chat-msg-role-assistant">
67
- <span class="ai-streaming-icon"><i class="fa fa-robot" style="display: inline-block;"></i></span>
68
- Assistant
68
+ {{ assistantIcon }}
69
+ {{ assistantLabel }}
69
70
  </div>
70
71
  `,
71
72
  untitledText: "Untitled",
@@ -381,8 +382,19 @@
381
382
  if (!source || typeof source !== "object") return {};
382
383
  return Object.fromEntries(Object.entries(source).filter(([, value]) => value !== void 0));
383
384
  }
385
+ function resolveIndicatorTemplate(template, assistantLabel, assistantAvatarUrl) {
386
+ if (!template) return template;
387
+ const encodedAssistantLabel = escapeHtmlEntities(assistantLabel || "Assistant");
388
+ const avatarUrl = sanitizeUrl(assistantAvatarUrl);
389
+ const assistantIcon = avatarUrl ? "<span class=\"ai-streaming-avatar\"><img class=\"ai-chat-assistant-avatar\" src=\"" + escapeHtmlAttribute(avatarUrl) + "\" alt=\"\" /></span>" : "<span class=\"ai-streaming-icon\"><i class=\"fa fa-robot\" style=\"display: inline-block;\"></i></span>";
390
+ return template.replace(/\{\{\s*assistantLabel\s*\}\}/g, encodedAssistantLabel).replace(/\{\{\s*assistantIcon\s*\}\}/g, assistantIcon);
391
+ }
392
+ function escapeHtmlAttribute(text) {
393
+ return escapeHtmlEntities(text).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
394
+ }
384
395
  const initialize = (instanceConfig) => {
385
396
  const config = Object.assign({}, defaultConfig, compactObject(instanceConfig));
397
+ config.indicatorTemplate = resolveIndicatorTemplate(config.indicatorTemplate, config.assistantLabel, config.assistantAvatarUrl);
386
398
  defaultConfig = config;
387
399
  if (!config.signalRHubUrl) {
388
400
  console.error("The signalRHubUrl is required.");
@@ -448,6 +460,7 @@
448
460
  notificationDismissTimers: {},
449
461
  copyTitle: config.copyTitle,
450
462
  copiedTitle: config.copiedTitle,
463
+ assistantAvatarUrl: sanitizeUrl(config.assistantAvatarUrl) || null,
451
464
  copiedMessageIndex: -1,
452
465
  copyResetTimeoutId: null,
453
466
  activeCopyButton: null
@@ -622,10 +635,10 @@
622
635
  else this.audioChunks.push(bytes);
623
636
  }
624
637
  });
625
- this.connection.on("ReceiveAudioComplete", (itemId) => {
638
+ this.connection.on("ReceiveAudioComplete", () => {
626
639
  this.playCollectedAudio();
627
640
  });
628
- this.connection.on("HistoryCleared", (itemId) => {
641
+ this.connection.on("HistoryCleared", () => {
629
642
  this.messages = [];
630
643
  this.showPlaceholder();
631
644
  const clearHistoryBtn = document.getElementById("clearHistoryBtn");
@@ -716,8 +729,13 @@
716
729
  },
717
730
  getAssistantIconClasses(message, index) {
718
731
  var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
732
+ if (this.getAssistantAvatarUrl(message)) return [this.shouldAnimateAssistantIcon(message, index) ? "ai-streaming-avatar" : "ai-bot-avatar"];
719
733
  return [this.shouldAnimateAssistantIcon(message, index) ? "ai-streaming-icon" : "ai-bot-icon", appearance && appearance.cssClass ? appearance.cssClass : ""];
720
734
  },
735
+ getAssistantAvatarUrl(message) {
736
+ if (!this.assistantAvatarUrl || message && this.normalizeAssistantAppearance(message.appearance)) return null;
737
+ return this.assistantAvatarUrl;
738
+ },
721
739
  getAssistantIcon(message) {
722
740
  var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
723
741
  return appearance && appearance.icon ? appearance.icon : "fa fa-robot";
@@ -846,7 +864,7 @@
846
864
  icons[i].classList.add("ai-bot-icon");
847
865
  }
848
866
  }
849
- for (var i = 0; i < this.messages.length; i++) if (this.messages[i].isStreaming) this.messages[i].isStreaming = false;
867
+ for (i = 0; i < this.messages.length; i++) if (this.messages[i].isStreaming) this.messages[i].isStreaming = false;
850
868
  if (this.settingsDirty) this.debouncedSaveSettings();
851
869
  },
852
870
  handleExternalTtsStop(e) {
@@ -1252,7 +1270,7 @@
1252
1270
  getSettingInputs() {
1253
1271
  const explicitInputs = document.querySelectorAll(".setting-input[data-setting]");
1254
1272
  if (explicitInputs.length > 0) return explicitInputs;
1255
- return document.querySelectorAll("input[name^=\"ChatInteraction.\"]:not([name*=\".Tools[\"]):not([name*=\".Connections[\"]), select[name^=\"ChatInteraction.\"]:not([name*=\".Tools[\"]):not([name*=\".Connections[\"]), textarea[name^=\"ChatInteraction.\"]:not([name*=\".Tools[\"]):not([name*=\".Connections[\"])");
1273
+ return document.querySelectorAll("input[name^=\"ChatInteraction.\"]:not([name*=\".Tools[\"]):not([name*=\".Connections[\"]):not([name*=\".Instances[\"]), select[name^=\"ChatInteraction.\"]:not([name*=\".Tools[\"]):not([name*=\".Connections[\"]):not([name*=\".Instances[\"]), textarea[name^=\"ChatInteraction.\"]:not([name*=\".Tools[\"]):not([name*=\".Connections[\"]):not([name*=\".Instances[\"])");
1256
1274
  },
1257
1275
  getSelectedGroupValues(groupName, fallbackSelector) {
1258
1276
  const explicitSelections = document.querySelectorAll(`.capability-checkbox[data-save-group="${groupName}"]:checked, .capability-checkbox[data-group="${groupName}"]:checked`);
@@ -1361,7 +1379,7 @@
1361
1379
  if (e.target.value.trim()) this.buttonElement.removeAttribute("disabled");
1362
1380
  else this.buttonElement.setAttribute("disabled", true);
1363
1381
  });
1364
- this.inputElement.addEventListener("paste", (e) => {
1382
+ this.inputElement.addEventListener("paste", () => {
1365
1383
  setTimeout(() => {
1366
1384
  this.prompt = this.inputElement.value;
1367
1385
  if (this.inputElement.value.trim()) this.buttonElement.removeAttribute("disabled");
@@ -1633,6 +1651,12 @@
1633
1651
  getSelectedAgentNames() {
1634
1652
  return this.getSelectedGroupValues("agentNames", "input[type=\"checkbox\"][name$=\"].IsSelected\"][name^=\"ChatInteraction.Agents[\"]:checked");
1635
1653
  },
1654
+ hasToolInstanceSelector() {
1655
+ return document.querySelector(".capability-checkbox[data-save-group=\"toolInstanceNames\"], .capability-checkbox[data-group=\"toolInstanceNames\"]") !== null;
1656
+ },
1657
+ getSelectedToolInstanceNames() {
1658
+ return this.getSelectedGroupValues("toolInstanceNames");
1659
+ },
1636
1660
  saveSettings() {
1637
1661
  const itemId = this.getItemId();
1638
1662
  if (!itemId) return Promise.resolve();
@@ -1652,6 +1676,7 @@
1652
1676
  settings.mcpConnectionIds = this.getSelectedMcpConnectionIds();
1653
1677
  settings.a2aConnectionIds = this.getSelectedA2AConnectionIds();
1654
1678
  settings.agentNames = this.getSelectedAgentNames();
1679
+ if (this.hasToolInstanceSelector()) settings.toolInstanceNames = this.getSelectedToolInstanceNames();
1655
1680
  const promptTemplates = this.getPromptTemplateSelections();
1656
1681
  if (promptTemplates.length > 0) {
1657
1682
  settings.promptTemplates = promptTemplates;