Build this - Chattybot

  1. GitHub Repository Link

  2. Download Python

  3. Download MySQL Workbench

Introduction

In today’s article, I’m going to walk you through how to build your own chatbot using Python with Rasa for the backend and React for the frontend.

First things first, we need to make sure we have Python installed on our machine. I’ll be using Python 3.9 for this setup, so let’s download that version using Homebrew.

brew install [email protected]

Check the version check

python3.9 --version

Create Project structure

mkdir chattybot
cd chattybot
mkdir backend
cd backend

Creating a virtual environment

python3.9 -m venv rasa-env

To activate the environment, simply run this command:

source rasa-env/bin/activate

Now that we’re inside the virtual environment, let’s install Rasa itself!

pip install rasa

We’ll also install some additional dependencies that we’ll use later for handling HTTP requests and cross-origin resource sharing (CORS).

pip install flask flask-cors requests

Now that we have everything installed, let’s initialize our Rasa project. This is where the magic happens!

rasa init

Rasa will ask if we want to create the default directory structure and sample files. Let’s go ahead and select ‘yes’ for that."

Rasa will automatically train a model based on this initial setup. The model helps the chatbot understand user inputs and generate appropriate responses.

Creating More Intents

Now that we’ve set up the basic Rasa chatbot, it’s time to make it more intelligent by adding custom intents. Intents are essentially the user’s purpose or goal when they interact with the bot. Let’s add a few more to make our chatbot smarter.

Update nlu.yml file looks like. I’ve already added several new intents. For example, a greet intent that recognizes different ways users say hello, and an ask_programming_languages intent that allows the user to ask about different programming languages.

version: "3.1"

nlu:
  - intent: greet
    examples: |
      - hey
      - hello
      - hi
      - hello there
      - good morning
      - good evening
      - moin
      - hey there
      - let's go
      - hey dude
      - goodmorning
      - goodevening
      - good afternoon      

  - intent: ask_programming_languages
    examples: |
      - What programming languages do you know?
      - Tell me about different programming languages.
      - What is Python?
      - What are the best programming languages for web development?
      - Can you list some programming languages?      

  - intent: ask_web_development
    examples: |
      - What is web development?
      - Tell me about front-end development.
      - What is the difference between front-end and back-end?
      - How does React work?
      - What is the role of a web developer?      

  - intent: goodbye
    examples: |
      - cu
      - goodbye
      - see you later
      - bye bye      

Once we’ve defined our intents, we need to add appropriate responses in the domain.yml file. This is where we define how the bot responds to different user inputs.

Add responses in domain.yml

responses:
  utter_greet:
    - text: "Hey! How are you?"

  utter_programming_languages:
    - text: "I know about various programming languages including Python, JavaScript, Java, C++, Ruby, and many more. Each has its own strengths and weaknesses."

  utter_web_development:
    - text: "Web development involves creating websites and applications for the internet. It can be divided into front-end (client-side) and back-end (server-side) development."

Finally, to make sure the chatbot behaves correctly, we’ll create stories. Stories are training examples of the conversation flow. They help Rasa understand how to respond based on user inputs.

[On-screen: Stories in stories.yml]

stories:
  - story: ask about programming languages
    steps:
      - intent: ask_programming_languages
      - action: utter_programming_languages

  - story: ask about web development
    steps:
      - intent: ask_web_development
      - action: utter_web_development

With these new intents, responses, and stories, our chatbot is ready to handle more complex conversations. In the next section, we’ll connect this to our React frontend and test the entire flow.

Creating the Flask Backend

Create app.py in backend folder.

Flask backend will serve as the bridge between Rasa and our frontend, along with handling user conversations and storing chat history in a MySQL database.

Let’s start by creating our app.py file. First, we’ll set up Flask and enable CORS, which allows cross-origin requests from the frontend.

from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # Enable CORS for all routes

Now, we need to integrate MySQL to store the conversation data. I’ve already set up a simple MySQL database with two tables: one for conversations and one for storing messages. You can adapt this based on your needs.

db_config = {
    'user': 'root',
    'password': 'password',
    'host': 'localhost',
    'database': 'chatbot'
}

