Skip to main content
Instance methods enable your code to interact with or modify an active chat instance at runtime. They provide a way to programmatically control the behavior and state of the embedded chat component after it initializes based on user actions, application logic, or external events.

List of instance methods

The following tables list all supported instance methods, grouped by category.

Message

Message methods allow you to manage and interact with the chat’s message flow in real time. These methods provide functionality for sending messages, navigating through the message list, and updating message history.
Method nameDescription
sendSends the specified message to the agent.
doAutoScrollScrolls to the most recent message in the list.
scrollToMessageScrolls the messages list to a specific message.
updateMessageStateUserDefinedUpdates a user_defined property in message state for persistence across thread reloads.
loadThreadByIdLoads an existing conversation thread by its ID.
restartConversationRestarts the conversation with the agent.

User interface

User interface methods allow you to control and customize the visual and interactive aspects of the chat component at runtime. These methods help adapt the chat experience to different contexts and user preferences.
Method nameDescription
changeViewChanges the current view state of the chat widget.
updateCustomHeaderItemsUpdates the display of custom menu items that appear in the chat header.
getCustomHeaderItemsRetrieves the current set of custom header items that are configured in the chat widget.
updateLocaleUpdates the display language of the chat.

Chat widget state

Chat widget state methods allow you to retrieve and modify the overall state and configuration of the chat widget.
Method nameDescription
getLocaleReturns the current locale that the chat widget uses.

Security and identity

Security and identity methods help maintain secure communication and manage the lifecycle of the chat instance. These methods ensure that user authentication and session integrity are preserved during runtime.
Method nameDescription
updateAuthTokenReplaces the current JWT with a new one for continued secure communication.
destroyDestroys the web chat and removes it from the page.

Events

Event methods allow you to subscribe to, manage, and remove event listeners for the chat instance. These methods enable developers to respond to user actions or system events dynamically.
Method nameDescription
onSubscribes to a type of event.
onceSubscribes a handler function to an event so it runs only once when that event occurs.
offRemoves a subscription to an event type.

Details of instance methods

The following sections detail the supported instance methods.

send()

Sends a message to the agent. Supports both visible messages and silent messages that are hidden from the chat UI. Syntax
await instance.send(message);
await instance.send(message, options);
Parameters
ParameterTypeRequiredDescription
messagestringobjectYesThe message to send. Can be a simple string or a MessageRequest object.
optionsobjectNoOptional settings. Use { silent: true } to hide the message from the UI.
Returns Promise<void> - Resolves when the message is sent. Example
// Send a visible message
await instance.send('Hello, how can you help me?');

// Send a silent message (hidden from UI)
await instance.send('User clicked help button', { silent: true });
silent option Silent messages are sent to the agent but remain hidden from the chat UI. Use the silent option when you need to trigger agent responses without cluttering the conversation. When you send a silent message, the following sequence occurs:
  1. Message is sent to the agent for processing.
  2. Message is hidden from the chat UI.
  3. Agent can respond (response is visible to user).
  4. User only sees the agent’s response, not the trigger.
