How to prototype product value — part 2
By: Aric Cheston
Tags: Prototyping, Low Code, UX, Dialogflow, Chatbots

How to prototype product value — part 2

In case you haven’t read the previous article in this series, here’s the gist: I wanted to build myself a chatbot, Magpie, that would save all the books, movies, podcasts, TV, and music that people recommended to me. And when I wanted something new to watch, read, or listen to, the chatbot would then give me lists of all the stuff I’d saved. I wanted all of this to happen over text messaging because the last thing I needed was another app or website to go to.

I was inspired by Robin Sloan’s article ‘An app can be a home-cooked meal’ which argues that you (and should) can put effort and love into a product intended for a very small number of people.

Even so, the first order of business was to test if this is even a good idea. What follows is a walk through of how I created a prototype to discover if the idea has enough utility to be worth the effort of building.

The approach to this guide is to explain how Magpie was built, the basic concepts of how the component pieces fit together, and how data moves across them. The approach and concepts can be applied to any prototype that you may want to put together to test the utility of a concept.

Instead of giving you step-by step instructions, I’ll link to the resources that I used to do specific tasks. I will provide code snippets with explanation and walk through the things I figured out myself that will be useful for your projects.

While the emphasis is on relying on low, or no-code, tools, this tutorial does assume that you’re familiar with basic coding concepts like Variables, Functions, and Arrays, and some working knowledge of Javascript. As a side note/rant, I do think basic familiarity with coding and development practices are table stakes for any UX designer practicing today. As someone who hires a lot of designers, I’m increasingly uninterested in candidates that work in complete isolation from the means of production.

Getting Started

To build the core functionality for Magpie I used Google’s DialogFlow, Firebase Realtime Database, and CloudRun for application hosting. For all of these, you simply need a Google account. The same one you use for Gmail will get you access to all of these. I suggest you bookmark the links above, you’re going to be visiting them a lot. I used Twilio for the SMS interface, but we’ll get to that later.

Useful Tools

Since there was some code to write, I found it convenient to test code for features that didn’t depend on interaction with the chatbot on a node.js server on my machine. This included testing interactions with the Firebase database and Twilio. I found Visual Studio Code to be pretty handy and straightforward to use. It often makes helpful corrections to the syntax of my code that add a shine to the otherwise complete car crash of hackery that is my work.

You can download Visual Studio Code here for free. This is a useful tutorial on setting up Node.js on your machine. The Node.js bit isn’t required, but it’s worth the investment of time if you’re doing anything reasonably complex.

I am not a particularly organized thinker. Apt metaphors for my ability to concentrate on only one thing at a time include: an adolescent sparrow with an amphetamine addiction, a small child made of sugar, an acrophobic astronaut with fears of flying and small spaces.

Therefore, Trello (also free) was indispensable in keeping me more or less on track. I used it to capture, organize and prioritize the tasks I needed to do to move the project forward. It was also a great place to capture ideas and distill them into implementable features.

Given my shockingly terrible coding ability, Stack Overflow and W3Schools were essential to solving coding problems. Stack Overflow was a great resource for figuring out how to work with Firebase and W3Schools filled in the (huge) gaps in my knowledge of Javascript.

Finally, fear not the command line. In a couple of instances throughout this process, you may have to perform tasks using the command line interface on your machine. For Mac users this is the Terminal that you can find under Utilities in your Applications folder. Windows users, you have, apparently, many, many paths to the command line on your machine.

While this can be intimidating, do not be dissuaded. The command line is simply used to set up resources you’ll need locally on your machine, and to communicate directly with services you’ll depend on for the project. Fortunately, there are very straightforward step-by-step walkthroughs to guide you through the moments where you’ll need to rely on the command line.

This tutorial covers how to create the basic features that make up Magpie: saving something, retrieving the list of things saved, selecting something, and archiving something. I’ll start by walking you through setting up the project.

Setting up

First, create your DialogFlow project.

Start by going to the DialogFlow console here. You’ll be able to login with you personal (or burner) Google account.

You’ll start in the DialogFlow Essential environment. There’s a new offering called DialogFlow CX that Google characterizes as providing “an advanced agent type suitable for large or very complex agents.”. That’s not what we’re doing here, so stick with the Essentials — which is fairly good life advice generally.

Select one of the ‘Create Agent’ buttons. This launches the interface for creating an agent. All you have to do here is name your agent, then select ‘Create’.

