Chatbot, chatbot, I'm a chatbot, and baby you can code me on.

8 mins

Chatbots are all the rage these days, and luckily you don’t need to be an AI programmer to get one yourself.

While this tutorial won’t go through how to configure your Bot to get the most out of it (that will come later), I’m going to go cover how to get yours working in Drupal (Including GovCMS SaaS) and allow you to style and customise it as you see fit.

In this tutorial we will be using DialogFlow which is Google’s free-ish ‘natural language processor’ (ChatBot) and good ol’ ReactJS.

If you want to get a simple and plain ChatBot, you can use Google’s DialogFlow system, go to the Integrations tab, turn on Web Demo, and paste an iFrame code into your site.  And while that totally works, it also looks like this: Dialog flow chatbot

While it looks slick and has Microphone support out of the box, it’s also very generic looking and being in an iFrame means it’s not easily modified or very well integrated into your site.

Instead, what we will do will integrate it with our site using ReactJS.

You first need to have ReactJS working on your site, and if you don’t have that, don’t worry, I’ve already written a tutorial on that.

So go read my ‘Create a rich SaaS website using ReactJS’ blog and get that working first… I’ll wait here…

All done? Good.

First thing we need to do is create a new DialogFlow Agent.  Follow Google’s instructions and be sure to enable the Smalltalk components.  This give’s us the simple “Hello” “What are you” call and responses that we will use to test it out.

Under the Configuration section of the Agent (Which you can access by clicking the Cog icon next to the title in the top left), there will be a ‘Client access token’.  Copy that into a notepad, we will use that later on to talk to the Bot.

Next, we start with the actual ReactJS part of it.  If you recently followed my other tutorial, make a backup of that App.js and App.css files and clear them out for our new project.  If not, make sure you have a JS and CSS file setup and ready to go.

Let’s start with a new class for our Bot with some basic Props and a Handler.

class ChatBot extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      textResponse: [],
      question: '',
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) { }

  render() {
    return (
      <form className="chatbox">Chat goes here</form>
    )
  }
}

const e = React.createElement;
const domContainer = document.querySelector('#chatbot');
ReactDOM.render(e(ChatBot), domContainer);

This will allow us to store the question we are asking, as well as the responses from the Bot in the this.state.  And at the bottom of the file we have the renderer like we did in the other tutorial, and ensure you have a div with an id of ‘chatbot’ in the Drupal site somewhere:

Save all that, clear your cache if you need to, and then make sure the page displays without any errors in the Console, and that we see the words ‘Chat goes here’ on the page.

Now, we make the Form itself, which will consist of a text input field, a submit button, and an area where our conversation history will be displayed.

<form className="chatbox">
   { this.state.textResponse.map((textValue, index) =>
     <div key={index}>
       <div className={textValue.user}>
         <p>{textValue.response}</p>
      </div>
       <div className="clearfix"></div>
     </div>
   )}
   <input
     name="question"
     type="text"
     value={this.state.question}
     onChange={this.handleChange}
   />
  
   <input
     className="askButton"
     name="ask"
     type="submit"
     value="Ask"
     onClick={this.handleChange}
   />
</form>

The xxx.map() function is the way that ReactJS likes to loop through a list of Objects.  And in our loop, we are rendering out the Response text, and using the User variable as the class, this will let you float items left and right and colour code them based on if it was said by the user, or a response by the system.

Again, save, and all this should render without issue.  If you type or press the button though, not so much.

Now that we have our two fields (Input and Submit) let’s update the submit handler to begin the processing part of this:

handleChange(event) {
  if (event.target.name === 'question') {
    this.setState({question: event.target.value});
  }
  if (event.target.name === 'ask') { }
  event.preventDefault();
}

So far, so good, all we have done is setup a fairly simple ReactJS form on a page, extremely similar to the other blog post.

When we type, the Question state gets updated, and when we Ask, nothing happens.

Before we continue, we need to add a new Javascript library that will take ALL the hassle out of talking with DialogFlow.  It’s called ‘api-ai-javascript’ and can be found on GitHub or installed via NPM.