The following example triggers a welcome message:
await instance.send('', { silent: true });
The following example sends a feedback notification:
async function handleFeedback(rating, comment) {
    const message = `User feedback: ${rating} stars - ${comment}`;
    await instance.send(message, { silent: true });
}
The following example tracks a user action:
await instance.send('User viewed pricing page', { silent: true });
Advanced implementation with MessageRequest with additional_properties For more control, use a MessageRequest object to specify message properties including visibility, thread targeting, and custom context. MessageRequest structure
{
  message: {
    role: 'user',
    content: 'Your message text',
    additional_properties: {
      display_properties: {
        skip_render: boolean  // true = hidden, false = visible
      }
    }
  },
  context: {                  // Optional: custom context data
    key: 'value'
  }
}
The following example sends a silent message with MessageRequest:
await instance.send({
    message: {
        role: 'user',
        content: 'User clicked help button',
        additional_properties: {
            display_properties: {
                skip_render: true  // Hide from UI
            }
        }
    }
});
The following example sends a message with Thread and Context:
await instance.send({
    message: {
        role: 'user',
        content: 'I need help with my order'
    },
    context: {
        user_location: 'checkout_page',
        cart_value: 150.00
    }
});
Override behavior When you use both a MessageRequest object and the { silent: true } option, the { silent: true } option takes precedence:
// The { silent: true } option overrides skip_render in the message
await instance.send({
    message: {
        role: 'user',
        content: 'Message',
        additional_properties: {
            display_properties: { skip_render: false }  // Will be overridden!
        }
    }
}, { silent: true });  // Final result: message is hidden
Priority rules for override behavior:
  1. { silent: true } option It always hides the message.
  2. skip_render: true in message
    It hides the message.
  3. skip_render: false in message It shows the message.
  4. No option or property It shows the message. This is the default.
When to use each approach:
  • Use { silent: true } for simple cases and quick overrides.
  • Use additional_properties.display_properties.skip_render when building MessageRequest objects.
  • Combine both when you need to override message object settings at runtime.

doAutoScroll()

Automatically scrolls the chat view to the latest message in the conversation. Syntax
instance.doAutoScroll();
Parameters None. Returns void Example
// Scroll to the latest message after receiving a response
instance.on('receive', (event, instance) => {
    instance.doAutoScroll();
});

scrollToMessage()

Scrolls the chat view to a specific message by its ID. Syntax
instance.scrollToMessage(messageId);
Parameters
ParameterTypeRequiredDescription
messageIdstringYesThe unique identifier of the message to scroll to.
Returns void Example
// Scroll to a specific message
const messageId = 'msg-12345';
instance.scrollToMessage(messageId);

updateMessageStateUserDefined()

Updates the user_defined field inside a specific message’s state. This provides a mechanism to store custom state on a message that will be reloaded if a thread is reloaded from history. The new data merges with existing data. When a thread loads, this data can be found on the message at message.message_state.content[<contentId>].user_defined. Syntax
instance.updateMessageStateUserDefined(messageId, contentId, data);
Parameters
ParameterTypeRequiredDescription
messageIdstringYesThe ID of the message to update.
contentIdstringYesThe ID of the message content that must store the user_defined data.
dataunknownYesThe user-defined value to merge into the message state.
Returns void Example
// Store custom metadata on a message
instance.updateMessageStateUserDefined(
    'msg-12345',
    'content-1',
    { customField: 'value', timestamp: Date.now() }
);

// Access the data when thread is reloaded
instance.on('pre:threadLoaded', (event, instance) => {
    event.messages.forEach((message) => {
        const userData = message.message_state?.content?.['content-1']?.user_defined;
        console.log('Custom data:', userData);
    });
});

loadThreadById()

Loads an existing conversation thread by its ID, allowing users to resume a previous conversation. This method enables applications to restore chat history and continue from where the user previously left off. Syntax
instance.loadThreadById(threadId);
Parameters
ParameterTypeRequiredDescription
threadIdstringNoThe ID of the conversation thread to load. If not provided, a new thread is created when the user sends a message.
Returns void Behavior
  • With threadId: The chat loads the conversation history associated with the specified thread ID.
  • Without threadId: A new conversation thread is created when the user sends their first message.
  • Invalid threadId: If an invalid or deleted thread ID is provided, a console error is logged. The chat continues to function normally, allowing the user to proceed with the current conversation.
Getting the Thread ID Thread IDs are available through the chat event system. You can capture them from the send or pre:send events, for example:
// Capture thread ID from send event
instance.on('send', (event) => {
    const threadId = event.message.thread_id;
    console.log('Current thread:', threadId);
});

restartConversation()

Restarts the current chat conversation, resetting all related state and starting a new conversation thread. Syntax
await instance.restartConversation();
Parameters None. Returns Promise<void> - A promise that resolves when the conversation has been restarted. Example
// Restart the conversation
await instance.restartConversation();

