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: add custom styling feature #137

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
de065ce
feat: add new global vars
anfibiacreativa Nov 11, 2023
40f2402
feat: add width vars
anfibiacreativa Nov 11, 2023
57173d5
feat: normalize design system
anfibiacreativa Nov 11, 2023
8d8cca4
feat: add font normalization
anfibiacreativa Nov 11, 2023
45313c2
feat: add cosmetic improvements
anfibiacreativa Nov 11, 2023
d879e1f
fix: fix widths issue with bubbles
anfibiacreativa Nov 11, 2023
0a04c2e
feat: add color picker to developer panel
anfibiacreativa Nov 11, 2023
3910b3c
feat: add color picker to developer panel in webapp
anfibiacreativa Nov 11, 2023
78ecd64
feat: update dynamic styles
anfibiacreativa Nov 11, 2023
0c9111b
feat: add a toggle to hide customization
anfibiacreativa Nov 11, 2023
b4f9be1
chore: save state in local storage to initialize with new values
anfibiacreativa Nov 12, 2023
3f2f69f
chore: improve design system definitions
anfibiacreativa Nov 12, 2023
02e52b8
feat: add additional dimensions and colors
anfibiacreativa Nov 13, 2023
9cfda4a
feat: add additional dimensions to chat page
anfibiacreativa Nov 13, 2023
0fdd80d
feat: improve initialization of style defaults
anfibiacreativa Nov 13, 2023
f8680ac
feat: fix minor bugs
anfibiacreativa Nov 13, 2023
5a79b0c
chore: extend comment for better maintenance
anfibiacreativa Nov 13, 2023
f86f481
feat: add branding capability
anfibiacreativa Nov 13, 2023
bec7a44
feat: add persistence in client
anfibiacreativa Nov 13, 2023
d71cd3f
fix: move branding toggle out of styling area
anfibiacreativa Nov 13, 2023
38f5180
feat: add theme switch
anfibiacreativa Nov 13, 2023
a05cf86
feat: complete dark theme (some bugs pending)
anfibiacreativa Nov 13, 2023
4dfbf74
fix: fix theme toggle bug
anfibiacreativa Nov 14, 2023
faa2d5a
fix: fix additional bug when closing config panel
anfibiacreativa Nov 14, 2023
a5d39f9
fix: fix additional bug for dark theme
anfibiacreativa Nov 15, 2023
ea25016
chore: refine theme switch
anfibiacreativa Nov 15, 2023
4a891ac
fix: initialization of color pickers
anfibiacreativa Nov 15, 2023
d1bab48
test: add test coverage
anfibiacreativa Nov 15, 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions packages/chat-component/public/branding/brand-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/chat-component/src/config/global-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ const globalConfig = {
],
DEFAULT_PROMPTS_HEADING_CHAT: 'Chat with our support team',
DEFAULT_PROMPTS_HEADING_ASK: 'Ask now',
// Custom Branding
IS_CUSTOM_BRANDING: true,
// Custom Branding details
// All these should come from persistence config
BRANDING_URL: '#',
BRANDING_LOGO_ALT: 'Brand Logo',
BRANDING_HEADLINE: 'Welcome to the Support Assistant of our Brand',
// This are the chat bubbles that will be displayed in the chat
CHAT_MESSAGES: [],
// This are the labels for the chat button and input
Expand Down
109 changes: 84 additions & 25 deletions packages/chat-component/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import iconSpinner from '../public/svg/spinner-icon.svg?raw';
import iconMicOff from '../public/svg/mic-icon.svg?raw';
import iconMicOn from '../public/svg/mic-record-on-icon.svg?raw';

// Branding
import iconLogo from '../public/branding/brand-logo-contoso.svg?raw';

import { marked } from 'marked';

