Blog

Building a chatbot app in Java

By  
Alejandro Duarte
Alejandro Duarte
·
On Jun 2, 2020 8:50:19 PM
·

Ever since I played my first role-based video game, I’ve been interested in chatbots (aka chatterbots). How could I implement something like this? Sure, I could code a bunch of if...else statements, but that sounded like a lot of work. Some years ago, I discovered AIML (Artificial Intelligence Markup Language), an XML dialect designed by Richard Wallace to create a bot called A.L.I.C.E. (Artificial Linguistic Internet Computer Entity).

In this article, I'll share my experience writing a web-based chatbot with Spring, Vaadin, and AIML. You can see the final app in action at https://alejandro.app.fi/ai-chat and the source code on GitHub.

What is AIML?

AIML is remarkably versatile. It is used in not only the gaming industry, but also education, marketing, and e-commerce. There's a good chance you have interacted with bots in, for example, lead generation processes where you answer a set of questions and in return get information or even products delivered to you. Customer service is another business-oriented application that harnesses the potential of bots by providing answers to frequently asked questions or redirecting to human agents according to the customer’s answers.

An AIML interpreter takes AIML files (typically with the .aiml extension) and an input string, and produces an output string—the bot's answer. To give you an idea of what AIML files look like, study the following example:

<aiml>
    <category>
        <pattern>HELLO</pattern>
        <template>Hello there!</template>
    </category> 
</aiml>

The category element defines an interaction unit. The pattern element defines a possible input by the user. The template defines a possible answer by the bot. This would lead to the following possible dialog:

User: Hello

Bot: Hello there!

It is possible to define wildcards. For example:

<category>
    <pattern>I like *</pattern>
    <template>I like <star/> too!</template>
</category>

The wildcard character (*) represents what the user says they like. The value is used in the template with the <star/> tag. The following is a possible conversation using this category:

User: I like Vaadin

Bot: I like Vaadin too!

You can also add multiple possible answers per pattern and let the interpreter pick one randomly. For example:

<category>
    <pattern>Hi</pattern>
    <template>
        <random>
            <li>Hi!</li>
            <li>Hello!</li>
            <li>Hello, how are you?</li>
        </random>
    </template>
</category>

There's a lot more to AIML and plenty of good online resources to learn about it. For now, all you need to understand is the format.

Using AIML in Java applications

So, how do you use AIML in Java? As I expected, there are AIML interpreters for many programming languages. I found an old, but good, free Java AIML interpreter at https://code.google.com/archive/p/program-ab. It is available through Maven using the JCenter repository:

<repository>
    <id>JCenter</id>
    <url>https://jcenter.bintray.com</url>
</repository>

<dependency>
    <groupId>org.goldrenard</groupId>
    <artifactId>ab</artifactId>
    <version>1.0.7</version>
</dependency>

Using this library was very straightforward. You have to create a Bot and a Chat object:

Bot bot = new Bot(BotConfiguration.builder()
                            .name("alice")
                            .path("src/main/resources")
                            .build());

chatSession = new Chat(bot);

With the previous configuration, I had to put the .aiml files in the src/main/resources/alice/aiml directory. The interpreter reads all the files and prepares to provide intelligent-looking answers. To get an answer I used something like this:

String answer = chatSession.multisentenceRespond("Hello");

After a first successful experiment, I decided to build a real web chatbot application. And since I'm a Java guy, I had to use Vaadin.

Building a web UI to interact with AIML bots

I won't go through the exact steps to create a web app with Vaadin here. Instead, I'll focus on the parts I found more interesting while developing this application. For a step-by-step tutorial on how to build a basic chat application from scratch, see https://dzone.com/articles/implementing-a-web-chat-with-ai-in-java.

I started by creating a new Spring Boot + Vaadin app at https://start.vaadin.com. At first I thought I could tweak the theme there, but then I remember there's a theme editor that I could use later, so I didn't modify the theme at that point, since I wasn't completely sure what I wanted in terms of looks just yet.

I knew the key to the app was the message list that shows the conversation. So I quickly coded a custom Div that allows lines of text to be added to it:

public class MessageList extends Div {
    public void addMessage(String from, String text) {
        Div line = new Div(new Text(from + ": " + text));
        add(line);
    }
}

I implemented a view that added this component alongside a TextField and a Button to allow the user to send messages to the bot. Something like this:

@Route(value = "chat", layout = MainView.class)
public class ChatView extends VerticalLayout {

    public ChatView() {
        Bot bot = new Bot(BotConfiguration.builder()
                .name("alice")
                .path("src/main/resources")
                .build());
        Chat chatSession = new Chat(bot);

        MessageList messageList = new MessageList();
        TextField message = new TextField();

        Button send = new Button(VaadinIcon.ENTER.create(), event -> {
            String text = message.getValue();
            String answer = chatSession.multisentenceRespond(text);

            message.clear();
            messageList.addMessage("You", text);
            messageList.addMessage("Alice", answer);
        });
        add(messageList, new HorizontalLayout(message, send));
    }

}

