Documentation

Powered by Algolia

Creating a voice-enabled food delivery app: complete tutorial

In this tutorial, we will create a completely voice-enabled web app for food ordering and delivery from scratch. The app users will be able to do the following with voice:

  • Add items to the cart
  • Remove items
  • Perform checkout
  • Check the order details
  • Check the balance
  • Set the delivery time

What you will learn

While going through this tutorial, we will cover the Alan essentials: intents, alternatives, slots, contexts and so on. Once you complete the tutorial, you will know how to use them to create a voice assistant for your app.

What you will need

This is a getting started tutorial. No prior knowledge is required.

Step 1: Try the voice interactions

Before we start creating a voice assistant, you can try a finished sample. This will give you an idea of what you are building.

Open the app. In the bottom right corner, click the Alan button, enable microphone access if required and try saying the following commands:

  • I want pepperoni
  • My order or Order details
  • Checkout

Then provide your delivery address and delivery time.

Step 2: Create the app

Let's start. We will first create a webpage. Copy the code below and save it as an HTML file.

tutorial.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Food Delivery Example</title>
    <link href="https://storage.googleapis.com/alan-tutorial/web-sdk/styles.css" rel="stylesheet">
</head>

<body>
    <h1>Food Delivery</h1>
    <h3>Menu</h3>
    <div class="menu">
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/pepperoni.jpg"/>
            Pepperoni
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/margherita.jpg"/>
            Margherita
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burrito.jpg"/>
            Burrito
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burger.jpg"/>
            Burger
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/taco.jpg"/>
            Taco
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/applepie.jpg"/>
            Apple Pie
        </div>
    </div>

    <ul id="order"></ul>
    <div id="address"></div>
</body>

</html>

We can open this file in the browser:

Step 3: Create a project

To add a voice assistant to the app, we need to create a project in Alan Studio. In the project, we will write a voice script with commands for our app.

In Alan Studio, click Create Voice Assistant, choose to create an empty project, specify the project name and click Create.

Step 4: Add the Alan button

To let users communicate with our app through voice, we need to add the Alan button to it. In Alan Studio, click Integrations.

Here we can see snippets for the following elements:

  • Container for the Alan button
  • Link to the Alan SDK library and snippet with the Alan button

Click Copy next to each snippet and paste the code to the webpage.

Now we have a webpage with the Alan button.

tutorial.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Food Delivery Example</title>
    <link href="https://storage.googleapis.com/alan-tutorial/web-sdk/styles.css" rel="stylesheet">
    
</head>

<body>
    <h1>Food Delivery</h1>
    <h3>Menu</h3>
    <div class="menu">
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/pepperoni.jpg"/>
            Pepperoni
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/margherita.jpg"/>
            Margherita
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burrito.jpg"/>
            Burrito
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burger.jpg"/>
            Burger
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/taco.jpg"/>
            Taco
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/applepie.jpg"/>
            Apple Pie
        </div>
    </div>

    <ul id="order"></ul>
    <div id="address"></div>
	
	<!-- Alan button container -->
    <div class="alan-btn"></div>
	
	<!-- Link to the Alan SDK library -->
    <script type="text/javascript" src="https://tutor.alan.app/web/lib/alan_lib.js"></script>
	
	<!-- Script section for Alan button -->
    <script>	
        var alanBtnInstance = alanBtn({
			key: "3bfbf8d2a24e47b23446ed6112aad3fd2e956eca572e1d8b807a3e2338fdd0dc/stage",
			onCommand: function(commandData) {
				if (commandData.command === "go:back") {
				// Call client code that will react to the received command
			}
		},
		rootEl: document.getElementById("alan-btn"),
	});
    </script>
</body>

</html>

In the Alan button snippet, you can see the Alan button key. This key is unique for every project.

We can now refresh the page and use voice commands with Alan. Try clicking the Alan button and saying Hello.

The page works in the latest versions of Chrome, Firefox, Safari and Microsoft Edge. After clicking the Alan button, you need to give microphone access. Some browsers may block microphone access on unsecure pages or allow it only during the current session.

