Skip to main contentCarbon AI chat

Using AI chat with React

The React component for AI chat contains helpful facades to the underlying class for ease of use.

Contents

Overview
Installation

Using ChatContainer

Config object changes

Using ChatCustomElement

Accessing instance methods

User-defined responses

Overview

AI chat shows two React components that act as a facade in front of the core AI chat.

If you want to use the float layout, use ChatContainer. Use the ChatCustomElement for custom sizes, such as a sidebar, full screen, or nested in your UI.

The React components provide helpful alternative ways to manage user_defined responses, writeable elements, and the AI chat rendering cycle. For more information, see Using the API to fully understand the references.

Currently, this component does not support SSR, so if you are using Next.js or similar frameworks, make sure you render this component in client only modes.

For more information, see the examples page for more examples.

Installation

Install by using npm:

npm install @carbon/ai-chat

Or using yarn:

yarn add @carbon/ai-chat

Basic example

Render this component in your application and provide the configuration options for the AI chat as a prop. Refer to the following example.

import React from 'react';
import { ChatContainer } from '@carbon/ai-chat';

const chatOptions = {
  // Your configuration object.
};

function App() {
  return <ChatContainer config={chatOptions} />;
}

Using ChatContainer

The ChatContainer is a functional component that loads and renders an instance of the AI chat when it mounts, and deletes the instance when unmounted. If the configuration for the AI chat changes, it also deletes the previous AI chat and creates a new one with the new configuration. It can also manage React portals for user-defined responses.

Note: This component calls the instance.render() method for you. You do not need to call it yourself. You can use the onBeforeRender or onAfterRender properties to run operations before or after render calls. Refer to the following table.

Props

ChatContainer has the following props.

Attribute Required Type Description
config Yes object The options object for configuring the chat.
onBeforeRender No function This callback function calls after the AI chat loads but before calling the render function. It passes a single argument, the loaded instance of the AI chat. To use the available instance methods, you can use this function to obtain a reference to the AI chat
onAfterRender No function This callback function calls after the AI chat loads but before calling the render function. It passes a single argument, the loaded instance of the AI chat. To use the available instance methods, you can use this function to obtain a reference to the AI chat instance.
renderUserDefinedResponse No function This function is a callback that the container calls to render user-defined responses. When you provide this prop, the container listens for user-defined response events from the AI chat and generates a React portal for each event. The function calls once during the component render for each user-defined response event.
This function takes two arguments:
  • First argument: A RenderUserDefinedState interface that triggered the user-defined response. This property includes partialItems during a stream (if required), a messageItem when the stream for that single item is complete, and a fullMessage when the entire message resolves (in cases where multiple items are streaming within the same message).
  • Second argument: A convenience argument, the instance of the AI chat. The function returns a ReactNode that renders the user-defined content for the response. |
    | renderWriteableElement | No | Object | This takes an Object with writeable element key names and a ReactNode as the value. The ReactNode is rendered into the writeable element slot. |

Config object changes

The chat is not capable of handling in-place changes to the config object. If any of the properties in the config object are changed, then the existing chat will be discarded and a whole new chat will be created using the new config properties. A deep equal comparison of the config object is done to detect changes. Note that DOM elements and functions are compared using a strict === comparison.

Of note, the above means that if you are creating a new callback function for something like customSendMessage on each render of your component, this would cause the chat to be discarded and recreated each time your component is rendered.

Below is an example of bad code where a new customSendMessage function causes the chat to get recreated each time App is rendered.

function App() {
  // This is bad because it creates a new customSendMessage function every time App is rendered which will cause the
  // ChatContainer to create a new chat every time.
  const customSendMessage = (message: MessageRequest) => {
    console.log('Sending message', message);
  };

  const config: PublicConfig = { messaging: { customSendMessage } };

  return <ChatContainer config={config} />;
}

The quickest solution to this is to move the config object outside of your render function.

const customSendMessage = (message: MessageRequest) => {
  console.log('Sending message', message);
};

const config: PublicConfig = { messaging: { customSendMessage } };

function App() {
  return <ChatContainer config={config} />;
}

However this does not work if your customSendMessage function requires access to state or props from your application. If you need that, you can use useCallback to ensure only a single instance is created from the dependencies. Note in this example that if the instance of the messageService prop changes, the whole chat will get recreated. If you don't expect that to happen, this will work for you. Otherwise, you may need to use a ref.

function App({ messageService }: any) {
  // Creates a new customSendMessage for each instance of messageService.
  const customSendMessage = useCallback(
    (message: MessageRequest) => {
      messageService(message);
    },
    [messageService], // If messageService changes, the chat will be recreated.
  );

  const config: PublicConfig = { messaging: { customSendMessage } };

  return <ChatContainer config={config} />;
}

If you have dependencies that shouldn't recreate the chat, you can store them in a ref. In this example, the chat will not be recreated.

function App({ messageService }: any) {
  const messageServiceRef = useRef(messageService);
  messageServiceRef.current = messageService;

  const customSendMessage = useCallback((message: MessageRequest) => {
    messageServiceRef.current(message);
  }, []);

  const config: PublicConfig = { messaging: { customSendMessage } };

  return <ChatContainer config={config} />;
}

