Skip to main content

Chat Widget (JavaScript)

The Chat Widget JavaScript module allows you to quickly integrate our agents into your site. This module is designed to be as simple as possible to integrate, and it provides a simple API to interact with the agent.

Setup

To add a new instance of the chat widget, you'll need to add the following resources to your HTML file:

  • Empty HTML element that will be replaced by the component
  • Link to the JS file
  • Component initialization script
  1. Add an empty HTML element that will be replaced by the chat widget:

    <!-- Empty HTML element to be replaced by the widget -->
    <div id="aihub-chat"></div>
  2. Add the JS file to the end of the <body> section of your HTML file:

    <!-- Add JS for chat widget -->
    <script src="https://hub.serenitystar.ai/resources/chat.js"></script>
  3. Initialize the chat widget with the following script:

    <script>
    document.addEventListener("DOMContentLoaded", function () {
    const chat = new AIHubChat("aihub-chat", {
    apiKey: "<Your API key>", // Or use publicKey + tokenProvider. See the Authentication section.
    agentCode: "<Your Agent Code>",
    baseURL: "https://api.serenitystar.ai/api",
    });
    chat.init();
    });
    </script>

If you want to extract the content of current page and send the data to the agent, you can use the Content Extractor feature.

Initialization

You can initialize the chat widget by creating a new instance of the AIHubChat class and calling the init method:

const chat = new AIHubChat("aihub-chat", {
// Configuration options here
});
chat.init();

Configuration Options

The chat widget accepts the following configuration options:

{
apiKey: "<Your API key>", // Required when tokenProvider is not used. See the Authentication section.
publicKey: "<Your Public Key>", // Alternative to apiKey. Required when using tokenProvider. See the Authentication section.
channel: "web", // The channel identifier (e.g., "web", "mobile"). Optional. Used with tokenProvider authentication.
tokenProvider: async ({ context }) => { /* ... */ }, // Async function that returns a Client Token (signed JWT). Required when using publicKey instead of apiKey. See the Authentication section.
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
logoURL: "<URL to the logo image>", // Optional. The URL to the logo image.
conversationId: "<Conversation ID>", // The ID of the conversation to load. Only required if you want to load an existing conversation.
mode: "compact", // String that indicates the chat mode (compact, floating, fullscreen, side-panel or floating-side-panel). Default is "floating"
readOnly: false, // Boolean that indicates whether the chat is read-only.
scrollToBottom: true, // Boolean that indicates whether the chat should scroll to the bottom by default. Optional. Default is true.
allowUpload: false, // Boolean that indicates whether file uploads are allowed.
allowAudioRecording: false, // Boolean that indicates whether the user can record and send audio messages. Optional. Default is false.
showReasoning: false, // Boolean that indicates whether the agent's reasoning (chain-of-thought) is displayed in a collapsible box while the response is generated. Optional. Default is false.
skillWaitingMessages: { GetProductInfo: "Looking up the product..." }, // Object that maps skill codes to the message (or messages) shown while that skill runs. Optional. Requires stream: true.
skillWaitingMessagesInterval: 3000, // Number of milliseconds between messages when a skill defines more than one. Optional. Default is 3000.
maxHeight: "500px", // String that indicates the maximum height of the chat widget.
showTermsAndConditions: false, // Boolean that indicates whether the terms and conditions should be displayed.
showAcceptCommunications: false, // Boolean that indicates whether the accept communications checkbox should be displayed.
showMetaAnalysisInfo: false, // Boolean that indicates whether the meta analysis info should be displayed on each message. Optional. Default is false.
showTokenUsageInfo: false, // Boolean that indicates whether the token usage info should be displayed on each message. Optional. Default is false.
showTimeToFirstTokenInfo: false, // Boolean that indicates whether the time to first token info should be displayed on each message. Optional. Default is false.
showExecutorTaskLogsInfo: false, // Boolean that indicates whether the executor task logs info should be displayed on each message. Optional. Default is false.
preferredLanguage: "en", // String (BCP 47 language tag) that sets the lang attribute on the widget root element. Optional. Default is "en".
storeConversation: true, // Boolean that indicates whether the conversation should be stored in local storage.
opened: false, // Boolean that indicates whether the chat window is already open when the page loads. Only relevant when mode is floating, side-panel or floating-side-panel. Optional. Default is false.
expanded: false, // Boolean that indicates whether the chat widget is expanded by default. This is only relevant in when mode is floating. Optional. Default is false.
disclaimer: "<Disclaimer text>", // String displayed below the message composer, above the system logo. Optional.
stream: true, // Boolean that indicates whether the chat should stream responses as they are generated. Optional. Default is true.
extractPageContent: false, // Boolean that indicates whether the page content should be extracted and sent to the agent. Optional. Default is false.
showPreviousConversations: false, // Boolean that indicates whether previous conversations should be accessible by scrolling up. Optional. Default is false.
userIdentifier: "user123", // String that indicates the user identifier. Optional.
engagementMessage: {
enabled: true, // Boolean that indicates whether the engagement message should be displayed. Optional. Default is true.
message: "Hello, how can I help you?", // The message to display in the engagement message. Optional. It will show the initial message from the agent if not provided.
showAfter: 10 // The time in seconds after which the engagement message should be displayed. Optional. Default is 10 seconds.
},
locale: {
uploadFileErrorMessage: "", // The error message for file uploads.
uploadFilesErrorMessage: "", // The error message for multiple file uploads.
chatErrorMessage: "", // The error message for chat messages.
headerTitle: "<Chat header title>", // The title of the chat header.
finalizedMessage: "", // The message displayed when the chat is finalized.
chooseAnOptionMessage: "", // The message displayed when choosing an option.
limitExceededMessage: "", // The message displayed when the character limit is exceeded.
waitUntilMessage: "", // The message displayed when waiting for a response.
remainingMessage: "", // The message displayed when there are remaining characters.
termsAndConditionsMessage: "<Your terms and conditions message>", // The terms and conditions message.
acceptCommunicationsMessage: "<Your accept communications message>", // The accept communications message.
inputPlaceholder: "write your message here...", // The placeholder text for the chat input.
metaAnalysisTitle: "Meta Analysis", // The title for the meta analysis card on each message.
completionUsageTitle: "Completion Usage", // The title for the token completion usage card on each message.
timeToFirstTokenTitle: "Time to first token", // The title for the time to first token tooltip.
newChatBtnMessage: "New Chat", // The message for the "New chat" button in the header. (Only visible when storeConversation is true)
executorTaskLogsTitle: "Executor Task Logs" // The title for the executor task logs card on each message.
millisecondsUnit: "ms", // The unit for milliseconds,
exceededMaxNumberOfFilesMessage: "You can only upload up to 5 files per message", // The message displayed when the user tries to upload more than the allowed number of files
exceededMaxFileStatusChecksMessage: "It seems that the file upload is taking longer than expected. Please try again later.", // Message to display when files are taking longer than expected to upload
chatInitConversationErrorMessage: "Error initializing conversation", // The error message to display when there is an error initializing the conversation
closedConversationMessage: "", // The message displayed when the conversation has been closed.
loadingPreviousChatMessage: "Loading previous chat...", // The message displayed while loading a previous conversation. Optional.
endOfChatHistoryMessage: "End of chat history", // The message displayed at the boundary between previous conversations and the current one. Optional.
loadPreviousChatButtonMessage: "Load previous chat", // The label for the button that loads a previous conversation. Optional.
newChatSeparatorMessage: "New Chat", // The text displayed on the separator between conversations when a new chat is started. Optional.
microphonePermissionDeniedMessage: "Microphone permission denied", // The message displayed when the browser denies access to the microphone.
recordingLabel: "Recording", // The status text displayed while an audio message is being recorded.
recordingPausedLabel: "Recording paused", // The status text displayed while an audio recording is paused.
uploadingAudioLabel: "Uploading audio", // The status text displayed while a recorded audio message is being uploaded.
recordingErrorLabel: "There was an error trying to process the audio recording", // The message displayed when an audio recording cannot be processed.
connectButtonLabel: "Connect", // The label for the button on a connection card.
connectionRequiredSingularMessage: "To finish processing your request, please sign in to the following service:", // The message displayed when one connector must be authorized.
connectionRequiredPluralMessage: "To finish processing your request, please sign in to the following services:", // The message displayed when several connectors must be authorized.
disclaimerSeeMoreLabel: "See more", // The label of the toggle that expands a truncated disclaimer.
disclaimerSeeLessLabel: "See less", // The label of the toggle that collapses an expanded disclaimer.
sourcesTitle: "Sources", // The title of the sources view.
viewSourcesLabel: "View sources", // The label of the pill that opens the sources view. The number of sources is appended in parentheses.
citationPageLabel: "p.", // The prefix used for the page number of a cited document.
reasoningLabel: "Reasoning", // The header label of the reasoning box.
reasoningTooltip: "The model's chain-of-thought while producing the answer.", // The tooltip displayed next to the reasoning box header. When omitted, no tooltip is shown.
},
theme: {
header: {
bgColor: "<Header background color>",
textColor: "<Text color for the header>",
resetChatBtn: {
bgColor: "<Background color for reset button>",
hoverBgColor: "<Hover background color for reset button>",
textColor: "<Text color for reset button>",
},
minimizeBtn: {
iconStrokeColor: "<Line Stroke color for the icon in the minimize button>"
},
expandBtn: {
iconStrokeColor: "<Line Stroke color for the icon in the expand button>"
}
},
fabButton: {
bgColor: "<Background color for the fab button>",
iconStrokeColor: "<Line Stroke color for the icon in the fab button>",
buttonSize: "<Floating action button size>", // Optional. Default is 50
iconSize: "<Floating action button icon size>", // Optional. Default is 25
},
sendButton: {
bgColor: "<Background color for the send button>",
iconStrokeColor: "<Line Stroke color for the icon in the send button>"
},
attachments: {
borderColor: "<Border color for attachments container>",
},
engagementMessage: {
bgColor: "<Background color for the engagement message>",
textColor: "<Text color for the engagement message>",
},
uploadFileBtn: {
iconStrokeColor: "<Line Stroke color for the icon in the upload file button>"
},
citations: {
highlightBgColor: "<Background color of the inline highlighted cited span>",
highlightTextColor: "<Text color of the inline highlighted cited span>",
cardBgColor: "<Background color of the citation hover card and the source cards>",
cardTextColor: "<Text color of the citation hover card and the source cards>",
},
messageBubble: {
user: {
bgColor: "<Background color for user message bubble>",
textColor: "<Text color for user message bubble>",
},
assistant: {
bgColor: "<Background color for assistant message bubble>",
textColor: "<Text color for assistant message bubble>",
connections: {
connectionCard: {
bgColor: "<Background color for the connection card>",
textColor: "<Text color for the connection card>",
connectButton: {
bgColor: "<Background color for the Connect button>",
textColor: "<Text color for the Connect button>",
},
},
},
userChoice: {
card: {
bgColor: "<Background color for the user choice card>", // Optional. Falls back to the assistant bubble
textColor: "<Text color for the user choice card>", // Optional. Falls back to the assistant bubble
borderColor: "<Border color for the user choice card>",
pendingAccentColor: "<Left border color while the questions are unanswered>",
answeredAccentColor: "<Left border color once the questions are answered>",
optionSelectedBgColor: "<Background color for a selected option>",
optionSelectedBorderColor: "<Border color for a selected option>",
progressColor: "<Stroke color for the filled part of the progress ring>",
submitButton: {
bgColor: "<Background color for the Submit button>",
textColor: "<Text color for the Submit button>",
},
navButton: {
bgColor: "<Background color for the Back / Next buttons>",
textColor: "<Text color for the Back / Next buttons>",
},
},
},
},
},
conversationStarters: {
bgColor: "<Background color for conversation starters>",
textColor: "<Text color for conversation starters>",
containerBgColor: "<Background color for conversation starters container>",
initialMessageBgColor: "<Background color for initial message in conversation starters>",
initialMessageTextColor: "<Text color for initial message in conversation starters>",
},
scrollToBottomIndicator: {
bgColor: "<Background color for scroll to bottom indicator>",
iconStrokeColor: "<Line Stroke color for the icon in the scroll to bottom indicator>"
}
},
// Optional static input parameters to send to the agent
inputParameters: {
name: "John Doe",
email: "[email protected]",
},
// Optional callback to resolve input parameters at runtime.
// Runs when the conversation starts and before each message.
// If provided, it takes precedence over inputParameters.
getInputParameters: () => ({
page: window.location.pathname,
locale: navigator.language,
})
};