Step 5: Add commands to the voice script

When we created the project in Alan Studio, Alan automatically created a voice script in it. Let's add a simple voice command, or intent, to our voice script. In Alan Studio, in the code editor, enter the following intent and save the script.

tutorial script:

intent(`I want pepperoni`, reply(`Adding pepperoni`));

Now we can check how our command works in the Debugging Chat in the right pane of Alan Studio or directly in the webpage. Click the Alan button and say to Alan: I want pepperoni. Alan should answer: Adding pepperoni.

If you are using the Debugging Chat, you can also type the command in the text field.

Step 6: Specify alternatives

What if the user instead says: Add pepperoni or Pepperoni? To be sure the voice command works properly, we can add alternatives and optional alternatives to the command.

  • To add alternatives, enclose all possible variants in brackets and use the | delimiter.
  • To add optional parts of the user command, add the | character at the end of the phrase or alternatives set.

tutorial script:

intent(`(Add|I want|) pepperoni`, reply(`Adding pepperoni`));

Now Add pepperoni or just Pepperoni will work.

Step 7: Set the command handler

In the app, we want to not only get a reply but also add the named item to the cart. To do this, let's first add the addItemToCart() function to the webpage:

<script>
...
	// Adding items to the cart
	function addItemToCart(name) {
		var li = document.createElement("li");
		var text = document.createTextNode(name);
		li.appendChild(text);
		document.getElementById("order").appendChild(li);
	}
...
</script>

Now we need to make sure this function is invoked when the user says the necessary command. Let's modify the script in Alan Studio.

At the previous step, we used the reply() function in the intent to play a response. We can also use the play() function to play a response or send a command to our app.

tutorial script:

intent(`(Add|I want|) pepperoni`, p => {
    p.play({command: 'addItem', item: 'pepperoni'});
    p.play(`Adding pepperoni`);
});

The p variable here references an internal object that we can use to call Alan Studio SDK methods. We will call the play() method of this object. This method can generate voice responses or send commands to the app. To send a command, we will pass the following JSON object to it: {command: 'addItem', item: 'pepperoni'}.

Now let's change the onCommand function for the Alan button in the webpage so that the addItemToCart() function is invoked once the app receives this command.

tutorial.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Food Delivery Example</title>
    <link href="https://storage.googleapis.com/alan-tutorial/web-sdk/styles.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i" rel="stylesheet" />
</head>

<body>
    <h1>Food Delivery</h1>
    <h3>Menu</h3>
    <div class="menu">
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/pepperoni.jpg"/>
            Pepperoni
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/margherita.jpg"/>
            Margherita
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burrito.jpg"/>
            Burrito
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burger.jpg"/>
            Burger
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/taco.jpg"/>
            Taco
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/applepie.jpg"/>
            Apple Pie
        </div>
    </div>

    <ul id="order"></ul>
    <div id="address"></div>
    <div class="alan-btn"></div>
    <script type="text/javascript" src="https://tutor.alan.app/web/lib/alan_lib.js"></script>
    <script>
		// Adding items to cart
        function addItemToCart(name) {
            var li = document.createElement("li");
            var text = document.createTextNode(name);
            li.appendChild(text);
            document.getElementById("order").appendChild(li);
        }

        var alanBtnInstance = alanBtn({
            key: "3bfbf8d2a24e47b23446ed6112aad3fd2e956eca572e1d8b807a3e2338fdd0dc/stage",
			
			// Invoking addItemToCart when the addItem command is received
            onCommand: function (commandData) {
                if (commandData.command == "addItem") {
                    addItemToCart(commandData.item);
                }
            },
            rootEl: document.getElementById("alan-btn"),
        });
    </script>
</body>

</html>

Here is how it works: when the user says: Add pepperoni, Alan now plays a response and sends the command to the app. On the app side, we check if the command is addItem, and if so, invoke the addItemToCart() function and pass item to it. The item is added to the order.

Step 8: Add more items

We can add a pepperoni pizza to the order, but we have other items. Let's add a slot to our intent. A slot allows Alan to identify and get the necessary information from the user input. It is defined with $ followed by brackets. In brackets, we provide the slot name and values.