// Restart and send a new message
instance.on('restartConversation', async (event, instance) => {
    await instance.send('Starting fresh conversation');
});

changeView()

Updates the chat UI view by showing or hiding the launcher, main window, or both. Use this method to programmatically control the visibility of chat components. Syntax
await instance.changeView(viewUpdate);
Parameters
ParameterTypeRequiredDescription
viewUpdatestringobjectYesEither a string key (‘launcher’ or ‘mainWindow’) or an object with optional launcher or mainWindow, or both boolean properties to control their visibility.
Returns Promise<void> - A promise that resolves when the view is updated. Example
// Show the main window and hide the launcher
await instance.changeView({ launcher: false, mainWindow: true });

// Toggle just the launcher
await instance.changeView({ launcher: true });

// Use string shorthand to show main window
await instance.changeView('mainWindow');

updateCustomHeaderItems()

Updates the display of custom items that appear in the chat header. It provides a container for custom items which can be either menu items or dropdown menus. Each item is configurable with a custom callback function that is called when a user selects an item. The method accepts an array of objects that define the properties of each custom header item. You can call this method anytime to change the current set of options. Syntax
instance.updateCustomHeaderItems(items);
Parameters
ParameterTypeRequiredDescription
itemsarrayYesAn array of custom header items to display. Each item is an object that defines the properties of the header element.
Item Properties
PropertyTypeRequiredDescription
idstringYesA unique identifier for the header item.
textstringYesThe text to display for the header item.
alignstringNoThe alignment of the header item. Possible values: left (default) or right.
iconstringNoA URL or data URI to an icon image to display alongside the text.
typestringYesThe type of header item. Possible values: menu-item or dropdown. Default: menu-item.
onClickfunctionConditionalA callback function called when the button is clicked. Required for menu-item type.
itemsarrayConditionalAn array of submenu items for the dropdown. Required for dropdown type.
Dropdown: Specific properties
PropertyTypeRequiredScopeDescription
showSelectedAsTitlebooleanNofloat and custom form layoutWhen set to true, the detail view title automatically updates to show the text of the currently selected dropdown item. When set to false (default), the detail view title displays the dropdown’s main label.
Dropdown: Submenu item properties
PropertyTypeRequiredScopeDescription
idstringYes-A unique identifier for the submenu item.
textstringYes-The text to display for the submenu item.
alignstringNo-The alignment of the submenu item. Possible values: left (default) or right.
iconstringNo-A URL or data URI to an icon image to display alongside the submenu item text.
onClickfunctionYes-A callback function called when the submenu item is clicked.
Returns void Design Considerations
  • Form factor support: Chat header customization is available for all form factors: fullscreen-overlay, float, and custom.
  • Side panel visibility: The side panel appears when at least one of the following features is enabled:
  • Responsive behavior: Custom header items dynamically migrate between the header and side panel based on container width:
    Viewport widthBreakpointItems in headerItems in side panel
    < 320pxxs0All items
    320px - 671pxsm1Remaining items
    672px - 1055pxmd2Remaining items
    ≥ 1056pxlg/xl/max3 (maximum)Remaining items
    The chat component uses a responsive layout with an optional side panel for rendering the chat thread list and custom header content. When implementing a custom layout, ensure the chat container height is at least 510px. In narrower viewports, the side panel collapses and the threads list container may not be rendered as expected.
    Example scenarios:
    • 5 custom items at 1200px width: 3 in header, 2 in side panel
    • 5 custom items at 800px width: 2 in header, 3 in side panel
    • 5 custom items at 400px width: 1 in header, 4 in side panel
    • 5 custom items at 280px width: 0 in header, 5 in side panel
  • Float form factor: All custom items always appear in the side panel because Float has a fixed width of 380px.
  • Dropdown behavior: Dropdown items behave differently based on form layout:
    Form layoutDropdown behaviorNavigation pattern
    FloatDetail view (slide-in)Back button + Toggle button
    CustomDetail view (slide-in)Back button + Toggle button
    FullscreenInline expansionExpand and collapse in place
  • Ordering: Items render in the UI in the same order they are defined in the configuration array.
  • Navigation structure: Supports a maximum two-level hierarchy (main menu and one submenu level). Deeper nesting is not supported.
