




















































Today, we are going to build a chatbot that can search for restaurants based on user goals and preferences. Let us begin by building Node.js modules to get data from Zomato based on user preferences. Create a file called zomato.js. Add a request module to the Node.js libraries using the following command in the console:
This tutorial has been taken from Hands-On Chatbots and Conversational UI Development.
> npm install request --save
In zomato.js, add the following code to begin with:
var request = require('request');
var baseURL = 'https://developers.zomato.com/api/v2.1/';
var apiKey = 'YOUR_API_KEY';
var catergories = null;
var cuisines = null;
getCategories();
getCuisines(76);
Replace YOUR_API_KEY with your Zomato key. Let's build functions to get the list of categories and cuisines at startup. These queries need not be run when the user asks for a restaurant search because this information is pretty much static:
function getCuisines(cityId){
var options = {
uri: baseURL + 'cuisines',
headers: {
'user-key': apiKey
},
qs: {'city_id':cityId},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
console.log(body);
cuisines = JSON.parse(body).cuisines;
}
}
request(options,callback);
}
The preceding code will fetch a list of cuisines available in a particular city (identified by a Zomato city ID). Let us add the code for identifying the list of categories:
function getCategories(){
var options = {
uri: baseURL + 'categories',
headers: {
'user-key': apiKey
},
qs: {},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
categories = JSON.parse(body).categories;
}
}
request(options,callback);
}
Now that we have the basic functions out of our way, let us code in the restaurant search code:
function getRestaurant(cuisine, location, category){
var cuisineId = getCuisineId(cuisine);
var categoryId = getCategoryId(category);
var options = {
uri: baseURL + 'locations',
headers: {
'user-key': apiKey
},
qs: {'query':location},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
console.log(body);
locationInfo = JSON.parse(body).location_suggestions;
search(locationInfo[0], cuisineId, categoryId);
}
}
request(options,callback);
}
function search(location, cuisineId, categoryId){
var options = {
uri: baseURL + 'search',
headers: {
'user-key': apiKey
},
qs: {'entity_id': location.entity_id,
'entity_type': location.entity_type,
'cuisines': [cuisineId],
'categories': [categoryId]},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
console.log('Found restaurants:')
var results = JSON.parse(body).restaurants;
console.log(results);
}
}
request(options,callback);
}
The preceding code will look for restaurants in a given location, cuisine, and category. For instance, you can search for a list of Indian restaurants in Newington, Edinburgh that do delivery. We now need to integrate this with the chatbot code. Let us create a separate file called index.js. Let us begin with the basics:
var restify = require('restify');
var builder = require('botbuilder');
var request = require('request');
var baseURL = 'https://developers.zomato.com/api/v2.1/';
var apiKey = 'YOUR_API_KEY';
var catergories = null;
var cuisines = null;
Chapter 6
[ 247 ]
getCategories();
//setTimeout(function(){getCategoryId('Delivery')}, 10000);
getCuisines(76);
//setTimeout(function(){getCuisineId('European')}, 10000);
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat connector for communicating with
// the Bot Framework Service
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
// Listen for messages from users
server.post('/foodiebot', connector.listen());
Add the bot dialog code to carry out the restaurant search. Let us design the bot to ask for cuisine, category, and location before proceeding to the restaurant search:
var bot = new builder.UniversalBot(connector, [
function (session) {
session.send("Hi there! Hungry? Looking for a restaurant?");
session.send("Say 'search restaurant' to start searching.");
session.endDialog();
}
]);
// Search for a restaurant
bot.dialog('searchRestaurant', [
function (session) {
session.send('Ok. Searching for a restaurant!');
builder.Prompts.text(session, 'Where?');
},
function (session, results) {
session.conversationData.searchLocation = results.response;
builder.Prompts.text(session, 'Cuisine? Indian,
Italian, or anything else?');
},
function (session, results) {
session.conversationData.searchCuisine = results.response;
builder.Prompts.text(session, 'Delivery or Dine-in?');
},
function (session, results) {
session.conversationData.searchCategory = results.response;
session.send('Ok. Looking for restaurants..');
getRestaurant(session.conversationData.searchCuisine,
session.conversationData.searchLocation,
session.conversationData.searchCategory,
session);
}
])
.triggerAction({
matches: /^search restaurant$/i,
confirmPrompt: 'Your restaurant search task will be abandoned.
Are you sure?'
});
Notice that we are calling the getRestaurant() function with four parameters. Three of these are ones that we have already defined: cuisine, location, and category. To these, we have to add another: session. This passes the session pointer that can be used to send messages to the emulator when the data is ready. Notice how this changes the getRestaurant() and search() functions:
function getRestaurant(cuisine, location, category, session){
var cuisineId = getCuisineId(cuisine);
var categoryId = getCategoryId(category);
var options = {
uri: baseURL + 'locations',
headers: {
'user-key': apiKey
},
qs: {'query':location},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
console.log(body);
locationInfo = JSON.parse(body).location_suggestions;
search(locationInfo[0], cuisineId,
categoryId, session);
}
}
request(options,callback);
}
function search(location, cuisineId, categoryId, session){
var options = {
uri: baseURL + 'search',
headers: {
'user-key': apiKey
},
qs: {'entity_id': location.entity_id,
'entity_type': location.entity_type,
'cuisines': [cuisineId],
'category': categoryId},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
console.log('Found restaurants:')
console.log(body);
//var results = JSON.parse(body).restaurants;
//console.log(results);
var resultsCount = JSON.parse(body).results_found;
console.log('Found:' + resultsCount);
session.send('I have found ' + resultsCount +
' restaurants for you!');
session.endDialog();
}
}
request(options,callback);
}
Once the results are obtained, the bot responds using session.send() and ends the dialog:
Now that we have the results, let's present them in a more visually appealing way using cards. To do this, we need a function that can take the results of the search and turn them into an array of cards:
function presentInCards(session, results){
var msg = new builder.Message(session);
msg.attachmentLayout(builder.AttachmentLayout.carousel)
var heroCardArray = [];
var l = results.length;
if (results.length > 10){
l = 10;
}
for (var i = 0; i < l; i++){
var r = results[i].restaurant;
var herocard = new builder.HeroCard(session)
.title(r.name)
.subtitle(r.location.address)
.text(r.user_rating.aggregate_rating)
.images([builder.CardImage.create(session, r.thumb)])
.buttons([
builder.CardAction.imBack(session,
"book_table:" + r.id,
"Book a table")
]);
heroCardArray.push(herocard);
}
msg.attachments(heroCardArray);
return msg;
}
And we call this function from the search() function:
function search(location, cuisineId, categoryId, session){
var options = {
uri: baseURL + 'search',
headers: {
'user-key': apiKey
},
qs: {'entity_id': location.entity_id,
'entity_type': location.entity_type,
'cuisines': [cuisineId],
'category': categoryId},
method: 'GET'
}
var callback = function(error, response, body) {
if (error) {
console.log('Error sending messages: ', error)
} else if (response.body.error) {
console.log('Error: ', response.body.error)
} else {
console.log('Found restaurants:')
console.log(body);
var results = JSON.parse(body).restaurants;
var msg = presentInCards(session, results);
session.send(msg);
session.endDialog();
}
}
request(options,callback);
}
Here is how it looks:
We saw how to build a restaurant search bot, that gives you restaurant suggestions as per your preference.
If you found our post useful check out Chatbots and Conversational UI Development.
Top 4 chatbot development frameworks for developers
How to create a conversational assistant using Python
My friend, the robot: Artificial Intelligence needs Emotional Intelligence