Authentication

The chat widget supports two authentication modes: API Key and Token Provider. Both are suitable for production use — choose the one that best fits your needs. You must use one or the other — they are mutually exclusive. If both apiKey and tokenProvider are provided, the widget will throw an error.

To use a Token Provider, you first need to create an Agent Client, which provides the credentials required to authenticate your client application. See the Client Credentials documentation for more details.

API Key

The simplest way to authenticate. Provide your API key directly in the configuration:

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
});
chat.init();

API keys are safe to include in client-side code — they are scoped with limited permissions and can be configured to only access specific Agents. The API key is sent with every request to authenticate the widget.

Token Provider

If you want an extra layer of security by managing authentication through your own server, you can use a token provider function. This delegates authentication to your own backend, which signs a short-lived Client Token (JWT) using the Client Secret.

The token provider consists of two components: the Client Token Provider, which runs in the client application (e.g., within the chat widget), and the Server Token Provider, which runs on your server.

Instead of providing an apiKey, you provide a publicKey and a tokenProvider callback:

const chat = new AIHubChat("aihub-chat", {
agentCode: "<Your Agent Code>",
channel: "web",
baseURL: "https://api.serenitystar.ai/api",
publicKey: "<Your Public Key>",
tokenProvider: async ({ context }) => {
const response = await fetch("https://your-server.com/auth/serenity-token", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `<if needed, add your server's authorization token here>` },
body: JSON.stringify({ /* pass any necessary context information here as needed */ }),
});
const { token } = await response.json();
return token; // Returns the Client Token (signed JWT)
},
});
chat.init();

Configuration

PropertyTypeRequiredDescription
publicKeystringYesThe Public Key provided when creating the Agent Client.
channelstringNoA channel identifier (e.g., "web", "mobile", "kiosk"). Defaults to undefined. Passed to the tokenProvider callback as part of the context.
tokenProvider({ context }) => Promise<string>YesAsync function that returns a Client Token (signed JWT) from your backend. The context parameter contains information such as channel and agent details.
info

apiKey and tokenProvider are mutually exclusive. When using tokenProvider, you must provide publicKey instead of apiKey.

How it works

The authentication flow works as follows:

  1. Widget calls Client Token Provider — On initialization (and on every token refresh), the widget calls your tokenProvider function, passing context information (such as channel and agent details).

  2. Server Token Provider signs a Client Token — The Client Token Provider sends a token request to your Server Token Provider. Your server creates a short-lived Client Token (JWT with recommended TTL: 30–60 seconds) signed with the Client Secret, and returns it.

  3. Widget exchanges the Client Token — The widget sends the Client Token to the Serenity API, which validates the signature using the associated Client Secret stored on our servers.

  4. Serenity API returns an Access Token — If the Client Token is valid, the Serenity API returns an Access Token that the widget uses for all subsequent API requests via the Authorization: Bearer {accessToken} header.

┌────────────┐     ┌──────────────────────┐     ┌──────────────────────┐     ┌──────────────┐
│ Widget │ │ Client Token │ │ Server Token │ │ Serenity API │
│ (browser) │ │ Provider (your code) │ │ Provider (your code) │ │ │
└─────┬──────┘ └──────────┬───────────┘ └──────────┬───────────┘ └─────┬────────┘
│ │ │ │
│ 1. call with │ │ │
│ {context} │ │ │
│──────────────────────>│ │ │
│ │ 2. POST {tokenRequest} │ │
│ │───────────────────────────>│ │
│ │ │ 3. Sign Client Token │
│ │ │ with Client Secret│
│ │ 4. Client Token (JWT) │ │
│ │<───────────────────────────│ │
│ 5. Client Token │ │ │
│<──────────────────────│ │ │
│ │
│ 6. Exchange Client Token │
│───────────────────────────────────────────────────────────────────────────>│
│ 7. Validate signature │
│ 8. Access Token │
│<───────────────────────────────────────────────────────────────────────────│
│ │
│ 9. Authorization: Bearer {accessToken} │
│───────────────────────────────────────────────────────────────────────────>│

Token refresh

The Access Token issued by the Serenity API has a limited lifetime. The widget automatically handles token refresh by calling your tokenProvider function again when needed, exchanging the new Client Token for a fresh Access Token, and resuming normal operation. No manual intervention or additional configuration is required.

Security best practice

Your tokenProvider function runs in the browser, so never include your Client Secret in client-side code. The signing must always happen on your server.

API Keys vs Public Keys

  • API Keys can be used to authenticate a client application to access the Serenity Star API. They can be configured to have specific permissions and access to specific Agents. Anyone with the API Key can access the API and perform actions based on the permissions granted to that key.
  • Public Keys are used in conjunction with the Client ID and Client Secret to authenticate a client application. They don't grant access to the API nor hold any permissions on their own.

Presentation modes

You can choose between different presentation modes for the chat widget. We have 2 inline modes and 3 floating modes available:

Inline modes

  • compact: The chat widget will be displayed as a box. This is useful when you want to manually position the chat widget on your page. Compact mode

  • fullscreen: Similar to compact mode, but the chat widget will take up the entire screen.

Floating modes

Floating modes will always show a floating button on the bottom right corner of the page. When clicked, the chat widget will be displayed in different ways:

  • side-panel: The chat widget will be displayed as a side panel on the right side of the page, pushing the content to the left. Side panel mode

  • floating: The chat widget will be displayed as a floating chat window. (Default) Floating mode

  • floating-side-panel: The chat widget will be displayed as a floating side panel on the right side of the page. It won't push the content to the left. Floating Side panel mode

Stream / Single shot responses

You can choose between streaming responses as they are generated, or get a single-shot response at the end. By default, the chat widget will stream responses as they are generated. If you want to get a single-shot response at the end, you can set the stream option to false.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
stream: false, // Get a single-shot response at the end
});