Example: Menu item
instance.updateCustomHeaderItems([
    {
        id: 'help-button',
        text: 'Help',
        align: 'right',
        icon: 'https://example.com/help-icon.svg',
        type: 'menu-item',
        onClick: () => {
            window.open('https://example.com/help', '_blank');
        }
    }
]);
Example: Dropdown menu
instance.updateCustomHeaderItems([
    {
        id: 'settings-menu',
        text: 'Settings',
        align: 'right',
        icon: 'https://example.com/settings-icon.svg',
        type: 'dropdown',
        items: [
            {
                id: 'profile',
                text: 'Profile',
                align: 'left',
                icon: 'https://example.com/profile-icon.svg',
                onClick: () => console.log('Profile clicked')
            },
            {
                id: 'preferences',
                text: 'Preferences',
                align: 'left',
                icon: 'https://example.com/preferences-icon.svg',
                onClick: () => console.log('Preferences clicked')
            }
        ]
    }
]);
Example: Dropdown with dynamic title for float and custom form layout This example demonstrates the showSelectedAsTitle property, which updates the detail view title to display the selected item’s text. In the example, when a user selects “Spanish”, the detail view title automatically updates from “Language” to “Spanish”.
instance.updateCustomHeaderItems([
    {
        id: 'language',
        type: 'dropdown',
        text: 'Language',
        showSelectedAsTitle: true,  // Title updates to show selected language
        items: [
            {
                id: 'en',
                text: 'English',
                onClick: () => {
                    console.log('English selected');
                }
            },
            {
                id: 'es',
                text: 'Spanish',
                onClick: () => {
                    console.log('Spanish selected');
                }
            },
            {
                id: 'fr',
                text: 'French',
                onClick: () => {
                    console.log('French selected');
                }
            }
        ]
    }
]);
Example: Language Selector with Dynamic Updates
const ICONS = {
    globe: 'data:image/svg+xml;base64,' + btoa('<svg>...</svg>')
};

const updateMenuItems = (locale) => {
    const languageNames = {
        en: 'English',
        es: 'Español',
        fr: 'Français'
    };
    
    instance.updateCustomHeaderItems([
        {
            id: 'language-selector',
            text: languageNames[locale],
            type: 'dropdown',
            icon: ICONS.globe,
            items: [
                {
                    id: 'lang-en',
                    text: 'English',
                    onClick: () => {
                        instance.updateLocale('en');
                        updateMenuItems('en');
                    }
                },
                {
                    id: 'lang-es',
                    text: 'Español',
                    onClick: () => {
                        instance.updateLocale('es');
                        updateMenuItems('es');
                    }
                },
                {
                    id: 'lang-fr',
                    text: 'Français',
                    onClick: () => {
                        instance.updateLocale('fr');
                        updateMenuItems('fr');
                    }
                }
            ]
        }
    ]);
};

// Initialize with English
updateMenuItems('en');

getCustomHeaderItems()

Retrieves the current set of custom header items that are configured in the chat widget. You can use the output to access and inspect the custom menu items that are currently displayed in the chat header. Syntax
const items = instance.getCustomHeaderItems();
Parameters None. Returns CustomHeaderItem[] - An array of custom header item objects currently configured in the chat. Example
// Get current custom header items
const currentItems = instance.getCustomHeaderItems();
console.log('Current header items:', currentItems);

// Check if a specific item exists
const hasHelpButton = currentItems.some(item => item.id === 'help-button');

updateLocale()

