How to build a chatbot from scratch with JavaScript — using State Machines

Roger Junior
7 min readJan 27, 2023

--

(No ChatGPT has been used, sorry guys)

When you hear the word Chatbot one of those two things might come into your mind: the first might be the future, a highly intelligent robot that can respond to any question, a true personal assistant; the second, and most probably if you actually use the internet, is a crappy support substitute.

The bad reputation of chatbots is a true shame. A chatbot per se is not an inherently bad experience, and I myself have interacted with some very well-designed chatbots. And well-designed chatbots can be nailed down to well-planned interaction flows.

Planning is key to designing a good State Machine. At the end of the day, that's what non-AI chatbots are, just State Machines. You have the messages — the states, that have options to interact — the actions, that then bring you to a new message — or state and the cycles start again.

For this article, I'm going to walk you through how I planned, designed, and developed a simple chatbot for my new personal website.

The design

Who remembers that little Clippy guy that use to h̶a̶u̶n̶t help us whenever we were writing a document on MS Word? I know this is no exactly the peak of artificial intelligence that we think of when we talk about Chatbots, but MS Clippy will forever be a warm-hearted memory of my first years playing on a computer.

Other designs for the MS Word Virtual Assistant also include the Wizzard, a Dog, and many others.

I wanted my website to feel modern and nostalgic at the same time, so Clippy was my best choice to bring my chatbot to life!

My illustration was inspired by the Cippy

The language

I wanted my chatbot to have some personality, they should feel friendly but not robotic, they're allowed to break out of character when they feel like it, and they can be happy, surprised, or sad based on the user input.

The conversation flow/State Machine Diagram

The goal of my bot is to lead the user to take an action on my website, that action is hiring me. So I plan different conversation scenarios where the bot will lead the user to different endpoints depending on how likely they are to actually hire me in the short term.

Basically, If they are ready to start a project, I want the user to be redirected to a contact point or even my calendar to schedule a call; but if they're not, I still want to keep in touch, so I want to suggest connecting over on LinkedIn.

So I jumped into FigJam and started my diagram, which ended up looking something like this:

This might be overwhelming at first, but it's actually a very simple bot conversational flow. The bot presents a message (purple boxes) and based on the responses (written in the arrows) the user can either be redirected to a different message or an external site (green circles).

If you're creating a bot, I highly suggest you start with this diagram. The actions can be anything, can be settings, different forms on your support page, or even complete flows on your application like resetting a password. The sky is the limit.

The code

I decided to go with an object-oriented approach. I've created a controller for the state machine that will basically have:

  • The current state
  • All states available
  • A method to interact
  • A method to render the current state

That was the most basic structure that I could come up with. I separated the interaction and rendering because you could really go crazy with the render. You could have a message chat style with the last messages, you could have different videos with the messages as subtitles, and you could scroll to a certain section based on the current state, the possibilities are endless.

In this example, I'll always render the current state inside a <div> and have the interactions as <button>. There will be no history. I'll also show how you could add some animations at the end.

That should look something like this:

var StateMachine = {
currentState: "",
states: {},
interact: function() {},
render: function() {}
}

So the first step is to transform our visual flow map into a machine-friendly format. So I basically created a big object where each entry would be a state. That is helpful because I can later find easily each state by its key name.

I'm going to use this simplified diagram for the sake of this tutorial:

Inside each entry, I added a message and an array of options:

StateMachine.states = {
welcome: {
message: "Hi there! How are you?",
options: [
{ title: "Good",
next: "website" },
{ title: "Not great",
next: "sorry" }]
},
website: {
message: "Wanna see a cool website?",
options: [
{ title: "Sounds great",
href: "https://rogerjunior.com" }]
},
sorry: {
message: "I'm sorry to hear that. Wanna a website to make you feel better?",
options: [
{ title: "Yeah, why not",
href: "https://rogerjunior.com" }]
}
}

In this example, I decided to go with an array of options because I never really had more than 2 options and it was easier to code that way, but you could also have an object with options, or even use an array but have slugs inside the options. It's up to you.

You would do that for every entry you have on your diagram.

Next, you define what is the initial state:

StateMachine.currentState = "welcome";

The string is referring to the key name of the state inside StateMachine.states object.

Now, we need to define how we interact with our state machine.

If you look closely, at our defined options, we have two different structures. One containing the key next and one the key href. We'll use that to differentiate the action in our code — either take the user to the next step or redirect to an URL.

StateMachine.interact = function(option) {
// First thing, we need to retrive the
// available options inside our current state

// The 'option' parameter is a identifier
// It could be a slug, and in my case, is an index
// used to match the array item.

var currentState = this.states[this.currentState]
var selectedOption = currentState.options[option]

// If the option is a link, redirect the user
if (selectedOption.href) { window.open(selectedOption.href, '_blank'); return; }

// If is not, update the state and render everything
this.currentState = selectedOption.next;
this.render();
}

This is the base of your state machine almost fully working, if you wanted, you could have the render() function to be logging on to your console and you could use the StateMachine.interact() function to interact with it.

Let's try that first:

StateMachine.render = function() {
var currentState = this.states[this.currentState]
console.log(currentState.message);
currentState.options.forEach((option, i) => {
console.log(i, currentState.options[i].title);
});
}

And then to start playing with it you can just type StateMachine.render() onto your console to get it running.

Cool right? What do you mean this is boring? I thought you'd like it… Okay, okay, I get it, not everyone knows how to use the console. So we definitely can make better use of the render() function.

In the next code, I'll show how to update the DOM with the current state

StateMachine.render = function() {
var message = document.getElementById("message")
var buttons = document.getElementById("buttons")
var currentState = this.states[this.currentState];

// First, we add the message to the message container
message.innerHTML = currentState.message;

// Then, clear the current buttons
buttons.innerHTML = "";

// For each option, we add a button with the
// stateMachine.interact() function on click
currentState.options.forEach((option, i) => {
currentState.innerHTML +=
`<a href="javascript:stateMachine.interact(${i});" class="button">${option.title}</a>`
});

// This is *optional*
// Here's an example of me using GSAP to animate the message
// after it being populated with text
animation.restart();
animation.play();

return;
}

It should look something like this:

It's a very simple code implementation, and since you can build and model it to look exactly how you want it to be, it actually opens a sea of possibilities and integrations with systems that you can make.

I hope this state machine framework was helpful to you.

If you have any questions I'll be happy to help in the comment section :)

If you are looking for a UX consultant with experience in digital products and front-end development for your online business, I would love to have a chat with you :)

Check my website here or message me on LinkedIn.

--

--

Roger Junior
Roger Junior

Written by Roger Junior

I am a digital designer open to work on freelance projects. You can check out my website at rogerjunior.com where you can find more details about me and my work

No responses yet