DEV Community

Cover image for Building a chatbot with DialogFlow, Node.js, and React
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Building a chatbot with DialogFlow, Node.js, and React

Written by Piyush Sinha✏️

To us humans, conversation is second-nature; it comes naturally to us, but the same can’t be said for bots. Even a simple question like, “How was your day?”, could be rephrased several ways (for example, “How’s it going?”, “How are you?”), none of which a bot can ordinarily understand.

We can interpret the intent behind the question, but a lot goes into building the logic to facilitate a smarter conversation with a bot, and for most developers, coding it from scratch isn't feasible.

Well, fortunately, there's something called NLU (natural-language understanding), which enables better human-computer conversation – in other words, a smart chatbot that utilizes machine learning and other technologies in order to better understand human interactions.

An NLU algorithm doesn't just recognize text, but also interprets the intent behind it. Hence, it’s an ideal algorithm for chatbots. Read more about NLU here.

And this is where the purpose of this article comes in. We are going to build our chatbot using Google's NLU platform, DialogFlow. Read on to learn more about DialogFlow, and how to integrate it into a React application with this follow along tutorial.

Getting started with DialogFlow

In simple terms, DialogFlow is an end-to-end tool powered by NLU to design and integrate chatbots into our interfaces. It translates natural language into machine-readable data using machine learning (ML) models trained by the language we provide. How does it work? Let's learn while we build our chatbot.

Open the DialogFlow console and log in with your Google account. Upon successful login, we see the following interface: Welcome To Dialogflow Dashboard

The first thing that may catch your attention is the Create Agent option.

What is an agent?

Nothing fancy! The chatbot itself is an agent. Collecting the user's query, acting on it, and finally sending a response is all handled by our agent.

Let's create our chatbot; how about a bot for a coffee shop, for our example? No prizes for guessing my coffee shop inspiration. Central Perk Create Agent Dashboard

This is how the console should look now: DialogFlow Intents Dashboard

Now, more jargon has appeared on the screen – Intents.

What are intents?

The console says that, "Intents are mappings between a user's queries and actions fulfilled by your software". So, what does this mean?

Let me explain: in the case of our bot, we expect to receive queries like, "I would like a cappuccino" and, "When does the shop open?", etc. We can categorize these queries into user intentions such as "Take Order", "Timings", etc. To handle them, we define these categories as intents in our agent.

We also see that our agent comes with two default intents; Default Welcome Intent and Default Fallback Intent. Let's explore them in a little more detail: Default Welcome Intent

Quite a bit of jargon in here; let's go through them one by one:

Contexts

In a human conversation, to understand phrases, we usually need some context. Likewise, with the bot, an intent needs to know the context of the query. To make this possible, we connect one or more intents via contexts. We will learn more about this later in this article.

Training phrases

These are example phrases to train and help our agent match queries with the right intent. More phrases and variations will improve the accuracy of intent matching.

By seeing the default list of phrases, it's clear that this intent is used to greet our users.

Events

We just learned the agent looks for training phrases to trigger intents. However, intents can also be triggered by events. There are two types of events:

  • Platform events: These are provided by the platform itself and occur when platform-specific events occur (e.g., a Welcome event)
  • Custom events: These are defined by us (e.g., the response from an API call made by us)

Actions and parameters

Once the query is matched with the right intent, next comes action on it. To act, sometimes we need to extract some data from the query. To extract, we define parameters with entity types. Take this for example: "Is the coffee shop open today?"; the parameter to extract here is today, which is critical info needed to perform some logic and respond accordingly. We will learn more about this later in this tutorial.

Responses

Response to the user. Here, we see static phrases, but they can be dynamic, too, if we make use of parameters or fulfillment.

Fulfillment

When we enable fulfillment, the agent gives a dynamic response by making an API call defined by us (e.g., if a user wants to book a table, we can check the database and respond based on availability). Again, more on this a little later.

Let's try this default intent first before going any further. On the right side, we have a Dialogflow Simulator to test our agent. We say something similar to the training phrases, and the agent responds from the list of responses: Agent Default Response Screen

And, if we say something completely different, the agent triggers a Default Fallback Intent: Default Response User Question

So, if the agent fails to find the right intent, it triggers the fallback intent. Pretty smooth, right? But, let's try to avoid it and add intents to carry out the whole conversation.

Adding regular intents

Let's try to create this conversation shown in the diagram below: Chatbot Flow Chart

Modify the response of Default Welcome Intent to greet and ask for the order. Something like this; "Greetings! What would you like to order?".

Create an intent to take the order, and name the intent to something simple that makes sense, like Take Order.

Now, we add training phrases. The user might say, “I would like to have 2 lattes”. Adding this to the list of training phrases is enough to get it matched with this intent. But, we also need the qualifying information; ‘2’ and ‘lattes’.

To extract them, we will add a couple of parameters within the phrase – quantity (@sys.number entity type) and item (@sys.any entity type): Take Order Flow Dashboard