Changes the display language of the chat widget at runtime. By default, the locale is English (en). Syntax
await instance.updateLocale(locale);
Parameters
ParameterTypeRequiredDescription
localestringYesA locale string representing the desired language and region.
Supported Locales
LanguageLanguage Code
Chinese - Simplifiedzh-CN
Chinese - Traditionalzh-TW
Englishen
Frenchfr
Germande
Italianit
Japaneseja
Koreanko
Portuguese - Brazilpt-BR
Spanishes
Returns Promise<void> - A promise that resolves when the locale is updated. Example
// Change to Japanese
await instance.updateLocale('ja');

// Change to Brazilian Portuguese
await instance.updateLocale('pt-BR');

// Change based on user selection
const userLanguage = getUserPreferredLanguage();
await instance.updateLocale(userLanguage);

getLocale()

Returns the language code for the current language that the chat widget uses, for example, en for English or fr for French. If a region is specified, it returns the language and region codes, for example, pt-BR for Brazilian Portuguese. Syntax
const locale = instance.getLocale();
Parameters None. Returns string - The current locale code (e.g., en, fr, pt-BR). Example
// Get current locale
const currentLocale = instance.getLocale();
console.log('Current locale:', currentLocale);

// Use locale to customize behavior
if (instance.getLocale() === 'ja') {
    console.log('Japanese locale is active');
}

updateAuthToken()

Replaces the current JWT authentication token with a new one for continued secure communication. This is commonly used when tokens expire or are refreshed during a session. Syntax
instance.updateAuthToken(token);
Parameters
ParameterTypeRequiredDescription
tokenstringYesThe new JWT token to be set in the chat configuration.
Returns void Example
// Update token when it expires
instance.on('authTokenNeeded', async (event, instance) => {
    const newToken = await fetchNewAuthToken();
    instance.updateAuthToken(newToken);
});

// Proactively refresh token
const refreshToken = async () => {
    const newToken = await getRefreshedToken();
    instance.updateAuthToken(newToken);
};

destroy()

Destroys the web chat instance and removes it from the page, cleaning up all associated resources and event listeners. Syntax
instance.destroy();
Parameters None. Returns void Example
// Destroy chat when user logs out
const handleLogout = () => {
    instance.destroy();
    // Perform other logout actions
};

// Destroy chat when navigating away
window.addEventListener('beforeunload', () => {
    instance.destroy();
});

on()

Subscribes a handler function to a specific chat event. The handler will be called every time the event occurs. Syntax
instance.on(eventName, handler);
Parameters
ParameterTypeRequiredDescription
eventNamestringYesThe name of the event to subscribe to (e.g., 'send', 'receive', 'chat:ready').
handlerfunctionYesThe callback function to run when the event occurs. Receives (event, instance) as parameters.
Returns void Common Events
  • chat:ready
    Fired when the chat is fully loaded and ready.
  • pre:send
    Fired before a message is sent.
  • send
    Fired after a message is sent.
  • pre:receive
    Fired before a message is received.
  • receive
    Fired after a message is received.
  • feedback
    Fired when user provides feedback.
  • userDefinedResponse
    Fired for custom response types.
  • pre:restartConversation
    Fired before conversation restart.
  • restartConversation
    Fired after conversation restart.
  • pre:threadLoaded
    Fired before a thread is loaded from history.
  • authTokenNeeded
    Fired when authentication token needs refresh.
  • view:pre:change
    Fired before view state changes.
  • view:change
    Fired after view state changes.
For more details about events, see Event categories. Example
// Subscribe to send event
instance.on('send', (event, instance) => {
    console.log('Message sent:', event);
});

// Subscribe to receive event
instance.on('receive', (event, instance) => {
    console.log('Message received:', event);
});

// Subscribe to chat ready event
instance.on('chat:ready', (event, instance) => {
    console.log('Chat is ready');
    instance.send('Hello!');
});

once()