Multiple instances on the same page

The chat widget supports multiple instances on the same page. You can create multiple instances of the chat widget by providing a unique ID for each instance:

<!-- These ids will be used to identify each instance -->
<div id="chat-1"></div>
<div id="chat-2"></div>
const chat1 = new AIHubChat("chat-1", {
...
});

const chat2 = new AIHubChat("chat-2", {
...
});

The ID you pass as the first argument is also the instance identifier used to keep each widget's state and stored conversation separate. There is no separate option for it — give each container a unique ID.

Keeping the conversation alive

By default, the chat widget will store the current conversation so that the user can continue the conversation even after refreshing the page.

If you want to start a new chat every time the page is refreshed, you can disable this behavior by setting the storeConversation option to false.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
storeConversation: false, // Do not store the conversation
});

When storeConversation is set to true (by default), the header of the chat widget will display a "New Chat" button that allows the user to start a new chat.

You can even keep multiple conversations alive by providing a unique key for each conversation (as mentioned above):

<div id="chat-1"></div>
<div id="chat-2"></div>
const chat1 = new AIHubChat("chat-1", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
storeConversation: true, // Store the conversation in local storage
});

const chat2 = new AIHubChat("chat-2", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
storeConversation: true, // Store the conversation in local storage
});

Previous conversations

When showPreviousConversations is enabled, the chat widget stores all past conversation IDs in local storage. As the user scrolls up in the chat body, previous conversations are lazily loaded (most-recent-first) and rendered read-only above the current chat, separated by visual dividers.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
showPreviousConversations: true,
});
chat.init();

How it works

  1. Every time a new conversation is created, its ID is persisted in local storage.
  2. When the user clicks "New Chat", the current conversation is finalized and a new one begins.
  3. As the user scrolls to the top of the chat body, previous conversations are loaded one at a time (most recent first) and displayed above the current messages.
  4. A visual separator with the text configured in locale.endOfChatHistoryMessage marks the boundary between previous conversations and the current one.
  5. While a previous conversation is being fetched, a loading indicator with the text configured in locale.loadingPreviousChatMessage is shown at the top of the chat.

Interaction with storeConversation

showPreviousConversations works independently of storeConversation. Even when storeConversation is false (meaning the current conversation is not persisted across page reloads), the previous conversation IDs are still stored. This allows the user to chat, reload the page, start a new conversation, and then scroll up to access their previous conversations.

Multiple instances

When using multiple chat widget instances on the same page, each instance stores its previous conversations separately using its own instance-scoped local storage key.

Edge cases

ScenarioBehavior
No previous conversations existScroll-to-top detection is skipped; nothing is rendered above the current chat.
All previous conversations already loadedScroll-to-top detection becomes a no-op; the "End of chat history" separator is displayed.
A previous conversation cannot be found (404)The entry is skipped and the next one is loaded.
Agent code or base URL changedPrevious conversation history is cleared to avoid loading chats from a different agent.

Customization

There are certain options available to customize the look and feel & general behavior of the chat widget:

  • maxHeight: The maximum height of the chat widget. This value will be ignored if the chat is in fullscreen mode.
  • mode: The mode of the chat widget. The available options are compact, floating, and fullscreen, side-panel, and floating-side-panel. Default is floating.
    • compact: The chat widget will be displayed as a box. This is useful when you want to manually position the chat widget on your page.
    • floating: The chat widget will be displayed as a floating button in the bottom right corner of the page. This is useful when you want the chat widget to be easily accessible. Once clicked, the chat will be displayed as a floating chat window (Default).
    • fullscreen: The chat widget will be displayed as a fullscreen chat window. This is useful when you want the chat widget to take up the entire screen.
    • side-panel: The chat widget will be displayed as a side panel on the right side of the page, pushing the content to the left.
    • floating-side-panel: The chat widget will be displayed as a floating side panel on the right side of the page. It won't push the content to the left.
  • readOnly: Boolean that indicates whether the chat is read-only. When set to true, the chat will not accept any user input.
  • scrollToBottom: Boolean that indicates whether the chat should scroll to the bottom by default. Optional. Default is true.
  • allowUpload: Boolean that indicates whether file uploads are allowed.
  • allowAudioRecording: Boolean that indicates whether the user can record and send audio messages. When enabled, a microphone button is displayed in the footer. Optional. Default is false.
  • showReasoning: Boolean that indicates whether the agent's reasoning is displayed while the response is generated. Optional. Default is false. Click here to learn more.
  • skillWaitingMessages: Object that maps skill codes to the message shown while that skill is executing. The value is a string (one message) or an array of strings (rotated while the skill runs). Requires stream: true. Optional. Default is undefined. Click here to learn more.
  • skillWaitingMessagesInterval: Number of milliseconds between messages when a skill defines more than one. Optional. Default is 3000. Click here to learn more.
  • stream: Boolean that indicates whether the chat should stream responses as they are generated. Default is true.
  • showTermsAndConditions: Boolean that indicates whether the terms and conditions should be displayed before starting the chat.
  • showAcceptCommunications: Boolean that indicates whether the accept communications checkbox should be displayed before starting the chat.
  • opened: Boolean that indicates whether the chat window is already open when the page loads, instead of collapsed to the floating action button. This is only relevant when mode is floating, side-panel, or floating-side-panel. Optional. Default is false.
  • expanded: Boolean that indicates whether the chat widget is expanded by default. This is only relevant in when mode is floating, side-panel, or floating-side-panel. Optional. Default is false.
  • disclaimer: Optional disclaimer text displayed below the message composer, above the system logo. Long text is truncated with a "See more" / "See less" toggle, whose labels are customizable through locale.disclaimerSeeMoreLabel and locale.disclaimerSeeLessLabel.
  • extractPageContent: Boolean that indicates whether the page content should be extracted and sent to the agent. Optional. Default is false.
  • showPreviousConversations: Boolean that indicates whether previous conversations should be accessible by scrolling up in the chat body. When enabled, past conversation IDs are stored in local storage and their messages are lazily loaded as the user scrolls to the top. Optional. Default is false. Click here to learn more.
  • inputParameters: Optional static input parameters object to send to the agent. Click here to learn more.
  • getInputParameters: Optional callback that dynamically resolves input parameters during conversation setup and before each message. It can return the object directly or as a Promise, and it takes precedence over inputParameters. Click here to learn more.
  • userIdentifier: String that indicates the user identifier. This will be sent to the agent only when the conversation starts. Optional.
  • engagementMessage: Object that contains the configuration for the engagement message. Click here to learn more.
  • logoURL: The URL to the logo image. Optional.
  • preferredLanguage: Sets the lang attribute on the widget root element, which determines the language context for screen readers and assistive technology. Accepts a BCP 47 language tag (e.g., "en", "es", "fr"). Optional. Default is "en".

Reasoning

Some models expose the chain-of-thought they produce before writing an answer. When showReasoning is set to true and the agent's model returns reasoning, the widget renders it in a collapsible box above the answer bubble, streaming live while the response is produced. The box starts expanded and collapses on its own as soon as the answer starts arriving, unless the user has already toggled it manually.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
showReasoning: true, // Show the agent's reasoning while the answer is generated
});

The reasoning box is a sibling of the message bubble, not part of the message text. It never appears in the content of an onAgentResponse payload, and it is not restored when a conversation is loaded from its conversationId — it exists only for the live response.

You can customize it with:

  • locale.reasoningLabel: the header label. Default is "Reasoning".
  • locale.reasoningTooltip: the tooltip displayed next to the label. Default is "The model's chain-of-thought while producing the answer.". When set to an empty string, no tooltip is shown.
  • locale.accessibility.toggleReasoning: the accessible label of the box, which acts as a toggle button. Default is "Toggle reasoning".
  • The sc-reasoning-box* classes in the customizable build, for CSS overrides. The box inherits its colors from theme.messageBubble.assistant.

The box only appears for models that actually return reasoning. Enabling showReasoning for a model that does not is a no-op.

Skill waiting messages

Some skills take a while — a report generation, a lookup against a slow system. By default the widget only shows the typing dots while they run, which gives the user nothing to read. skillWaitingMessages maps skill codes to text rendered inside the typing indicator, next to the dots, for as long as that skill is executing.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
stream: true, // Required
skillWaitingMessages: {
// One message, shown for as long as the skill runs.
GetProductInfo: "Looking up the product...",
// Several messages, advanced every interval.
GenerateReport: [
"Crunching the numbers...",
"Building your report...",
"Almost there...",
],
},
skillWaitingMessagesInterval: 3000, // Optional. Default is 3000.
});

Single message vs. multiple messages

  • A string shows that one message until the skill finishes.
  • An array advances to the next message every skillWaitingMessagesInterval milliseconds and then holds on the last message until the skill finishes. It never loops back to the first one, so a long-running skill ends on your most reassuring line instead of cycling.

Skill codes

Keys are skill codes, matched case-insensitively against the code the agent reports while it runs (the code you see on the skill in AIHub). Wildcards are not supported: a skill with no entry in the object shows the plain typing dots, exactly as before.

Requires streaming

The feature is driven by the task events the server emits while streaming, so it requires stream: true. With stream: false there are no such events and the option is silently ignored.