Add more variations to make it better. Here’s some I added: Alternative Response

Once the parameter-rich phrases are added, this table is automatically filled. We can mark them as required/optional and set a default value if needed. In addition, we need to define a question as a prompt, to force the user to enter the required parameter, if not entered the first time.

So, this means that if we get the first phrase from the list, “I would like to order”, and the item (required) is missing, the prompt will get triggered to ask for it. If we get the second phrase from the list; “I would like to order a mocha”, and quantity (optional) isn’t there, then, instead of prompting the user to enter the quantity, we will just set the default value as 1 and use that.

We can return a dynamic response to make use of parameters to repeat the order and ask if they would like an add-on: Text Response

To do this, create an intent to handle the user’s response to the add-on question.

The response will be either Yes or No. This is the ideal scenario to introduce follow-up intents. As the name suggests, these are used to follow-up with the conversation in the parent intent.

DialogFlow provides many predefined follow-up intents for common replies like “Yes”, “No” or “Cancel”. Also, If you need to handle custom replies, you can create your own custom follow-ups. We’re going to use the predefined ones for this tutorial, but it’s up to you if you wish to put your own spin on it.

From the intent list, hover the mouse over the TakeOrder intent and click Add follow-up intent. Select Yes and the follow-up intent is created named Take Order - yes. Take Order Yes Template

You may have noticed there’s an input context added (TakeOrder-followup). For follow-ups to work, they need to be linked to the parent intent, and that’s where the context comes in handy.

When we create a follow-up intent, an output context is automatically added to the parent intent and an input context of the same name is added to the follow-up intent.

We don’t need to worry about the training phrases, as they are already in place. Now, add a dynamic response for this intent. As contexts are in place, we can use the parameters from the parent intent, like this: Text Response 1

Similarly, create a predefined follow-up intent to handle No in response. This one will be named Take Order - no. Just add a dynamic response here, as well: Text Response 2

The agent is ready to carry out the conversation. We can always test it in the simulator, but let’s try something different. Click Integrations in the sidebar and enable Web Demo. Open the URL provided there and start the conversation: Dialogflow Central Perk Agent

The agent is working fine as expected, but wouldn’t it be nice to show the total billing amount of the order? We can fetch the prices from a server and return the calculated amount to the user.

Connecting with a NodeJS server

While discussing intents, we came across Fulfillment, which helps to return dynamic responses by making API calls defined by us. That’s what we need here to interact with our server.

Find Fulfillment in the sidebar and open it. DialogFlow provides two ways to use fulfillment:

  • An in-line editor powered by Google Cloud Functions. But, to enable this, we need to add a valid billing account as this integration has charges, if used beyond a certain limit
    • A webhook service

I’m going to build a webhook service and make it public. Before proceeding, make sure the service meets the requirements mentioned here.

Building our server

Create a node app in a new directory and install the necessary dependencies:

mkdir centralperk-server
cd centralperk-server
npm init -y
npm i express dialogflow-fulfillment

Start with a basic server in index.js:

const express = require("express");
const app = express();

app.get("/", (req, res) => {
  res.send("Hi from server!");
});

app.listen(8080, () => {
  console.log("server running...");
});
Enter fullscreen mode Exit fullscreen mode

This simply runs the server at port 8080. Now, let’s write some code to handle webhook requests from DialogFlow:

const express = require("express");
const app = express();

const { WebhookClient } = require("dialogflow-fulfillment");
const getPrice = require("./helpers");

app.get("/", (req, res) => {
  res.send("Hi from server!");
});

app.post("/", express.json(), (req, res) => {
  const agent = new WebhookClient({ request: req, response: res });

  function handleIntent(agent) {
    const intent = agent.intent;
    const item = agent.contexts[0].parameters.item;
    const quantity = agent.contexts[0].parameters.quantity;
    const billingAmount = getPrice(intent, item, quantity);

    const response =
      intent === "Take Order - yes"
        ? `Great! Your ${quantity} ${item} and cookies will be ready in no time. Please pay ${billingAmount}$.`
        : `Okay! Your ${quantity} ${item} will be ready in no time. Please pay ${billingAmount}$.`;

    agent.add(response);
  }

  const intentMap = new Map();
  intentMap.set("Take Order - yes", handleIntent);
  intentMap.set("Take Order - no", handleIntent);
  agent.handleRequest(intentMap);
});

app.listen(8080, () => {
  console.log("server running...");
});
Enter fullscreen mode Exit fullscreen mode

Helpers to calculate price:

const priceList = {
  mocha: 5,
  latte: 7,
  cookies: 2,
};

module.exports = function (intent, item, quantity) {
  const total =
    intent === "Take Order - yes"
      ? priceList[`${item}`] * quantity + priceList["cookies"]
      : priceList[`${item}`] * quantity;

  return total;
};
Enter fullscreen mode Exit fullscreen mode

