Skip to content

Commit

Permalink
feat: add markdown preview for citation (#103)
Browse files Browse the repository at this point in the history
* feat: markdown preview for citations

* show the previewed citation

* test: add e2e for citations preview

* fix: change to use marked
  • Loading branch information
shibbas authored Nov 7, 2023
1 parent a127a29 commit fb0316b
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 20 deletions.
14 changes: 13 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/chat-component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"license": "MIT",
"dependencies": {
"dompurify": "^3.0.6",
"lit": "^2.8.0"
"lit": "^2.8.0",
"marked": "^9.1.5"
},
"devDependencies": {
"@types/dompurify": "^3.0.3",
Expand Down
103 changes: 85 additions & 18 deletions packages/chat-component/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import iconCopyToClipboard from '../public/svg/copy-icon.svg?raw';
import iconSend from '../public/svg/send-icon.svg?raw';
import iconClose from '../public/svg/close-icon.svg?raw';
import iconQuestion from '../public/svg/question-icon.svg?raw';
import { marked } from 'marked';

/**
* A chat component that allows the user to ask questions and get answers from an API.
Expand Down Expand Up @@ -106,6 +107,8 @@ export class ChatComponent extends LitElement {
chatRequestOptions: ChatRequestOptions = requestOptions;
chatHttpOptions: ChatHttpOptions = chatHttpOptions;

selectedAsideTab: 'tab-thought-process' | 'tab-support-context' | 'tab-citations' = 'tab-thought-process';

static override styles = [mainStyle];

// debounce dispatching must-scroll event
Expand Down Expand Up @@ -199,6 +202,49 @@ export class ChatComponent extends LitElement {
this.currentQuestion = this.questionInput.value;
}

async handleCitationClick(sourceUrl: string, event: Event): Promise<void> {
if (sourceUrl?.endsWith('.md')) {
event?.preventDefault();

if (!this.isShowingThoughtProcess) {
this.selectedAsideTab = 'tab-citations';
this.showThoughtProcess();
}

const response = await fetch(sourceUrl);
if (response.ok) {
// highlight the clicked citation to make it clear which is being previewed
const citationsList = this.shadowRoot?.querySelectorAll(
'.aside .items__list.citations .items__listItem--citation',
);

if (citationsList) {
const citationsArray = [...citationsList];
const clickedIndex = citationsArray.findIndex((citation) => {
const link = citation.querySelector('a');
return link?.href === sourceUrl;
});

for (const citation of citationsList) {
const index = citationsArray.indexOf(citation);
if (index === clickedIndex) {
citation.classList.add('active');
} else {
citation.classList.remove('active');
}
}
}

// update the markdown previewer with the content of the clicked citation
const previewer = this.shadowRoot?.querySelector('#citation-previewer');
if (previewer) {
const markdownContent = await response.text();
previewer.innerHTML = DOMPurify.sanitize(marked.parse(markdownContent));
}
}
}
}

// Handle the click on the chat button and send the question to the API
async handleUserChatSubmit(event: Event): Promise<void> {
event.preventDefault();
Expand Down Expand Up @@ -304,13 +350,19 @@ export class ChatComponent extends LitElement {
this.isResponseCopied = true;
}

handleShowThoughtProcess(event: Event): void {
event?.preventDefault();
this.selectedAsideTab = 'tab-thought-process';
this.showThoughtProcess();
}

// show thought process aside
showThoughtProcess(event: Event): void {
event.preventDefault();
showThoughtProcess(): void {
this.isShowingThoughtProcess = true;
this.shadowRoot?.querySelector('#overlay')?.classList.add('active');
this.shadowRoot?.querySelector('#chat__containerWrapper')?.classList.add('aside-open');
}

// hide thought process aside
hideThoughtProcess(event: Event): void {
event.preventDefault();
Expand Down Expand Up @@ -386,6 +438,8 @@ export class ChatComponent extends LitElement {
data-testid="citation"
target="_blank"
rel="noopener noreferrer"
@click="${(event: Event) =>
this.handleCitationClick(`${this.apiUrl}/content/${citation.text}`, event)}"
>${citation.ref}. ${citation.text}</a
>
</li>
Expand Down Expand Up @@ -454,7 +508,7 @@ export class ChatComponent extends LitElement {
title="${globalConfig.SHOW_THOUGH_PROCESS_BUTTON_LABEL_TEXT}"
class="button chat__header--button"
data-testid="chat-show-thought-process"
@click="${this.showThoughtProcess}"
@click="${this.handleShowThoughtProcess}"
?disabled="${this.isShowingThoughtProcess || !this.canShowThoughtProcess}"
>
<span class="chat__header--span"
Expand Down Expand Up @@ -613,12 +667,12 @@ export class ChatComponent extends LitElement {
<ul class="aside__list" role="tablist">
<li class="aside__listItem">
<a
id="tab-1"
class="aside__link active"
id="tab-thought-process"
class="aside__link${this.selectedAsideTab === 'tab-thought-process' ? ' active' : ''}"
role="tab"
href="#"
aria-selected="true"
aria-hidden="false"
aria-selected="${this.selectedAsideTab === 'tab-thought-process'}"
aria-hidden="${this.selectedAsideTab !== 'tab-thought-process'}"
aria-controls="tabpanel-1"
@click="${(event: Event) => this.activateTab(event)}"
title="${globalConfig.THOUGHT_PROCESS_LABEL}"
Expand All @@ -628,12 +682,12 @@ export class ChatComponent extends LitElement {
</li>
<li class="aside__listItem">
<a
id="tab-2"
class="aside__link"
id="tab-support-context"
class="aside__link${this.selectedAsideTab === 'tab-support-context' ? ' active' : ''}"
role="tab"
href="#"
aria-selected="false"
aria-hidden="true"
aria-selected="${this.selectedAsideTab === 'tab-support-context'}"
aria-hidden="${this.selectedAsideTab !== 'tab-support-context'}"
aria-controls="tabpanel-2"
@click="${(event: Event) => this.activateTab(event)}"
title="${globalConfig.SUPPORT_CONTEXT_LABEL}"
Expand All @@ -643,12 +697,12 @@ export class ChatComponent extends LitElement {
</li>
<li class="aside__listItem">
<a
id="tab-3"
class="aside__link"
id="tab-citations"
class="aside__link${this.selectedAsideTab === 'tab-citations' ? ' active' : ''}"
role="tab"
href="#"
aria-selected="false"
aria-hidden="true"
aria-selected="${this.selectedAsideTab === 'tab-citations'}"
aria-hidden="${this.selectedAsideTab !== 'tab-citations'}"
aria-controls="tabpanel-3"
@click="${(event: Event) => this.activateTab(event)}"
title="${globalConfig.CITATIONS_LABEL}"
Expand All @@ -659,22 +713,35 @@ export class ChatComponent extends LitElement {
</ul>
</nav>
<div class="aside__content">
<div id="tabpanel-1" class="aside__tab active" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<div id="tabpanel-1" class="aside__tab${
this.selectedAsideTab === 'tab-thought-process' ? ' active' : ''
}" role="tabpanel" tabindex="${
this.selectedAsideTab === 'tab-thought-process' ? 0 : -1
}" aria-labelledby="tab-thought-process">
<h3 class="subheadline--small">${globalConfig.THOUGHT_PROCESS_LABEL}</h3>
<div class="aside__innerContainer">
${this.chatThoughts ? html` <p class="aside__paragraph">${unsafeHTML(this.chatThoughts)}</p> ` : ''}
</div>
</div>
<div id="tabpanel-2" class="aside__tab" role="tabpanel" aria-labelledby="tab-2" tabindex="-1">
<div id="tabpanel-2" class="aside__tab${
this.selectedAsideTab === 'tab-support-context' ? ' active' : ''
}" role="tabpanel" aria-labelledby="tab-support-context" tabindex="${
this.selectedAsideTab === 'tab-support-context' ? 0 : -1
}">
<h3 class="subheadline--small">${globalConfig.SUPPORT_CONTEXT_LABEL}</h3>
<ul class="defaults__list always-row">
${this.chatDataPoints.map(
(dataPoint) => html` <li class="defaults__listItem">${dataPoint}</li> `,
)}
</ul>
</div>
<div id="tabpanel-3" class="aside__tab" role="tabpanel" tabindex="-1" aria-labelledby="tab-3">
<div id="tabpanel-3" class="aside__tab${
this.selectedAsideTab === 'tab-citations' ? ' active' : ''
}" role="tabpanel" tabindex="${
this.selectedAsideTab === 'tab-citations' ? 0 : -1
}" aria-labelledby="tab-citations">
${this.renderCitation(this.chatThread.at(-1)?.citations)}
<div id="citation-previewer"></div>
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions packages/chat-component/src/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,19 @@ export const mainStyle = css`
margin-top: 5px;
font-size: small;
}
.items__listItem--citation.active {
background-color: var(--accent-high);
}
.items__listItem--citation:not(first-child) {
margin-left: 5px;
}
.items__link {
text-decoration: none;
color: var(--text-color);
}
.items__listItem--citation.active .items__link {
color: var(--white);
}
.steps .items__listItem--step {
padding: 5px 0;
border-bottom: 1px solid var(--light-gray);
Expand Down
Loading

0 comments on commit fb0316b

Please sign in to comment.