Using ChatCustomElement

This library provides the ChatCustomElement component, which can be used to render the AI chat inside a custom element. Use it you want to change the location where the AI chat renders. This component renders an element in your React app and uses that element as the custom element for rendering the AI chat.

This component's default behavior adds and removes a class from the main window of the AI chat. It also applies the same behavior to your custom element to manage the visibility of the AI chat when it opens or closes. When the AI chat closes, it adds a classname to the AI chat main window to hide the element. Your custom element receives another classname to set its width and height to 0, so that it doesn't take up space.

Note: The use case where you are using a custom element but also by using the AI chat's native launcher, the custom element remains visible as it also contains the launcher.

If you don't want these behaviors, provide your own onViewChange prop to ChatCustomElement and provide your logic for controlling the visibility of the AI chat. If you want custom animations when the AI chat opens and closeds, it is the mechanism to do that. Refer to the following example.

import React from 'react';
import { ChatCustomElement } from '@carbon/ai-chat';

import './App.css';

const chatOptions = { /* AI chat options */ };

function App() {
  return <ChatCustomElement className="MyCustomElement" config={chatOptions} />;
}
.MyCustomElement {
  position: absolute;
  left: 100px;
  top: 100px;
  width: 500px;
  height: 500px;
}

ChatCustomElement inherits all of the props from ChatContainer. Refer to the following table for optional props.

Attribute Type Description
className string An optional classname that adds to the custom element.
id string An optional id that adds to the custom element.
onViewChange function A custom element controlling the visiblity of the AI chat's main window requires an optional listener for "view:change" events. If you do not provide a callback, the default one employs certain classnames to manage the AI chat and the custom element. You can provide a different callback if you want custom behavior, such as an animation when the main window opens or closes.

Note: Provide this function before you load the AI chat. The event handler does not update after the AI chat loads.

Accessing instance methods

You can use the onBeforeRender or onAfterRender props to access the AI chat’s instance if you need to call instance methods later. This example renders a button that toggles the AI chat open and only renders after the instance becomes available. Refer to the following example.

import React, { useCallback, useState } from 'react';
import { ChatContainer } from '@carbon/ai-chat';

const chatOptions = {
  // Your configuration object.
};

function App() {
  const [instance, setInstance] = useState(null);

  const toggleWebChat = useCallback(() => {
    instance.toggleOpen();
  }, [instance]);

  function onBeforeRender(instance) {
    // Make the instance available to the React components.
    setInstance(instance);
  }

  return (
    <>
      {instance && (
        <button type="button" onClick={toggleWebChat}>
          Toggle AI chat
        </button>
      )}
      <ChatContainer config={chatOptions} onBeforeRender={onBeforeRender} />
    </>
  );
}

User-defined responses

This component can also manage user-defined responses. You must pass a renderUserDefinedResponse function as a render prop. This function returns a React component that renders content for the specific message that relates to that response. Keep the ReactChatContainer component mounted during your application’s lifecycle, as it loses all user-defined responses that the AI chat previously received.

Treat the renderUserDefinedResponse prop like any typical React render prop; it is different from the userDefinedResponse event or a typical event handler. The event fires only once when the AI chat initially receives the response from the server. The renderUserDefinedResponse prop calls every time the App rerenders, returning an up-to-date React component for the provided message item, just as the render function does for a typical React component. Refer to the following example.

import React from 'react';
import { ChatContainer } from '@carbon/ai-chat';

const chatOptions = {
  // Your configuration object.
};

function App() {
  return <ChatContainer renderUserDefinedResponse={renderUserDefinedResponse} config={chatOptions} />;
}

function renderUserDefinedResponse(state, instance) {
  const { messageItem } = state;
  // The event here contains details for each user defined response that needs to be rendered.
  // You can also pass information from your components props or state into the component your are returning.
  if (messageItem) {
    switch (messageItem.user_defined?.user_defined_type) {
      case 'chart':
        return (
          <div className="padding">
            <Chart content={messageItem.user_defined.chart_data as string} />
          </div>
        );
      case 'green':
        return <UserDefinedResponseExample text={messageItem.user_defined.text as string} />;
      default:
        return undefined;
    }
    // We are just going to show a skeleton state here if we are waiting for a stream, but you can instead have another
    // switch statement here that does something more specific depending on the component.
    return <AISkeletonPlaceholder className="fullSkeleton" />;
  }
  return <ChatContainer renderUserDefinedResponse={renderUserDefinedResponse} config={chatOptions} />;
}

Testing with Jest

AI chat exports as an ES module and does not include a CJS build. Please refer to the Jest documentation for information about transforming ESM to CJS for Jest using babel-jest or ts-jest.

You may need to add configuration similar to the following to your Jest configuration.

"transform": {
  "\\.[jt]sx?$": "babel-jest",
},
{
  transformIgnorePatterns: [
    '/node_modules/(?!(@?lit.*|@carbon/.+)/)'
  ],
}