tutorial script:

const itemList = "pepperoni|margherita|burrito|burger|taco|apple pie";

intent(`(add|I want|) $(ITEM ${itemList})`, p => {
    p.play({command: 'addItem', item: p.ITEM.value});
    p.play('adding ' + p.ITEM.value);
});

Here the slot values are saved to itemList. Now the user can add a burger, burrito or other items from itemList to the order.

Step 9: Perform checkout

We want to have the ability to check out, ask users for their addresses and deliver the food. For the checkout intent, we will use a context.

In Alan, a voice script can include one or more contexts. In the context, you can put a part of the dialog that can occur only in specific circumstances (within a context). For example, in our voice script asking for the user address makes sense only if the user has initiated the checkout process. Without checkout, getting the address would be meaningless.

Let's modify our script.

tutorial script:

const itemList = "pepperoni|margherita|burrito|burger|taco|apple pie";

intent(`(add|I want|) $(ITEM ${itemList})`, p => {
    p.play({command: 'addItem', item: p.ITEM.value});
    p.play('adding ' + p.ITEM.value);
});

// Creating a context for getting address details
var whatAddress = context(() => {
    follow('$(LOC)', p => {
        p.play({command: "address", address: p.LOC.value});
        p.play("We will deliver your order to " + p.LOC.value);
    });
});

intent(`that's (all|it)`, '(ready to|) checkout', p => {
    p.play('What is the delivery address?');
    p.then(whatAddress);
});

Now we have the whatAddress context in the script.

To let the user run commands placed in the context, we must activate this context. The context is activated by a matching intent (a command that the user says), or manually with the then() function. Here we are using the latter variant: we are creating a new intent for checkout and adding to it the then() function that activates the whatAddress context.

In the context, we use the follow() function for user commands. Just like intents, follows let Alan give a response or send a command. Follows, however, are context-dependent: they can be matched only after the context is activated.

In the follow inside the context, we have a predefined LOC slot. This slot interprets the user input as a location.

We need to modify our webpage. We will add a new command to the command handler and a new function — setAddress() — that will be invoked when this command is received.

tutorial.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Food Delivery Example</title>
    <link href="https://storage.googleapis.com/alan-tutorial/web-sdk/styles.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i" rel="stylesheet" />
</head>

<body>
    <h1>Food Delivery</h1>
    <h3>Menu</h3>
    <div class="menu">
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/pepperoni.jpg"/>
            Pepperoni
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/margherita.jpg"/>
            margherita
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burrito.jpg"/>
            Burrito
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/burger.jpg"/>
            Burger
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/taco.jpg"/>
            Taco
        </div>
        <div class="menu-item">
            <img src="https://storage.googleapis.com/alan-tutorial/web-sdk/applepie.jpg"/>
            Apple Pie
        </div>
    </div>

    <ul id="order"></ul>
    <div id="address"></div>
    <div class="alan-btn"></div>
    <script type="text/javascript" src="https://tutor.alan.app/web/lib/alan_lib.js"></script>
    <script>
        function addItemToCart(name) {
            var li = document.createElement("li");
            var text = document.createTextNode(name);
            li.appendChild(text);
            document.getElementById("order").appendChild(li);
        }

	    // Setting the user address
        function setAddress(address) {
            document.getElementById("address").innerHTML = "Address: " + address;
        }

        var alanBtnInstance = alanBtn({
            key: "3bfbf8d2a24e47b23446ed6112aad3fd2e956eca572e1d8b807a3e2338fdd0dc/stage",
            onCommand: function (commandData) {
                if (commandData.command == "addItem") {
                    addItemToCart(commandData.item);
				// Handling the address command
                } else if (commandData.command == "address") {
                    setAddress(commandData.address);
                }
            },
            rootEl: document.getElementById("alan-btn"),
        });
    </script>
</body>

</html>

Here is how it works: after adding the food, the user says: Checkout. Alan answers with: What is the delivery address? and then activates the whatAddress context. The context captures the location from the user input, sends the address command to the webpage together with the location value, and the address is set in the app.

