How to Build a Smart Chatbot Assistant with ChatEngine, React and IBM Watson Assistant

Chatbots, whether they be customer service agents or shopping assistants in an online store, have become commonplace amongst the way we interact with businesses. Intelligent bots and assistants serve to be the frontline for many businesses and now is a better time than any to build a smart approach to solving problems. 

You might be wondering if it’s possible to create an intelligent assistant of your own. The short answer is yes, and that’s exactly what we’ll walk through today, using ChatEngine and IBM Watson Assistant. Our bot will answer basic questions for people looking to learn more information about PubNub, but your bot can be trained and customized however you wish. We’ll show you how to do this too.

The full project GitHub repo is available here.

How It Works

Before we get into the code, let’s map out the logic in terms of how our user will interact with our assistant.

At first, the user asks a question via ChatEngine to send a message to out our chatbot assistant. The reason we have two ChatEngine connections is to make sure that our bot can handle multiple connections at once. In our specific case we’ll make our chatbot on the same client but in an ideal situation, it would be a good idea to have the chatbot running on a separate client.

Once the bot receives the message via ChatEngine it sends the message to PubNub Functions. The goal behind this is to reduce the amount of backend code we have and just focus on the things that matter — the experience. Functions is how we connect our messaging application to the IBM Watson Assistant service. 

In our BLOCKS Catalog, there is an IBM Assistant BLOCK which needs a bit of configuration, and once that’s done it will be connected to IBM Watson. The function will query Watson and get a reply back, which it will then send to our customer.

Getting Started with ChatEngine and Watson

Configuring ChatEngine

Building the chat interface and backend for a chatbot can be challenging and time-consuming. We’ll mitigate that using ChatEngine, our PubNub-powered framework that makes building cross-platform a breeze.

You’ll first have to configure a new ChatEngine application in PubNub. You can do this in our ChatEngine quickstart, and it will provide you your pub/sub keys as well as configuring your keyset for ChatEngine.

Watson + BLOCKS

Once we have ChatEngine configured, we can start building our assistant.

First, create a new Function within our ChatEngine Module. We can call this function ‘Assistant’, have an event of ‘On Request’ and make our channel name be ‘assistant’. After adding our new Function we can go ahead and add the code for it to connect to IBM Assistant.

This is actually a version of the code available in the IBM Assistant BLOCK but slightly changed so that we could fit it with our interaction model. In particular, if we look at lines 55 – 65 we’ll that the way we handle our response and the method we get data from our client has changed.

You’ll notice that in the Function code above that we’re using the vault to get access to certain keys available to our module. We’ll need to add three more keys to our vault which can be found when setting up Watson.

Watson Configuration

Head over to Watson to get your keys. If you haven’t already, feel free to set up a Watson instance on your Bluemix console. You’ll see that with your new Watson resource you’ll have a username and password. View those and insert them into the vault in the Assistant Function we just created. The next key we need is the workspaceID, we can go to the launch tool in the Bluemix console and create a workspace.

In here, we can either create a workspace from scratch or use a sample. For this example, let’s go with the customer service example. Once we’re in the workspace we can open the hamburger menu on the side and go to the improve tab. Near the top of the page, we should see a credentials button, and by clicking that we can get our workspaceID.

So, now that we have our keys, our vault should look something like this.

At this point, we have the cogs of our application working. We created the structure that will receive messages from our bot and send that data to our assistant and also have our assistant wired up so that those messages can get a meaningful response.

Let’s start developing our client and its interface.

Me, My Bot, and I

Looking at the client, there are two things that we’re going to make. The first being the input for where the user can submit their message and the second being the bot that can send that data to our function. Ideally, we’d separate these two components but for simplicity, we’ll have them both running on the same client.

To begin, let’s create a basic React project which we can do by using the create-react-app tool.

$ npx create-react-app Watson-Assistan

If your version of npm is less than 5.2 feel free to use npm instead of npx. The difference between the two is based on the scope of the installation and whether the command is a one-off or not. If you need more information read about it in npm’s blogpost.

The folder should now be set up so let’s navigate into it.

cd Watson-Assistant/

Great! Our project tree should look something like this.

|-- Watson-Assistant
    |-- .gitignore
    |-- README.md
    |-- package-lock.json
    |-- package.json
    |-- public
    |   |-- favicon.ico
    |   |-- index.html
    |   |-- manifest.json
    |-- src
        |-- App.css
        |-- App.js
        |-- App.test.js
        |-- index.css
        |-- index.js
        |-- logo.svg
        |-- registerServiceWorker.js

In here we’re going to take out some of the boilerplate that the create-react-app tool as graciously left for us.

We can take out logo.svg and registerServiceWorker.js. There’s no particular reason other than to clean up our project, but if you feel that you the need to look more into why the service worker file is there have a look at this issue on the master GitHub repo.

After cleaning up our directory our structure should look something like this.

|-- Watson-Assistant
    |-- .DS_Store
    |-- .eslintrc.yml
    |-- .gitignore
    |-- package-lock.json
    |-- package.json
    |-- public
    |   |-- favicon.ico
    |   |-- index.html
    |   |-- manifest.json
    |-- src
        |-- index.css
        |-- index.js

