Conversational UI is a user interface that emulates a human to human conversation, while in reality, the human is interacting with a computer program. We’ve often seen this with the use of chatbots and voice assistants. Conversational UI that involves speaking allows you to interact with a voice assistant, while typing allows you to interact with a chatbot. They have emerged as a way to provide relevant information to consumers and service them much like they would have wanted if they were talking to a real human. We've seen chatbots used for taking lunch orders, scheduling meetings, making gift item recommendations, and much more.
Designing and building a conversational interface may not always be easy for some people. When building a conversational UI, you need to consider two things:
- The user interface design
- How to implement real-time messaging/communication
We want to build applications rapidly in today’s agile world. With the use of third-party components and services, we can enrich our application and increase development speed. In the case of a conversational UI, we can use a UI component already designed for this purpose, and a chat API for real-time communication.
In this tutorial, I'm going to show you how to use the conversational UI component from Telerik’s KendoReact UI library, and the Stream Chat SDK. Together, they can be used to build a text-based conversational app which you can further extend to meet your requirement.
Setting up the React Project
Since we're building a React application, we're going to start by creating a new React application using create-react-app and install the following packages:
@progress/kendo-react-conversational-ui
: contains the Conversational UI component@progress/kendo-react-buttons
: includes the styled buttons@progress/kendo-react-intl
: provides services for parsing and formatting of dates and numbers, and is needed by the components in@progress/kendo-react-conversational-ui
@progress/kendo-theme-default
: contains the styles that'll be applied to the components
Open your command-line application and run the commands below:
1yarn create-react-app conversational-ui && cd conversational-ui
Then
1yarn add @progress/kendo-react-conversational-ui @progress/kendo-react-buttons @progress/kendo-react-intl @progress/kendo-theme-default
This sets up the React application and installs the KendoReact packages needed for the app. These packages are part of the components included in Telerik's KendoReact library. KendoReact is a commercial UI library designed and built for developing business applications with React. Every UI component in the KendoReact suite has been built from the ground-up specifically for React, and they are distributed through yarn packages.
Adding the Chat Component
With the React project created, we're going to add the chat component. Open App.js
and import the KendoReact CSS Styles and Chat component.
12import "@progress/kendo-theme-default/dist/all.css"; import { Chat } from "@progress/kendo-react-conversational-ui";
The KendoReact style adds the default theme from Kendo, to make the components you use from their library have the same look and feel. The Chat
component will be used to present a conversational interface.
Next, we're going to change the component to a class component and render the Chat component as follows:
class App extends React.Component { render() { return ( <div className="App"> <h1>Conversational UI</h1> <Chat placeholder={"Type here..."} width={400}></Chat> </div> ); } }
This uses the Chat component from KendoReact, designed to display a conversation interface. The placeholder
prop contains the text that will be displayed as a placeholder in the input area of the chat UI.
We want to get the user's name the first time they open the chat. We would do this by displaying a message asking the user to enter a name and then save it. To do this, add a constructor with the content below:
constructor(props) { super(props); this.user = undefined; this.bot = { id: "0", name: "bot" }; this.state = { messages: [ { author: this.bot, timestamp: new Date(), text: "Hello! Please enter a name in order to start a chat" } ] }; }
In this code, we defined the following:
- a
user
variable which will be set when the user enters their name in the chatbox - a
bot
variable which will be used as the author of the first message asking the user to enter a name - a
messages
state which will hold the list of messages that will be displayed in the Chat component
We will then update the Chat component props to set some additional props. Update the code for the Chat component with the code below:
<Chat user={this.user} messages={this.state.messages} onMessageSend={this.addMessage} placeholder={"Type here..."} width={400} ></Chat>
This will configure the component to set the user through the user
prop, the messages to display through the messages
prop, and then listen for when the user sends a message through the onMessageSend
event prop.
Now add the addMessage
function referenced in the last code snippet to the class.
addMessage = ({ message }) => { if (!this.user) { this.user = { name: message.text, id: Date.now().toString() }; let newMessage = Object.assign({}, message); newMessage.text = `Welcome to the chat ${message.text}!`; newMessage.author = this.bot; this.setState({ messages: [...this.state.messages, newMessage] }); } else { //TODO: Send message across the channel } };
This function is used as the callback function for when the user sends a message. It receives a single argument, which is an object that contains details about the triggered event, including the message text and the author.
When addMessage
is called, it checks if this.user
is empty. If it is, it sets the user
variable to an object containing an id
and name
. We expect that the first message the user enters is the user’s name, according to the message prompt to enter their name. Because of that, we're taking the value of message.text
as the user's name.
We want to show a message when this happens. Therefore, we simply add one saying Welcome to the chat
followed by the name entered. We add this message to the list of existing chat messages through setState()
, which will update the state and then display the message to the user.
When the user is set, we want every new message to be sent across the messaging channel so that users on both ends can chat in real-time. We will do this with the Stream Chat JavaScript client.
Adding Stream Chat for Real-time Messaging
Stream Chat provides API and SDKs for building real-time chat solutions in less time. It provides one of the best (if not the best) chat API and infrastructure as a service. We're going to work with the Stream Chat JavaScript library and install it from yarn.
Open your command-line application to the project directory and run the following command:
1yarn add stream-chat
You'll need a security token to work with the chat library, and for that, you'll need to sign up for a Stream Chat account if you don't have an account, sign up and go to your dashboard. If you have an account, sign in and go to the dashboard. You will need to create an application from the dashboard. You can have different applications, with each of them having a unique app secret and key. We will create a new application for what we're building in this tutorial.
To do this, follow the instructions below:
- Click the Create App button at the upper right corner of the page.
- Enter an app name in the option for App Name. For our example, we'll use
conversational-ui
as the name. - Select your preferred server location.
- For the last option asking if it's the app is in production or development, select development and click Submit.
This should create the application and show it on the dashboard. You will see the generated application KEY and SECRET, which you'll be needing later.
Using the Stream Chat JavaScript Client
To use the chat client, we'll need to import the stream-chat
module. Open App.js and add the import statement below:
1import { StreamChat } from "stream-chat";
Add the code below to set up the chat client and listen for new messages:
initialiseChatClient = async () => { const response = await fetch("http://localhost:8080/v1/token", { method: "POST", mode: "cors", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: this.user.id, name: this.user.name }) }); const streamServerInfo = await response.json(); this.chatClient = new StreamChat(streamServerInfo.apiKey); this.chatClient.setUser(this.user, streamServerInfo.token); this.conversation = this.chatClient.channel("commerce", "conversational-ui"); await this.conversation.watch(); this.conversation.on("message.new", this.onNewMessage; };
This function is called to initialize and configure the chat client to listen for messages. You will need your API key to initialize the StreamChat
object, and a token to set the user of the chat client. These values will be generated on the server based on the user's name and a unique identifier. We use the Fetch API to request this information from the server (we will set up this token server later) and use it to initialize the chat client by calling this.chatClient = new StreamChat(streamServerInfo.apiKey)
.
Then we call this.chatClient.setUser(this.user, streamServerInfo.token)
to set the user, using the user
object which contains id and name properties. After the user is set, we need to establish a bi-directional communication channel in which messages will pass through. To do that, we call this.chatClient.channel()
, passing it two parameters: the channel type, and the channel identifier. Channel types are a way to enable specific features for a channel. There are some built-in channel types, and you can also create your own. The built-in channel types are:
- livestream: Sensible defaults in case you want to build chat like YouTube or Twitch.
- messaging: Configured for social apps such as WhatsApp or Facebook Messenger.
- gaming: Configured for in-game chat.
- commerce: Good defaults for building something like your own version of Intercom or Drift.
- team: If you want to build a custom version of Slack or something similar, start here.
In our example, we used commerce
as the channel type and set the channel identifier to conversational-ui
. The return value of calling that function is a Channel object which gets assigned to the conversation
variable. In order to receive events for that channel, we call this.conversation.watch()
and then subscribe to listen for new messages in the channel by calling this.conversation.on("message.new", this.onNewMessage)
, with onNewMessage
as the callback function.
Add the code below as the definition for the onNewMessage
function:
onNewMessage = event => { let message = { text: event.message.text, author: event.message.user, timestamp: event.message.created_at }; this.setState({ messages: [...this.state.messages, message] }); };
What this code does is build a message object from the information we get from the event object, then add that message to the messages array.
The initialiseChatClient()
function will be called when the user enters their name and the user object is already set. We will do this in the addMessage()
function. Go ahead and add this.initialiseChatClient()
statement inside the if block statement in the addMessage()
function.
Sending Messages With Stream Chat Client
We have code to initialize the Stream Chat client, set the user for the client, and display new messages when they arrive. Now we will add code to send messages across the channel. To send a message to the channel, you will have to call the sendMessage
function of the Channel object. We will call this function when the addMessage
function is called. Go to the addMessage
function and add the statement this.conversation.sendMessage({ text: message.text });
inside the else block.
If you’ve followed the instructions to add the code throughout the tutorial, your App.js should be similar to the code below:
import React from "react"; import "./App.css"; import "@progress/kendo-theme-default/dist/all.css"; import { Chat } from "@progress/kendo-react-conversational-ui"; import { StreamChat } from "stream-chat"; class App extends React.Component { constructor(props) { super(props); this.user = undefined; this.bot = { id: "0", name: "bot" }; this.state = { messages: [ { author: this.bot, timestamp: new Date(), text: "Hello! Please enter a name in order to start a chat" } ] }; } initialiseChatClient = async () => { const response = await fetch("http://localhost:8080/v1/token", { method: "POST", mode: "cors", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: this.user.id, name: this.user.name }) }); const streamServerInfo = await response.json(); this.chatClient = new StreamChat(streamServerInfo.apiKey); this.chatClient.setUser(this.user, streamServerInfo.token); this.conversation = this.chatClient.channel( "commerce", "conversational-ui" ); await this.conversation.watch(); this.conversation.on("message.new", this.onNewMessage); }; addMessage = ({ message }) => { if (!this.user) { this.user = { name: message.text, id: Date.now().toString() }; let newMessage = Object.assign({}, message); newMessage.text = `Welcome to the chat ${message.text}!`; newMessage.author = this.bot; this.setState({ messages: [...this.state.messages, newMessage] }); this.initialiseChatClient(); } else { this.conversation.sendMessage({ text: message.text }); } }; onNewMessage = event => { let message = { text: event.message.text, author: event.message.user, timestamp: event.message.created_at }; this.setState({ messages: [...this.state.messages, message] }); }; render() { return ( <div className="App"> <h1>Conversational UI</h1> <Chat user={this.user} messages={this.state.messages} onMessageSend={this.addMessage} placeholder={"Type here..."} width={400} ></Chat> </div> ); } } export default App;
Setting Up the Token Server
Earlier on, you added code to make an HTTP request to http://localhost:8080/v1/token
to get a token that'll be used to authenticate the Stream Chat client. We will not build that token server from scratch but instead, use an already built project for that. The project is on GitHub. You can clone it by running git clone https://github.com/pmbanugo/stream-chat-boilerplate-api.git
. When you’ve cloned it, follow the instructions in the README file to install the dependencies and configure it with your Stream Chat API key and secret.
Testing the Application
You'll need to start the Node and React application separately. Open your command-line application to the directories of both projects and run yarn start
. The React application should open in your default browser when the server starts. Follow the instructions on the screen to enter a user name and send messages.
Summary
The importance of Conversational UI cannot be understated; we frequently see it used in the e-commerce industry through AI-driven chatbots. In this tutorial, we built a Conversational UI using Stream Chat for real-time communication, and Telerik's KendoReact component library. Stream Chat provides the infrastructure to handle real-time messaging as well as client libraries that you can use to build a robust real-time messaging solution. To learn more about Stream Chat and everything that it has to offer, check out the docs.
You can find the code for this application on GitHub.