Skip to content

Commit

Permalink
feat: move copy to clipboard as composable
Browse files Browse the repository at this point in the history
  • Loading branch information
Shibani Basava authored and shibbas committed Feb 23, 2024
1 parent 738a393 commit eae0fee
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 24 deletions.
31 changes: 11 additions & 20 deletions packages/chat-component/src/components/chat-thread-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@ import { styles } from '../styles/chat-thread-component.js';
import { globalConfig } from '../config/global-config.js';
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { chatEntryToString } from '../utils/index.js';

import iconSuccess from '../../public/svg/success-icon.svg?raw';
import iconCopyToClipboard from '../../public/svg/copy-icon.svg?raw';
import iconQuestion from '../../public/svg/bubblequestion-icon.svg?raw';

import './citation-list.js';
import './chat-action-button.js';
import { type ChatActionButton } from './chat-action-button.js';
import { type ChatEntryActionButtonComponent, ComponentType, lazyMultiInject } from './composable.js';

@customElement('chat-thread-component')
export class ChatThreadComponent extends LitElement {
Expand All @@ -41,12 +37,16 @@ export class ChatThreadComponent extends LitElement {
@property({ type: Object })
selectedCitation: Citation | undefined = undefined;

// Copy response to clipboard
copyResponseToClipboard(entry: ChatThreadEntry): void {
const response = chatEntryToString(entry);
@lazyMultiInject(ComponentType.ChatEntryActionButtonComponent)
actionCompontents: ChatEntryActionButtonComponent[] | undefined;

navigator.clipboard.writeText(response);
this.isResponseCopied = true;
constructor() {
super();
if (this.actionCompontents) {
for (const component of this.actionCompontents) {
component.attach(this);
}
}
}

actionButtonClicked(actionButton: ChatActionButton, entry: ChatThreadEntry, event: Event) {
Expand Down Expand Up @@ -116,16 +116,7 @@ export class ChatThreadComponent extends LitElement {
></chat-action-button>
`,
)}
<chat-action-button
.label="${globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}"
.svgIcon="${this.isResponseCopied ? iconSuccess : iconCopyToClipboard}"
.isDisabled="${this.isDisabled}"
actionId="copy-to-clipboard"
.tooltip="${this.isResponseCopied
? globalConfig.COPIED_SUCCESSFULLY_MESSAGE
: globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}"
@click="${this.copyResponseToClipboard}"
></chat-action-button>
${this.actionCompontents?.map((component) => component.render(entry, this.isDisabled, () => {}))}
</div>
</header>
`;
Expand Down
17 changes: 13 additions & 4 deletions packages/chat-component/src/components/composable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { injectable, Container } from 'inversify';
import { type TemplateResult } from 'lit';
import { html } from 'lit';
import { html, type ReactiveControllerHost, type TemplateResult } from 'lit';
import getDecorators from 'inversify-inject-decorators';

export const container = new Container();
Expand All @@ -9,9 +8,14 @@ export const { lazyMultiInject } = getDecorators(container);
export const ComponentType = {
ChatInputComponent: Symbol.for('ChatInputComponent'),
ChatInputFooterComponent: Symbol.for('ChatInputFooterComponent'),
ChatEntryActionButtonComponent: Symbol.for('ChatEntryActionButtonComponent'),
};

export interface ChatInputComponent {
export interface ComposableComponent {
attach: (host: ReactiveControllerHost) => void;
}

export interface ChatInputComponent extends ComposableComponent {
position: 'left' | 'right' | 'top';
render: (
handleInput: (event: CustomEvent<InputValue>) => void,
Expand All @@ -20,10 +24,15 @@ export interface ChatInputComponent {
) => TemplateResult;
}

export interface ChatInputFooterComponent {
export interface ChatInputFooterComponent extends ComposableComponent {
render: (handleClick: (event: Event) => void, isChatStarted: boolean) => TemplateResult;
}

export interface ChatEntryActionButtonComponent extends ComposableComponent {
attach: (host: ReactiveControllerHost) => void;
render: (entry: ChatThreadEntry, isDisabled: boolean, handleClick: (event: Event) => void) => TemplateResult;
}

// Add a default component since inversify currently doesn't seem to support optional bindings
// and bindings fail if no component is provided
@injectable()
Expand Down
53 changes: 53 additions & 0 deletions packages/chat-component/src/components/copy-entry-to-clipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { injectable } from 'inversify';
import { container, type ChatEntryActionButtonComponent, ComponentType } from './composable.js';
import { type ReactiveControllerHost, html } from 'lit';
import { chatEntryToString } from '../utils/index.js';
import { globalConfig } from '../config/global-config.js';

import iconSuccess from '../../public/svg/success-icon.svg?raw';
import iconCopyToClipboard from '../../public/svg/copy-icon.svg?raw';

@injectable()
export class CopyToClipboardAction implements ChatEntryActionButtonComponent {
private _isResponseCopied: boolean = false;

set isResponseCopied(value: boolean) {
this._isResponseCopied = value;
this.host.requestUpdate();
}

get isResponseCopied() {
return this._isResponseCopied;
}

host: ReactiveControllerHost;

attach(host: ReactiveControllerHost) {
this.host = host;
}

// Copy response to clipboard
copyResponseToClipboard(entry: ChatThreadEntry): void {
const response = chatEntryToString(entry);

navigator.clipboard.writeText(response);
this.isResponseCopied = true;
}

render(entry: ChatThreadEntry, isDisabled: boolean) {
return html`
<chat-action-button
.label="${globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}"
.svgIcon="${this.isResponseCopied ? iconSuccess : iconCopyToClipboard}"
.isDisabled="${isDisabled}"
actionId="copy-to-clipboard"
.tooltip="${this.isResponseCopied
? globalConfig.COPIED_SUCCESSFULLY_MESSAGE
: globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}"
@click="${() => this.copyResponseToClipboard(entry)}"
></chat-action-button>
`;
}
}

container.bind<ChatEntryActionButtonComponent>(ComponentType.ChatEntryActionButtonComponent).to(CopyToClipboardAction);
2 changes: 2 additions & 0 deletions packages/chat-component/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import './citation-list.js';
import './chat-thread-component.js';
import './chat-action-button.js';

import './copy-entry-to-clipboard.js';

// [COMPOSE COMPONENTS START]
import './voice-input.js';
import './voice-input-button.js';
Expand Down

0 comments on commit eae0fee

Please sign in to comment.