It looked like this:

Building a chatbot with Vaadin-3

That was quick! However…

Challenge 1: Processing and memory issues

At this point, I realized the first problem with the application. The bot object was expensive to build and it was created every time I invoked the application. I figured I could share the instance with all the views, so I went ahead and defined a Spring-managed bean for that:

@Bean // default scope is singleton
public Bot alice() {
    return new Bot(BotConfiguration.builder()
            .name("alice")
            .path("src/main/resources")
            .build()
    );
}

I injected this bean into the view and things were much faster:

public ChatView(Bot alice) { // Spring injects the instance
    Chat chatSession = new Chat(alice);

    MessageList messageList = new MessageList();
    TextField message = new TextField();

    Button send = new Button(VaadinIcon.ENTER.create(), event -> {
        String text = message.getValue();
        String answer = chatSession.multisentenceRespond(text);

        message.clear();
        messageList.addMessage("You", text);
        messageList.addMessage("Alice", answer);
    });
    add(messageList, new HorizontalLayout(message, send));
}

Always remember that the application will create an instance of the view for each user or browser tab. In a web environment, this could potentially mean a lot of instances!

Now that the bot is shared between users, I went ahead and tried the application and it performed much faster every time I refreshed the browser or opened a new tab. But, after using the app again, I noticed that the layout wasn't what I expected.

Challenge 2: Scrolling and UX

When I sent several messages to the bot, the layout started to look odd:

Screenshot 2020-05-20 at 18.43.23-1

The whole layout scrolled. I wanted the text field and button to remain at the bottom of the page and only the messages to scroll. Also, the layout should scroll down automatically, so you can see the latest message.

At that point there wasn't a Scroller component (only available since Vaadin 14.2), so I had to use some CSS to make a Div scrollable. That was easy though, I just added a CSS class name to the component in Java using addClassName("MessageList") and defined the selector in a CSS file as:

.MessageList {
    overflow-y: scroll;
    width: 100%;
    height: 100%;
}

Again, this's only if you are using earlier versions than 14.2. Since version 14.2 you can simply use the Scroller class.

Next, I made the view and text field full size and set the width of the horizontal layout that contained the text field and the button to 100%. Basic Vaadin layouting stuff.

The view looked better, but still I could not see the latest message sometimes. Fixing this was easy with the help of some JavaScript. I added the following to the MessageList class just after adding a new message to the layout:

line.getElement().callJsFunction("scrollIntoView");

This ensures that the line UI component is visible when it's added.

The other thing I didn't like was that I had to click the button to send a message. That's not how I expect a chat to work. So I added a click shortcut to the button. Again, basic Vaadin stuff.

I was happy with the app, it was a minimal, but complete, chat application that allowed users to interact with bots. However, I'm kind of a go-getter and thought "this would look way cooler with some custom styles and avatars...".

Challenge 3: Theming

Theming was a lot of fun to do. Especially since it changes the appearance of the app so dramatically. While I was developing the app, I shared several thoughts and screenshots with my colleague Marcus H. and mentioned that the app would look nicer with avatars. He pointed me to Artur Signell's avataaar component, which looked just perfect for what I needed. So I went ahead, added the dependency, and modified the code to something like this:

public void addMessage(String from, Avataaar avatar, String text) {
    Span fromContainer = new Span(new Text(from));
    Div textContainer = new Div(new Text(text));
    Div avatarContainer = new Div(avatar, fromContainer);

    Div line = new Div(avatarContainer, textContainer);
    add(line);

    line.getElement().callJsFunction("scrollIntoView");
}

From the view, I passed avatars like new Avataaar("Alice") and new Avataaar("You"). The component creates an avatar from the string. So the app looked like this:

Screenshot 2020-05-20 at 19.34.26It was good to see those avatars, but the layout… it wasn't that great.

If you look at the previous code, you'll notice that I used mostly divs everywhere. I knew that I could play with CSS until the layout looked exactly like what I wanted. First, I added a CSS class name to every UI component:

Span fromContainer = new Span(new Text(from));
fromContainer.addClassName(getClass().getSimpleName() + "-name");

Div textContainer = new Div(new Text(text));
textContainer.addClassName(getClass().getSimpleName() + "-bubble");

Div avatarContainer = new Div(avatar, fromContainer);
avatarContainer.addClassName(getClass().getSimpleName() + "-avatar");

Div line = new Div(avatarContainer, textContainer);
line.addClassName(getClass().getSimpleName() + "-row");
add(line);

The easiest one here is the fromContainer span element. I wanted that to be shown in bold font:

.MessageList-name {
    font-weight: bold;
}

Done:

Screenshot 2020-05-20 at 20.44.47The second element I wanted to fix was the "line", the one with the MessageList-row CSS class. I wanted this to be a row, so that the message was next to the avatar. Using the browser inspector I edited the MessageList-row selector to set the display property to flex and align-items to center:

Screenshot 2020-05-20 at 20.50.13I copied this CSS code and put it in the .css file in the project.

Finally, I wanted to show the avatar above the name of the person (or bot). Once again, using the browser inspector, I edited the MessageList-avatar selector to set the display property to flex, the flex-direction to column and centered the contained items:

Screenshot 2020-05-20 at 20.54.07It worked, so I copied the CSS to the project.

I used a similar approach to tweak the CSS until I got the look I wanted. Something worth mentioning is that I used the Lumo CSS variables to change the colors. For example:

.MessageList-bubble {
    margin: .5em;
    padding: 1em;
    border-radius: var(--lumo-border-radius-s);
    background-color: var(--lumo-shade-20pct);
}

By using the CSS variables I was sure that if I changed the theme later, things would be in sync with the colors of the theme. So at this point I had this:

Screenshot 2020-05-20 at 21.00.21Almost there! The final touch was pretty cool as well, and easy to implement. I wanted the user aligned to the right of the page. Since I was using flex display, I knew I could invert the direction of the layout for the messages from the user. I used the browser inspector to test my idea:

Screenshot 2020-05-20 at 21.03.16Bang! A one-liner. On the Java side, I just added a flag to indicate whether the message came from the user or not:

public void addMessage(String from, Avataaar avatar, String text, boolean isCurrentUser) {
    ...
    if (isCurrentUser) {
        line.addClassName(getClass().getSimpleName() + "-row-currentUser");
        textContainer.addClassName(getClass().getSimpleName() + "-bubble-currentUser");
    }
    ...
}

Then I added the one-liner to the CSS selector plus some colors and got this:

Screenshot 2020-05-20 at 21.07.54Since I was excited to see how Vaadin allows me to do anything with CSS, I went ahead and customized the styles a bit more. I got some help though: The awesome Lumo theme editor. I also added some other tricks that you can explore in the source code on GitHub. Here's the final result:

Screenshot 2020-05-20 at 21.30.39

Btw, did you notice the menu? That brings me to…

Challenge 4: Adding multiple bots

I figured it'd be a good idea to add multiple bots to the application. First I searched for ready-made bots online and found some interesting ones. I downloaded the files for some of these and put each set of files in a different directory in resources/bots/. I wanted the bots to be discovered at runtime, so that if I want to add more in the future, I’d only have to add the files and do nothing to the source code of the application.

Again, this was easy to do. I used Spring's events to dynamically define beans when the application starts. I scanned the directory containing the bots and looped through each one, creating a new instance and registering a new bean for each bot. Here's how:

@EventListener
public void createBots(ApplicationReadyEvent event) {
    File[] directories = new File(botPath + "/bots")
            .listFiles((FileFilter) FileFilterUtils.directoryFileFilter());

    if (directories != null) {
        Arrays.stream(directories)
                .map(File::getName)
                .map(name -> new Bot(BotConfiguration.builder()
                        .name(name)
                        .path(botPath)
                        .build()))
                .forEach(bot -> event.getApplicationContext().getBeanFactory().registerSingleton(bot.getName(), bot));
    }
}

I let the ChatView accept a parameter that would contain the name of the bot (which is the name of the Spring bean as well) and get the bean using an ApplicationContext bean (that you can autowire or inject in your view):

public class ChatView extends VerticalLayout implements HasUrlParameter<String> {

    ...
    private Bot bot;
    private Chat chatSession;

    public ChatView(ApplicationContext applicationContext) {
        ...
    }

    ...

    @Override
    public void setParameter(BeforeEvent event, String botName) {
        bot = (Bot) applicationContext.getBean(botName);
        chatSession = new Chat(bot);
        messageList.clear();
    }

}

So if I want to chat with Alice, I navigate to http://localhost:8080/chat/Alice, if I want to chat with Sara, to http://localhost:8080/chat/Sara, and so forth.

Conclusion

I added some other features to the app. For example, a join view to allow the user to select a name that determines the avatar. I externalized the AIML files so I could deploy the app to a production environment. I also added a random delay to the bot answers to make it look more human.

There are still a lot of things you could try yourself. For example, adding an option to make two bots talk to each other. Or designing an AIML that returns answers in a structured way and processes the answer to show, for example, videos, maps, images, or even Vaadin components in the chat.

Also, check out the Chat and Avatar components of the Vaadin ComponentFactory. They will increase your productivity when developing interactive or collaborative applications.

Alejandro Duarte
Alejandro Duarte
Software Engineer and Developer Advocate at MariaDB Corporation. Author of Practical Vaadin (Apress), Data-Centric Applications with Vaadin 8 (Packt), and Vaadin 7 UI Design by Example (Packt). Passionate about software development with Java and open-source technologies. Contact him on Twitter @alejandro_du or through his personal blog at www.programmingbrain.com.
Other posts by Alejandro Duarte