Concurrent skills

When an agent runs more than one configured skill at a time, the most recently started one is shown. When it finishes, the message falls back to whichever configured skill is still running, and to the plain dots once none are.

Styling and accessibility

The message inherits the assistant bubble's colors (theme.messageBubble.assistant) and carries the sc-skill-waiting-message class for CSS overrides in the customizable build. It is announced politely to screen readers through role="status".

Citations and sources

Agents that answer from knowledge sources return citations along with their response. No option gates this — citations are rendered whenever the agent returns them.

The widget renders them in two places:

  • Inline. The cited span of the answer is highlighted and followed by a numbered mark. Hovering or focusing the mark opens a small hover card with the source behind it.
  • Sources view. When a message has citations, a View sources pill is displayed in its metadata row, with the number of distinct sources in parentheses. Activating it swaps the message list for a full-height list of the sources used in that message, with a back bar that returns to the conversation. File, website and web-search sources are rendered as distinct card types (sc-sources-card--file, --website and --websearch).

Both are available for messages loaded from an existing conversation, not just for live responses.

You can customize them with:

  • locale.sourcesTitle: the title of the sources view. Default is "Sources".
  • locale.viewSourcesLabel: the label of the pill that opens the sources view. Default is "View sources".
  • locale.citationPageLabel: the prefix used for the page number of a cited document. Default is "p.".
  • theme.citations: highlightBgColor and highlightTextColor for the inline highlighted span, cardBgColor and cardTextColor for the hover card and the source cards.
  • The sc-citation* and sc-sources* classes in the customizable build.

Screen reader labels for the citation marks, the sources list and the back control are configured through locale.accessibility (citationReference, sourcesListLabel, openSourceInNewTab, viewSources and backToMessages). See the Accessibility labels table and the accessibility reference for details.

Connections

Some skills act on a third-party service on the end user's behalf and need that user to authorize a connector first. When an agent runs into one, its response carries a pending_actions array instead of completing the request.

Each entry in pending_actions is discriminated by a type field. This section covers type: "connection".

For a connection action, the widget:

  1. Replaces the answer with a short message naming the services that need to be authorized (locale.connectionRequiredSingularMessage or locale.connectionRequiredPluralMessage, depending on how many there are).
  2. Renders a card inside the bubble for each connector, showing its icon and name and a Connect button.
  3. Opens the provider's consent flow in a new tab when the button is activated, and polls the connection status for up to three minutes.
  4. Resends the user's message automatically once every required connector is connected, so the agent can finish the request. If the user sends another message while the widget is still polling, the automatic resend is cancelled.

You can customize this with:

  • locale.connectButtonLabel: the label of the button on the card. Default is "Connect".
  • locale.connectionRequiredSingularMessage: the message displayed when one connector must be authorized. Default is "To finish processing your request, please sign in to the following service:".
  • locale.connectionRequiredPluralMessage: the same message for several connectors. Default is "To finish processing your request, please sign in to the following services:".
  • theme.messageBubble.assistant.connections.connectionCard: bgColor and textColor for the card, plus a connectButton object with its own bgColor and textColor.
  • The sc-pending-action* classes in the customizable build.

User choices

Agents can ask the user for input before they carry on. When an agent uses the Request User Choice skill, the widget shows the questions it asked as a card in the conversation, with the options to choose from.

No configuration is needed to enable this: the card appears whenever an agent asks, in both streaming and single-shot mode.

What the user sees

The card presents one question at a time:

  • Options are radio buttons when a single answer is expected, and checkboxes when the agent allows several.
  • Every question also offers a free-text Other field, so the user can answer in their own words when none of the options fit.
  • When the agent asked more than one question, a step counter and a progress ring show how far along the user is, and Back / Next buttons move between the questions. Picking an option with the mouse moves on to the next question automatically; with the keyboard, Enter or Next does.
  • Submit sends the answers once every question has been answered, and the agent continues its response from there.
  • The card then shows a summary of what was answered, which can be collapsed and expanded from its header.

Answering is optional. The chat input stays available, so the user can ignore the questions and simply reply instead. The card is then marked Skipped and the conversation carries on without the answers.

If the user reloads the page and the conversation is restored, a card they have not answered yet comes back and can still be answered.

When one of the show*Info options is enabled, the card also shows the metrics of the execution that asked the questions.

Customisation

The wording of the card is configured through locale:

PropertyDefaultDescription
userChoiceRequiredLabel"Choose an option"Header label while the questions are unanswered
userChoiceSubmittingLabel"Sending..."Header label while the answers are being sent
userChoiceAnsweredLabel"Answered"Header label of an answered card
userChoiceSkippedLabel"Skipped"State label of a card superseded by a reply
userChoiceQuestionCountSingular"question"Noun after the count when a single question was asked
userChoiceQuestionCountPlural"questions"Noun after the count when several were asked
userChoiceQuestionFallback"Question {index}"Label of a question the agent gave no header. Supports an {index} placeholder
userChoiceHintSingleSelect"Choose one"Hint below a single-select question
userChoiceHintMultiSelect"Choose one or more"Hint below a multiselect question
userChoiceOtherLabel"Other"Label of the free-text field offered on every question
userChoiceOtherPlaceholder"Type your own answer..."Placeholder of the free-text field
userChoiceReviewAnswerLabel"Answer:"Prefix of each line in the review list of an answered card
userChoiceBackLabel"Back"Label of the button that goes back one question
userChoiceNextLabel"Next"Label of the button that goes forward one question
userChoiceSubmitLabel"Submit"Label of the button that sends the answers
userChoiceCounter"{current} of {total}"Step counter next to the progress ring. Supports {current} and {total} placeholders
const chat = new AIHubChat("aihub-chat", {
..., // Other options
locale: {
userChoiceRequiredLabel: "Your input is needed",
userChoiceOtherLabel: "Something else",
userChoiceSubmitLabel: "Send answers",
},
});