Step 10: Add several items

What if the user wants to add several items at the same time? To allow the user to do this, let's first modify the command for ordering items in the voice script:

...
const itemList = "pepperoni|margherita|burrito|burger|taco|apple pie";

// Adding items to the order
intent(`(Add|I want|Order) $(ITEM ${itemList})`,
       `(Add|I want|Order) $(NUMBER) $(ITEM ${itemList})`, p => {
    let number = p.NUMBER ? p.NUMBER.number : 1;
    p.play({command: 'changeOrder', item: p.ITEM.value, quantity: number});
    p.play(`Adding ${number} ${p.ITEM.value}`);
});
...

Possible voice commands here are listed using a comma. Now, the user can say: Add burrito or Add five burrito.

To get the number from the user utterance, we have added the predefined $(NUMBER) slot to the command. It is a special slot that can interpret the user input as a number.

Now, when the user orders something, Alan plays a confirmation and sends a command together with the number of ordered items and the item name to the app.

Next, let's modify our webpage to handle this command. We will add a <div> container for the order, the order object and two functions to the script:

  • changeOrder() will change the number of items in the order
  • updateCart() will update the cart

We also need to replace the addItemToCart command with changeOrder in the command handler.

...
    // Order container
	<div id="order"></div>
...
        // Setting the order
		let order = {};

		// Updating the cart
        function updateCart() {
            let html = "";
            for (let key in order) {
                html += `<tr><td>${key}</td><td>${order[key]}</td>`;
            }
            html = `<table border="0">${html}</table>`;
            document.getElementById("order").innerHTML = html;
        }

		// Changing the number of items in the order
        function changeOrder(item, quantity) {
            let number = (order[item] ? order[item] : 0) + quantity;
            if (number <= 0) {
                delete order[item];
            } else {
                order[item] = number;
            }
            updateCart();
        }
...
        var alanBtnInstance = alanBtn({
            key: "a3e24e9d94d2e5aa3446ed6112aad3fd2e956eca572e1d8b807a3e2338fdd0dc/stage",
            onCommand: function (commandData) {
                if (commandData.command === "changeOrder") {
                    changeOrder(commandData.item, commandData.quantity);
                } else if (commandData.command == "address") {
                    setAddress(commandData.address);
                }
            },
            rootEl: document.getElementById("alan-btn"),
        });
...

see full source | try in browser | try in jsfiddle

Now the app accepts the changeOrder command, and we can pass the quantity to it.

However, our script won't accept the correct grammar form: Add five burritos. To fix this, we have two options:

  • We can add plural alternatives like burrito|burritos in the intent.
  • We can use the pluralizer syntax — add an underscore to item names: burrito_.

Let's add the underscore to all of our items:

// menu items
const items = ['pepperoni', 'margherita', 'burrito', 'burger', 'taco', 'apple pie'];

// itemList = "pepperoni_|margherita_|burrito_|burger_|taco_|apple pie_";
const itemList = items.map(i => i + '_').join('|');

// Adding items to the order
intent(`(Add|I want|Order) $(ITEM ${itemList})`,
       `(Add|I want|Order) $(NUMBER) $(ITEM ${itemList})`, p => {
    let number = p.NUMBER ? p.NUMBER.number : 1;
    const item = items.find(i => p.ITEM.startsWith(i));
    p.play(`Adding ${number} ${p.ITEM.value}`);
    p.play({command: 'changeOrder', item: item, quantity: number});
});
...

see full source | try in browser | try in jsfiddle

The user can now say: Add five burgers.

Step 11: Remove items

Now we will implement removing items. To remove items, we just need to pass negative numbers to the changeOrder() function:

...
// Removing or updating order items
follow(`(Remove|Delete) $(ITEM ${itemList})`,
       `(Remove|Delete) $(NUMBER) $(ITEM ${itemList})`, p => {
    const item = items.find(i => p.ITEM.startsWith(i));
    let deleteQnty = p.NUMBER ? p.NUMBER.number : 1;

    p.play('removing ' + p.ITEM.value);
    p.play({command: 'changeOrder', item: item, quantity: -deleteQnty});
});
...