Here’s a useful video that will walk you through the process of setting up your agent and introduces you to the basic process for creating your chatbot — the term used here is ‘Agent’.

Dialogflow Chatbot basics.

Next, create your Firebase Realtime Database

The database is where you’ll store the data captured by the chatbot. Fortunately, both DialogFlow and Firebase are part of the Google Cloud Platform, so working between them is very easy. It’s also pretty straightforward to set up a database on Firebase. We’re using the Realtime database, not the Cloudstore database. So, be sure to choose that one.

To set up the Realtime Database start by going here to create a new Firebase project. You’ll have to name it, I suggest choosing the same name as your DialogFlow agent for the sake of simplicity. It’s also useful to bookmark the link above. You’ll see frequent references to your Google Cloud Console. This is that — it’s where you can find all your projects. So if you find yourself deep in the GCP forest, lost, exhausted, with your underpants wrapped around your head, you can always return here to start from the top.

Select Realtime Database from the left-hand nav, then click on the ‘Create Database’ button. This will create the database you’ll use to save the data your chatbot collects.

Finally, get integrated

To integrate your DialogFlow Agent with you Firebase Realtime Database, you’ll need certain identifying information for your Database. Again, because DialogFlow and Firebase are both part of the Google Cloud Platform, integration between the two is pretty straightforward. Incidentally, I’m not hyping Google in particular here. I’d imagine that working with AWS’s Lex and database options are similarly easy, as are the equivalent Microsoft offerings.

To get the identifying information, go to your Firebase console, and select the project you created. This will take you the main console page for that particular project. You’ll see options for adding Firebase to different types of apps. We’re going to choose the web app option ‘</>’, because that’s what we’re doing here. You’ll be asked to name your app. Again, I recommend just naming it whatever you’ve named your Agent. You can skip the option to add Firebase Hosting. It’s not necessary for a prototype like this one.

You’ll land on a page that offers a code snippet that you’d need to access Firebase services if you were building a standalone web app, which we’re not. Instead, the information we care about here is simply the databaseURL field. You can copy and save that for later now, or you can always find it under the project settings link.

Ok! Now that you have your Agent, your database, and the information you need to connect them, the next thing we’ll do is put them to work saving something.

Saving something

This video was crucial to get me started.

Reading and writing data with Dialogflow and Firebase

The key points you need to take away from it are as follows:

How to create Intents. The concept of Intents is a big one in the DialogFlow system. It’s and apt term, you can think of this as the task your user is tying to accomplish. In constructing your Agent, you create Intents for each of those tasks. For each Intent, you’ll need to supply training phrases. The training phrases should encompass all the ways someone might express what they’re trying to do. As you’ll find, the more complex your Agent, the more (and more specific) Intents you’ll create and the more training you’ll need to do. This can get out of hand pretty quickly, so for prototypes, it’s important to reign it in and settle on some really basic Intents that represent the fundamental functionality you’re exploring.

How to use the DialogFlow Inline Editor. This is a pretty important piece of the puzzle. DialogFlow presents two ways to integrate your application logic (the code that determines what happens and when). You can find them both in the Fulfillment section of your DialogFlow console. The InLine Editor is intended for prototyping and scripting really basic interactions. That said, I definitely pushed it to its limits and hacked my way toward reasonably sophisticated interaction. The alternative method is to create a webhook — essentially an application hosted on the web. The process for creating a web hook is well-documented and worth the trouble if you’re doing anything that interacts with multiple services or requires access to the full DialogFlow API. Given we’re talking about prototypes here, the InLine Editor should get you what you need.

How to establish the link between your DialogFlow Agent and your Firebase Realtime Database. This is where you’ll make use of the identifying information for your Firebase Database that you created above. As the video shows it’s simply a matter of establishing the connection to Firebase and pasting your database’s url, with some slight modification, into the app initialization script.

How to map Intents to Functions. A key concept you should understand is that the Intents you create in DialogFlow need to be associated with the action that you want to happen which are expressed as functions. The bit of code you’ll need to be concerned with is “intentMap.set(‘INTENTNAME’, FUNCTIONNAME);”. This links the Intent you created (INTENTNAME) with the function (FUNCTIONNAME) you’ll write elsewhere in the InLine Editor. To make things easy on myself, I made the Function names slight variations of the names of the Intents. The function contains the actual instructions for what you want to happen when the Intent is triggered. In the case of the video, the functions are writing (saving) and reading (retrieving) the data captured in the Intent to and from the Firebase database.