You can also customise:

  • theme.messageBubble.assistant.userChoice.card: bgColor, textColor and borderColor for the card (the first two fall back to the assistant bubble's own values), pendingAccentColor and answeredAccentColor for the left border of an unanswered and an answered card (defaults #F59E0B and #059669), optionSelectedBgColor and optionSelectedBorderColor for a selected option, progressColor for the filled part of the progress ring (default #353FE0), plus submitButton and navButton objects with their own bgColor and textColor.
  • The sc-user-choice-card* classes in the customizable build, for CSS overrides.
  • locale.accessibility.userChoiceCardLabel, userChoiceRequestedAnnouncement, userChoiceAnsweredAnnouncement and userChoiceQuestionProgress, for the screen reader labels and announcements. See the Accessibility labels table.

The card appearing and the answers being sent are both announced to screen readers, and its controls are reachable with the keyboard through the widget's message navigation. See the accessibility reference for details.

Message feedback

When feedback recollection is enabled for the chat widget channel, every assistant message shows a thumbs-up / thumbs-down pair in its metadata row. Both thumbs stay on screen once a rating is given, so the user can switch straight to the other one, and clicking the current rating again clears it.

Optional comment on a negative rating

Rating a message as not helpful also opens a small comment box under the message, so the user can say what went wrong. The comment is always optional: the rating itself is already recorded by the click on the thumb, so skipping the box costs nothing.

  • Enter sends the comment, Shift+Enter inserts a newline.
  • Escape, the dismiss button, or leaving the box empty skips it. While the box is open, Escape closes the box only, it does not close the chat widget.
  • Comments are limited to 1000 characters, and a counter appears over the last 100.
  • Once the comment is sent, the box is replaced by a short confirmation.

A rating and its comment are stored together, and a rating submitted on its own clears any comment previously stored for that message. So switching to thumbs-up, or clearing the rating, discards both the draft in the box and any comment already sent for that message.

Ratings are written after a short delay, so toggling between the thumbs a few times only costs one call. Sending a comment submits its rating with it straight away.

Customisation

  • Visible wording: locale.feedbackCommentPlaceholder, locale.feedbackCommentHint and locale.feedbackCommentThanksLabel. See Localization.
  • Screen reader labels: locale.accessibility.feedbackCommentLabel, feedbackCommentOpened, sendFeedbackComment and dismissFeedbackComment. See the Accessibility labels table.
  • Styling: the sc-feedback* classes in the customizable build, plus the --sc-feedback-comment-text-color and --sc-feedback-comment-border-color custom properties.

The comment box takes focus as it opens, which is announced to screen readers, and its field and buttons are reachable through the widget's message navigation. See the accessibility reference for details.

Ratings and comments collected this way are readable through the feedback endpoint.

Message metadata

You can optionally show metadata for each message, such as meta analysis, token completion usage, and time to first token. All this information is optional and can be enabled/disabled using the following options:

const chat = new AIHubChat("aihub-chat", {
..., // Other options
showMetaAnalysisInfo: true, // Show meta analysis info on each message
showTokenUsageInfo: true, // Show token usage info on each message
showTimeToFirstTokenInfo: true, // Show time to first token info on each message
showExecutorTaskLogsInfo: true, // Show executor task logs info on each message
});

Localization

You can customize the text displayed in the chat widget by providing a locale object with the following properties:

  • uploadFileErrorMessage: The error message for file uploads.
  • uploadFilesErrorMessage: The error message for multiple file uploads.
  • chatErrorMessage: The error message for chat messages.
  • headerTitle: The title of the chat header.
  • finalizedMessage: The message displayed when the chat is finalized.
  • chooseAnOptionMessage: The message displayed when choosing an option.
  • limitExceededMessage: The message displayed when the character limit is exceeded.
  • waitUntilMessage: The message displayed when waiting for a response.
  • remainingMessage: The message displayed when there are remaining characters.
  • termsAndConditionsMessage: The terms and conditions message.
  • acceptCommunicationsMessage: The accept communications message.
  • inputPlaceholder: The placeholder text for the chat input.
  • metaAnalysisTitle: The title for the meta analysis card on each message.
  • completionUsageTitle: The title for the token completion usage card on each message.
  • newChatBtnMessage: The message for the "New chat" button in the header. (Only visible when storeConversation is true)
  • timeToFirstTokenTitle: The title for the time to first token tooltip.
  • executorTaskLogsTitle: The title for the executor task logs card on each message.
  • millisecondsUnit: The unit for milliseconds.
  • exceededMaxNumberOfFilesMessage: The message displayed when the user tries to upload more than the allowed number of files.
  • exceededMaxFileStatusChecksMessage: Message to display when files are taking longer than expected to upload.
  • chatInitConversationErrorMessage: The error message to display when there is an error initializing the conversation.
  • closedConversationMessage: The message displayed when the conversation has been closed by the server.
  • loadingPreviousChatMessage: The message displayed while loading a previous conversation. Default is "Loading previous chat...".
  • endOfChatHistoryMessage: The message displayed at the boundary between previous conversations and the current one. Default is "End of chat history".
  • loadPreviousChatButtonMessage: The label for the button that loads a previous conversation. Default is "Load previous chat".
  • newChatSeparatorMessage: The text displayed on the separator between conversations when a new chat is started. Default is "New Chat".
  • microphonePermissionDeniedMessage: The message displayed when the browser denies access to the microphone. Default is "Microphone permission denied".
  • recordingLabel: The status text displayed while an audio message is being recorded. Default is "Recording".
  • recordingPausedLabel: The status text displayed while an audio recording is paused. Default is "Recording paused".
  • uploadingAudioLabel: The status text displayed while a recorded audio message is being uploaded. Default is "Uploading audio".
  • recordingErrorLabel: The message displayed when an audio recording cannot be processed. Default is "There was an error trying to process the audio recording".
  • connectButtonLabel: The label of the button on a connection card. Default is "Connect".
  • connectionRequiredSingularMessage: The message displayed when one connector must be authorized before the agent can continue. Default is "To finish processing your request, please sign in to the following service:".
  • connectionRequiredPluralMessage: The same message when several connectors must be authorized. Default is "To finish processing your request, please sign in to the following services:".
  • disclaimerSeeMoreLabel: The label of the toggle that expands a truncated disclaimer. Default is "See more".
  • disclaimerSeeLessLabel: The label of the toggle that collapses an expanded disclaimer. Default is "See less".
  • sourcesTitle: The title of the sources view. Default is "Sources".
  • viewSourcesLabel: The label of the pill that opens the sources view. The number of sources is appended in parentheses. Default is "View sources".
  • citationPageLabel: The prefix used for the page number of a cited document. Default is "p.".
  • reasoningLabel: The header label of the reasoning box. Default is "Reasoning".
  • reasoningTooltip: The tooltip displayed next to the reasoning box header. Default is "The model's chain-of-thought while producing the answer.".
  • userChoice*: The labels of the user-choice card (state, question hints, free-text field, wizard buttons and step counter). See User choices for the full list and their defaults.
  • feedbackCommentPlaceholder: The placeholder of the comment box offered after a negative rating. Default is "What went wrong? (optional)".
  • feedbackCommentHint: The keyboard hint displayed under that comment box. Default is "Press Enter to send, Esc to skip".
  • feedbackCommentThanksLabel: The confirmation displayed once a feedback comment has been sent. Default is "Thanks for your feedback".
  • accessibility: An object containing all accessibility-related labels used by screen readers and assistive technology. See below for the full list of properties and their defaults.

Accessibility labels

The locale.accessibility object allows you to customize all ARIA labels and screen reader announcements used throughout the widget. All properties are optional and have sensible English defaults.

PropertyDefaultDescription
widgetLabel"Chat"Label for the chat widget region
messageListLabel"Chat messages"Label for the message list
audioMessageLabel"Audio message"Label for audio messages
assistantMessageLabel"Assistant message"Label for assistant messages
userMessageLabel"Your message"Label for user messages
minimizeChat"Minimize chat"Label for the minimize button
maximizeChat"Maximize chat"Label for the maximize button
startNewConversation"Start new conversation"Label for the new conversation button
expandChat"Expand chat"Label for the expand button
collapseChat"Collapse chat"Label for the collapse button
sendMessage"Send message"Label for the send button
attachFile"Attach file"Label for the file upload button
recordAudioMessage"Record audio message"Label for the audio record button
openChat"Open chat"Label for the FAB button
scrollToLatestMessages"Scroll to latest messages"Label for the scroll-to-bottom button
rateAsHelpful"Rate as helpful"Label for the thumbs-up feedback button
rateAsNotHelpful"Rate as not helpful"Label for the thumbs-down feedback button
feedbackCommentLabel"Tell us what went wrong (optional)"Label for the comment box offered after a negative rating
feedbackCommentOpened"Comment box opened. Press Enter to send, Escape to skip."Announced when that comment box opens and takes focus
sendFeedbackComment"Send comment"Label for the button that sends the feedback comment
dismissFeedbackComment"Skip comment"Label for the button that skips the feedback comment
dismissMessage"Dismiss message"Label for dismissing a message
stopGenerating"Stop generating"Label for the stop streaming button
loading"Loading"Label for loading indicators
recordingStarted"Recording audio. Press Escape to cancel."Announced when audio recording starts
recordingStopped"Audio recording complete."Announced when audio recording completes
recordingCancelled"Audio recording cancelled."Announced when audio recording is cancelled
cancelRecording"Cancel recording"Label for the cancel recording button
pauseRecording"Pause recording"Label for the pause recording button
resumeRecording"Recording paused. Resume recording"Label for the resume recording button
sendRecording"Send recording"Label for the send recording button
rateLimitCountdown"Rate limit countdown"Label for the rate limit countdown timer
conversationStartersLabel"Suggested conversation starters"Label for the conversation starters section
removeAttachment"Remove attachment"Label for the remove attachment button
attachedFiles"Attached files"Label for the attached files area
skipToMessageInput"Skip to message input"Label for the skip link
accessibilityMenuButton"Accessibility options"Label for the accessibility menu button
accessibilityMenu"Accessibility options menu"Label for the accessibility menu
highContrastMode"High contrast mode"Label for the high contrast mode toggle
viewSources"View sources for this message"Label for the "View sources" pill
backToMessages"Back to messages"Label for the button that leaves the sources view
citationReference"Citation source"Label for an inline citation mark
sourcesListLabel"Sources used in this message"Label for the sources list
openSourceInNewTab"Open source in a new tab"Label for a source card that links out
toggleReasoning"Toggle reasoning"Label for the reasoning box, which acts as a toggle
userChoiceCardLabel"Question from the assistant"Label for the user-choice card
userChoiceRequestedAnnouncement"The assistant is asking {count} question(s)."Announced when a user-choice card appears. Supports a {count} placeholder
userChoiceAnsweredAnnouncement"Your answers were sent."Announced once the answers have been sent
userChoiceQuestionProgress"Question {current} of {total}"Label for the wizard progress indicator. Supports {current} and {total} placeholders
const chat = new AIHubChat("aihub-chat", {
..., // Other options
locale: {
uploadFileErrorMessage: "Error uploading file",
uploadFilesErrorMessage: "Error uploading files",
chatErrorMessage: "Error sending message",
headerTitle: "Chat with us",
finalizedMessage: "Chat finalized",
chooseAnOptionMessage: "Choose an option",
limitExceededMessage: "Character limit exceeded",
waitUntilMessage: "Please wait until we respond",
remainingMessage: " characters remaining",
termsAndConditionsMessage: "By using this chat, you agree to our terms",
acceptCommunicationsMessage: "I agree to receive communications",
inputPlaceholder: "Write your message here...",
newChatBtnMessage: "New Chat",
timeToFirstTokenTitle: "Time to first token",
executorTaskLogsTitle: "Executor Task Logs",
millisecondsUnit: "ms",
exceededMaxNumberOfFilesMessage: "You can only upload up to 5 files per message",
exceededMaxFileStatusChecksMessage: "It seems that the file upload is taking longer than expected. Please try again later.",
chatInitConversationErrorMessage: "Error initializing conversation",
closedConversationMessage: "This conversation has ended.",
loadingPreviousChatMessage: "Loading previous chat...",
endOfChatHistoryMessage: "End of chat history",
loadPreviousChatButtonMessage: "Load previous chat",
newChatSeparatorMessage: "New Chat",
accessibility: {
widgetLabel: "Chat",
messageListLabel: "Chat messages",
audioMessageLabel: "Audio message",
assistantMessageLabel: "Assistant message",
userMessageLabel: "Your message",
minimizeChat: "Minimize chat",
maximizeChat: "Maximize chat",
startNewConversation: "Start new conversation",
expandChat: "Expand chat",
collapseChat: "Collapse chat",
sendMessage: "Send message",
attachFile: "Attach file",
recordAudioMessage: "Record audio message",
openChat: "Open chat",
scrollToLatestMessages: "Scroll to latest messages",
rateAsHelpful: "Rate as helpful",
rateAsNotHelpful: "Rate as not helpful",
dismissMessage: "Dismiss message",
stopGenerating: "Stop generating",
loading: "Loading",
recordingStarted: "Recording audio. Press Escape to cancel.",
recordingStopped: "Audio recording complete.",
recordingCancelled: "Audio recording cancelled.",
cancelRecording: "Cancel recording",
pauseRecording: "Pause recording",
resumeRecording: "Recording paused. Resume recording",
sendRecording: "Send recording",
rateLimitCountdown: "Rate limit countdown",
conversationStartersLabel: "Suggested conversation starters",
removeAttachment: "Remove attachment",
attachedFiles: "Attached files",
skipToMessageInput: "Skip to message input",
accessibilityMenuButton: "Accessibility options",
accessibilityMenu: "Accessibility options menu",
highContrastMode: "High contrast mode",
},
},
});

Theme

You can customize the look and feel of the chat widget by providing a theme object with the following properties:

The theme object is optional. If not provided, the default theme will be used.

const chat = new AIHubChat("aihub-chat", {
...
theme: {
header: {
bgColor: "<Header background color>",
textColor: "<Text color for the header>",
resetChatBtn: {
bgColor: "<Reset chat button background color>",
hoverBgColor: "<Hover background color for reset chat button>",
textColor: "<Reset chat button text color>",
},
minimizeBtn: {
iconStrokeColor: "<Line Stroke color for the icon in the minimize button>"
},
expandBtn: {
iconStrokeColor: "<Line Stroke color for the icon in the expand button>"
},
},
fabButton: {
bgColor: "<Floating action button background color>",
iconStrokeColor: "<Line Stroke color for the icon in the fab button>",
buttonSize: "<Floating action button size>", // Optional. Default is 50
iconSize: "<Floating action button icon size>", // Optional. Default is 25
},
sendButton: {
bgColor: "<Send button background color>",
iconStrokeColor: "<Line Stroke color for the icon in the send button>"
},
attachments: {
borderColor: "<Border color for attachments container>",
},
engagementMessage: {
bgColor: "<Background color for the engagement message>",
textColor: "<Text color for the engagement message>",
},
uploadFileBtn: {
iconStrokeColor: "<Line Stroke color for the icon in the upload file button>"
},
citations: {
highlightBgColor: "<Background color of the inline highlighted cited span>",
highlightTextColor: "<Text color of the inline highlighted cited span>",
cardBgColor: "<Background color of the citation hover card and the source cards>",
cardTextColor: "<Text color of the citation hover card and the source cards>",
},
messageBubble: {
user: {
bgColor: "<Background color for user message bubble>",
textColor: "<Text color for user message bubble>",
},
assistant: {
bgColor: "<Background color for assistant message bubble>",
textColor: "<Text color for assistant message bubble>",
connections: {
connectionCard: {
bgColor: "<Background color for the connection card>",
textColor: "<Text color for the connection card>",
connectButton: {
bgColor: "<Background color for the Connect button>",
textColor: "<Text color for the Connect button>",
},
},
},
userChoice: {
card: {
bgColor: "<Background color for the user choice card>", // Optional. Falls back to the assistant bubble
textColor: "<Text color for the user choice card>", // Optional. Falls back to the assistant bubble
borderColor: "<Border color for the user choice card>",
pendingAccentColor: "<Left border color while the questions are unanswered>",
answeredAccentColor: "<Left border color once the questions are answered>",
optionSelectedBgColor: "<Background color for a selected option>",
optionSelectedBorderColor: "<Border color for a selected option>",
progressColor: "<Stroke color for the filled part of the progress ring>",
submitButton: {
bgColor: "<Background color for the Submit button>",
textColor: "<Text color for the Submit button>",
},
navButton: {
bgColor: "<Background color for the Back / Next buttons>",
textColor: "<Text color for the Back / Next buttons>",
},
},
},
},
},
conversationStarters: {
bgColor: "<Background color for conversation starters>",
textColor: "<Text color for conversation starters>",
containerBgColor: "<Background color for conversation starters container>",
initialMessageBgColor: "<Background color for initial message in conversation starters>",
initialMessageTextColor: "<Text color for initial message in conversation starters>",
},
scrollToBottomIndicator: {
bgColor: "<Background color for scroll to bottom indicator>",
iconStrokeColor: "<Line Stroke color for the icon in the scroll to bottom indicator>"
}
}
})

CSS Styling Considerations

Default build (Shadow DOM)

The default chat widget uses Shadow DOM, which fully isolates the widget's styles from your page. No special CSS precautions are needed — your page styles will not affect the widget, and the widget's styles will not leak into your page.

If you need to adjust the widget's appearance, use the theme configuration object.

Customizable build (no Shadow DOM)

If you are using the customizable build, the widget renders without Shadow DOM and exposes stable sc-* semantic CSS classes for overriding styles. See the Customizable Build section for full setup and usage details.

Page CSS can affect the widget

Because the customizable build does not use Shadow DOM, overly broad CSS selectors on your page can unintentionally break the widget. Keep these guidelines in mind:

  • Avoid generic selectors like *, div, button, input, p — scope them to your own containers instead (e.g., .my-page-content button)
  • Don't use !important in global styles — the widget's baseline CSS does not use !important, so generic !important rules will override it unexpectedly
  • Watch for box-sizing resets — the widget relies on box-sizing: border-box
  • Be mindful of z-index — very high z-index values in your styles might cover the widget's floating elements
  • Test the widget after making global CSS changes to ensure it still renders correctly

Loading an existing conversation

You can load an existing conversation by using the conversationId option like this:

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
conversationId: "<Conversation ID>", // <-- Required
});

