Building A Simple AI Chatbot With Web Speech API And Node.js
Building A Simple AI Chatbot With Web Speech API And Node.js
Summary:
The app will listen to the user’s voice and reply with a synthetic voice. Because the Web Speech API is still experimental, the app works only in supported browsers.
Browser compatibility of web speech API:
To build the web app, we’re going to take three major steps:
- Use the web speech API's speech recogination to listen to user's voice
- Send the user’s message to a commercial natural-language-processing API as a text string.
- Once API.AI returns the response text back, use the SpeechSynthesis interface to give it a synthetic voice
PREREQUISITES #
- Basic knowledge of Node.js and javascript. We are going to use node.js for serverside programming.
- Socket.Io basics.
Setting Up Your Node.Js Application #
- index.js
- openai.js
-
public
-
css
- style.css
-
js
- script.js
-
css
-
views
- index.ejs
Then, run this command to initialize your Node.js app:
$ npm init -f
The -f accepts the default setting, or else you can configure the app manually without the flag. Also, this will generate a package.json file that contains the basic info for your app.
Now, install all of the dependencies needed to build this app:
$ npm install express socket.io apiai --save
First of all we will create a server using express and attach socket.io to it.
require("dotenv").config(); const express = require("express"); //import generateText function from openai.js file const generateText = require("./openai"); const http = require("http"); const socketIo = require("socket.io"); const app = express(); const port = 3000; //create http server using express app. const Server = http.createServer(app); //attach socker.io const io = socketIo(Server); // Set the view engine to EJS app.set("view engine", "ejs"); app.set("views", __dirname + "/views"); // Serve static files from the public directory app.use(express.static(__dirname + "/public")); app.get("/", (req, res) => { res.render("index"); }); io.on("connection", (socket) => { console.log("client connected"); socket.on("message", async (data) => { // console.log(data); var res = await generateText(data); socket.emit("response", res); }); // Handle disconnection socket.on("disconnect", () => { console.log("User disconnected"); }); }); Server.listen(port, () => { console.log(`server is running on port ${port}`); });
Create openai.js file
const OpenAI = require("openai"); const openai = new OpenAI({ apiKey: process.env["OPENAI_API_KEY"], // This is the default and can be omitted }); async function generateText(prompt) { const chatCompletion = await openai.chat.completions.create({ messages: [{ role: "user", content: prompt }], model: "gpt-3.5-turbo", }); console.log(chatCompletion.choices[0].message.content); const text = chatCompletion.choices[0].message.content; return text; } module.exports = generateText;
Now we have to create the index.ejs file for frontend interface.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Socket.IO Example</title> <link rel="stylesheet" href="css/style.css" /> <script src="/socket.io/socket.io.js"></script> <!-- we have to add this line to use socket io in our project. The line <script src="/socket.io/socket.io.js"></script> is an HTML script tag that includes the Socket.IO JavaScript library in a web page.--> </head> <body> <div class="chat-container"> <h1>Chatbot</h1> <div class="chatbox"> <div class="messagebox"></div> </div> <form id="messageForm" class="textbox"> <input type="text" id="messageInput" autocomplete="off" placeholder="Enter message" /> <button class="submit-btn hidden" type="submit" id="sendbtn"> <img src="./img/send-message.png" alt="" srcset="" /> </button> <button class="submit-btn mic-btn" id="mic"> <img src="./img/mic_black.png" alt="" srcset="" /> </button> </form> </div> <script type="text/javascript" src="js/script.js"></script> <script> var socket = io(); var parentElement = document.querySelectorAll(".messagebox")[0]; var loadingElement = document.createElement("div"); loadingElement.classList.add("message-left"); loadingElement.classList.add("typing"); loadingElement.classList.add("ellipse"); loadingElement.innerHTML = ` <p class="message-owner-tag">Bot</p> <p id="ellipsisAnimation"></p> `; document .getElementById("messageForm") .addEventListener("submit", function (event) { event.preventDefault(); var message = document.getElementById("messageInput").value; if (message === "") { return; } document.getElementById("messageInput").value = ""; // console.log(message); var divElement = document.createElement("div"); divElement.classList.add("message-right"); divElement.innerHTML = `<p class="message-owner-tag">${user}</p> <p>${message}</p>`; // console.log(divElement); parentElement.appendChild(divElement); parentElement.appendChild(loadingElement); scrollMessageBoxToBottom(); // console.log(parentElement); socket.emit("message", message); }); socket.on("response", function (data) { // document.getElementById("response").innerHTML = data; var loadingIndicator = parentElement.querySelector(".typing"); if (loadingIndicator) { parentElement.removeChild(loadingIndicator); } var divElement = document.createElement("div"); divElement.classList.add("message-left"); divElement.innerHTML = `<p class="message-owner-tag">Bot</p> <p>${data}</p>`; parentElement.appendChild(divElement); scrollMessageBoxToBottom(); synthVoice(data); }); </script> </body> </html>
CSS file
body { position: relative; margin: 0; min-height: 100vh; min-width: 100vw; background-color: #d2e8ff; display: flex; justify-content: center; align-items: center; } .chat-container { display: flex; flex-direction: column; flex: 1; max-width: 300px; /* height: 400px; */ background-color: white; box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; border-radius: 8px; overflow: hidden; /* padding: 10px; */ } h1 { color: white; padding: 4px 8px; margin: 0; background-color: #3691ef; } .chatbox { display: flex; flex-direction: column; height: 400px; /* background-color: red; */ position: relative; padding: 8px; padding-bottom: 0; } .messagebox { overflow: auto; padding-top: 8px; flex: 1; display: flex; flex-direction: column; } .messagebox::-webkit-scrollbar { display: none; } /* CSS for messages aligned to the left (received messages) */ .message-left { position: relative; align-self: flex-start; background-color: #f2f2f2; /* Light gray background */ color: #333; /* Dark text color */ padding: 10px; margin: 25px 20px 5px 10px; /* Adjust margins as needed */ border-radius: 15px; /* Rounded corners */ max-width: 70%; /* Maximum width of the message */ } /* CSS for messages aligned to the right (sent messages) */ .message-right { position: relative; align-self: flex-end; background-color: #007bff; /* Blue background */ color: #fff; /* White text color */ padding: 10px; margin: 25px 10px 5px 20px; /* Adjust margins as needed */ border-radius: 15px; /* Rounded corners */ max-width: 70%; /* Maximum width of the message */ text-align: right; /* Align text to the right */ } p { margin: 0; font-size: 1.2rem; } .message-owner-tag { position: absolute; /* Position absolute for positioning at the top */ top: -15px; /* Adjust the distance from the top of the message */ font-size: 12px; /* Adjust font size as needed */ color: #888; /* Color of the tag */ } /* CSS for the tag indicating the message owner in left-aligned messages */ .message-left .message-owner-tag { left: 5px; /* Adjust the distance from the left side */ } /* CSS for the tag indicating the message owner in right-aligned messages */ .message-right .message-owner-tag { right: 5px; /* Adjust the distance from the right side */ } .textbox { width: 100%; display: flex; box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 14%); /* Shadow effect */ } #messageInput { border: none; outline: none; font-size: 1rem; height: 40px; padding: 2px 8px; border-radius: 4px; flex: 1; margin-right: 8px; } .submit-btn { background: none; outline: none; border: none; cursor: pointer; } .submit-btn img { width: 25px; aspect-ratio: 1; cursor: pointer; } #ellipsisAnimation { font-size: 24px; line-height: 0; } .ellipse { width: 20px; height: 15px; margin-bottom: 8px; } #ellipsisAnimation::before { content: "..."; animation: typing-ellipsis 1s steps(3) infinite; } @keyframes typing-ellipsis { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } } .hidden { display: none; } @media screen and (max-width: 1180px) { body { background: none; } .chat-container { width: 100%; height: 100vh; max-width: 100%; } .chatbox { height: auto; flex: 1; } .chat-container { border-radius: 0; } .submit-btn img { width: 35px; } #messageInput { font-size: 16px; height: 64px; } }
Capturing Voice With JavaScript #
In script.js, invoke an instance of SpeechRecognition, the controller interface of the Web Speech API for voice recognition:
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
we are using some of ECMAScript6 syntax in this tutorial, because the syntax, including the const and arrow functions, are available in browsers that support both Speech API interfaces, SpeechRecognition and SpeechSynthesis. Optionally, you can set varieties of properties to customize speech recognition:
recognition.lang = "en-US";
recognition.interimResults = false;
Voice response using speech Synthesis
We will use speech synthesis and create a voice response for the user from the bot.const synth = window.speechSynthesis; const utterance = new SpeechSynthesisUtterance(); utterance.text = text; synth.speak(utterance);
const user = prompt("Enter your name"); function scrollMessageBoxToBottom() { var messageBox = document.getElementsByClassName("messagebox")[0]; messageBox.scrollTop = messageBox.scrollHeight; } // Get reference to the input box const inputBox = document.getElementById("messageInput"); const mic = document.getElementById("mic"); const sendbtn = document.getElementById("sendbtn"); // Add event listener inputBox.addEventListener("focus", function (event) { // This function will be called whenever the input value changes mic.classList.add("hidden"); sendbtn.classList.remove("hidden"); // You can perform any actions you need here }); // Add event listener inputBox.addEventListener("blur", function (event) { // This function will be called whenever the input blur mic.classList.remove("hidden"); sendbtn.classList.add("hidden"); // You can perform any actions you need here }); var socket = io(); var parentElement = document.querySelectorAll(".messagebox")[0]; var loadingElement = document.createElement("div"); loadingElement.classList.add("message-left"); loadingElement.classList.add("typing"); loadingElement.classList.add("ellipse"); loadingElement.innerHTML = ` <p class="message-owner-tag">Bot</p> <p id="ellipsisAnimation"></p> `; const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SpeechRecognition(); recognition.lang = "en-US"; recognition.interimResults = false; recognition.onresult = function (event) { const transcript = event.results[0][0].transcript; var divElement = document.createElement("div"); divElement.classList.add("message-right"); divElement.innerHTML = `<p class="message-owner-tag">${user}</p> <p>${transcript}</p>`; // console.log(divElement); parentElement.appendChild(divElement); parentElement.appendChild(loadingElement); scrollMessageBoxToBottom(); // console.log(parentElement); socket.emit("message", transcript); console.log("Speech Recognition Result:", transcript); }; let isRecording = false; document.getElementById("mic").addEventListener("click", function () { if (!isRecording) { // Start Speech Recognition recognition.start(); isRecording = true; console.log("recording started"); } else { // Stop Speech Recognition recognition.stop(); isRecording = false; console.log("recording stop"); } }); function synthVoice(text) { const synth = window.speechSynthesis; const utterance = new SpeechSynthesisUtterance(); utterance.text = text; synth.speak(utterance); }
https://github.com/silentvoice143/chatbot
Comments
Post a Comment