To send the user’s message to the Rasa server, we’ll define the Rasa API URL and create a function that makes an HTTP POST request to Rasa.

RASA_SERVER_URL = "http://localhost:5005/webhooks/rest/webhook"

@app.route('/chat', methods=['POST'])
def chat():
    data = request.json
    print(f"Received data: {data}")  # Log incoming data
    user_message = data.get("message")
    user_id = data.get("user_id")
    conversation_id = data.get("conversation_id")  # Conversation ID

    if not user_message or not user_id or not conversation_id:
        return jsonify({"error": "No message, user_id, or conversation_id provided"}), 400

    print(f"Received message from user {user_id} in conversation {conversation_id}: {user_message}")

    # Send message to Rasa
    response = requests.post(
        RASA_SERVER_URL,
        json={"sender": user_id, "message": user_message}
    )

    if response.status_code != 200:
        print(f"Error communicating with Rasa: {response.status_code}")
        return jsonify({"error": "Error communicating with Rasa"}), 500

    bot_response = response.json()
    print(f"Received response from Rasa: {bot_response}")

    # Save user message and bot response to MySQL
    save_chat_to_db(user_id, conversation_id, user_message, bot_response[0]['text'])

    return jsonify(bot_response)

To store the conversation history, we’ll create a function that saves each user message and bot response in the MySQL database.

def save_chat_to_db(user_id, conversation_id, user_message, bot_response):
    conn = mysql.connector.connect(**db_config)
    cursor = conn.cursor()
    cursor.execute("""
        INSERT INTO messages (conversation_id, user_message, bot_response, timestamp)
        VALUES (%s, %s, %s, %s)
    """, (conversation_id, user_message, bot_response, datetime.now(timezone.utc)))
    conn.commit()
    cursor.close()
    conn.close()
    print(f"Saved chat to DB: user_id={user_id}, conversation_id={conversation_id}, user_message={user_message}, bot_response={bot_response}")

We’ll also create an endpoint to retrieve the chat history for a user and their conversation ID, so the frontend can display previous messages.

Let’s go over the get_chat_history function. This helper function retrieves all messages between the user and the chatbot for a specific conversation, sorted by the timestamp.

def get_chat_history(user_id, conversation_id):
    conn = mysql.connector.connect(**db_config)
    cursor = conn.cursor(dictionary=True)
    cursor.execute("""
        SELECT user_message, bot_response
        FROM messages
        WHERE conversation_id = %s
        ORDER BY timestamp ASC
    """, (conversation_id,))
    history = cursor.fetchall()
    cursor.close()
    conn.close()
    print(f"Retrieved chat history for user {user_id} in conversation {conversation_id}: {history}")
    return history

Now, let’s add the final backend endpoints for managing chat history and conversations. This will enable our frontend to retrieve past conversations, start new ones, and delete conversations as needed.

The /history route is a GET endpoint. It retrieves the chat history based on a user_id and a specific conversation_id. If either parameter is missing, it returns an error message.

@app.route('/history', methods=['GET'])
def chat_history():
    user_id = request.args.get("user_id")
    conversation_id = request.args.get("conversation_id")  # Get conversation_id from query params
    
    if not user_id or not conversation_id:
        return jsonify({"error": "No user_id or conversation_id provided"}), 400

    history = get_chat_history(user_id, conversation_id)
    
    return jsonify(history)

Our next endpoint is /new_conversation, a POST endpoint that generates a new conversation ID. We save this ID along with the user ID and a timestamp to the database, allowing us to retrieve it later.

@app.route('/new_conversation', methods=['POST'])
def new_conversation():
    data = request.json
    user_id = data.get("user_id")

    if not user_id:
        return jsonify({"error": "No user_id provided"}), 400

    # Create a new conversation ID (could be a UUID or a timestamp-based string)
    conversation_id = str(datetime.now(timezone.utc).timestamp())  # Use timezone-aware datetime

    conn = mysql.connector.connect(**db_config)
    cursor = conn.cursor()
    cursor.execute("""
        INSERT INTO conversations (user_id, conversation_id, timestamp)
        VALUES (%s, %s, %s)
    """, (user_id, conversation_id, datetime.now(timezone.utc)))
    conn.commit()
    cursor.close()
    conn.close()

    return jsonify({"conversation_id": conversation_id})

