Skip to content

Commit

Permalink
fix: enable chat generation cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
manekinekko authored and anfibiacreativa committed Nov 10, 2023
1 parent 1d80f2b commit 3d397b9
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 35 deletions.
3 changes: 3 additions & 0 deletions packages/chat-component/public/svg/cancel-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/src/config/global-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const globalConfig = {
CHAT_MESSAGES: [],
// This are the labels for the chat button and input
CHAT_BUTTON_LABEL_TEXT: 'Ask Support',
CHAT_CANCEL_BUTTON_LABEL_TEXT: 'Cancel Generation',
CHAT_VOICE_BUTTON_LABEL_TEXT: 'Voice input',
CHAT_VOICE_REC_BUTTON_LABEL_TEXT: 'Listening to voice input',
CHAT_INPUT_PLACEHOLDER: 'Type your question, eg. "How to search and book rentals?"',
Expand Down
20 changes: 13 additions & 7 deletions packages/chat-component/src/core/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { createReader, readStream } from '../stream/index.js';
export async function parseStreamedMessages({
chatThread,
apiResponseBody,
visit,
signal,
onChunkRead: onVisit,
onCancel,
}: {
chatThread: ChatThreadEntry[];
apiResponseBody: ReadableStream<Uint8Array> | null;
visit: () => void;
signal: AbortSignal;
onChunkRead: () => void;
onCancel: () => void;
}) {
const reader = createReader(apiResponseBody);
const chunks = readStream<BotResponseChunk>(reader);
Expand All @@ -27,6 +31,11 @@ export async function parseStreamedMessages({
};

for await (const chunk of chunks) {
if (signal.aborted) {
onCancel();
return result;
}

const { content, context } = chunk.choices[0].delta;
if (context?.data_points) {
result.data_points = context.data_points ?? [];
Expand Down Expand Up @@ -137,12 +146,9 @@ export async function parseStreamedMessages({
const citations = parseCitations(streamedMessageRaw.join(''));
updateCitationsEntry({ citations, chatThread });

visit();
onVisit();
}
return {
result,
reader,
};
return result;
}

// update the citations entry and wrap the citations in a sup tag
Expand Down
9 changes: 4 additions & 5 deletions packages/chat-component/src/core/stream/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NdJsonParserStream } from './data-format/ndjson.js';
// import { globalConfig } from '../../config/global-config.js';
import { globalConfig } from '../../config/global-config.js';

export function createReader(responseBody: ReadableStream<Uint8Array> | null) {
return responseBody?.pipeThrough(new TextDecoderStream()).pipeThrough(new NdJsonParserStream()).getReader();
Expand All @@ -14,10 +14,9 @@ export async function* readStream<T>(reader: any): AsyncGenerator<T, void> {
let done: boolean;
while ((({ value, done } = await reader.read()), !done)) {
yield new Promise<T>((resolve) => {
resolve(value as T);
/* setTimeout(() => {
}, globalConfig.BOT_TYPING_EFFECT_INTERVAL); */
setTimeout(() => {
resolve(value as T);
}, globalConfig.BOT_TYPING_EFFECT_INTERVAL);
});
}
}
Expand Down
79 changes: 56 additions & 23 deletions packages/chat-component/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import iconSuccess from '../public/svg/success-icon.svg?raw';
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 iconCancel from '../public/svg/cancel-icon.svg?raw';
import iconQuestion from '../public/svg/bubblequestion-icon.svg?raw';
import iconSpinner from '../public/svg/spinner-icon.svg?raw';
import iconMicOff from '../public/svg/mic-icon.svg?raw';
Expand Down Expand Up @@ -120,13 +121,16 @@ export class ChatComponent extends LitElement {

abortController: AbortController = new AbortController();
// eslint-disable-next-line unicorn/no-abusive-eslint-disable
streamReader: ReadableStreamDefaultReader<JSON> | null | undefined = null; // eslint-disable-line

chatRequestOptions: ChatRequestOptions = requestOptions;
chatHttpOptions: ChatHttpOptions = chatHttpOptions;

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

// Is currently processing the response from the API
// This is used to show the cancel button
isProcessingResponse = false;

static override styles = [mainStyle];

// debounce dispatching must-scroll event
Expand Down Expand Up @@ -161,20 +165,33 @@ export class ChatComponent extends LitElement {
},
];

const { result, reader } = await parseStreamedMessages({
this.isProcessingResponse = true;

const result = await parseStreamedMessages({
chatThread: this.chatThread,
signal: this.abortController.signal,
apiResponseBody: (this.apiResponse as Response).body,
visit: () => {
onChunkRead: () => {
// NOTE: this function is called whenever we mutate sub-properties of the array
// so we need to trigger a re-render
this.requestUpdate('chatThread');
},
// this will be processing thought process only with streaming enabled
onCancel: () => {
this.isProcessingResponse = false;
// TODO: show a message to the user that the response has been cancelled
},
});
this.streamReader = reader;

// this will be processing thought process only with streaming enabled
this.chatThoughts = result.thoughts;
this.chatDataPoints = result.data_points;
this.canShowThoughtProcess = true;

this.isProcessingResponse = false;
// NOTE: for whatever reason, Lit doesn't re-render when we update isProcessingResponse property
// so we need to trigger a re-render manually
this.requestUpdate('isProcessingResponse');

return true;
}

Expand Down Expand Up @@ -375,6 +392,7 @@ export class ChatComponent extends LitElement {
this.hasDefaultPromptsEnabled = true;
this.isResponseCopied = false;
this.hideThoughtProcess(event);
this.handleUserChatCancel(event);
}

// Show the default prompts when enabled
Expand Down Expand Up @@ -403,12 +421,13 @@ export class ChatComponent extends LitElement {
}

// Stop generation
stopResponseGeneration(): any {
console.log('Stopping response generation');
this.isStreamCancelled = true;
this.streamReader?.cancel();
handleUserChatCancel(event: Event): any {
event?.preventDefault();
this.isProcessingResponse = false;
this.abortController.abort();
return false;

// we have to reset the abort controller so that we can use it again
this.abortController = new AbortController();
}

handleShowThoughtProcess(event: Event): void {
Expand Down Expand Up @@ -481,7 +500,9 @@ export class ChatComponent extends LitElement {
);
}
// scroll to the bottom of the chat
this.debounceScrollIntoView();
if (this.isProcessingResponse) {
this.debounceScrollIntoView();
}
return entries;
}

Expand Down Expand Up @@ -539,6 +560,29 @@ export class ChatComponent extends LitElement {
return '';
}

renderChatOrCancelButton() {
const submitChatButton = html`<button
class="chatbox__button"
data-testid="submit-question-button"
@click="${this.handleUserChatSubmit}"
title="${globalConfig.CHAT_BUTTON_LABEL_TEXT}"
?disabled="${this.isDisabled}"
>
${unsafeSVG(iconSend)}
</button>`;
const cancelChatButton = html`<button
<button
class="chatbox__button"
data-testid="cancel-question-button"
@click="${this.handleUserChatCancel}"
title="${globalConfig.CHAT_CANCEL_BUTTON_LABEL_TEXT}"
>
${unsafeSVG(iconCancel)}
</button>`;

return this.isProcessingResponse ? cancelChatButton : submitChatButton;
}

// Render the chat component as a web component
override render() {
return html`
Expand Down Expand Up @@ -663,9 +707,6 @@ export class ChatComponent extends LitElement {
id="chat-form"
class="form__container ${this.inputPosition === 'sticky' ? 'form__container-sticky' : ''}"
>
<button type="button" class="button chat__button" @click="${this.stopResponseGeneration}">
Stop Response
</button>
<div class="chatbox__container container-col container-row">
<div class="chatbox__input-container display-flex-grow container-row">
<input
Expand Down Expand Up @@ -695,15 +736,7 @@ export class ChatComponent extends LitElement {
</button>`
: ''}
</div>
<button
class="chatbox__button"
data-testid="submit-question-button"
@click="${this.handleUserChatSubmit}"
title="${globalConfig.CHAT_BUTTON_LABEL_TEXT}"
?disabled="${this.isDisabled}"
>
${unsafeSVG(iconSend)}
</button>
${this.renderChatOrCancelButton()}
<button
title="${globalConfig.RESET_BUTTON_TITLE_TEXT}"
class="chatbox__button--reset"
Expand Down

0 comments on commit 3d397b9

Please sign in to comment.