Extracting the page content

We have a content extractor that can be used to extract the content from the current page and send it to the agent.

This way, you can easily improve the agent response by providing more context about the user's current page.

To use the content extractor, use the extractPageContent flag like this:

<script>
document.addEventListener("DOMContentLoaded", function () {
const chat = new AIHubChat("aihub-chat", {
apiKey: "API_KEY",
agentCode: "AGENT_CODE",
baseURL: "https://api.serenitystar.ai/api",
extractPageContent: true,
});
chat.init();
});
</script>

Input parameters

You can optionally provide input parameters to the agent to send additional context with the conversation.

Use inputParameters when the values are static and use getInputParameters when the values need to be resolved at runtime.

If you provide both options, getInputParameters takes precedence over inputParameters.

Static input parameters

Use inputParameters when you want to send the same object during conversation setup and with each message.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
inputParameters: {
name: "John Doe",
email: "[email protected]",
},
});

Static input parameters are sent to the agent when the conversation starts and with each message.

Dynamic input parameters

Use getInputParameters when you want the widget to read the latest values from your page or application state before sending data to the agent.

The callback runs when the conversation is created and again before each message is sent. It can return the object directly or return a Promise that resolves to the object.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
getInputParameters: () => ({
currentPath: window.location.pathname,
language: navigator.language,
}),
});

Use this callback when the values may change between messages, such as the current route, a selected account, or a session value.

Agent Response Callbacks

You can capture each response from the agent by providing an onAgentResponse callback function. This allows you to process or use the agent's response data in your application.

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
onAgentResponse: (response) => {
console.log("Agent response:", response);
// Process the response data as needed
}
});

The response object contains detailed information about the agent's response, including:

  • content: The text content of the agent's response
  • json_content: Any JSON content that might be returned (null if not applicable)
  • completion_usage: Token usage statistics for the response
    • completion_tokens: Number of tokens used for the completion
    • prompt_tokens: Number of tokens used for the prompt
    • total_tokens: Total number of tokens used
  • action_results: Results of any actions performed by the agent
  • meta_analysis: Any meta analysis data (null if not applicable)
  • time_to_first_token: Time in milliseconds until the first token was received
  • instance_id: Unique identifier for the response instance
  • executor_task_logs: Detailed logs of the execution tasks, including:
    • description: Description of the task
    • duration: Duration of the task in milliseconds
    • success: Boolean indicating if the task was successful
  • pending_actions: Actions the end user must resolve before the agent can continue. Each entry is discriminated by a type field — for example, type: "connection" for a connector the user has to authorize. See Connections.

Example response object:

{
"content": "To create a contact form in HTML, you'll need to use the <form> element along with various input fields. Here's a basic example:\n\n```html\n<form action=\"submit_form.php\" method=\"POST\">\n <div>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n </div>\n \n <div>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n </div>\n \n <div>\n <label for=\"message\">Message:</label>\n <textarea id=\"message\" name=\"message\" rows=\"5\" required></textarea>\n </div>\n \n <button type=\"submit\">Submit</button>\n</form>\n```\n\nYou can style this form using CSS and add JavaScript for validation. Remember to implement proper server-side processing for the form submission.",
"json_content": null,
"completion_usage": {
"completion_tokens": 182,
"prompt_tokens": 125,
"total_tokens": 307
},
"action_results": {},
"meta_analysis": null,
"time_to_first_token": 1520,
"instance_id": "675e9321-4b76-c3e8-f5a2-9c41b7d32e45",
"executor_task_logs": [
{
"description": "Loading configuration",
"duration": 18,
"success": true
},
{
"description": "Validating configuration",
"duration": 7,
"success": true
},
{
"description": "Calculating remaining token budget",
"duration": 45,
"success": true
},
{
"description": "Appending context",
"duration": 39,
"success": true
},
{
"description": "Getting AI response",
"duration": 2458,
"success": true
},
{
"description": "Calculating prices",
"duration": 3,
"success": true
},
{
"description": "Updating chat",
"duration": 10,
"success": true
}
]
}

This callback is particularly useful for applications that need to:

  • Track and analyze agent responses
  • Extract specific data from responses
  • Trigger additional actions based on agent responses
  • Log conversation metrics

Engagement message

By default, the chat widget will display an engagement message (popup) after a certain amount of time.

This message is intended to engage the user and encourage them to start a conversation with the agent.

You can customize the engagement message by providing an engagementMessage object with the following properties:

  • enabled: Boolean that indicates whether the engagement message should be displayed. Optional. Default is true.
  • message: The message to display in the engagement message. Optional. It will show the initial message from the agent if not provided.
  • showAfter: The time in seconds after which the engagement message should be displayed. Optional. Default is 10 seconds.
const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
engagementMessage: {
enabled: true,
message: "Hello, how can I help you?",
showAfter: 10, // seconds
},
});

If the message is not provided, the initial message from the agent will be displayed instead.

Error Handling

The chat widget handles errors gracefully and displays appropriate messages to users when something goes wrong. Depending on the type of error, the widget will show a message in the chat, display a countdown timer, or disable the input.

Error scenarios

ScenarioWhat the user seesChat input behavior
Rate limit exceededA countdown timer indicating when messages can be sent again.Disabled until the countdown expires.
Conversation closedAn informational message indicating the conversation has ended.Permanently disabled.
Quota or balance exceededA generic error message.Re-enabled for retry.
Validation errorsThe specific error details returned by the server.Re-enabled for retry.
Network or unexpected errorsA generic error message.Re-enabled for retry.
Streaming errorsThe server-provided error message, or a generic fallback.Re-enabled for retry.

Error display styles

Errors are displayed differently depending on their nature:

  • Error messages appear as chat bubbles with error styling (e.g., quota exceeded, network failures, streaming errors).
  • Info messages appear with informational styling (e.g., conversation closed).
  • Countdown timer replaces the chat input area when a rate limit is hit, showing a timer until the user can send messages again.

Customizing error messages

You can customize the error messages shown to users through the locale configuration:

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
locale: {
chatErrorMessage: "Something went wrong. Please try again.",
closedConversationMessage: "This conversation has ended.",
limitExceededMessage: "You've reached the message limit.",
waitUntilMessage: "Please wait until",
},
});
PropertyDescription
chatErrorMessageGeneric fallback message for most error types. Defaults to "Unexpected error" if not set.
closedConversationMessageMessage shown when the conversation has been closed by the server.
limitExceededMessageText displayed alongside the countdown timer when a rate limit is hit.
waitUntilMessageText displayed alongside the countdown timer to indicate when the user can retry.
info

If no custom error messages are configured through locale, the widget will fall back to "Unexpected error" as the default message.

Usage examples

Basic usage

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
});
chat.init();

Read-only mode

const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
readOnly: true,
messages: [
{
sender: "bot",
createdAt: "2024-04-26T12:00:00Z",
type: "text",
value: "Hello, how can I help you?",
},
{
sender: "user",
createdAt: "2024-04-26T12:01:00Z",
type: "text",
value: "Hello, I have a question about cooking recipes",
},
{
sender: "bot",
createdAt: "2024-04-26T12:02:00Z",
type: "text",
value: "Sure, what would you like to know?",
},
],
});

Customizable Build (Advanced CSS Overrides)

The default chat widget uses Shadow DOM to isolate its styles from your page. This is ideal for most use cases, but it prevents you from overriding the widget's internal CSS.

For clients who need full control over the widget's appearance, we provide an alternative customizable build that renders without Shadow DOM and ships styles as a separate CSS file. This allows you to override any style using standard CSS selectors.

Important

The customizable build intentionally exposes the widget's internals to your page CSS. This means global styles on your page can affect the widget. Use this build only when you need deep CSS customization and are prepared to manage potential style conflicts.

Setup

Replace the default resources with the customizable ones:

<!-- 1. Widget CSS (load first) -->
<link
rel="stylesheet"
href="https://hub.serenitystar.ai/resources/chat-customizable.css"
/>

<!-- 2. Your CSS overrides (load after the widget CSS) -->
<link rel="stylesheet" href="your-overrides.css" />

<!-- 3. Widget container -->
<div id="aihub-chat"></div>

<!-- 4. Widget JS -->
<script src="https://hub.serenitystar.ai/resources/chat-customizable.js"></script>

<!-- 5. Initialize (same API as the default build) -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const chat = new AIHubChat("aihub-chat", {
apiKey: "<Your API key>",
agentCode: "<Your Agent Code>",
baseURL: "https://api.serenitystar.ai/api",
});
chat.init();
});
</script>

How it works

  • The widget renders in the light DOM (no Shadow DOM), so your CSS rules can reach its internal elements.
  • The widget's own stylesheet uses all: initial (without !important) on the root element, giving it a clean baseline.
  • Because !important is not used, any CSS you load after the widget's stylesheet wins naturally via the cascade — no need for !important on your side.

Overriding styles with semantic classes

Every meaningful element in the widget has a stable semantic CSS class prefixed with sc- (serenity chat). These classes carry no styles by themselves — they exist purely as hooks for your overrides.

/* your-overrides.css */

/* Change header appearance */
.sc-header {
background: #1a1a2e;
border-radius: 0;
}

/* Style user message bubbles */
.sc-message-bubble--user {
background: #1a73e8;
color: white;
border-radius: 16px 16px 4px 16px;
}

/* Style agent message bubbles */
.sc-message-bubble--agent {
background: white;
border: 1px solid #e0e0e0;
border-radius: 16px 16px 16px 4px;
}

/* Customize the send button */
.sc-footer-send-btn {
background: #1a73e8;
border-radius: 50%;
}

/* Customize the floating action button */
.sc-fab-btn {
width: 64px;
height: 64px;
background: #1a73e8;
}

Naming convention

PatternExampleMeaning
sc-{component}sc-header, sc-footerComponent root
sc-{component}-{element}sc-header-title, sc-footer-textareaChild element
sc-{component}--{variant}sc-message-bubble--userVariant/modifier

Default build vs. Customizable build

FeatureDefault buildCustomizable build
Files neededchat.js onlychat-customizable.js + chat-customizable.css
Shadow DOMYes (styles fully isolated)No (styles exposed to page CSS)
CSS overridesOnly via theme config optionsFull CSS control via sc-* classes
Risk of style conflictsNoneClient accepts this trade-off
Configuration APIIdenticalIdentical
Semantic class stability

The sc-* classes are treated as a public API. They follow semantic versioning — class names will not change in minor or patch releases.

For a complete list of all available semantic classes, see the Appendix: Semantic CSS Classes Reference at the end of this page.


Accessibility

The Chat Widget is designed to be fully accessible and compliant with WCAG 2.1 AA and UNE-EN 301549 standards. It includes semantic HTML structure, comprehensive ARIA attribute support, full keyboard navigation, visual accessibility features, and a built-in high contrast mode.

All accessibility labels used by screen readers and assistive technology can be customized through the locale.accessibility configuration.

For a detailed breakdown of all accessibility measures, implementation details, and the full WCAG 2.1 AA compliance matrix, see the Chat Widget — Accessibility page.


Appendix: Semantic CSS Classes Reference

Below is the complete list of semantic CSS classes available in the customizable build, grouped by component. Use these classes in your override stylesheet to target specific elements.

Layout

Chat (root wrapper)

ClassElement
sc-rootOutermost wrapper
sc-wrapperPositioned container
sc-wrapper-innerAnimated inner wrapper
sc-chatMain chat card
sc-fab-areaFAB + engagement message container
ClassElement
sc-headerHeader root
sc-header--side-panelSide-panel header variant
sc-header-actionsTop buttons row (side-panel mode)
sc-header-contentLogo + title area (side-panel mode)
sc-header-logoLogo image
sc-header-titleTitle text
sc-header-toggle-btnMinimize / expand button
sc-header-expand-btnExpand to side-panel button
sc-header-reset-btnNew chat / reset button

Body

