DEV Community

Cover image for Make a Simple Chatbot with JavaScript!
Sylvia Pap
Sylvia Pap

Posted on • Updated on

Make a Simple Chatbot with JavaScript!

Working from home got you feeling lonely? Missing human social interaction? Well it's finally acceptable to suggest making yourself a nice chat bot to talk to instead of going out into the world.

When I say 'from scratch' or 'vanilla JS,' I just mean I'm not using any additional libraries or APIs. This is more an exercise in JS fundamentals than any kind of artificial intelligence or machine learning.

Alt Text

But I also got a lot of this code/inspiration from existing blog posts and YouTube tutorials! So basically I'm trying to be as original as possible here, but you can only avoid re-inventing the wheel for so long.

Alt text of image

Step 1

First off is a simple index.html file:

<!DOCTYPE html>
<html>
<head>
<title>Chatbot</title>
<script type="text/javascript" src="index.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="main">
    <div><input id="input" type="text" placeholder="Say something..." autocomplete="off"/></div>
</div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

And similarly simple styles.css file:

body { 
    color: #421; 
    font-weight: bold; 
    font-size: 18px; 
    font-family: "Courier New"; 
    background: rgb(200, 232, 241); 


}
body::after {
    content: "";
    background-image: url("bot.png");
    background-repeat: repeat-y; 
    opacity: 0.5;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    position: absolute;
    z-index: -1;   
  }
span { 
    color: rgb(36, 17, 119); 
} 
::-webkit-input-placeholder { 
    color: #711 
}
#main { 
    position: fixed; 
    top: 40%; 
    right: 200px; 
    width: 400px; 
    border: 0px solid #421; 
    padding: 40px; 
}
#main div { 
    margin: 10px; 
} 
#input { 
    border: 0; 
    padding: 5px; 
    border: 1px solid #421; 
}
Enter fullscreen mode Exit fullscreen mode

I am clearly not an HTML or CSS expert, but that's not what I came here to do! Maybe I left these intentionally basic so you can be free to customize without trying to understand my complex styling. Here is a tip I found especially helpful on making the background slightly transparent, considering my background image was a little too dark if the window is condensed and the text shows over it. The background is just a bot.png image that I found on Google images. You could replace that with anything!

Alt Text
I am replaceable

Step 2

Now for the fun stuff! Create a .js file, and start with some basics.

//index.js