How to set up the data structure in the Firebase Realtime Database. Before you can add data to the database, you’ll need to set up the basic hierarchy of the data you want to save. Data structure is a dark art with different strategies and approaches that are beyond the scope of a prototype like this one. Just know that how you set up the data has a lot of bearing on the functionality that you can create.

How to write to the Firebase Realtime Database. What you’ll see in the video is the construction of a function that writes data to specific locations in the database. The function contains the path to the location where the data will be stored. A really important note is that the function is “setting” the data in specific and static location. A separate function is reading that data by following that same path to that location. In our prototype we’re doing something pretty different in that we’re adding to the data saved rather than updating data in a static structure. As you’ll see, this requires a slightly different approach than what we see in the video.

A really important DialogFlow concept that the video does not cover is that of Entities. If Intents are the verbs of the DialogFlow system, Entities are the nouns. They are the specific data that you Agent needs to be able to recognize to create the functionality you’re looking for. Just as you will train the Agent to recognize user Intent, you will train the Agent to pick out the Entities from user input. A lot of the value of DialogFlow comes in the form of a very large library of pre-trained Entities covering common data types like dates, locations, and names. You can find the list of pre-trained Entities here. You will still likely need to create and train custom Entities. Fortunately, this is a pretty straightforward exercise.

To create and train your Entities choose “Entities” from the main navigation on your DialogFlow console. You establish an Entity by entering it’s name then any synonyms for that Entity. You then refer to these Entities when you construct your Intents. This all becomes clear below. To learn more about Entities and how to train and use them, it’s helpful to watch this video .

Creating the ‘Save’ Intent

With those concepts in mind, I was able to piece together the functionality I wanted. To start, I created a single Intent, ’SaveThing’, to, well, save things. What I was after was something like this:

User: Save the movie Pain and Glory

Agent: Ok! The movie Pain and Glory is saved.

Saving a book.

The input text needs to be really short and I want to get a confirmation back that thing I wanted to save was saved successfully.

SaveThing does exactly what it sounds like, it detects when a user is trying (intends) to save something. I trained it on phrases like “Save the movie Pain and Glory” and variants like “Save Pain and Glory movie”. The intent looks for three custom Entities:

“Title” is the name of the thing being save — “Pain and Glory”. “Type” is the kind of thing being saved — “Movie”. The training of Entities includes all the ways a user might refer to that Entity. You do this by supplying synonyms for the Entity. For example, for the Entity “Movie” I included defined synonyms like “film”, “flick”, “documentary” to capture the variety of likely ways someone might refer to a movie.

“Author” refers to whom made the thing. This allowed me to capture the director of films, the author of books, the band that made an album. For example, the phrase “Save the film Pain and Glory by Pedro Almodovar” recognizes Pedro Almodovar as the “Author”.

To tell the Intent to look for your Entities, you need to enter them as Parameters in the the Actions and Parameters pane of the Create Intent screen. You’ll need to define a Parameter Name, choose the Entity and indicate the Value as you see below.

Assigning names to parameters

All three of these Entities indicate data that I will save in the Firebase Realtime Database. An important, not to be missed point is that you can indicate which Entities are required for the Intent to be successfully interpreted and the data captured. If a required Entity is not detected in the users input phrase, your Agent can follow up with a request for that specific data. You can indicate which Entities are required in the Actions and Parameters area of the Create Intent screen by simply clicking the Required checkbox texted to the Entity. For the SaveThing Intent, I set “Title” and “Type” as required Entities. Here’s an example of this at work:

User: “Save Pain and Glory”

Agent: “What type of thing is this?”

User: “Film”

Agent: “Ok! The movie Pain and Glory is saved.”

I imagined this to be useful in that a user is likely capturing the movie name in the middle of a conversation, or reading about it, or passing a poster, or hearing about it on a podcast. Building flexibility into the required input will accommodate greater variety of user situations.

With this first Intent constructed and trained with phrases and Entities, the next step is to set up the structure of the database to the data has someplace to go. The video above explains the basic mechanics of creating nodes. Here’s the structure I went with.

Really simple data structure