/**
Expand Down Expand Up @@ -56,9 +59,14 @@ export class ChatComponent extends LitElement {
@property({ type: String, attribute: 'data-use-stream', converter: (value) => value?.toLowerCase() === 'true' })
useStream: boolean = chatHttpOptions.stream;

@property({ type: String, attribute: 'data-custom-branding', converter: (value) => value?.toLowerCase() === 'true' })
isCustomBranding: boolean = globalConfig.IS_CUSTOM_BRANDING;

@property({ type: String, attribute: 'data-overrides', converter: (value) => JSON.parse(value || '{}') })
overrides: RequestOverrides = {};

@property({ type: String, attribute: 'data-custom-styles', converter: (value) => JSON.parse(value || '{}') })
customStyles: any = {};
//--

@property({ type: String })
Expand Down Expand Up @@ -133,6 +141,26 @@ export class ChatComponent extends LitElement {

static override styles = [mainStyle];

// The following block is only necessary when you want to override the component from settings in the outside.
// Remove this block when not needed, considering that updated() is a LitElement lifecycle method
// that may be used by other components if you update this code.
override updated(changedProperties: Map<string | number | symbol, unknown>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

check out and see if styleMap directive for lit would be useful here. https://lit.dev/docs/components/styles/#dynamic-classes-and-styles

super.updated(changedProperties);

if (changedProperties.has('customStyles')) {
this.style.setProperty('--c-accent-high', this.customStyles.AccentHigh);
this.style.setProperty('--c-accent-light', this.customStyles.AccentLight);
this.style.setProperty('--c-accent-dark', this.customStyles.AccentDark);
this.style.setProperty('--c-text-color', this.customStyles.TextColor);
this.style.setProperty('--c-light-gray', this.customStyles.BackgroundColor);
this.style.setProperty('--c-dark-gray', this.customStyles.ForegroundColor);
this.style.setProperty('--c-base-gray', this.customStyles.FormBackgroundColor);
this.style.setProperty('--radius-base', this.customStyles.BorderRadius);
this.style.setProperty('--border-base', this.customStyles.BorderWidth);
this.style.setProperty('--font-base', this.customStyles.FontBaseSize);
}
}

// debounce dispatching must-scroll event
debounceScrollIntoView(): void {
let timeout: any = 0;
Expand Down Expand Up @@ -609,6 +637,33 @@ export class ChatComponent extends LitElement {
return this.isProcessingResponse ? cancelChatButton : submitChatButton;
}

// Render Branding if custom branding is enabled
renderBrandingBanner() {
if (this.isCustomBranding) {
return html`
<header class="branding__banner">
<a class="branding__link" href="${globalConfig.BRANDING_URL}" target="_blank" rel="noopener noreferrer">
${unsafeSVG(iconLogo)}
</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

reuse this.renderBrandingAvatar

<h1 class="branding__hl">${globalConfig.BRANDING_HEADLINE}</h1>
</header>
`;
}
return '';
}

// Render assistant logo as per branding
renderBrandingAvatar() {
if (this.isCustomBranding) {
return html`
<a class="branding__link" href="${globalConfig.BRANDING_URL}" target="_blank" rel="noopener noreferrer">
${unsafeSVG(iconLogo)}
</a>
`;
}
return '';
}

// Render the chat component as a web component
override render() {
return html`
Expand Down Expand Up @@ -636,32 +691,35 @@ export class ChatComponent extends LitElement {
${message.isUserMessage
? ''
: html` <div class="chat__header">
<button
title="${globalConfig.SHOW_THOUGH_PROCESS_BUTTON_LABEL_TEXT}"
class="button chat__header--button"
data-testid="chat-show-thought-process"
@click="${this.handleShowThoughtProcess}"
?disabled="${this.isShowingThoughtProcess || !this.canShowThoughtProcess}"
>
<span class="chat__header--span"
>${globalConfig.SHOW_THOUGH_PROCESS_BUTTON_LABEL_TEXT}</span
<div class="chat__header--avatar">${this.renderBrandingAvatar()}</div>
<div class="chat__header--buttons">
<button
title="${globalConfig.SHOW_THOUGH_PROCESS_BUTTON_LABEL_TEXT}"
class="button chat__header--button"
data-testid="chat-show-thought-process"
@click="${this.handleShowThoughtProcess}"
?disabled="${this.isShowingThoughtProcess || !this.canShowThoughtProcess}"
>

${unsafeSVG(iconLightBulb)}
</button>
<button
title="${globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}"
class="button chat__header--button"
@click="${this.copyResponseToClipboard}"
?disabled="${this.isDisabled}"
>
<span class="chat__header--span"
>${this.isResponseCopied
? globalConfig.COPIED_SUCCESSFULLY_MESSAGE
: globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}</span
<span class="chat__header--span"
>${globalConfig.SHOW_THOUGH_PROCESS_BUTTON_LABEL_TEXT}</span
>

${unsafeSVG(iconLightBulb)}
</button>
<button
title="${globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}"
class="button chat__header--button"
@click="${this.copyResponseToClipboard}"
?disabled="${this.isDisabled}"
>
${this.isResponseCopied ? unsafeSVG(iconSuccess) : unsafeSVG(iconCopyToClipboard)}
</button>
<span class="chat__header--span"
>${this.isResponseCopied
? globalConfig.COPIED_SUCCESSFULLY_MESSAGE
: globalConfig.COPY_RESPONSE_BUTTON_LABEL_TEXT}</span
>
${this.isResponseCopied ? unsafeSVG(iconSuccess) : unsafeSVG(iconCopyToClipboard)}
</button>
</div>
</div>`}
${message.text.map((textEntry) => this.renderTextEntry(textEntry))}
${this.renderCitation(message.citations)}
Expand Down Expand Up @@ -694,7 +752,8 @@ export class ChatComponent extends LitElement {
<!-- Conditionally render default prompts based on hasDefaultPromptsEnabled -->
${this.hasDefaultPromptsEnabled
? html`
<div class="defaults__container">
${this.renderBrandingBanner()}
<div class="defaults__container" data-testid="chat-branding">
Copy link
Contributor

Choose a reason for hiding this comment

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

this should probably be on the branding header and not the default container.

Copy link
Member Author

Choose a reason for hiding this comment

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

I created a rendering function, so I'm not sure exactly what you mean here. But let's put it in the back burner to review when we have all merged in.

<h1 class="headline">
${this.interactionModel === 'chat'
? this.title || globalConfig.DEFAULT_PROMPTS_HEADING_CHAT
Expand Down
Loading