ClassElement
sc-bodyScrollable outer area
sc-body-innerInner content wrapper
sc-messagesMessage list container
sc-prev-chats-loadingLoading indicator for previous chats
sc-messages-load-prev-btn"Load previous chat" button
sc-messages-typing-indicatorTyping animation wrapper
sc-skill-waiting-messageSkill waiting message inside the typing indicator
sc-messages-countdownRate-limit countdown wrapper
ClassElement
sc-footerFooter root container
sc-footer-input-wrapperInput area wrapper (textarea + buttons)
sc-footer-textareaText input
sc-footer-mic-btnMicrophone / audio recording button
sc-footer-upload-btnFile upload button
sc-footer-file-inputHidden file input
sc-footer-send-btnSend message button
sc-footer-stop-btnStop streaming button
sc-footer-finalizedEnd-of-conversation message
sc-footer-attachmentsAttachments scroll area
sc-footer-upload-errorFile validation error
sc-footer-disclaimerDisclaimer wrapper
sc-footer-disclaimer-textDisclaimer text
sc-footer-disclaimer-toggle"See more" / "See less" toggle

Messages

MessageBox

ClassElement
sc-message-wrapperMessage row wrapper
sc-message-wrapper--agentAgent message row
sc-message-wrapper--userUser message row
sc-message-bubbleMessage bubble
sc-message-bubble--agentAgent bubble variant
sc-message-bubble--userUser bubble variant
sc-message-bubble--errorError message variant
sc-message-bubble--infoInfo message variant
sc-message-textMessage text content
sc-message-audioAudio message player
sc-message-timeMessage timestamp

Message Metadata

ClassElement
sc-message-metadataMetadata row
sc-message-metadata-timeTimestamp
sc-message-metadata-iconsAction icons row
sc-message-metadata-analysisMeta analysis button
sc-message-metadata-usageToken usage button
sc-message-metadata-logsExecutor logs button
sc-message-metadata-ttftTime-to-first-token indicator

Reasoning Box

ClassElement
sc-reasoning-boxReasoning card root (also the toggle)
sc-reasoning-box__headerHeader row (chevron, icon, label, tooltip, spinner)
sc-reasoning-box__spinnerSpinner shown while the reasoning streams
sc-reasoning-box__bodyCollapsible body
sc-reasoning-box__contentReasoning content

Citations

ClassElement
sc-citationInline citation mark and its highlighted span
sc-citation-popoverHover card anchored to a citation mark
sc-citation-popover-sourceSingle source row inside the hover card

Sources

ClassElement
sc-message-view-sources"View sources" pill in the message metadata row
sc-sources-viewSources view root (replaces the message list)
sc-sources-back-barBack bar above the list
sc-sources-back-btn"Back to messages" button
sc-sources-titleSources view title
sc-sources-listScrollable list of source cards
sc-sources-cardSingle source card
sc-sources-card--fileFile source variant
sc-sources-card--websiteWebsite source variant
sc-sources-card--websearchWeb search source variant

Message Pending Actions

ClassElement
sc-pending-actionsActions container
sc-pending-actionSingle action card
sc-pending-action-iconConnector icon
sc-pending-action-nameConnector name
sc-pending-action-connect-btnConnect button

User Choice Card

ClassElement
sc-user-choice-cardCard root
sc-user-choice-card--pendingUnanswered variant
sc-user-choice-card--submittingVariant shown while the answers are being sent
sc-user-choice-card--answeredAnswered variant
sc-user-choice-card--skippedVariant of a card superseded by a reply
sc-user-choice-card__headerHeader row (chevron, icon, label, question count, state chip)
sc-user-choice-card__countQuestion count in the header
sc-user-choice-card__stateState chip (icon + label)
sc-user-choice-card__spinnerSpinner shown while the answers are being sent
sc-user-choice-card__bodyWizard body (current question)
sc-user-choice-card__optionSingle option row
sc-user-choice-card__option--selectedSelected option row
sc-user-choice-card__otherFree-text "Other" field
sc-user-choice-card__actionsFooter row with the wizard buttons
sc-user-choice-card__back"Back" button
sc-user-choice-card__next"Next" button
sc-user-choice-card__submit"Submit" button
sc-user-choice-card__reviewReview list of an answered card
sc-user-choice-card__footerExecution metrics row

Message Skills

ClassElement
sc-message-skillsSkills container
sc-message-skillIndividual skill

Inline Attachments

ClassElement
sc-inline-attachmentsAttachments list
sc-inline-attachmentSingle attachment
sc-inline-attachment-iconFile icon
sc-inline-attachment-infoFile name + details
sc-inline-attachment-sizeFile size

Feedback

ClassElement
sc-feedbackFeedback container
sc-feedback-thumbs-upThumbs-up button
sc-feedback-thumbs-downThumbs-down button
sc-feedback-dimmedThe thumb that is not the current rating, dimmed
sc-feedback-commentComment box shown after a negative rating
sc-feedback-comment-inputComment textarea
sc-feedback-comment-sendSend comment button
sc-feedback-comment-dismissSkip comment button
sc-feedback-comment-footerRow holding the hint and the character counter
sc-feedback-comment-hintKeyboard hint under the comment box
sc-feedback-comment-counterRemaining characters counter
sc-feedback-comment-thanksConfirmation shown once the comment is sent

The comment box also reads two custom properties, so its colours can be adjusted without restyling each element: --sc-feedback-comment-text-color (default #1f2937) and --sc-feedback-comment-border-color (default rgba(0, 0, 0, 0.12)).

Interactive Components

Floating Action Button (FAB)

ClassElement
sc-fab-btnFloating action button
sc-fab-btn--photoFAB variant rendering a custom image instead of the built-in icon

Conversation Starters

ClassElement
sc-startersRoot container
sc-starters-welcomeWelcome message container
sc-starters-welcome-logoWelcome logo
sc-starters-welcome-textWelcome text
sc-starters-buttonsButtons wrapper
sc-starters-btnIndividual starter button

Engagement Message

ClassElement
sc-engagementPopup wrapper
sc-engagement-cardMessage card
sc-engagement-closeClose button
sc-engagement-textMessage text
sc-engagement-arrowArrow / triangle pointer

Scroll To Bottom Indicator

ClassElement
sc-scroll-indicatorOuter container
sc-scroll-indicator-btnClickable button

Accessibility Controls

ClassElement
sc-accessibility-menu-containerAccessibility menu wrapper in the header
sc-accessibility-buttonButton that opens the accessibility menu
sc-accessibility-menuAccessibility menu dropdown
sc-skip-link"Skip to message input" link at the top of the body

The message input also carries the sc-chat-input ID (not a class), which is the target of the skip link. Style it with the sc-footer-textarea class instead.

Audio Recorder

ClassElement
sc-audio-recorderRoot container
sc-audio-recorder-innerInner container
sc-audio-recorder-errorPermission error container
sc-audio-recorder-error-textError text
sc-audio-recorder-statusRecording status message
sc-audio-recorder-actionsButtons container
sc-audio-recorder-cancel-btnCancel button
sc-audio-recorder-pause-btnPause / resume button
sc-audio-recorder-send-btnSend recording button
sc-audio-recorder-uploadingUploading indicator

File Attachments

ClassElement
sc-attachmentsAttachments list
sc-attachmentSingle attachment
sc-attachment--errorAttachment with error
sc-attachment--processedSuccessfully processed attachment
sc-attachment-iconFile icon container
sc-attachment-infoFile name + details
sc-attachment-tagsTags wrapper
sc-attachment-sizeFile size tag
sc-attachment-error-tagError tag
sc-attachment-actionsRemove / action buttons
sc-attachments-max-error"Max files exceeded" error
sc-attachments-status-errorFile status check error

Pre-conditions

Terms and Conditions

ClassElement
sc-termsContainer
sc-terms-labelCheckbox label wrapper
sc-terms-checkboxCheckbox input
sc-terms-textTerms text

Accept Communications

ClassElement
sc-accept-commsContainer
sc-accept-comms-labelCheckbox label wrapper
sc-accept-comms-checkboxCheckbox input
sc-accept-comms-textCommunications text

Pre-conditions Wrapper

ClassElement
sc-preconditionsRoot container
sc-preconditions-termsTerms section wrapper
sc-preconditions-commsCommunications section wrapper

Utilities

Countdown Timer

ClassElement
sc-countdownTimer container
sc-countdown-titleLimit exceeded message
sc-countdown-subtitle"Wait until" message
sc-countdown-timeTime remaining display

Previous Chats

ClassElement
sc-prev-chatsRoot container
sc-prev-chatSingle previous chat group
sc-prev-chat-separatorSeparator between chats
sc-prev-chat-separator-lineSeparator line
sc-prev-chat-separator-textSeparator text
sc-prev-chats-end-separatorEnd-of-history separator

Realtime Voice

ClassElement
sc-realtimeRoot container
sc-realtime-avatarAgent avatar image
sc-realtime-greetingGreeting heading
sc-realtime-wavesurfWaveform wrapper
sc-realtime-errorError message
sc-realtime-actionsAction buttons container
sc-realtime-start-btnStart conversation button
sc-realtime-loadingLoading indicator
sc-realtime-mute-btnMute / unmute button
sc-realtime-stop-btnStop conversation button

Loading Indicators

ClassElement
sc-dotsAnimated dots container
sc-dotIndividual dot
sc-three-dotsThree-dots typing wrapper
sc-spinnerSpinner container
sc-spinner-rollerSpinner roller element

Audio Player

ClassElement
sc-audio-playerAudio element