Subscribes a handler function to an event so it runs only once when that event occurs. After the event fires, the handler is automatically removed and is not called again. Syntax
instance.once(eventName, handler);
Parameters
ParameterTypeRequiredDescription
eventNamestringYesThe name of the event to subscribe to.
handlerfunctionYesThe callback function to run once when the event occurs. Receives (event, instance) as parameters.
Returns void Example
// Run handler only on first send
instance.once('pre:send', (event, instance) => {
    console.log('First message being sent');
    event.message.message.content = event.message.message.content.toUpperCase();
});

// Initialize something once when chat is ready
instance.once('chat:ready', (event, instance) => {
    console.log('Performing one-time initialization');
    instance.updateCustomHeaderItems([/* ... */]);
});

off()

Removes a subscription to an event type. The specified handler will no longer be called when the event occurs. Syntax
instance.off(eventName, handler);
Parameters
ParameterTypeRequiredDescription
eventNamestringYesThe name of the event to unsubscribe from.
handlerfunctionYesThe specific callback function to remove. Must be the same function reference that was used with on() or once().
Returns void Example
// Define a handler
function receiveHandler(event, instance) {
    console.log('Message received:', event);
}

// Subscribe to event
instance.on('receive', receiveHandler);

// Later, unsubscribe from event
instance.off('receive', receiveHandler);

// Unsubscribe after first use
instance.on('receive', (event, instance) => {
    console.log('Received:', event);
    instance.off('receive', receiveHandler);
});

Complete example