Let’s walk through the above implementation.

The imported WebhookClient will handle the communication with DialogFlow's webhook fulfillment API. When a fulfillment-enabled intent is matched, an HTTPS POST webhook request is sent to our server. This request is handled by agent.handleRequest(intentMap). It takes in a map of handlers and each handler is a function callback. The one defined here extracts all the needed information from the passed instance, calculates the billing amount, and then finally returns the dynamic response.

Making the server public

Using ngrok is the fastest and easiest way to put the server on the Internet. Follow the steps here for quick setup and installation. Once done with the steps, run the following command:

ngrok http 8080
Enter fullscreen mode Exit fullscreen mode

And the secured public URL is ready in no time:

ngrok                                                                                                   (Ctrl+C to quit)                                                                                                                        Visit http://localhost:4040/ to inspect, replay, and modify your requests                                                                                                                                                                       Session Status                online                                                                                    Account                       Piyush (Plan: Free)                                                                       Version                       3.0.6                                                                                     Region                        India (in)                                                                                Latency                       55ms                                                                                      Web Interface                 http://127.0.0.1:4040                                                                     Forwarding                    https://2c73-182-64-199-236.in.ngrok.io -> http://localhost:8080                                                                                                                                                  Connections                   ttl     opn     rt1     rt5     p50     p90                                                                             5       0       0.00    0.01    2.37    5.21
Enter fullscreen mode Exit fullscreen mode

(Note: Remember to keep the local server up and running)

Enabling the webhook

Go ahead and enable the webhook in the Fulfillment window and enter the secured public URL. In addition, bear in mind that webhook calls need to be enabled for both follow-up intents.

All looks good. Let’s test it now: Using the Chatbot

Our chatbot is all set up!

Integrating the DialogFlow chatbot into a React App

There are many ways to integrate the chatbot into a React app:

  • Build the chat widget in React from scratch. Handle the state of incoming and outgoing messages using a library like Redux and modify the Node server to handle calls from the React app as well as sending them to DialogFlow. This does sound interesting, but is a lot to cover and is outside the scope of this particular article
  • Using Kommunicate to rather more effortlessly integrate the DialogFlow chatbot into React app

In this blog tutorial, we’re going with the Kommunicate option.

DialogFlow ES: Kommunicate integration

Follow these steps:

Go for Free Trial and sign up with your Google account.

Click Bot Integrations and select DialogFlow ES.

Get the JSON key from the DialogFlow cloud account using the instructions mentioned in the following image: Dialogflow Json Key

Next, we get to choose a customized avatar: Dialogflow Avatar

And that’s all we need to do for DialogFlow ES and Kommunicate integration! Chatbot Creation Completion

Integrate the Kommunicate chat widget into a React App

Create a chatbot component and paste the following code in useEffect():

(function (d, m) {
      var kommunicateSettings = {
        appId: "<YOUR APP_ID>",
        popupWidget: true,
        automaticChatOpenOnNavigation: true,
      };
      var s = document.createElement("script");
      s.type = "text/javascript";
      s.async = true;
      s.src = "https://widget.kommunicate.io/v2/kommunicate.app";
      var h = document.getElementsByTagName("head")[0];
      h.appendChild(s);
      window.kommunicate = m;
      m._globals = kommunicateSettings;
    })(document, window.kommunicate || {});
Enter fullscreen mode Exit fullscreen mode
import React, { useEffect } from "react";

function Chatbot() {
  useEffect(() => {
    (function (d, m) {
      var kommunicateSettings = {
        appId: "<YOUR APP_ID>",
        popupWidget: true,
        automaticChatOpenOnNavigation: true,
      };
      var s = document.createElement("script");
      s.type = "text/javascript";
      s.async = true;
      s.src = "https://widget.kommunicate.io/v2/kommunicate.app";
      var h = document.getElementsByTagName("head")[0];
      h.appendChild(s);
      window.kommunicate = m;
      m._globals = kommunicateSettings;
    })(document, window.kommunicate || {});
  }, []);
  return <div></div>;
}

export default Chatbot;
Enter fullscreen mode Exit fullscreen mode

Remember to replace the placeholder with your appId. Finally, import it in the App component:

import "./App.css";
import Chatbot from "./Chatbot";

function App() {
  return (
    <div className="App">
      <Chatbot />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Run the app locally to test the integration: Chatbot Run Locally

And just like that, we have added our chatbot into our React app. Visit this dashboard to add more customizations to things like color, icons, and notification sounds, etc.

Conclusion

That’s all for this blog! We learned several aspects of chatbot development; from building it with DialogFlow, connecting with the NodeJS server, to finally Integrating it into React app. I hope it made sense to you and you were able to follow along with this tutorial with ease.

If you have any questions, you can leave them in the comments and I’ll be happy to answer them. Feel free to reach out to me on LinkedIn or Twitter.


Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket Performance Monitoring

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.

Top comments (0)