Skip to content
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

feat: fix parser #95

Merged
merged 21 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d26e185
chore: update followup questions preliminary
anfibiacreativa Oct 26, 2023
e9075c6
chore: update styles
anfibiacreativa Oct 26, 2023
4868a48
chore: restyle followup questions
anfibiacreativa Oct 26, 2023
fb45e89
chore: fix parser for follow up questions (before TC)
anfibiacreativa Oct 27, 2023
8b04c0b
chore: fix bug and adapt styles
anfibiacreativa Oct 27, 2023
5a9e5aa
chore: add margin to citations
anfibiacreativa Oct 27, 2023
80436e3
chore: remove commented regex
anfibiacreativa Oct 27, 2023
6449f69
fix: remove unnecessary regex and replace
anfibiacreativa Oct 27, 2023
e0de7af
chore: update global config
anfibiacreativa Oct 27, 2023
1362575
chore: add question icon
anfibiacreativa Oct 27, 2023
8fe2d31
chore: update styles
anfibiacreativa Oct 27, 2023
f79735c
chore: update comments to reflect decisions
anfibiacreativa Oct 27, 2023
65d3533
chore: refine cosmetic styles
anfibiacreativa Oct 30, 2023
bb0baf3
chore: replace icons with iconcloud set
anfibiacreativa Oct 30, 2023
3ea28cd
fix: fix minor bug with index replacement
anfibiacreativa Oct 30, 2023
e7b394e
test: add E2E playwright tests for web app (#89)
shibbas Oct 26, 2023
0ff12b6
docs: update readme with auth info
anfibiacreativa Oct 27, 2023
f7e801f
test: add load tests (#93)
shibbas Oct 27, 2023
f6719f1
test: add playwright test for ask interaction (#94)
shibbas Oct 27, 2023
444eeef
ci: disable playwright for demo
anfibiacreativa Oct 31, 2023
7b1abaf
ci: revert previous commit and disable from UI
anfibiacreativa Oct 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/chat-component/public/svg/question-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/chat-component/public/svg/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All icons in this sample are free and open source
- [Double Check Icon](https://fontawesome.com/icons/check-double?f=classic&s=solid)
- [Email Icon](https://fontawesome.com/icons/envelope-open-text?f=classic&s=solid)
- [Lightbulb Icon](https://fontawesome.com/icons/lightbulb?f=classic&s=solid)
- [Question Icon](https://fontawesome.com/icons/circle-question?f=classic&s=solid)

## FontAwesome License

Expand Down
4 changes: 3 additions & 1 deletion packages/chat-component/src/config/global-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ const globalConfig = {
// This are the labels for the chat button and input
CHAT_BUTTON_LABEL_TEXT: 'Ask Support',
CHAT_INPUT_PLACEHOLDER: 'Type your question, eg. "How to search and book rentals?"',
USER_IS_BOT: 'Support Bot',
USER_IS_BOT: 'Support Assistant',
RESET_BUTTON_LABEL_TEXT: 'X',
RESET_BUTTON_TITLE_TEXT: 'Reset current question',
RESET_CHAT_BUTTON_TITLE: 'Reset chat',
// Copy response to clipboard
COPY_RESPONSE_BUTTON_LABEL_TEXT: 'Copy Response',
COPIED_SUCCESSFULLY_MESSAGE: 'Response copied!',
// Follow up questions text
FOLLOW_UP_QUESTIONS_LABEL_TEXT: 'You can also ask...',
SHOW_THOUGH_PROCESS_BUTTON_LABEL_TEXT: 'Show thought process',
HIDE_THOUGH_PROCESS_BUTTON_LABEL_TEXT: 'Hide thought process',
LOADING_INDICATOR_TEXT: 'Please wait. We are searching for an answer...',
Expand Down
33 changes: 26 additions & 7 deletions packages/chat-component/src/core/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export async function parseStreamedMessages({
let isProcessingStep = false;
let isLastStep = false;
let isFollowupQuestion = false;
let followUpQuestionIndex = 0;
let stepIndex = 0;
let textBlockIndex = 0;
const result = {
Expand All @@ -41,7 +42,7 @@ export async function parseStreamedMessages({

// we use numeric values to identify the beginning of a step
// if we match a number, store it in the buffer and move on to the next iteration
const LIST_ITEM_NUMBER = /(\d+)/;
const LIST_ITEM_NUMBER: RegExp = /(\d+)/;
let matchedStepIndex = chunkValue.match(LIST_ITEM_NUMBER)?.[0];
if (matchedStepIndex) {
stepsBuffer.push(matchedStepIndex);
Expand All @@ -50,15 +51,31 @@ export async function parseStreamedMessages({

// followup questions are marked either with the word 'Next Questions:' or '<<text>>' or both at the same time
// these markers may be split across multiple chunks, so we need to buffer them!
// TODO: support followup questions wrapped in <<text>> markers
const matchedFollowupQuestionMarker = !isFollowupQuestion && chunkValue.includes('Next');
// TODO: remove all this logic from the frontend and implement a solution on the backend or with TypeChat
// we start by creating a buffer when we match the first marker
const matchedFollowupQuestionMarker =
(!isFollowupQuestion && chunkValue.includes('Next')) || chunkValue.includes('<<');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw should we still parse Next markers? Is the new model still using Next to format followup questions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the updated model uses only << and >> to format followup questions, then I'd suggest changing the condition to:

const matchedFollowupQuestionMarker = !isFollowupQuestion && chunkValue.includes('<<');

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. @sinedied can you clarify?

// once we do, we can assume that we are processing a followup question and set the flag to true
if (matchedFollowupQuestionMarker) {
isFollowupQuestion = true;
followupQuestionsBuffer.push(chunkValue);
continue;
} else if (followupQuestionsBuffer.length > 0 && chunkValue.includes('Question')) {
isFollowupQuestion = true;
followupQuestionsBuffer.push(chunkValue);
continue;
// if we're already processing questions, we don't need to check for the marker again
// but we need to check if we reached the end of the followup questions
} else if (chunkValue.includes('<<') && isFollowupQuestion) {
isFollowupQuestion = true;
continue;
// this updates the index, so we add each question to a different array entry
// to simplify styling
} else if (chunkValue.includes('?>') || chunkValue.includes('>')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we checking for ?> && > and not ?>> && >>?

Copy link
Member Author

@anfibiacreativa anfibiacreativa Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because in all tests I ran, ?> and the last > were returned as part of 2 different chunks, and there was never a match.

followUpQuestionIndex = followUpQuestionIndex + 1;
isFollowupQuestion = true;
continue;
// additional returns need to be removed, but only after we have processed the whole set of chunks
} else if (isFollowupQuestion) {
isFollowupQuestion = true;
chunkValue = chunkValue.replace(/:?\n/, '');
manekinekko marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -87,18 +104,18 @@ export async function parseStreamedMessages({

// if we are at the beginning of a step, we need to remove the step number and dot from the chunk value
// we simply clear the current chunk value
if (matchedStepIndex || isProcessingStep) {
if (matchedStepIndex || isProcessingStep || isFollowupQuestion) {
if (matchedStepIndex) {
chunkValue = '';
}

// set the step index that is needed to update the correct step entry
stepIndex = matchedStepIndex ? Number(matchedStepIndex) - 1 : stepIndex;
updateFollowingStepOrFollowupQuestionEntry({
chunkValue,
textBlockIndex,
stepIndex,
isFollowupQuestion,
followUpQuestionIndex,
chatThread,
});

Expand Down Expand Up @@ -136,7 +153,7 @@ export function updateCitationsEntry({
const updateCitationReference = (match, capture) => {
const citation = citations.find((citation) => citation.text === capture);
if (citation) {
return `<sup>[${citation.ref}]</sup>`;
return `<sup class="citation">${citation.ref}</sup>`;
}
return match;
};
Expand Down Expand Up @@ -202,18 +219,20 @@ export function updateFollowingStepOrFollowupQuestionEntry({
textBlockIndex,
stepIndex,
isFollowupQuestion,
followUpQuestionIndex,
chatThread,
}: {
chunkValue: string;
textBlockIndex: number;
stepIndex: number;
isFollowupQuestion: boolean;
followUpQuestionIndex: number;
chatThread: ChatThreadEntry[];
}) {
// following steps and followup questions are treated the same way. They are just stored in different arrays
const { followupQuestions, text: lastChatMessageTextEntry } = chatThread.at(-1) as ChatThreadEntry;
if (isFollowupQuestion && followupQuestions) {
followupQuestions[stepIndex] = (followupQuestions[stepIndex] || '') + chunkValue;
followupQuestions[followUpQuestionIndex] = (followupQuestions[followUpQuestionIndex] || '') + chunkValue;
return;
}

Expand Down
45 changes: 27 additions & 18 deletions packages/chat-component/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import iconDoubleCheck from '../public/svg/doublecheck-icon.svg?inline';
import iconCopyToClipboard from '../public/svg/copy-icon.svg?inline';
import iconSend from '../public/svg/send-icon.svg?inline';
import iconClose from '../public/svg/close-icon.svg?inline';
import iconQuestion from '../public/svg/question-icon.svg?inline';

/**
* A chat component that allows the user to ask questions and get answers from an API.
Expand Down Expand Up @@ -360,8 +361,7 @@ export class ChatComponent extends LitElement {
// render citations
if (citations && citations.length > 0) {
return html`
<h3 class="subheadline--small">Citations</h3>
<ol class="items__list">
<ol class="items__list citations">
${citations.map(
(citation) => html`
<li class="items__listItem--citation">
Expand All @@ -383,23 +383,32 @@ export class ChatComponent extends LitElement {
}

renderFollowupQuestions(followupQuestions: string[] | undefined) {
// render followup questions
if (followupQuestions && followupQuestions.length > 0) {
return html`
<h3 class="subheadline--small">You may also want to ask...</h3>
<ol class="items__list followup">
${followupQuestions.map(
(followupQuestion) => html`
<li class="items__listItem--followup">
<a
class="items__link"
href="#"
@click="${(event: Event) => this.handleDefaultPromptClick(followupQuestion, event)}"
>${followupQuestion}</a
>
</li>
`,
)}
</ol>
<div class="items__listWrapper">
<img
class="icon"
src="${iconQuestion}"
alt="${globalConfig.FOLLOW_UP_QUESTIONS_LABEL_TEXT}"
width="35"
height="35"
/>
<ul class="items__list followup">
${followupQuestions.map(
(followupQuestion) => html`
<li class="items__listItem--followup">
<a
class="items__link"
href="#"
@click="${(event: Event) => this.handleDefaultPromptClick(followupQuestion, event)}"
>${followupQuestion}</a
>
</li>
`,
)}
</ul>
</div>
`;
}

Expand Down Expand Up @@ -468,8 +477,8 @@ export class ChatComponent extends LitElement {
</button>
</div>`}
${message.text.map((textEntry) => this.renderTextEntry(textEntry))}
${this.renderFollowupQuestions(message.followupQuestions)}
${this.renderCitation(message.citations)}
${this.renderFollowupQuestions(message.followupQuestions)}
</div>
<p class="chat__txt--info">
<span class="timestamp">${message.timestamp}</span>,
Expand Down
Loading