However, because we are aiming for a SaaS setup, you should go to this GitHub folder, and download the ApiAi.js file, and then include it in your theme, either by adding it to the theme.libraries.yml, via a pre-processor, or html.html.twig.  Take your pick.

Again, clear your cache and make sure everything is still loading, and make sure your new JS file is being included where you need it.

Finally, we get around to the actual nitty gritty.  Here is the code, and then I’ll explain it… the code goes in the handleChange function and replaces the Ask statement that was blank before:

if (event.target.name === 'ask') {
  var sliced = this.state.textResponse.slice();
  sliced.push({
    'response': this.state.question,
    'user': 'user'
  });
  this.setState({textResponse: sliced});
  const client = new ApiAi.ApiAiClient({accessToken: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX'})
    .textRequest(this.state.question)
    .then((response) => {
      var sliced = this.state.textResponse.slice();
      sliced.push({
        'response': response.result.fulfillment.speech,
        'user': 'system'
      });
      this.setState({textResponse: sliced});
      this.setState({question: ''});
    })
  .catch((error) => {
    console.log(error);
  })
}

We start off making sure we only run this when the Ask button has been activated.

Then, we get the existing list of responses and slice it up into an array (Similar to PHP’s explode) and then Push a new item into the end of the array that contains the question we are asking and the fact that the ‘user’ is the ‘user’, and finally we update the state of the textResponse.

Next is the magic.  We create a new ApiAiClient call using our Agent Token we saved from earlier, and we ask the question which is saved in this.state.question prop.

If we get a response, we do the same Slice/Push/SetState method as above, adding our new response (Which is returned from DialogFlow in the response.result.fulfillment.speech object.

We also set the user for that one to ‘system’ so we know where it came from.

Finally, we set the Question to blank so the user can ask us a new one.

And that’s it.Custom Chatbot

Now, I’ve styled mine with a tiny bit of CSS, and added in a timestamp per message, but that’s for you to figure out yourself.

I would also HIGHLY recommend doing a console.log(response) in the .then((response) section and seeing all the data that Google returns, there might be some extra information there that you could use and expand your Bot with.

I would also like to say that this is probably not the most efficient way of achieving this, but this tutorial is meant to help you get the system up and running, you will need to do some work yourself.

 

And as before, here is in the entire App.JS code.

'use strict';
// The main form
class ChatBot extends React.Component {
 
  // All my variables and data stores
  constructor(props) {
    super(props);
    this.state = {
      textResponse: [],
      question: '',
    };
 
    // Bind all functions to This
    this.handleChange = this.handleChange.bind(this);
 
  }
 
  // Global field change handler
  handleChange(event) {
   
    if (event.target.name === 'question') {
      this.setState({question: event.target.value});
    }
    
    if (event.target.name === 'ask') {
     
      var sliced = this.state.textResponse.slice();
     
      sliced.push({
        'response': this.state.question,
        'user': 'user'
      });
     
      this.setState({textResponse: sliced});
     
      const client = new ApiAi.ApiAiClient({accessToken: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX'})
        .textRequest(this.state.question)
          .then((response) => {
           
            var sliced = this.state.textResponse.slice();
           
            sliced.push({
              'response': response.result.fulfillment.speech,
              'user': 'system'
            });
           
            this.setState({textResponse: sliced});
            this.setState({question: ''});
          })
          .catch((error) => {
            console.log(error);
          })
     
    }
   
    event.preventDefault();
  }
   
  render() {
   
    return (
      <form className="chatbox">
 
        { this.state.textResponse.map((textValue, index) =>
          <div key={index}>
            <div className={textValue.user}>
              <p>{textValue.response}</p>
            </div>
            <div className="clearfix"></div>
          </div>
       
        )}
       
        <input
          name="question"
          type="text"
          value={this.state.question}
          onChange={this.handleChange}
        />
       
        <input
          className="actionButton2"
          name="ask"
          type="submit"
          value="Ask"
          onClick={this.handleChange}
        />
      </form>
    );
   
  }
}
 
// Render our Form Component into our #myreactapp2 DOM element
const e = React.createElement;
const domContainer = document.querySelector('#chatbot');
ReactDOM.render(e(ChatBot), domContainer);
Date posted:
14 December 2018
Authored by :
Toby Wild