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.
Psst....Are you looking to integrate an AI chatbot into your Java application? Check out our latest tutorial: Building an OpenAI-powered chatbot in Java.
What is AIML?
AIML is remarkably versatile. It is used not only in the gaming industry but also in 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 remembered 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:
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. It performed much faster every time I refreshed the browser or opened a new tab. However, 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:
The whole layout was 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 only applies to 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, this is 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:
It was good to see those avatars, but the layout… 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:
The 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
:
I 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:
It 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:
Almost 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 a 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:
Bang! 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:
Since 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:
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, such as a join view that allows 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 them 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.