document.addEventListener("DOMContentLoaded", () => {
  document.querySelector("#input").addEventListener("keydown", function(e) {
    if (e.code === "Enter") {
        console.log("You clicked the form and pressed the enter button!")
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Adding an event listener to the document for the condition of DOMContentLoaded means your JS won't run until the HTML has loaded. This is almost always good practice. Then the EventListener for keypress enter button. Notice we must also select the #input for the form submission, or else our event listener would respond every time we pressed the enter key!

There are some interesting and deprecated alternatives here. .keycode, .which, and keypress are all deprecated. These are all just ways of telling the event listener that we only care about the enter key - that's what makes the nice, dynamic effect of instant rendering when we type a message and press enter! No more tedious clicking of a 'submit' button while messaging our bot friend. See more on the KeyboardEvent object, but basically it seems like the most up-to-date, accessible, and universal method for this event listener, if your browser supports it. But you might still see something with a code of 13 to represent the enter key.

document.addEventListener("DOMContentLoaded", () => {
    const inputField = document.getElementById("input")
    inputField.addEventListener("keydown", function(e) {
        if (e.code === "Enter") {
            let input = inputField.value;
            inputField.value = "";
            output(input);
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Now we are moving past that console.log() and onto some important functions. But first! Notice we select .value and set it to a variable for input. This is whatever we type into the form. We can verify this with another con log!

    if (e.code === "Enter") {
      let input = inputField.value;
      console.log(`I typed '${input}'`)
    }
Enter fullscreen mode Exit fullscreen mode

Alt Text

Cool! One last thing on this part - setting .value = "" ensures our form is cleared after submission. You can also do .reset() on an HTMLFormElement, but it doesn't work here since our input field isn't really a form tag.

Step 3: Functions!

Now for the functions that actually make this guy a bot.

function () {

//remove all characters except word characters, space, and digits
  let text = input.toLowerCase().replace(/[^\w\s\d]/gi, "");

// 'tell me a story' -> 'tell me story'
// 'i feel happy' -> 'happy'
  text = text
    .replace(/ a /g, " ")
    .replace(/i feel /g, "")
    .replace(/whats/g, "what is")
    .replace(/please /g, "")
    .replace(/ please/g, "");
}
Enter fullscreen mode Exit fullscreen mode

Before anything, I want to take whatever the user types in the input field, and make it a little more standard with some basic RegExp action. As noted in the comments, these methods make everything in the input lowercase, remove any rogue characters that would make matches difficult, and replace certain things like whats up to what is up. If the user says what is going on, whats going on, or what's going on, they will all lead to the same valid bot response, instead of having to account for these differences separately somehow.

Now that we've got a good idea of what our text input could look like, I'm going to make some simple arrays of arrays that include possible triggers (user text) and responses (bot text). To start I'll keep them short, and defined in global variables:

const trigger = [
//0 
["hi", "hey", "hello"],
//1
["how are you", "how are things"],
//2
["what is going on", "what is up"],
//3
["happy", "good", "well", "fantastic", "cool"],
//4
["bad", "bored", "tired", "sad"],
//5
["tell me story", "tell me joke"],
//6
["thanks", "thank you"],
//7
["bye", "good bye", "goodbye"]
];

const reply = [
//0 
["Hello!", "Hi!", "Hey!", "Hi there!"], 
//1
[
    "Fine... how are you?",
    "Pretty well, how are you?",
    "Fantastic, how are you?"
  ],
//2
[
    "Nothing much",
    "Exciting things!"
  ],
//3
["Glad to hear it"],
//4
["Why?", "Cheer up buddy"],
//5
["What about?", "Once upon a time..."],
//6
["You're welcome", "No problem"],
//7
["Goodbye", "See you later"],
];

const alternative = [
  "Same",
  "Go on...",
  "Try again",
  "I'm listening...",
  "Bro..."
];
Enter fullscreen mode Exit fullscreen mode

Notice the comments for index at each of the arrays, and how they line up. If we get user input that matches an option at trigger[0], such as 'hi', the bot will respond with an option from its reply[0], such as 'Hello!' and so on. The alternative array is, of course, for everything that doesn't match in the first array! This kind of explains why every basic chatbot you've ever used, let's say on a customer service website, is so.. limited. AI isn't going to kill us all yet! Right now, this bot is pretty much as intelligent as this guy...

Alt text of image

That is, if you don't say something that falls into one of our defined responses, there's a very high chance he will say something like...

Alt Text

Now I add the function that actually compares these arrays:

function compare(triggerArray, replyArray, text) {
  let item;
  for (let x = 0; x < triggerArray.length; x++) {
    for (let y = 0; y < replyArray.length; y++) {
      if (triggerArray[x][y] == text) {
        items = replyArray[x];
        item = items[Math.floor(Math.random() * items.length)];
      }
    }
  }
  return item;
}
Enter fullscreen mode Exit fullscreen mode

and then add this function back into our original, plus accounting for the 'alternative' response:

function output(input) {
  let product;
  let text = input.toLowerCase().replace(/[^\w\s\d]/gi, "");
  text = text
    .replace(/ a /g, " ")
    .replace(/i feel /g, "")
    .replace(/whats/g, "what is")
    .replace(/please /g, "")
    .replace(/ please/g, "");

//compare arrays
//then search keyword
//then random alternative

  if (compare(trigger, reply, text)) {
    product = compare(trigger, reply, text);
  } else if (text.match(/robot/gi)) {
    product = robot[Math.floor(Math.random() * robot.length)];
  } else {
    product = alternative[Math.floor(Math.random() * alternative.length)];
  }

  //update DOM
  addChat(input, product);
}
Enter fullscreen mode Exit fullscreen mode

I added another option for matching user input to bot response here. It adds a bit more flexibility in user input, but less specificity in the response. See where I added an else if for text.match(/robot/gi) - this guarantees a response from a separate "robot related" array if the user enters anything with the word robot anywhere in it.

const robot = ["How do you do, fellow human", "I am not a bot"];
Enter fullscreen mode Exit fullscreen mode

Alt Text

You can imagine abstracting this out to be a separate kind of dynamic search function... or just have multiple else ifs, or case and switch.

Alt Text

The final step is to update the DOM so our messages actually display! A simple way to do this is by having a single element for User and Bot text that is updated every time you enter a new message, and this only requires changing the first event listener function to:

document.addEventListener("DOMContentLoaded", () => {
...
    if (e.code === "Enter") {
        let input = document.getElementById("input").value;
        document.getElementById("user").innerHTML = input;
        output(input);    
     }
  });
});
Enter fullscreen mode Exit fullscreen mode

and then in function output():

function output(input) {
    let product;
    let text = (input.toLowerCase()).replace(/[^\w\s\d]/gi, "");
...
    document.getElementById("chatbot").innerHTML = product;
    speak(product);

    //clear input value
    document.getElementById("input").value = "";
}
Enter fullscreen mode Exit fullscreen mode

Or, you could do it so that the user and bot fields are updated every time, creating a thread of messages. I wanted to keep them all on the page, so my current function looks like..

function addChat(input, product) {
  const mainDiv = document.getElementById("main");
  let userDiv = document.createElement("div");
  userDiv.id = "user";
  userDiv.innerHTML = `You: <span id="user-response">${input}</span>`;
  mainDiv.appendChild(userDiv);

  let botDiv = document.createElement("div");
  botDiv.id = "bot";
  botDiv.innerHTML = `Chatbot: <span id="bot-response">${product}</span>`;
  mainDiv.appendChild(botDiv);
  speak(product);
}
Enter fullscreen mode Exit fullscreen mode

There are so many different ways to accomplish this DOM manipulation. .innerHTML vs. .innerText is a good one. .append vs. .appendChild fulfill almost the exact same purpose here, but can have different uses later on. And if I get around to adding a Rails backend to this guy, I would have liked to add .dataset attributes for each message. It also seems that I do not have the ability to scroll once the thread gets long enough. Once again, I am a beginner, and this post is more about JS logic than views!

Another final note...

I said I wasn't going to use APIs, but one of the example videos I found while trying to do this used voice to text, and all you have to do for that is add the following:

function speak(string) {
  const u = new SpeechSynthesisUtterance();
  allVoices = speechSynthesis.getVoices();
  u.voice = allVoices.filter(voice => voice.name === "Alex")[0];
  u.text = string;
  u.lang = "en-US";
  u.volume = 1; //0-1 interval
  u.rate = 1;
  u.pitch = 1; //0-2 interval
  speechSynthesis.speak(u);
}
Enter fullscreen mode Exit fullscreen mode

I actually couldn't quite figure out how to specify different voice names here, but looking into the Web Speech API docs was interesting, and I can recommend altering .pitch to 2 for a truly terrifying voice that does sound capable of taking over the human race.

Further Reading

Top comments (24)

Collapse
 
gus007br profile image
Gus007-BR

What is the advantage of creating a chat like this vs. using the Socket.io library? Your method seems to be far more straight-forward than using Socket.io... Note that I'm very new to NodeJS in general, so maybe I'm comparing Apples to Oranges.

Collapse
 
sylviapap profile image
Sylvia Pap

I’d say the only real advantage here, over any chatbot library, is just the educational purposes lol 😂 I’m a coding bootcamp student so I write these blog posts usually thinking about things that have helped me actually learn, I’ve tried to use libraries before without understanding the fundamentals and it can lead to a difficult mess later.

Collapse
 
dividedbynil profile image
Kane Ong

You: are you a robot?
Chatbot: I am not a bot
You: I don't believe you
Chatbot: Bro...
You: Alright, you pass. How's your day?

Collapse
 
tr11 profile image
Tiago Rangel
Me: are you a robot?
Chatbot: Bro...

Me: Are you a robot?
Chatbot: I'm listening...

Me: are you a bot?
Chatbot: I am a bot. What are you?

Me: I'm a person
Chatbot: I don't understand :/

Me: you should
Chatbot: I don't understand :/

Me: Why?
Chatbot: Great question
Enter fullscreen mode Exit fullscreen mode
Collapse
 
poojpg profile image
poo-jpg

I love this! Hilarious and educational!

Collapse
 
vojtektomascz profile image
YT_YouTomCZ

Hi, thanks for the great tutorial! How can I add UTF-8 encoding? Thanks.

Collapse
 
ardianpjetri0 profile image
Ardianpjetri

Save as UTF-8 encoded document.

Collapse
 
Sloan, the sloth mascot
Comment deleted
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
ben profile image
Ben Halpern

Nice!

Collapse
 
sylviapap profile image
Sylvia Pap

Thank you!

Collapse
 
tootsz_na_899e2d6acef32b profile image
Tootsz N/A

Hello! I am a self-learning beginner in programming. I find these kinds of posts very helpful, however, it would be much better if you either tell us where you are placing each snippet of code, or show us what the entire page of finished code is supposed to look like.

Throughout this whole article, I was wondering if I was placing my code in the right place, and, as an autodidactic beginner, I need to know if I am doing it exactly right.

Would you please keep this in mind the next time you create and educational post?

Thanks!

Collapse
 
sylviapap profile image
Sylvia Pap

Hey! Thanks! This is why I link to the GitHub repo - you can see everything and even download the whole project yourself from there

Collapse
 
rickasterlyvevoofficalnotfake profile image
rick asterly

i alredy made the chat app is there a way just to add the bot here is the code :

note after you close the tab you will lose your conversation


note the ai is not completed yet so dont judge yet !


chat

<br> var username = window.prompt(&quot;what should we call you&quot;);</p> <p>document.write(&quot;hey &quot;+username +&quot; how are you today ?&quot; );</p> <div class="highlight"><pre class="highlight plaintext"><code>&lt;/script&gt;&lt;/h1&gt; </code></pre></div> <p><body style="background-color: aquamarine; background-image:url(Background.max-x-PIC-MCH043354.jpg); " ><br> <input id="textbox" type="text" placeholder="chat with the ai :)" ><button id="button" onclick="ping.play()" >Send</button><br><br> <ul id="messages"></ul><br> <script src="script.js"> var messages = document.getElementById("messages"); var textbox = document.getElementById("textbox"); var button = document.getElementById("button"); button.addEventListener("click", function(){ var newMessage = document.createElement("li"); newMessage.innerHTML = textbox.value; messages.appendChild(newMessage); textbox.value = ""; }); var ping = new Audio(); ping.src = "ping.mp3";

Collapse
 
valhelbferh profile image
Fernando Valencia

Thank you so much, is very nice tutorial...
i'm a beginner in javascript world and i have a question...
In the declaration of constants in the constants.js file, all the possible answers are declared in text, but if I want to deliver a link, how can I add a url value or a link in that js, so that in the chat the user can use the link?

I would appreciate your answer to better understand the operation and learn ...

Collapse
 
valhelbferh profile image
Fernando Valencia

Nobody answers?
Nobody helpme?

Collapse
 
isratmarzan profile image
isratmarzan

Thank you @sylviapap for this post. I always wanted to make a chatbot.

Collapse
 
paratrink profile image
Paratik

Hello, i am changing the words in arrays with new (turkish or english ones) it doesn't work, why?

Collapse
 
gopalak24417825 profile image
gopalakrishna_chinta

hi,am getting an error ,'inputField is not defined'

Collapse
 
qiannn03 profile image
Hmmmmmmm

hi i need some help after i following your code i press enter on my website i doesn't work.

Collapse
 
pradeepradyumna profile image
Pradeep Pradyumna

This is helpful! I too am working on something similar to this one right now and was looking for more concepts like this with just JS and HTML.
Thanks

Collapse
 
sant77 profile image
Santiago Duque Ramos

Nice tutorial.

Collapse
 
paratrink profile image
Paratik

Hello, when i change the answers in the arrays, doesn't work why?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.