Skip to content

Commit

Permalink
chore: update all DI to be reactive controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
Shibani Basava authored and Shibani Basava committed Feb 13, 2024
1 parent e16dc15 commit e6f18be
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 103 deletions.
86 changes: 68 additions & 18 deletions packages/chat-component/src/components/chat-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import { ChatController } from './chat-controller.js';
import { ChatHistoryController } from './chat-history-controller.js';
import {
lazyMultiInject,
ComponentType,
type ChatInputComponent,
type ChatInputFooterComponent,
type CitationActionComponent,
ControllerType,
type ChatInputController,
type ChatInputFooterController,
type CitationController,
type ChatSectionController,
} from './composable.js';
import { ChatContextController } from './chat-context.js';

/**
* A chat component that allows the user to ask questions and get answers from an API.
Expand All @@ -50,10 +52,22 @@ export class ChatComponent extends LitElement {
inputPosition = 'sticky';

@property({ type: String, attribute: 'data-interaction-model' })
interactionModel: 'ask' | 'chat' = 'chat';
set interactionModel(value: 'ask' | 'chat') {
this.chatContext.interactionModel = value || 'chat';
}

get interactionModel(): 'ask' | 'chat' {
return this.chatContext.interactionModel;
}

@property({ type: String, attribute: 'data-api-url' })
apiUrl = chatHttpOptions.url;
set apiUrl(value: string = chatHttpOptions.url) {
this.chatContext.apiUrl = value;
}

get apiUrl(): string {
return this.chatContext.apiUrl;
}

@property({ type: String, attribute: 'data-custom-branding', converter: (value) => value?.toLowerCase() === 'true' })
isCustomBranding: boolean = globalConfig.IS_CUSTOM_BRANDING;
Expand All @@ -78,14 +92,20 @@ export class ChatComponent extends LitElement {
@state()
isDisabled = false;

@state()
isChatStarted = false;
set isChatStarted(value: boolean) {
this.chatContext.isChatStarted = value;
}

get isChatStarted(): boolean {
return this.chatContext.isChatStarted;
}

@state()
isResetInput = false;

private chatController = new ChatController(this);
private chatHistoryController = new ChatHistoryController(this);
private chatContext = new ChatContextController(this);

// Is showing thought process panel
@state()
Expand All @@ -104,14 +124,42 @@ export class ChatComponent extends LitElement {

static override styles = [chatStyle];

@lazyMultiInject(ComponentType.ChatInputComponent)
chatInputComponents: ChatInputComponent[] | undefined;
@lazyMultiInject(ControllerType.ChatInput)
chatInputComponents: ChatInputController[] | undefined;

@lazyMultiInject(ControllerType.ChatInputFooter)
chatInputFooterComponets: ChatInputFooterController[] | undefined;

@lazyMultiInject(ComponentType.ChatInputFooterComponent)
chatInputFooterComponets: ChatInputFooterComponent[] | undefined;
@lazyMultiInject(ControllerType.Citation)
citationControllers: CitationController[] | undefined;

@lazyMultiInject(ComponentType.CitationActionComponent)
citationActionComponents: CitationActionComponent[] | undefined;
@lazyMultiInject(ControllerType.ChatSection)
chatFooterComponents: ChatSectionController[] | undefined;

// Lifecycle method that runs when the component is first connected to the DOM
override connectedCallback(): void {
super.connectedCallback();
if (this.chatInputComponents) {
for (const component of this.chatInputComponents) {
component.attach(this, this.chatContext);
}
}
if (this.chatInputFooterComponets) {
for (const component of this.chatInputFooterComponets) {
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) {
component.attach(this, this.chatContext);
}
}
}

override updated(changedProperties: Map<string | number | symbol, unknown>) {
super.updated(changedProperties);
Expand Down Expand Up @@ -326,7 +374,7 @@ export class ChatComponent extends LitElement {
@on-citation-click="${this.handleCitationClick}"
></citation-list>
${this.selectedCitation
? this.citationActionComponents?.map((component) =>
? this.citationControllers?.map((component) =>
component.render(this.selectedCitation, `${this.apiUrl}/content/${this.selectedCitation.text}`),
)
: ''}
Expand Down Expand Up @@ -373,6 +421,7 @@ export class ChatComponent extends LitElement {
.selectedCitation="${this.selectedCitation}"
.isCustomBranding="${this.isCustomBranding}"
.svgIcon="${iconLogo}"
.context="${this.chatContext}"
@on-action-button-click="${this.handleChatEntryActionButtonClick}"
@on-citation-click="${this.handleCitationClick}"
@on-followup-click="${this.handleInput}"
Expand All @@ -385,7 +434,7 @@ export class ChatComponent extends LitElement {
? ''
: this.chatInputComponents
.filter((component) => component.position === position)
.map((component) => component.render(this.handleInput, this.isChatStarted, this.interactionModel));
.map((component) => component.render(this.handleInput));
}

// Render the chat component as a web component
Expand Down Expand Up @@ -479,7 +528,7 @@ export class ChatComponent extends LitElement {
)}
</form>
</section>
${this.isShowingThoughtProcess
<!-- ${this.isShowingThoughtProcess
? html`
<aside class="aside" data-testid="aside-thought-process">
<div class="aside__header">
Expand All @@ -494,7 +543,8 @@ export class ChatComponent extends LitElement {
${this.renderChatEntryTabContent(this.selectedChatEntry as ChatThreadEntry)}
</aside>
`
: ''}
: ''} -->
${this.chatFooterComponents?.map((component) => component.render())}
</section>
`;
}
Expand Down
72 changes: 72 additions & 0 deletions packages/chat-component/src/components/chat-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { type ReactiveController, type ReactiveControllerHost } from 'lit';

export class ChatContextController implements ReactiveController {
host: ReactiveControllerHost;

constructor(host: ReactiveControllerHost) {
(this.host = host).addController(this);
}

hostConnected() {
// no-op
}

hostDisconnected() {
// no-op
}

private _state: Record<string, any> = {};

private _selectedChatEntry: ChatThreadEntry | undefined = undefined;

private _apiUrl: string = '';

private _interactionModel: 'ask' | 'chat' = 'chat';

private _isChatStarted: boolean = false;

public set isChatStarted(value: boolean) {
this._isChatStarted = value;
this.host.requestUpdate();
}

public get isChatStarted() {
return this._isChatStarted;
}

public setState(key: string, value: any) {
this._state[key] = value;
this.host.requestUpdate();
}

public getState(key: string) {
return this._state[key];
}

public set selectedChatEntry(entry: ChatThreadEntry | undefined) {
this._selectedChatEntry = entry;
this.host.requestUpdate();
}

public get selectedChatEntry() {
return this._selectedChatEntry;
}

public set apiUrl(url: string) {
this._apiUrl = url;
this.host.requestUpdate();
}

public get apiUrl() {
return this._apiUrl;
}

public set interactionModel(model: 'ask' | 'chat') {
this._interactionModel = model;
this.host.requestUpdate();
}

public get interactionModel() {
return this._interactionModel;
}
}
32 changes: 11 additions & 21 deletions packages/chat-component/src/components/chat-thread-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import iconQuestion from '../../public/svg/bubblequestion-icon.svg?raw';

import { type ChatActionButton } from './chat-action-button.js';
import { type ChatEntryActionButtonComponent, ComponentType, lazyMultiInject } from './composable.js';
import { type ChatEntryActionController, ControllerType, lazyMultiInject } from './composable.js';
import { type ChatContextController } from './chat-context.js';

@customElement('chat-thread-component')
export class ChatThreadComponent extends LitElement {
Expand All @@ -19,9 +20,6 @@ export class ChatThreadComponent extends LitElement {
@property({ type: Array })
chatThread: ChatThreadEntry[] = [];

@property({ type: Array })
actionButtons: ChatActionButton[] = [];

@property({ type: Boolean })
isDisabled = false;

Expand All @@ -37,14 +35,17 @@ export class ChatThreadComponent extends LitElement {
@property({ type: Object })
selectedCitation: Citation | undefined = undefined;

@lazyMultiInject(ComponentType.ChatEntryActionButtonComponent)
actionCompontents: ChatEntryActionButtonComponent[] | undefined;
@property({ type: Object })
context: ChatContextController | undefined = undefined;

constructor() {
super();
@lazyMultiInject(ControllerType.ChatEntryAction)
actionCompontents: ChatEntryActionController[] | undefined;

connectedCallback() {
super.connectedCallback();
if (this.actionCompontents) {
for (const component of this.actionCompontents) {
component.attach(this);
component.attach(this, this.context);
}
}
}
Expand Down Expand Up @@ -105,18 +106,7 @@ export class ChatThreadComponent extends LitElement {
return html`
<header class="chat__header">
<div class="chat__header--button">
${this.actionButtons.map(
(actionButton) => html`
<chat-action-button
.label="${actionButton.label}"
.svgIcon="${actionButton.svgIcon}"
.isDisabled="${actionButton.isDisabled}"
.actionId="${actionButton.id}"
@click="${(event) => this.actionButtonClicked(actionButton, entry, event)}"
></chat-action-button>
`,
)}
${this.actionCompontents?.map((component) => component.render(entry, this.isDisabled, () => {}))}
${this.actionCompontents?.map((component) => component.render(entry, this.isDisabled))}
</div>
</header>
`;
Expand Down
6 changes: 3 additions & 3 deletions packages/chat-component/src/components/citation-previewer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { injectable } from 'inversify';
import { container, type CitationActionComponent, ComponentType } from './composable.js';
import { container, type CitationController, ControllerType, ComposableReactiveControllerBase } from './composable.js';
import { html } from 'lit';

@injectable()
export class CitationPreviewer implements CitationActionComponent {
export class CitationPreviewer extends ComposableReactiveControllerBase implements CitationController {
render(citation: Citation, url: string) {
return html`<document-previewer url="${url}"></document-previewer>`;
}
}

container.bind<CitationActionComponent>(ComponentType.CitationActionComponent).to(CitationPreviewer);
container.bind<CitationController>(ControllerType.Citation).to(CitationPreviewer);
Loading

0 comments on commit e6f18be

Please sign in to comment.