see full source | try in browser | try in jsfiddle

Now the user can add and remove items.

Step 12: Remove items - improvements we can make

We can remove items, but have another requirement: if the user tries to remove an item that is not present in the order, we need to answer: This item isn’t in your order. To implement this, we need to know the state of the order on the script side.

To pass some information from the app to the Alan script, we have two options:

  • Pass visualState — we will use this option at this step.
  • Invoke a script function using projectAPI and pass parameters to it. projectAPI is described later in this tutorial.

With visualState, we can pass to the Alan script some object that describes our app state. On the app side, we will add the sendVisualState() function that will get the order state. Let's modify the webpage:

...     // Sending visual state to the voice script
        function sendVisualState() {
            alanBtnInstance.setVisualState({order});
        }

        function updateCart() {
            let html = "";
            for (let key in order) {
                html += `<tr><td>${key}</td><td>${order[key]}</td>`;
            }
            html = `<table border="0">${html}</table>`;
            document.getElementById("order").innerHTML = html;
			
			// Calling the sendVisualState() function
            sendVisualState();
        }
...

see full source | try in browser | try in jsfiddle

In the voice script, passed objects are available in the p.visual runtime variable. We can use p.visual.order in our script to check if we have ordered anything or not:

...
follow(`(Remove|Delete) $(ITEM ${itemList})`,
       `(Remove|Delete) $(NUMBER) $(ITEM ${itemList})`, p => {
    let order = p.visual.order || {};
    const item = items.find(i => p.ITEM.startsWith(i));
    if (!order[item]) {
        p.play(`${p.ITEM.value} has not been ordered yet`);
    } else {
        let quantity = order[item] ? order[item] : 0;
        let deleteQnty = p.NUMBER ? p.NUMBER.number : quantity;

        if (quantity - deleteQnty <= 0) {
            p.play('Removing all ' + p.ITEM.value);
        } else {
            p.play('Updating ' + p.ITEM.value);
        }
        p.play({command: 'changeOrder', item: item, quantity: -deleteQnty});
    }
});
...

see full source | try in browser | try in jsfiddle

Step 13: Check the order details

We want Alan to describe what we have in our order. Let's add a new intent:

...
// Playing order details
intent(`(My order|Order details|Details)`, p => {
    p.play("You have ordered:");
    for (let product in p.visual.order) {
        p.play(p.visual.order[product] + " " + product);
    }
});
...

see full source | try in browser | try in jsfiddle

Now the user can get information about his or her order. But what if the user hasn't ordered anything yet? In this case, we can do either of the following:

  • Add an if statement in the function and check the size of p.visual.order
  • Add another intent and filter the intents by visual state
...
// Playing order details
intent(visual(v => !_.isEmpty(v.order)), `(What are|) my order details`, p => {
    p.play("You have ordered:");
    for (let product in p.visual.order) {
        p.play(p.visual.order[product] + " " + product);
    }
});

intent(visual(v => _.isEmpty(v.order)), `(My order|Order details|Details)`, p => {
    p.play('You have not ordered anything.');
});
...

see full source | try in browser | try in jsfiddle

The filter function is passed as the first parameter to the intent. We have added a function that accepts the visual state as the parameter. Now users will get the correct answer if their orders contain no items.

Step 14: Get the balance

Every user of our app has a dollar balance for his or her order. We need to show it on the page when the user logs in and allow the user to ask about the balance with voice.

To do this, let's modify our webpage. We will add the login form and login() function. This function will get the values we enter in the Enter Username and Enter Password fields, send a request to the server with the serverRequest() function and get an authentication token. The token will be saved to the token variable.

...
    <div id="login">
        <form onSubmit="login()">
            <label for="username">Username</label>
            <input id="username" type="text" placeholder="Enter Username">
            <label for="password">Password</label>
            <input id="password" type="password" placeholder="Enter Password">
            <input type="submit" value="Login" />
        </form>
    </div>
