RASA - Socket.IO integration
Tags: rasa, chatbot, nlp, ai
Published: 2021-08-30 07:01:05

We already saw how to integrate Rasa chatbot with [Tiwlio](https://dev.to/petr7555/rasa-messaging-channels-4c2k) and [Google Chat](https://dev.to/petr7555/rasa-google-chat-integration-3gkn). In this article, I will show you how to integrate the Rasa chatbot with another messaging channel – **Socket.IO**.

This will be the result:

![Web chat with Rasa](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8r1n6umbibtgj9eujkq2.png)

## What is Socket.IO?
Socket.IO is a library that enables real-time bi-directional communication between a client and a server. It uses the WebSocket protocol.

You can [write a simple chat application](https://socket.io/get-started/chat) to learn the basics of Socket.IO.

Socket.IO is composed of two parts:
- A server that integrates with the Node.JS HTTP Server – `socket.io`.
- A client library that loads on the browser side – `socket.io-client`.

## Let's get started!
By adding the following configuration to `credentials.yml` in your Rasa chatbot project, you will enable the Socket.IO **server**.

```yml
# credentials.yml
socketio:
  user_message_evt: user_uttered
  bot_message_evt: bot_uttered
  session_persistence: false
```

All that is left is a **client**.
Create a folder named `webchat` in the Rasa chatbot project.
Inside the `webchat` folder, create a new `index.html` file.
This file will contain the source code for the interactive web chat. You can view the complete code [here](https://github.com/petr7555/rasa-dev-tutorial/blob/22-socketio-integration/webchat/index.html).

Let's start with a markup.
At the top of the page, there will be a header.
All sent and received messages will be displayed in the middle. At the bottom, we will have a form with text input and a button to send messages.

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      /* styles */
    </style>
  </head>
  <body>
    <header class="header">
      <p class="title">Chat with Rasa chatbot</p>
    </header>
    <div id="messages"></div>
    <form id="form">
      <input id="message-input" autocomplete="off" autofocus/>
      <button class="button">Send</button>
    </form>
  </body>
</html>
```

At the current stage, the page looks like this and doesn't do anything:
![Page markup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wvp9udq0xu7vao2gi3x.png)

To connect to the Socket.IO server provided by Rasa, we need Socket.IO client library. We'll include it as a [script from CDN](https://cdnjs.com/libraries/socket.io) before the closing `</body>` tag in `index.html`.

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.1.3/socket.io.js"
        integrity="sha512-PU5S6BA03fRv1Q5fpwXjg5nlRrgdoguZ74urFInkbABMCENyx5oP3hrDzYMMPh3qdLdknIvrGj3yqZ4JuU7Nag=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
```

Below this `<script>` we have just added, add another `<script>` that will contain the application logic.

Connect to the Rasa Socket.IO server:

```html
<script>
    // Connect to RASA server
    const socket = io("http://localhost:5005");
</script>
```

The URL has a form of `http://<host>:<port>`, where the `host` and `port` are the appropriate values from your running Rasa server.

When the client connects, a `connected` event will be emitted. We can react to it and log a message into the console.
```js
socket.on('connect', function () {
    console.log("Connected to Socket.io server");
});
```

In a similar way, log any connection errors.
```js
socket.on('connect_error', (error) => {
    // Write any connection errors to the console 
    console.error(error);
});
```

When the user clicks the **Send** button, we obtain the input value and emit a `user_uttered` event, passing the input value as a value of a `message` field. 
`user_uttered` is the event name for user messages we configured in `credentials.yml`.
We also append the message to the page and add corresponding classes for adding CSS later on.
Lastly, scroll to the bottom of the page.

```js
const messages = document.getElementById('messages');
const form = document.getElementById('form');
const messageInput = document.getElementById('message-input');

form.addEventListener('submit', function (e) {
    e.preventDefault();
    const msg = messageInput.value;
    if (msg) {
        socket.emit('user_uttered', {
            "message": msg,
        });
        messageInput.value = '';

        appendMessage(msg, "sent");
    }
});

function appendMessage(msg, type) {
    const item = document.createElement('div');
    item.textContent = msg;
    item.classList.add("message");
    item.classList.add(`message_${type}`);
    messages.appendChild(item);
    scrollToBottom();
}

function scrollToBottom() {
    window.scrollTo(0, document.body.scrollHeight);
}
```

Rasa server processes the message and sends a response in the `bot_uttered` event payload.
`bot_uttered` is the event name for bot messages we configured in `credentials.yml`.
The response can contain various fields. We will handle `text`, `attachment` and `quick_replies`. 

```js
socket.on('bot_uttered', function (response) {
    console.log("Bot uttered:", response);
    if (response.text) {
        appendMessage(response.text, "received");
    }
    if (response.attachment) {
        appendImage(response.attachment.payload.src, "received");
    }
    if (response.quick_replies) {
        appendQuickReplies(response.quick_replies);
    }
});
```

If the response contains `text`, we want to display it in the chat as a message from the chatbot. This is done with the same method we used to append user's message but with a different class and therefore different CSS.

If the response contains `attachment`, we want to grab the attachment source and display it as an image sent by the chatbot.

```js
function appendImage(src, type) {
    const item = document.createElement('div');
    item.classList.add("message");
    item.classList.add(`message_${type}`);
    const img = document.createElement('img');
    img.src = src;
    img.onload = scrollToBottom;
    item.appendChild(img);
    messages.appendChild(item);
}
```

If the response contains `quick_replies`, we want to display them as buttons that the user can click to send a message without a need for typing.

We attach a `click` event listener to each button to:
- remove the quick replies,
- append the title of the quick reply the user clicked on as a message from the user,
- send the underlying quick reply payload.

```js
function appendQuickReplies(quickReplies) {
    const quickRepliesNode = document.createElement('div');
    quickRepliesNode.classList.add("quick-replies");
    quickReplies.forEach(quickReply => {
        const quickReplyDiv = document.createElement('button');
        quickReplyDiv.innerHTML = quickReply.title;
        quickReplyDiv.classList.add("button");
        quickReplyDiv.addEventListener("click", () => {
            messages.removeChild(quickRepliesNode);
            appendMessage(quickReply.title, "sent");
            socket.emit('user_uttered', {
                "message": quickReply.payload,
            });
        })
        quickRepliesNode.appendChild(quickReplyDiv);
    })
    messages.appendChild(quickRepliesNode);
    scrollToBottom();
}
```

## Styles
Finally, to make the page nicer, let's add some CSS.
First, add a [CSS reset](https://meyerweb.com/eric/tools/css/reset/). Then add the rest of the styles.

```CSS
/** CSS RESET        **/
/** ...              **/
/** END OF CSS RESET **/

body {
    --white-color: #f3f4fb;
    --black-color: black;
    --blue-color: #5a18ee;
    --light-purple-color: #7f7afc;
    --light-violet-color: #8c54f4;
    --darker-violet-color: #713dc3;
    --dark-violet-color: #5d2db0;
    font-family: Roboto, sans-serif;
    background-color: var(--blue-color);
}

#form {
    padding: 0.25rem;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    height: 3rem;
    box-sizing: border-box;
    background-color: var(--black-color);
}

#message-input {
    flex-grow: 1;
    padding: 0 1rem;
    border-radius: 2rem;
    margin: 0.25rem;
    background-color: var(--white-color);
}

#message-input:focus {
    outline: none;
}

.button {
    background: var(--light-violet-color);
    border: none;
    padding: 0 1rem;
    margin: 0.25rem;
    border-radius: 3px;
    outline: none;
    color: var(--white-color);
    font: inherit;
}

.button:hover {
    background: var(--darker-violet-color);
}

.button:active {
    background: var(--dark-violet-color);
}

#messages {
    display: flex;
    flex-direction: column;
    padding: 10px 10px 60px 10px;
}

.message {
    padding: 0.5rem 1rem;
    border-radius: 10px;
    word-wrap: break-word;
    max-width: calc(100% - 40px - 32px);
    margin-bottom: 10px;
}

.message_received {
    background: var(--white-color);
    color: var(--black-color);
    border-bottom-left-radius: 0;
    align-self: flex-start;
}

.message_sent {
    color: var(--white-color);
    background: var(--light-purple-color);
    border-bottom-right-radius: 0;
    align-self: flex-end;
}

.header {
    background-color: var(--black-color);
    color: var(--white-color);
    text-align: center;
    padding: 12px;
}

.title {
    font-size: 23px;
}

.quick-replies {
    display: flex;
    align-self: flex-end;
    height: 2.5rem;
    box-sizing: border-box;
}
```

## Run the application
Now let's start the application from the `webchat` folder by running `npx http-server .`.

Because the rasa server will be running on `http://localhost:5005` and our chat application is running on `http://localhost:8080`, we need to enable CORS for the rasa server.

1. Run the rasa server with enabled cors for all origins: `rasa run --cors "*"`. Alternatively, enable only a specific host and port: `rasa run --cors "http://127.0.0.1:8080"`.
2. In a new terminal window, run the actions server: `rasa run actions`.
3. Open `http://127.0.0.1:8080` and chat with the chatbot!

![Web chat with Rasa](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2hz5ct41u5soiaudwm12.gif)

The web chat we created does not support all Rasa functionality and it could be improved in many ways. If you want to add Rasa chat to your webpage, consider [rasa-webchat](https://github.com/botfront/rasa-webchat) which provides more features.
 
To learn more about Socket.IO channel connector, visit the Rasa [documentation](https://rasa.com/docs/rasa/connectors/your-own-website/#websocket-channel).

Repository for this tutorial:
{% github petr7555/rasa-dev-tutorial no-readme %}

You can checkout the state of the repository at the end of this tutorial by running:
```bash
git clone --branch 22-socketio-integration git@github.com:petr7555/rasa-dev-tutorial.git
```