You can see that I kept things super simple with a top level node called “Thing” that holds all the things that the user saves. Child nodes are the different categories of things; “books”, “movies”, “podcasts”, “TV”, and “music”. Within those are the titles that we’re capturing in the Intent.

Something that will likely give you pause are the “push keys” you’ll see as nodes between the category node and the actual titles. These are an artifact of the realtime nature of the Firebase Realtime database. The keys are dynamically generated to give each addition to the database a unique, time-stamped, identifier.

save the data captured by the Intent in the Firebase Realtime Database. Here’s where you’ll have to write a bit of code using the Inline Editor.

First, you’ll have to scroll to the Fulfillment pane at the bottom of the Create Intent screen and click on the “Enable webhook call for this intent” toggle. Next, you’ll navigate to the Inline Editor in the Fulfillment section of the DialogFlow console to write the function that will save the data captured in the Intent to the database.

I wrote the function in pretty much straight javascript. Here’s a simplified version of the function to illustrate how the data is saved:

function handleSaveThing(agent){

const title = agent.parameters.title;

const type = agent.parameters.type;

agent.add(‘Great! The ‘ + type + ‘ “‘ + title + ‘“‘ + ‘ has been saved.’)

return admin.database().ref( ‘/thing/’ + type).push({

title: title

});

}

The first task is create variables that store the Entity data captured as parameters in the SaveThing Intent.



const title = agent.parameters.title;

const type = agent.parameters.type;

Next, we create the feedback to the user that their input has been understood and captured.

agent.add(‘Great! The ‘ + type + ‘ “‘ + title + ‘“‘ + ‘ has been saved.’)

And finally, we save the data to the database. We have to specify the path to the location in the database we want to save too. Once the values from the variables are passed into it, the path translates to “YOURDATABASE/thing/TYPEOFTHING:

return admin.database().ref( ‘/thing/’ + type).push({

title: title

});

An important distinction from the code above from what you saw in the video is the use of ‘push’ instead of ’set’ when adding the data to the database. ’Set’ will change the value of a pre-existing node in a static data structure. “Push” adds new nodes, and this is what generates the Push Keys discussed above. Since we’re collecting data instead of changing set values, “Push” is what we need to use. While the Push Keys are useful, they do create some complications for retrieving data from the databases. We’ll dig into that in the next section.

The last thing you’ll need to do is to map your Intent to the Function you’ve written as described above:

intentMap.set(‘saveThing’, handleSaveThing);

OK! With the Intent trained, the code written, and the database set up, you can test the function using the the Test Console on the right of the DialogFlow interface. It should go a little something like this:

You: Save the movie Pain and Glory

Agent: Great! The movie Pain and Glory is saved.

And you should see the movie title with its associated push key saved under a Movie node in your Firebase Realtime database. Trying it again with another title will add another push key/title under that same Movie node.

Retrieving something

To use DialogFlow to get something out of the database requires the same elements as putting something in: an Intent and a Function. While I created a single “saveThing” Intent, I chose to make individual Intents for retrieving each kind of thing saved. This was to accommodate the fact that the types of user inputs would be different for each media type. For example, a user might ask “What book should I read?”, or say “give me a book to read”, or “show me books I saved” to view the books they’ve saved. To see their movies, they could say “I want to watch a movie”, “What movie should I watch?”, or “Show me my movies”. Each of these could conceivably elicit different responses and therefore merit their own Intents. However, for the sake of getting to a working prototype sooner, I collapsed them down to one intent per media type. The experience I wanted to create was:

User: I want to watch a movie

Agent: Here are the movies you’ve saved: Pain and Glory, The Wild Ones, Portrait of A Lady on Fire, Pierrot Le Fou.

Or:

User: What book should I read?

Agent: Here are the books you’ve saved: Earthsea, Dog of the South, London Fields, Minima Moralia, The Entrepreneurial State.

Following the same procedure as saveThing, I created a “getMovie” Intent and trained it on phrases like, “What movie should I watch”, “I want to watch a movie”, “What movies have I saved?”.

Just as I had to specify where in the database I wanted to save something, I needed to tell the function where to look for the information I wanted to retrieve. This is were the push keys became troublesome. Since push keys are dynamically generated, I had no idea what the path to the data was going to be.

Fortunately, I’m not the only one to encounter this particular pickle. After a fair amount of artful googling I came across this Stack Overflow post that gave me the clues I needed. Here’s the code that I derived from it:

function handleGetMovie(agent) {

var ref = admin.database().ref(source + ‘/thing/movie/’);

var recList = [];

return ref.once(‘value’, function(snapshot){

snapshot.forEach(function(movie){

recList.push(‘ ‘ + movie.val().title);

});

agent.add(‘Here are the movies you’ve saved “‘+ recList + ‘ “ would you like to check one of these out?’ );

});

}

This function cycles through the Movie node in the Firebase Database, isolates the values in the title field, and push them into an array, “recList”, that is then inserted into the Agent response.

Selecting Something

While using early versions of the prototype I realized two things. First, that there’s limited utility in just getting seeing the list of things you’ve saved. It would be so much more useful to be able to act on that information. So, I added a feature that lets me select an item from the list that the Agent retrieves and gives me a link to where I can find it:

Agent: Here are the movies you’ve saved: Pain and Glory, The Wild Ones, Portrait of A Lady on Fire, Pierrot Le Fou.

User: I want to watch Portrait of A Lady on Fire.

Agent: Enjoy! (Link to Portrait of A Lady on Fire on Reelgood.com)

The second realization was that selecting something from the list by typing in the title is kind of a pain in the butt, especially since you’re likely to type it incorrectly. I addressed this by reformatting the titles the agent returns as a numbered list. To select an item, the user simply enters the number of the item and the Agent then returns the link:

Agent: Here are the movies you’ve saved: 1. Pain and Glory, 2. The Wild Ones, 3. Portrait of A Lady on Fire, 4. Pierrot Le Fou.

User: 3.

Agent: Enjoy! [Link to Portrait of A Lady on Fire on Reelgood.com]

Getting movies and selecting one.

To do this I had to add some additional operations to my getMovie function and create a new Intent and Function pair to handle the selection itself.

To create the numbered list I created a new variable, “i”, and set the value to 1. I adjusted the code for the values that are pushed into the “recList” array to read :

recList.push(` ${i++}. ${movie.val().title}`);

The value of “I” increases with every loop through the values stored in the Movie node of the Database, producing a numbered list. Here’s the updated code:

function handleGetMovie(agent) {

var ref = admin.database().ref(source + ‘/thing/movie/’);

var recList =[] ;

var 1 = 1;

return ref.once(‘value’, function(snapshot){

snapshot.forEach(function(movie){

recList.push(` ${i++}. ${movie.val().title}`);

});

agent.add(‘Here are the movies you’ve saved “‘+ recList + ‘ “ would you like to check one of these out?’ );

});

}

The next step was to put that numbered list contained in the “recList” array into the Database itself so it could be accessed by the selection Intent/Function. I created a new node in the database, “hold”, containing the field “titles”, to hold those values in place. I then created another variable “theTitles” which would capture the titles separate from the numbered list. I changed the Function to set the “title” field in the ‘hold” node to equal the value of “theTitles” variable. Since the function loops for each value in the “movie” node, the result is a list of the movie titles in the same order as they’re presented to the user by the Agent. The “hold” node in the Database will look like this:

The “Hold” node in the database

The updated “handleGetMovie” function looks like this:

function handleGetMovie(agent) {

var ref = admin.database().ref(source + ‘/thing/movie/’);

var recList = [];

var theTitles = [];

var i = 1;



return ref.once(‘value’, function(snapshot){

snapshot.forEach(function(movie){

recList.push(` ${i++}. ${movie.val().title}`);

theTitles.push(movie.val().title);

});

agent.add(‘Here are your most recent movies: ‘+ recList + ‘ . Choose which you would like to see by typing the number of the item.’);

admin.database().ref(source + ‘/hold’).set({

titles: theTitles

});

});

}

Next I created a new Intent called “chooseMovie”. I trained it with phrases like “I’ll try 2”, “Give me 4”, and simply, “5”. In the Actions and Parameters pane, I created a new required Parameter called “selection” to hold the number of the title selected. I used one of the pre-trained entities, “@sys.number” to define what it was.

I named the Function “handleChooseFilm”. I needed it to do two things: first was to access the title that corresponded with the numeric input from the user and captured as the “selection” parameter value. Second, was to take that title and place it into a url string that would be passed back to the user in the form of a clickable link to the movie they want to see.