The following example demonstrates how to configure events and instance methods in the embedded web chat:
<script>
    function preSendHandler(event, instance) {
        console.log('pre:send event', event);
        if (event?.message?.message?.content) {
            event.message.message.content = event.message.message.content.toUpperCase();
        }
    }

    function sendHandler(event, instance) {
        console.log('send event', event);
    }

    async function feedbackHandler(event, instance) {
        console.log('feedback', event);
        
        // Send silent message for negative feedback
        if (!event.is_positive) {
            const feedbackComment = event.comment ? `: ${event.comment}` : '';
            const systemMessage = `User submitted negative feedback${feedbackComment}`;
            await instance.send(systemMessage, { silent: true });
        }
    }

    function preReceiveHandler(event, instance) {
        console.log('pre-receive event', event);
        event?.message?.content?.map((element) => {
            if (element?.text?.includes('assistant')) {
                element.text = element.text.replace('assistant', 'Agent');
            }
            element.type = 'user_defined';
        });

        const lastItem = event?.message?.content?.[event.message?.content.length - 1];
        if (lastItem) {
            lastItem.message_options = {
                feedback: {
                    is_on: true,
                    show_positive_details: false,
                    show_negative_details: true,
                    positive_options: {
                        categories: ['Funny', 'Helpful', 'Correct'],
                        disclaimer: "Provide content that can be shared publicly.",
                    },
                    negative_options: {
                        categories: ['Inaccurate', 'Incomplete', 'Too long', 'Irrelevant', 'Other'],
                        disclaimer: "Provide content that can be shared publicly.",
                    },
                },
            };
        }
    }

    function receiveHandler(event, instance) {
        console.log('received event', event);
        instance.off('pre:receive', preReceiveHandler);
    }

    function userDefinedResponseHandler(event, instance) {
        console.log('userDefinedResponse event', event);
        event.hostElement.innerHTML = `
            <cds-code-snippet>
                node -v Lorem ipsum dolor sit amet, consectetur adipisicing elit.
            </cds-code-snippet>
            <br><br>
            <div style="background-color:orange;color:white;padding:10px;">
                <p>${event.contentItem?.text || '[No message content]'}</p>
            </div>`;
    }

    function preRestartConversationHandler(event, instance) {
        console.log('pre:restartConversation event', event);
    }

    let calledRestartConversation = false;
    function restartConversationHandler(event, instance) {
        console.log('restartConversationHandler event', event);
        if (!calledRestartConversation) {
            setTimeout(() => {
                instance.send('Hello from embedded web chat second time');
            }, 3000);
            calledRestartConversation = true;
        }
    }

    function preThreadLoadedHandler(event, instance) {
        console.log('pre:threadLoaded event', event);
        event?.messages.forEach((message) => {
            if (message?.sender === 'response') {
                const [lastItem] = message.content;
                lastItem.message_options = {
                    feedback: {
                        is_on: true,
                    },
                };
                message.message_state = {
                    content: {
                        1: {
                            feedback: {
                                text: "",
                                is_positive: true,
                                selected_categories: []
                            }
                        }
                    }
                };
            }
        });
    }

    async function authTokenNeededHandler(event, instance) {
        console.log('authTokenNeeded event', event);
        event.authToken = "<Refreshed Token>";
    }

    async function viewPreChange(event, instance) {
        console.log('viewPreChange event', event);
        document.body.classList.add('chat-view-changing');
    }

    async function viewChange(event, instance) {
        console.log('viewChange event', event);
        console.log('View changed from', event.oldViewState, 'to', event.newViewState);
    }

    function onChatLoad(instance) {
        instance.on('chat:ready', (event, instance) => {
            console.log('chat:ready', event);
            
            // Add custom header items
            const ICONS = {
                user: 'data:image/svg+xml;base64,' + btoa('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16,4a5,5,0,1,1-5,5,5,5,0,0,1,5-5m0-2a7,7,0,1,0,7,7A7,7,0,0,0,16,2Z"/><path d="M26,30H24V25a5,5,0,0,0-5-5H13a5,5,0,0,0-5,5v5H6V25a7,7,0,0,1,7-7h6a7,7,0,0,1,7,7Z"/></svg>'),
                globe: 'data:image/svg+xml;base64,' + btoa('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16,2A14,14,0,1,0,30,16,14.0158,14.0158,0,0,0,16,2Zm12,13H23.9131a22.9,22.9,0,0,0-2.2-8.7168A12.0732,12.0732,0,0,1,28,15Z"/></svg>')
            };
            
            instance.updateCustomHeaderItems([
                {
                    id: 'language-selector',
                    text: 'English',
                    type: 'dropdown',
                    icon: ICONS.globe,
                    items: [
                        {
                            id: 'lang-en',
                            text: 'English',
                            onClick: () => instance.updateLocale('en')
                        },
                        {
                            id: 'lang-es',
                            text: 'Español',
                            onClick: () => instance.updateLocale('es')
                        }
                    ]
                },
                {
                    id: 'help-button',
                    text: 'Help',
                    type: 'menu-item',
                    align: 'right',
                    icon: ICONS.user,
                    onClick: () => window.open('https://example.com/help', '_blank')
                }
            ]);
        });
        
        instance.once('pre:send', preSendHandler);
        instance.on('send', sendHandler);
        instance.once('pre:receive', preReceiveHandler);
        instance.on('receive', receiveHandler);
        instance.on('feedback', feedbackHandler);
        instance.on('userDefinedResponse', userDefinedResponseHandler);
        instance.on('pre:restartConversation', preRestartConversationHandler);
        instance.on('restartConversation', restartConversationHandler);
        instance.on('pre:threadLoaded', preThreadLoadedHandler);
        instance.on('authTokenNeeded', authTokenNeededHandler);
        instance.on('view:pre:change', viewPreChange);
        instance.on('view:change', viewChange);
    }

    window.wxOConfiguration = {
        orchestrationID: '<tenantId>',
        hostURL: 'http://localhost:3000',
        showLauncher: true,
        rootElementID: 'root',
        chatOptions: {
            agentId: '<agentId>',
            agentEnvironmentId: '<agentEnvironmentId>',
            onLoad: onChatLoad,
        },
    };

    setTimeout(function () {
        const script = document.createElement('script');
        script.src = `${window.wxOConfiguration.hostURL}/wxoLoader.js?embed=true`;
        script.addEventListener('load', function () {
            wxoLoader.init();
        });
        document.head.appendChild(script);
    }, 0);
</script>