From a8ce365cf566afe76d5060269fc02b9f85ef0e4f Mon Sep 17 00:00:00 2001 From: Shibani Basava Date: Tue, 13 Feb 2024 06:49:28 +0000 Subject: [PATCH] feat: add working DI for thought process. citations don't work yet --- .../src/components/chat-component.ts | 153 +++--------------- .../src/components/chat-context.ts | 11 ++ .../components/chat-debug-thought-process.ts | 149 +++++++++++++++++ .../src/components/composable.ts | 10 +- .../src/components/debug-chat-entry.ts | 37 +++++ 5 files changed, 224 insertions(+), 136 deletions(-) create mode 100644 packages/chat-component/src/components/chat-debug-thought-process.ts create mode 100644 packages/chat-component/src/components/debug-chat-entry.ts diff --git a/packages/chat-component/src/components/chat-component.ts b/packages/chat-component/src/components/chat-component.ts index 43247470..c1149c13 100644 --- a/packages/chat-component/src/components/chat-component.ts +++ b/packages/chat-component/src/components/chat-component.ts @@ -2,7 +2,6 @@ import { LitElement, html } from 'lit'; import DOMPurify from 'dompurify'; import { customElement, property, query, state } from 'lit/decorators.js'; -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { chatHttpOptions, globalConfig, requestOptions, MAX_CHAT_HISTORY } from '../config/global-config.js'; import { chatStyle } from '../styles/chat-component.js'; import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; @@ -10,15 +9,12 @@ import { chatEntryToString, newListWithEntryAtIndex } from '../utils/index.js'; // TODO: allow host applications to customize these icons -import iconLightBulb from '../../public/svg/lightbulb-icon.svg?raw'; import iconDelete from '../../public/svg/delete-icon.svg?raw'; import iconCancel from '../../public/svg/cancel-icon.svg?raw'; import iconSend from '../../public/svg/send-icon.svg?raw'; -import iconClose from '../../public/svg/close-icon.svg?raw'; import iconLogo from '../../public/branding/brand-logo.svg?raw'; import iconUp from '../../public/svg/chevron-up-icon.svg?raw'; -import { type TabContent } from './tab-component.js'; import { ChatController } from './chat-controller.js'; import { ChatHistoryController } from './chat-history-controller.js'; import { @@ -26,7 +22,6 @@ import { ControllerType, type ChatInputController, type ChatInputFooterController, - type CitationController, type ChatSectionController, } from './composable.js'; import { ChatContextController } from './chat-context.js'; @@ -111,14 +106,6 @@ export class ChatComponent extends LitElement { @state() isShowingThoughtProcess = false; - @state() - selectedCitation: Citation | undefined = undefined; - - @state() - selectedChatEntry: ChatThreadEntry | undefined = undefined; - - selectedAsideTab: 'tab-thought-process' | 'tab-support-context' | 'tab-citations' = 'tab-thought-process'; - // These are the chat bubbles that will be displayed in the chat chatThread: ChatThreadEntry[] = []; @@ -130,11 +117,8 @@ export class ChatComponent extends LitElement { @lazyMultiInject(ControllerType.ChatInputFooter) chatInputFooterComponets: ChatInputFooterController[] | undefined; - @lazyMultiInject(ControllerType.Citation) - citationControllers: CitationController[] | undefined; - @lazyMultiInject(ControllerType.ChatSection) - chatFooterComponents: ChatSectionController[] | undefined; + chatSectionControllers: ChatSectionController[] | undefined; // Lifecycle method that runs when the component is first connected to the DOM override connectedCallback(): void { @@ -149,13 +133,8 @@ export class ChatComponent extends LitElement { component.attach(this, this.chatContext); } } - if (this.citationControllers) { - for (const component of this.citationControllers) { - component.attach(this, this.chatContext); - } - } - if (this.chatFooterComponents) { - for (const component of this.chatFooterComponents) { + if (this.chatSectionControllers) { + for (const component of this.chatSectionControllers) { component.attach(this, this.chatContext); } } @@ -193,15 +172,9 @@ export class ChatComponent extends LitElement { handleCitationClick(event: CustomEvent): void { event?.preventDefault(); - this.selectedCitation = event?.detail?.citation; - - if (!this.isShowingThoughtProcess) { - if (event?.detail?.chatThreadEntry) { - this.selectedChatEntry = event?.detail?.chatThreadEntry; - } - this.handleExpandAside(); - this.selectedAsideTab = 'tab-citations'; - } + this.chatContext.selectedCitation = event?.detail?.citation; + this.chatContext.selectedChatEntry = event?.detail?.chatThreadEntry; + this.chatContext.setState('showCitations', true); } getMessageContext(): Message[] { @@ -274,7 +247,7 @@ export class ChatComponent extends LitElement { this.isChatStarted = false; this.chatThread = []; this.isDisabled = false; - this.selectedCitation = undefined; + this.chatContext.selectedCitation = undefined; this.chatController.reset(); // clean up the current session content from the history too this.chatHistoryController.saveChatHistory(this.chatThread); @@ -293,22 +266,14 @@ export class ChatComponent extends LitElement { this.chatController.cancelRequest(); } - // show thought process aside - handleExpandAside(event: Event | undefined = undefined): void { - event?.preventDefault(); - this.isShowingThoughtProcess = true; - this.selectedAsideTab = 'tab-thought-process'; - this.shadowRoot?.querySelector('#overlay')?.classList.add('active'); - this.shadowRoot?.querySelector('#chat__containerWrapper')?.classList.add('aside-open'); - } - // hide thought process aside collapseAside(event: Event): void { - event.preventDefault(); - this.isShowingThoughtProcess = false; - this.selectedCitation = undefined; - this.shadowRoot?.querySelector('#chat__containerWrapper')?.classList.remove('aside-open'); - this.shadowRoot?.querySelector('#overlay')?.classList.remove('active'); + event?.preventDefault(); + if (this.chatSectionControllers) { + for (const component of this.chatSectionControllers) { + component.close(); + } + } } renderChatOrCancelButton() { @@ -333,64 +298,6 @@ export class ChatComponent extends LitElement { return this.chatController.isProcessingResponse ? cancelChatButton : submitChatButton; } - renderChatEntryTabContent(entry: ChatThreadEntry) { - return html` -
- ${entry && entry.thoughts ? html`

${unsafeHTML(entry.thoughts)}

` : ''} -
-
- ${entry && entry.dataPoints - ? html` ` - : ''} -
- ${entry && entry.citations - ? html` -
- - ${this.selectedCitation - ? this.citationControllers?.map((component) => - component.render(this.selectedCitation, `${this.apiUrl}/content/${this.selectedCitation.text}`), - ) - : ''} -
- ` - : ''} -
`; - } - - handleChatEntryActionButtonClick(event: CustomEvent) { - if (event.detail?.id === 'chat-show-thought-process') { - this.selectedChatEntry = event.detail?.chatThreadEntry; - this.handleExpandAside(event); - } - } - override willUpdate(): void { this.isDisabled = this.chatController.generatingAnswer; @@ -408,21 +315,12 @@ export class ChatComponent extends LitElement { renderChatThread(chatThread: ChatThreadEntry[]) { return html` @@ -439,9 +337,10 @@ export class ChatComponent extends LitElement { // Render the chat component as a web component override render() { + const isAsideEnabled = this.chatSectionControllers?.some((controller) => controller.isEnabled); return html` -
-
+
+
${this.isCustomBranding && !this.isChatStarted ? html`
- - ${this.chatFooterComponents?.map((component) => component.render())} + ${isAsideEnabled ? this.chatSectionControllers?.map((component) => component.render()) : ''}
`; } diff --git a/packages/chat-component/src/components/chat-context.ts b/packages/chat-component/src/components/chat-context.ts index 394dc249..22a4173a 100644 --- a/packages/chat-component/src/components/chat-context.ts +++ b/packages/chat-component/src/components/chat-context.ts @@ -25,6 +25,17 @@ export class ChatContextController implements ReactiveController { private _isChatStarted: boolean = false; + private _selectedCitation: Citation | undefined = undefined; + + public set selectedCitation(citation: Citation | undefined) { + this._selectedCitation = citation; + this.host.requestUpdate(); + } + + public get selectedCitation() { + return this._selectedCitation; + } + public set isChatStarted(value: boolean) { this._isChatStarted = value; this.host.requestUpdate(); diff --git a/packages/chat-component/src/components/chat-debug-thought-process.ts b/packages/chat-component/src/components/chat-debug-thought-process.ts new file mode 100644 index 00000000..c20d921c --- /dev/null +++ b/packages/chat-component/src/components/chat-debug-thought-process.ts @@ -0,0 +1,149 @@ +/* eslint-disable unicorn/template-indent */ +import { injectable } from 'inversify'; +import { + container, + lazyMultiInject, + type ChatSectionController, + type CitationController, + ControllerType, + ComposableReactiveControllerBase, +} from './composable.js'; +import { html } from 'lit'; +import { globalConfig } from '../config/global-config.js'; + +import iconClose from '../../public/svg/close-icon.svg?raw'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; + +@injectable() +export class ChatDebugThoughtProcessController + extends ComposableReactiveControllerBase + implements ChatSectionController +{ + constructor() { + super(); + this.close = this.close.bind(this); + } + + @lazyMultiInject(ControllerType.Citation) + citationControllers: CitationController[] | undefined; + + override hostConnected() { + if (this.citationControllers) { + for (const controller of this.citationControllers) { + controller.attach(this.host, this.context); + } + } + } + + handleCitationClick(event: CustomEvent): void { + event?.preventDefault(); + this.context.selectedCitation = event?.detail?.citation; + + this.context.setState('showCitations', true); + } + + renderChatEntryTabContent(entry: ChatThreadEntry) { + return html` + +
+ ${entry && entry.thoughts + ? html`

${unsafeHTML(entry.thoughts)}

` + : ''} +
+
+ ${entry && entry.dataPoints + ? html` + + ` + : ''} +
+ ${entry && entry.citations + ? html` +
+ + ${this.context.selectedCitation + ? this.citationControllers?.map((component) => + component.render( + this.context.selectedCitation, + `${this.context.apiUrl}/content/${this.context.selectedCitation.text}`, + ), + ) + : ''} +
+ ` + : ''} +
+ `; + } + + get isEnabled() { + return this.isShowingThoughtProcess; + } + + public close() { + this.isShowingThoughtProcess = false; + this.context.setState('showCitations', false); + this.context.selectedChatEntry = undefined; + } + + get isShowingThoughtProcess() { + return this.context.getState('showThoughtProcess') || this.context.getState('showCitations'); + } + + set isShowingThoughtProcess(value: boolean) { + this.context.setState('showThoughtProcess', value); + } + + get selectedAsideTab() { + if (this.context.getState('showCitations')) { + return 'tab-citations'; + } + + return 'tab-thought-process'; + } + + render() { + return html` + + `; + } +} + +container.bind(ControllerType.ChatSection).to(ChatDebugThoughtProcessController); diff --git a/packages/chat-component/src/components/composable.ts b/packages/chat-component/src/components/composable.ts index 955ddfe0..a5540332 100644 --- a/packages/chat-component/src/components/composable.ts +++ b/packages/chat-component/src/components/composable.ts @@ -42,6 +42,8 @@ export interface ChatInputFooterController extends ComposableReactiveController } export interface ChatSectionController extends ComposableReactiveController { + isEnabled: boolean; + close: () => void; render: () => TemplateResult; } @@ -67,8 +69,14 @@ export class DefaultInputController extends DefaultController implements ChatInp position: 'left' | 'right' | 'top' = 'left'; } +@injectable() +export class DefaultChatSectionController extends DefaultController implements ChatSectionController { + isEnabled = false; + close() {} +} + container.bind(ControllerType.ChatInput).to(DefaultInputController); container.bind(ControllerType.ChatInputFooter).to(DefaultController); -container.bind(ControllerType.ChatSection).to(DefaultController); +container.bind(ControllerType.ChatSection).to(DefaultChatSectionController); container.bind(ControllerType.ChatEntryAction).to(DefaultController); container.bind(ControllerType.Citation).to(DefaultController); diff --git a/packages/chat-component/src/components/debug-chat-entry.ts b/packages/chat-component/src/components/debug-chat-entry.ts new file mode 100644 index 00000000..9ab4fb53 --- /dev/null +++ b/packages/chat-component/src/components/debug-chat-entry.ts @@ -0,0 +1,37 @@ +import { injectable } from 'inversify'; +import { + container, + type ChatEntryActionController, + ControllerType, + ComposableReactiveControllerBase, +} from './composable.js'; +import { html } from 'lit'; +import { globalConfig } from '../config/global-config.js'; +import iconLightBulb from '../../public/svg/lightbulb-icon.svg?raw'; + +@injectable() +export class DebugChatEntryActionController + extends ComposableReactiveControllerBase + implements ChatEntryActionController +{ + handleClick(event: Event, entry: ChatThreadEntry) { + event.preventDefault(); + this.context.setState('showThoughtProcess', true); + this.context.selectedChatEntry = entry; + } + + render(entry: ChatThreadEntry, isDisabled: boolean) { + const isShowingThoughtProcess = this.context.getState('showThoughtProcess'); + return html` + + `; + } +} + +container.bind(ControllerType.ChatEntryAction).to(DebugChatEntryActionController);