-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(ai-assistant): changed sources section style #702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -424,24 +424,79 @@ export class ChatBubble { | |
| const wrapper = createTag("div", { | ||
| class: "chat-bubble-sources", | ||
| }); | ||
| const heading = createTag("p", { class: "chat-bubble-sources-heading" }); | ||
| heading.textContent = "Sources:"; | ||
| const heading = createTag("h6", { class: "chat-bubble-sources-heading" }); | ||
| const button = createTag("button", { | ||
| "aria-expanded": false, | ||
| "aria-controls": `sources-region-${this.id}`, | ||
| id: `sources-button-${this.id}`, | ||
| }); | ||
| const buttonIndicator = createTag("div", { | ||
| class: "chat-bubble-sources-status-indicator", | ||
| }); | ||
| buttonIndicator.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"> | ||
| <path fill="#222" d="M7.965 5.178C7.978 5.118 8 5.061 8 5s-.021-.118-.034-.178c-.01-.05-.01-.102-.03-.15-.023-.058-.068-.107-.104-.16-.03-.042-.047-.09-.084-.127l-.004-.003-.003-.004L3.615.303a.875.875 0 1 0-1.23 1.244L5.88 5 2.385 8.453a.875.875 0 1 0 1.23 1.244L7.74 5.622l.003-.004.004-.003c.037-.038.055-.085.084-.127.036-.053.08-.102.104-.16.02-.048.02-.1.03-.15"></path> | ||
| </svg>`; | ||
| const buttonText = createTag("span", {}); | ||
| buttonText.textContent = "Sources"; | ||
| const faviconsContainer = createTag('div', {class: 'chat-bubble-sources-favicons', 'aria-hidden': true}) | ||
| const faviconImages = createTag('div', { class: 'chat-bubble-sources-favicon-images' }); | ||
| faviconsContainer.appendChild(faviconImages); | ||
| if (references.length > 3) { | ||
| const faviconsContainerText = createTag('span', {}); | ||
| faviconsContainerText.innerText = `& ${references.length - 3} more`; | ||
| faviconsContainer.appendChild(faviconsContainerText); | ||
| } | ||
|
|
||
| button.appendChild(buttonIndicator); | ||
| button.appendChild(buttonText); | ||
| button.appendChild(faviconsContainer); | ||
| heading.appendChild(button); | ||
| wrapper.appendChild(heading); | ||
|
|
||
| const sourcesRegion = createTag("div", { | ||
| id: `sources-region-${this.id}`, | ||
| "aria-labelledby": `sources-button-${this.id}`, | ||
| role: "region", | ||
| hidden: true, | ||
| }); | ||
| const list = createTag("ol", { class: "chat-bubble-sources-list" }); | ||
| references.forEach(({ url, title }) => { | ||
| references.forEach(({ url, title }, idx) => { | ||
| const host = new URL(url).host; | ||
| const sourceFavicon = createTag('img', { src: `https://s2.googleusercontent.com/s2/favicons?domain=${host}&sz=16` }); | ||
| if (idx < 3) { | ||
| faviconImages.appendChild(sourceFavicon); | ||
| } | ||
| const li = createTag("li", { class: "chat-bubble-sources-item" }); | ||
| const marker = createTag("span", { | ||
| "aria-hidden": "true", | ||
| class: "chat-bubble-sources-item-marker", | ||
| }); | ||
| marker.textContent = String(idx + 1); | ||
| const a = createTag("a", { | ||
| href: url, | ||
| target: "_blank", | ||
| rel: "noopener noreferrer", | ||
| }); | ||
| a.textContent = title || url; | ||
| a.setAttribute("daa-ll", `DevsiteAI Assistant:Message:Sources:Link`); | ||
| a.setAttribute( | ||
| "daa-ll", | ||
| `DevsiteAI Assistant:Message:Sources:Link:${a.textContent}|${url}`, | ||
| ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. daa-ll analytics attribute now embeds raw title + URL. If a title contains either :/| of character (common in doc titles), it can corrupt how downstream analytics parses that field. |
||
| li.appendChild(marker); | ||
| li.appendChild(a); | ||
| list.appendChild(li); | ||
| }); | ||
| wrapper.appendChild(list); | ||
| sourcesRegion.appendChild(list); | ||
| wrapper.appendChild(sourcesRegion); | ||
|
|
||
| button.addEventListener("click", () => { | ||
| const isExpanded = button.ariaExpanded === "true"; | ||
| const newState = !isExpanded; | ||
| sourcesRegion.hidden = newState === false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be written as !newState instead of newState === false |
||
| button.ariaExpanded = String(newState); | ||
| buttonIndicator.classList.toggle("rotated"); | ||
| }); | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick question on the sources rendering change — looks like we moved from showing sources as soon as a citation comes in, to only rendering them once onComplete fires. What happens if the stream errors out after onCitation has already set accumulatedReferences, but before onComplete gets a chance to run? Since onError doesn't call appendReferences, wouldn't the sources we already got from the backend just get dropped in that case? Previously they'd have shown up immediately, so this feels like it could be a regression on the error path — might be worth appending whatever we've accumulated in onError too, unless I'm missing something. |
||
| this.element.appendChild(wrapper); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -430,16 +430,13 @@ export const handleUserQuery = async ( | |
| }) | ||
| .filter((r) => !!r); | ||
| if (references?.length) { | ||
| // Accumulate references but defer rendering until the response | ||
| // finishes streaming (see onComplete). | ||
| accumulatedReferences = references; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Named accumulatedReferences / commented "Accumulate," but this replaces. If onCitation fires more than once with disjoint payloads, only the last batch renders. Does it fire more than once per response?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the references event only fires once. I delayed when we show them because the browser limits how often you can update an element which lead to situations where you would get the sources section before the bubble with the response finished rendering all the content. In the future I also intend to improve the response streaming/parsing part so we are not hitting the browser so hard which will also limit the amount of updates that the bubble receives. I guess the comment is a bit confusing in this case so I'll update it. |
||
| targetBubble.appendReferences(references); | ||
| chatHistory.updateLast({ | ||
| content: responseContent, | ||
| references, | ||
| }); | ||
| if (!userScrolledUp && ELEMENTS.CHAT_WINDOW_CONTENT) { | ||
| ELEMENTS.CHAT_WINDOW_CONTENT.scrollTop = | ||
| ELEMENTS.CHAT_WINDOW_CONTENT.scrollHeight; | ||
| } | ||
| } | ||
| } | ||
| }, | ||
|
|
@@ -462,6 +459,9 @@ export const handleUserQuery = async ( | |
| return; | ||
| } | ||
| targetBubble.completeBubble(); | ||
| if (accumulatedReferences?.length) { | ||
| targetBubble.appendReferences(accumulatedReferences); | ||
| } | ||
| // a11y: announce the completed reply once, as plain text. | ||
| announce(`${CHAT_BUBBLE_AI_LABEL}: ${targetBubble.getPlainText()}`); | ||
| chatHistory.updateLast({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unguarded new URL(url).host — if any reference URL is ever malformed or relative, this throws inside the forEach and aborts the rest of appendReferences (and everything chained after it in onComplete) with no try/catch.