Now let’s look at the file where the majority of our app is going to sit: index.js.

We’ll start by looking at the lifecycle and what we’re going to render in our browser. First, ensure we have ChatEngine in our package.

$ npm i chat-engine

At this point, I’d also recommend checking out my package.json file on GitHub. Here you can follow along completely without breaking your code. I’ll try to make sure that I explain each package I use so that you get a better understanding of how everything fits in.

So now that we have the packages, let’s start coding.

To create our client we need to give our ChatEngine client the keys to the ChatEngine app we made earlier. Choosing our app on the PubNub Admin Dashboard then choosing our keyset we can get the publish and subscribe keys. We can then create our client object like this.

const ChatClient = ChatEngineCore.create({
    publishKey: 'pub-c-e1295433-4475-476d-9e37-4bdb84dacba0',
    subscribeKey: 'sub-c-890a0b26-6451-11e8-90b6-8e3ee2a92f04'
}, {
    globalChannel: 'watson-assistant'
});

Since we are creating the ChatClient and ChatBot on the same client we’re going to duplicate this but change the name to ChatBot.

It’s also nice to have a username for every connection we make so let’s make a template for usernames, something like this:

const now = new Date().getTime();
const username = ['user', now].join('-');

Connect our user and chatbot to our ChatEngine.

ChatClient.connect(username, {
    signedOnTime: now
});
ChatBot.connect(`${now}bot`, {
    signedOnTime: now
});

Create our chat class and look at the different lifecycle methods.

The constructor is a good place to begin, and in here we’re going to assign our variables with our chat instances. We can also initialize all the variables we want to store in the state.

Personally, I want to use this react-clippy package that will act as our assistant. We can use different animated characters to show our message and animate them. For Clippy we need to have an animation that plays so I’ll define that in my state. We also need the input the user gives and the response we get from our function.

Note: this post has no relation to Microsoft and they had nothing to do with this

In the end, our constructor looks like this.

constructor() {
        super();
        this.chat = new ChatClient.Chat(`${now}chat`);
        this.bot = new ChatBot.Chat(`${now}chat`);

        this.state = {
            reply: 'Ask Me Something!',
            chatInput: '',
            animation: 'Congratulate'
        };
}

Before going further, it’s a good idea to formulate a bit more about the render function. We know that we’re going to be accepting input so we’ll need some sort of input box. As well, we’ll be displaying a response and I’ll be adding the wonderful Clippy.

render() {
        return ( 
            <div style={container}>
                <div style={{height:100, width:100}}> 

                <p> {this.state.reply} </p>

                <Clippy
                    actor={ 'Clippy' } 
                    animation={ this.state.animation } />

                    <input id = "chat-input"
                        type = "text"
                        name = ""
                        value = { this.state.chatInput } 
                        onChange = { this.setChatInput } 
                        onKeyPress = { this.handleKeyPress } /> 

                    <input type = "button"
                        onClick = { this.sendChat } 
                        value = "Send Chat" />
                        
                </div>
                
            </div>
        );
    }

There are a couple other functions that complete some functionality in the chat class such as pressing enter to send the message but I’ll leave that up to you to explore. What we’ll look at now is the sending message part. There are two functions and they directly relate to how the flow of our project works.

sendChat = () => {

        if (this.state.chatInput) {
            this.chat.emit('message', {
                text: this.state.chatInput,
                channel: `${now}chat`
            });
            this.setState({ chatInput: '' })
            this.setState({
                animation: 'Processing',
                reply: ''
            });
        }
    }

    componentDidMount() {
        this.bot.on('message', (payload) => {
            axios.get(`https://pubsub.pubnub.com/v1/blocks/sub-key/sub-c-890a0b26-6451-11e8-90b6-8e3ee2a92f04/chat?    question=${payload.data.text}`)
            .then(response => {
                this.setState({
                    animation: 'Writing',
                    reply: response.data
                });
            });
        });
    }

The sendChat function lets the user send their message to the bot. Notice how we’re emitting the message and we’re defining the channel by the time. This allows making each chat in its own private session so that we don’t interrupt conversations other customers may be having at the same time.

We also need to ensure our bot listen for messages coming in. So that when the Chat component has mounted we need to make sure that our bot sends that message to our function and then updates our component with the message. For doing the request I’m using the axios package and you read more about here.

One last step. We need to make sure that once our ChatEngine connection is established we render our chat component. We can do that by adding this to the bottom of our file.

ChatClient.on('$.ready', () => {
    ChatBot.on('$.ready', () => {
        ReactDOM.render( 
            <Chat /> ,
            document.getElementById('root')
        );
    });
});

Our Chatbot is Up-and-Running!

This is a great starting point for building something more unique. It would definitely be a good idea to explore the documentation for IBM Assistant to train our bot to be clever and have more depth in their responses. ChatEngine can do a lot of cool things to connect instantly with people and services. I would love to see what you build next 🙂.

Try PubNub Today

Connect up to 100 devices for Free