...
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
...
        function serverRequest(method, data, callback) {
            $.ajax({
                type: 'POST',
                url: 'https://tutor.alan.app/api_playground/' + method,
                crossDomain: true,
                data: JSON.stringify(data),
                dataType: 'json',
                success: callback,
                error: () => alert('POST failed')
            });
        }

        function login() {
            event.preventDefault();
            var username = document.getElementById('username').value;
            var password = document.getElementById('password').value;
            serverRequest(
                'tutorialLogin',
                {username: username, password: password},
                function(res) {
                    if (res.error) {
                        alert(res.error);
                        return;
                    }
                    let token = res.token;
                    document.getElementById('login').innerHTML = 'Hello, ' + username;
                }
            );
        }

        var alanBtnInstance = alanBtn({
...

see full source | try in browser | try in jsfiddle

Now we can log in with the alan username and alan password.

To get the balance with voice, we need to pass the obtained access token from the page to the script. We can make it with visualState as we did before, or with projectAPI. Let's implement a function with projectAPI. As well as visualState, projectAPI allows you to send some data from the app to the Alan script.

To the voice script, add the following code. Here we are defining the setToken method that we can call later from our app:

...
projectAPI.setToken = function(p, param, callback) {
    if (!param || !param.token) {
        callback("error: token undefined");
    }
    p.userData.token = param.token;
    callback();
};
...

see full source | try in browser | try in jsfiddle

Let's get back to our webpage:

  • First, we will add a <div> container for the balance value.
  • Next, we will invoke the projectAPI method from the webpage. From the login() function, we will call the setToken() method and pass an object with the token data to the script.
...
    // Balance container
    <div id="balance"></div>
...
        function login() {
            event.preventDefault();
            var username = document.getElementById('username').value;
            var password = document.getElementById('password').value;
            serverRequest(
                'tutorialLogin',
                {username: username, password: password},
                function(res) {
                    if (res.error) {
                        alert(res.error);
                        return;
                    }
                    let token = res.token;
					
					// Invoking the projectAPI method
                    alanBtnInstance.callProjectApi("setToken", {token: token}, (err) => { if (err) alert(err)});
                    document.getElementById('login').innerHTML = 'Hello, ' + username;
                }
            );
        }
...

see full source | try in browser | try in jsfiddle

Here is how it works: when the user logs in to the app, the setToken() method passes the token to the app. On the app side, the token is saved to the p.userData object, and we can access it with p.userData.token. Similar to p.visual, we can store some data received from the app in this object.

The next step is to get the balance for the user. Let's get back to our voice script.

To get information about the current user's balance, we will make an API call to an external server from the voice script. We will use the request library to do this. To the script, add the following:

...
function getBalanceFromServer(token, callback) {
    let req = {
        url: "https://tutor.alan.app/api_playground/tutorialBalance",
        method: 'POST',
        json: {
        "token": token
        }
    };
    api.request(req, (err, res, body) => {
        const error = err || body.error;
        if (error) {
            callback(error);
        }
        callback(null, body.balance);
    });
}

projectAPI.getBalance = function(p, param, callback) {
    getBalanceFromServer(p.userData.token, callback);
}

intent('(What is my|) balance', p => {
    if (!p.userData.token) {
        p.play("Please log in to get balance");
        return;
    }
    getBalanceFromServer(p.userData.token, (err, balance) => {
        if (err) {
            p.error(err);
            p.play('error');
            return;
        }
        p.play("Your balance is " + balance);
    });
});

see full source | try in browser | try in jsfiddle

This code works in the following way: when the user asks: What is my balance?, we check the token received from the app. If the token is not available, Alan offers the user to log in to the system. If the token is available, Alan uses the getBalanceFromServer() function to make an API call to the external server and get the user's balance. The balance is then played back.

One more update is needed here — we want the balance to be displayed on the webpage when the user logs in.

In the code above, we have also added the projectAPI.getBalance() method. With projectAPI, we can not only pass data to the voice script, but also perform some logic without a voice command. Here we are using this method to illustrate the latter scenario. With it, we will request the balance when the user logs in to the app and display the balance on the webpage.

Let's update the webpage. We will add the requestBalance() function where we will call the projectAPI.getBalance() method. The requestBalance() function itself is called from the login() function.

...
    function login() {
        event.preventDefault();
        var username = document.getElementById('username').value;
        var password = document.getElementById('password').value;
        serverRequest(
            'tutorialLogin',
            {username: username, password: password},
            function(res) {
                if (res.error) {
                    alert(res.error);
                    return;
                }
                let token = res.token;
                alanBtnInstance.callProjectApi("setToken", {token: token}, (err) => { if (err) alert(err)});
                
				// Calling the requestBalance() function
				requestBalance();
                document.getElementById('login').innerHTML = 'Hello, ' + username;
            }
        );
    }

    // Requesting the balance
	function requestBalance() {
        alanBtnInstance.callProjectApi("getBalance", {}, (err, balance) => {
            if (err) {
                alert(err);
                return;
            }
            document.getElementById('balance').innerHTML = "Your balance is: " + balance;
        });
    }
...

see full source | try in browser | try in jsfiddle

Now we can see the balance on the page and can ask for it with voice:

Step 15: Set the delivery time

What if the user wants to have a delivery at specific time? Let's modify our page to add a command for setting the time:

...
    // Containers for address and time
    <div id="address"></div>
	<div id="time"></div>
...
	<script>
        let address, time;
...
        function sendVisualState() {
            alanBtnInstance.setVisualState({order, address, time});
        }
...
        function setAddress(deliveryAddress) {
            address = deliveryAddress;
            document.getElementById("address").innerHTML = "Address: " + address;
            sendVisualState();
        }

        function setTime(deliveryTime) {
            time = deliveryTime;
            document.getElementById("time").innerHTML = "Delivery time: " + time;
            sendVisualState();
        }
...
            onCommand: function (commandData) {
                if (commandData.command === "changeOrder") {
                    changeOrder(commandData.item, commandData.quantity);
                } else if (commandData.command === "address") {
                    setAddress(commandData.address);
                } else if (commandData.command === "time") {
                    setTime(commandData.date + " at " + commandData.time);
                }
            },
    ...
	</script>
...

see full source | try in browser | try in jsfiddle

And the voice script to add a new context for the delivery time:

...
// Requesting the delivery time
let whatTime = context(() => {
    follow('$(TIME)', '$(T now|asap|right now|as soon as possible)', '$(DATE)', p => {
        let time, date;
        if (p.T) {
            // Delivering in 30 minutes
            date = api.moment().tz(p.timeZone).format("MMMM Do");
            time = api.moment().tz(p.timeZone).add(30, 'minutes').format("h:mm a");
        } else if (p.TIME) {
            p.state.time = time = p.TIME.value;
            if (p.state.date) {
                date = p.state.date;
            } else {
                p.play("What date?");
                return;
            }
        } else if (p.DATE) {
            p.state.date = date = p.DATE.moment.format("MMMM Do");
            if (p.state.time) {
                time = p.state.time;
            } else {
                p.play("What time?");
                return;
            }
        }
        if (date && time) {
            p.play({command: "time", time: time, date: date});
            p.play(`We will deliver your order to ${p.visual.address}. Delivery date is ${date} at ${time}`);
        }
    });
});

// Requesting the delivery address
let whatAddress = context(() => {
    follow('$(LOC)', p => {
        p.play({command: "address", address: p.LOC.value});
        p.play("What is delivery time?");
        p.then(whatTime);
    });
});
...

see full source | try in browser | try in jsfiddle

Here we are adding one more context — whatTime — and activating it from whatAddress. In Alan, this is the recommended activation flow: rather than putting several then() functions in one intent, you should activate one context from another one.

To parse the date and time from the user request, we are using the $(TIME) and $(DATE) predefined slots. Also we are using api.moment — the moment NodeJS library.

In your app, you can make an order, add or delete items from it, or ask Alan to describe what is in the order. Then, our dialog flow allows us to check the balance and select a delivery address and time.

Congratulations! You have created a food delivery app and added a conversational voice experience to it!