Next, we have a GET endpoint called /conversations. This retrieves a list of conversations associated with a user ID, helping us populate the sidebar with past conversations.

@app.route('/conversations', methods=['GET'])
def get_conversations():
    user_id = request.args.get("user_id")
    
    if not user_id:
        return jsonify({"error": "No user_id provided"}), 400

    conn = mysql.connector.connect(**db_config)
    cursor = conn.cursor(dictionary=True)
    cursor.execute("""
        SELECT conversation_id
        FROM conversations
        WHERE user_id = %s
    """, (user_id,))
    conversations = cursor.fetchall()
    cursor.close()
    conn.close()

    return jsonify(conversations)

Finally, we have the DELETE endpoint at /conversation. This takes in the user_id and conversation_id and deletes the corresponding records from both the messages and conversations tables.

@app.route('/conversation', methods=['DELETE'])
def delete_conversation():
    data = request.json
    user_id = data.get("user_id")
    conversation_id = data.get("conversation_id")

    if not user_id or not conversation_id:
        return jsonify({"error": "No user_id or conversation_id provided"}), 400

    conn = mysql.connector.connect(**db_config)
    cursor = conn.cursor()
    cursor.execute("""
        DELETE FROM messages WHERE conversation_id = %s
    """, (conversation_id,))
    cursor.execute("""
        DELETE FROM conversations WHERE conversation_id = %s
    """, (conversation_id,))
    conn.commit()
    cursor.close()
    conn.close()

    return jsonify({"message": "Conversation deleted"})

    if __name__ == '__main__': 
    app.run(host='0.0.0.0', port=5001, debug=True) 

Create Database

To install the MySQL connector, simply run the following command in your terminal:"*

pip install mysql-connector-python

Let’s start by opening MySQL Workbench. If you haven’t already, connect to your MySQL server by clicking on the connection.

CREATE DATABASE chattybot; 
USE chattybot

This will create DB named chattybot.

Create Tables in DB.

CREATE TABLE `conversations` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) NOT NULL,
  `conversation_id` varchar(255) NOT NULL,
  `timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `conversation_id` (`conversation_id`)
) 

CREATE TABLE `messages` (
  `id` int NOT NULL AUTO_INCREMENT,
  `conversation_id` varchar(255) NOT NULL,
  `user_message` text,
  `bot_response` text,
  `timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `conversation_id` (`conversation_id`),
  CONSTRAINT `messages_ibfk_1` FOREIGN KEY (`conversation_id`) REFERENCES `conversations` (`conversation_id`)
) 

Once both tables are created, we’re all set to store and retrieve conversation data from our chatbot!

Creating the Frontend

Create components folder in src dir. And Then Create three files in it:

ChatSidebar.jsx
ChatWindow.jsx
ChatApp.jsx

The ChatSidebar component will serve as the navigation for all conversations in our chatbot. It lets the user toggle between different chat sessions, delete conversations, and start a new chat.

ChatSideabar

Now that we have the ChatSidebar component, let’s move on to the main interaction area, which is the ChatWindow. This is where the user can send and receive messages with the bot.

The ChatWindow component is where the conversation between the user and the bot happens. It manages the chat messages, displays them in a scrollable window, and provides an input form for the user to send messages.

ChatWindow

We’ve built the ChatSidebar and the ChatWindow components. Now it’s time to bring everything together in our main component: ChatApp. This is where all the interaction logic between the user, the bot, and our backend happens.

ChatApp

Now that we’ve completed our main ChatApp component, let’s wire everything together in our root file—App.js.

import React from 'react';
import ChatApp from './components/ChatApp';
import './App.css';

const App = () => {
  return (
    <div className="App">
      <ChatApp />
    </div>
  );
};

export default App;

Here, we simply import the ChatApp component and return it inside the App function. This makes ChatApp the central component of our application."*


So that’s pretty much all for this article. I hope it helped you. So I’ll see you in the next one. Till then bye bye.