To get the title, I created a variable, “numSelect” that stored the selection number that I could then use to form the path to the desired title. However, you’ll notice that in the screenshot of the ‘hold’ node that the numbering of the titles starts from zero. So, to get the correct title, I created a second variable “whichTitle” that is simply numSelect minus 1. The resulting title is returned as a variable “theTtitle”. The operation looks like this:

var numSelect = agent.parameters.selection;

var whichTitle = numSelect — 1;

return admin.database().ref(source + ‘/hold/titles/’ + whichTitle +’/’).once(‘value’).then(function(snapshot)

{ var theTitle = (snapshot.val());} });

To create and return the url, I had to reconfigure the title into a string that conformed to the formatting of search queries on Reelgood.com. You can see how this is done by searching for something on the site, and looking at the url of the search results page. I added this into the Function:

var newString = theTitle.replace(/ +/g, “+”);

agent.add(‘Enjoy! ‘ + ‘https://reelgood.com/search?q='+ newString);

The final Function looks like this:

function handleChooseFilm(agent) {

var numSelect = agent.parameters.selection;

var whichTitle = numSelect — 1;

return admin.database().ref(source + ‘/hold/titles/’ + whichTitle +’/’).once(‘value’).then(function(snapshot) {

var theTitle = (snapshot.val());

var newString = theTitle.replace(/ +/g, “+”);

agent.add(‘Enjoy! ‘ + ‘https://reelgood.com/search?q='+ newString);

});

}

Let’s talk about Context

This seems like a good moment to touch on about another DialogFlow concept: Context. As you can see from what we’ve covered so far, DialogFlow calls upon discreet bundles of Intents and Functions to interpret user input and perform the corresponding task. Very often, as in the example of selecting something from a list of items you’ve saved, an Intent relies on the output of the Intent/Function called before it. The Agent needs to know how the Intents are related so it can call the correct one at the correct time in the conversation. Here’s a much more [cogent explanation].

In the getMovie Intent I set the output contexts to be “chooseaMovie” and “removeaMovie”. When constructing the chooseMovie Intent, I set the input context as chooseaMovie and set removeaMovie as the input for the archiveMovie Intent. This aides the Agent in inferring which Intent to call.

Archiving

Once I was able to start successfully saving things, it became pretty clear that getting rid of things from the lists that I’d read, watched or listened to was just as important. The list quickly becomes pretty long and unwieldy. I didn’t want to simply delete the titles, there seemed to be some interesting things one could to do with a record of all the things you’ve read, watched or listened to. So, I decided to simply move things to archive node.

The interaction I was after was this:

User: I want to watch a movie.

Agent: Here are the movies you’ve saved: 1. Pain and Glory, 2. The Wild Ones, 3. Portrait of A Lady on Fire, 4. Pierrot Le Fou.

User: I’ve already see 3.

Agent: Ok. Portrait of A Lady on Fire has been archived.

Archiving a movie.

The problem to solve, as ever, was to be able to define the path to the item I wanted to archive. The answer was to capture the Push Keys of the titles when I generated the list in the course of the getMovies Intent, then save the Push Keys in a place that I could easily retrieve the right one when I wanted to specify a title to archive.

First I created two new node in the database. The first “archive” was on the same level as “thing” and was the destination for all things archived. The second, “keys” was in the “hold” node and was where I was to store the Push Keys.

I then added a new variable to the handleGetMovie function:

var theKeys = [];

Then I amended the operations of the function so that each of the keys would be added to the “hold/keys” node at the same time as the title were. The function now looked like this:

function handleGetMovie(agent) {

var ref = admin.database().ref(source + ‘/thing/movie/’);

var recList = [];

var theTitles = [];

var theKeys = [];

var i = 1;



return ref.once(‘value’, function(snapshot){

snapshot.forEach(function(movie){

recList.push(` ${i++}. ${movie.val().title}`);

theTitles.push(movie.val().title);

theKeys.push(movie.key);

});

agent.add(‘Here are your most recent movies: ‘+ recList + ‘ . Choose which you would like to see by typing the number of the item.’);

admin.database().ref(source + ‘/hold’).set({

titles: theTitles,

keys: theKeys

});

});

}

I then created an Intent “archiveMovie” and train it on phrases like: “archive 3”, “I’ve already seen 2”. The function “handleArchiveMovie” uses the same number selection operation as handleChooseMovie, removes the node of the selected title, and writes the same value into the archive node. Here’s the code:

function handleRemoveMovie (agent) {

var numSelect = agent.parameters.selection;

var whichKey = numSelect — 1;

return admin.database().ref(source + ‘/hold/keys/’ + whichKey).once(‘value’).then(function(snapshot) {

var theKey = (snapshot.val());

console.log(theKey);

admin.database().ref(source + ‘/thing/movie/’ + theKey).remove();



return admin.database().ref(source + ‘/hold/titles/’ + whichKey).once(‘value’).then(function(snapshot) {



var theTitle = (snapshot.val());

agent.add(‘OK! The movie “‘ + theTitle + ‘“ has been moved to your archive.’);



var archiveTitle = admin.database().ref(source + ‘/archive/movie’).push({

title:theTitle });

});

});

}

Connecting to the Agent to Twilio

A key part of this project was to make the Agent accessible via SMS. Like I described, I wanted it to be as close to hand as possible, and the native text messaging app is easily the most common, most used application on anyone’s phone.

Previously, DialogFlow had offered a one-click integration with Twilio, but has since deprecated it in favor of step-by-step instructions that you can find here. It’s a reasonably straightforward process and it’s well documented. It didn’t stop me from having to try it a number of times before I got it right.

You will have to create an account with Twilio and get a number with which to send and receive text messages. For the purposes of prototyping, you can get away with using a free account. But it’s not very much money to get a legit account and do away with the annoying “this is a free account” text that is delivered with every message from the free account.

Bonus section: Multiple users

The whole point of a prototype like this is to test the value of the idea. To do that, you need to share it with other people. I didn’t want to bother with setting up actual user accounts. Instead, I elected to use the source phone number of an incoming message as a proxy for individual users.

Warning: what follows is about as hacky as it gets. Actual software developers should avert their eyes.

The first thing to do was to isolate and capture the phone number embedded in the metadata that rides along with an incoming message from Twilio to DialogFlow. I grabbed the metadata by declaring the following variable:

var str = request.body.session;

The entire “body” metadata is pretty extensive, and you can see it all in the Execution Logs in the Google Cloud Cponsole. Within the body data you can find a parameter called “session” that looks like this:

“session”:”projects/magpie-oggpxl/agent/sessions/+1123456789” where +1123456789 is the phone number that originated the message.

I then split the session data at the “/“ and pushed the values into an array, “src”, then isolated the phone number by its position in the array “src[4]”.

Armed with the phone number, I added that to the destination path for saved items. The resulting code for the handleSaveThing function looks like this:

function handleSaveThing(agent){

const title = agent.parameters.title;

const type = agent.parameters.type;

var str = request.body.session;

var src = str.split(‘/’);

var source = src[4];

agent.add(‘Great! The ‘ + type + ‘ “‘ + title + ‘“‘ + ‘ has been saved.’);

console.log(‘Great!’ + source);

return admin.database().ref(source + ‘/thing/’ + type).push({

title: title

});



}

Unfortunately, I wasn’t able to create a sub-function or global variable that isolated or stored the phone number for use by the other functions. This was either due to a limitation of the Inline Editor, or more likely, my own abilities. As such, I run the operation in all the functions. Like I said, super hacky. For example, here’s the handleGetMovie function with this added:

function handleGetMovie(agent) {

var str = request.body.session;

var src = str.split(‘/’);

var source = src[4];

var ref = admin.database().ref(source + ‘/thing/movie/’);

var recList = [];

var theKeys = [];

var theTitles = [];

var i = 1;

return ref.once(‘value’, function(snapshot){

snapshot.forEach(function(movie){

recList.push(` ${i++}. ${movie.val().title}`);

theKeys.push(movie.key);

theTitles.push(movie.val().title);

});

agent.add(‘Here are your most recent movies: ‘+ recList + ‘ ‘.’);

admin.database().ref(source + ‘/hold’).set({

keys: theKeys,

titles: theTitles

});



});

}

I’m sure there are better, more efficient ways of doing pretty much everything I’ve described here. But my priorities were to test the value of this idea as quickly as I could using the tools and knowledge (not much of that) that I had at my disposal. Elegance was outside the scope of this project.

As I said at the outset, the goal here has been to offer a concrete example of how to use low or no-code services to assemble working prototypes of products and services. I hope that the approach is useful to you in your own projects.