DEV Community

Sami Ekblad
Sami Ekblad

Posted on • Updated on • Originally published at vaadin.com

Happy path: Building a Chatbot in Vaadin with OpenAI

Want to build better version of ChatGPT? Here is how to use OpenAI's Chat completions API to add a robot participant to your Vaadin chat app.

Image description

Create a new chat app project

Using the service at start.vaadin.com, create a new Vaadin application. There actually is a complete chat example template, but the "hello world" sample is enough for this.

Create a simple chat UI

Using MessageList and MessageInput from Vaadin, create a simple chat UI:

public class ChatView extends VerticalLayout {
    private MessageList chat;
    private MessageInput input;

    public ChatView() {
        chat = new MessageList();
        input = new MessageInput();
        add(chat, input);
    input.addSubmitListener(this::onSubmit);
    }
}
Enter fullscreen mode Exit fullscreen mode

And to make everything align nicely:

this.setHorizontalComponentAlignment(Alignment.CENTER, 
        chat, input);
this.setPadding(true); // Leave some white space
this.setHeightFull(); // We maximize to window
chat.setSizeFull(); // Chat takes most of the space
input.setWidthFull(); // Full width only
chat.setMaxWidth("800px"); // Limit the width
input.setMaxWidth("800px"); 
Enter fullscreen mode Exit fullscreen mode

Create OpenAI Java API to call

The next step is the interesting one. There is a Java API for OpenAI, but can ChatGPT create a simple REST API to call itself from Java? It took a few steps to get right, but yes, it can. Here is what I asked so you get the idea...

Write a simple Java class called "OpenAI" to call completions to chat API with one "send" method that takes the latest user input as a parameter and returns updated chat messages. Provide inner classes "ChatRequest" and "ChatResponse" and use Jackson ObjectMapper for communication.

[ ... ]

That code uses the basic completion API, not the chat completion API endpoint at api.openai.com/v1/chat/completions. Can you adjust the code?

[ ... ]

Change the return value of send to be a List of instances of inner class "ChatMessage" instead of an array of strings.

[ ... ]

Change the ChatResponse class to match the following JSON:

[ ... ]

Write a sendAsync method that calls the OpenAI.send method asynchronously.

[ ... ]

And so on. Eventually, with some manual tweaking, here we are. Just the way I wanted it. Spring Component, very readable code.

Make the Vaadin app chat with you

To call the OpenAI's chat endpoint synchronously is simple, add a submit listener to the MessageInput:

input.addSubmitListener(this::onSubmit);
Enter fullscreen mode Exit fullscreen mode

and implement the method:

private void onSubmit(MessageInput.SubmitEvent submitEvent) {
    List<OpenAI.Message> messages =
            openAI.send(submitEvent.getValue());     
    chat.setItems(messages.stream()
            .map(this::convertMessage)
            .collect(Collectors.toList()));
}
Enter fullscreen mode Exit fullscreen mode

That combined with straight-forward conversion from Message to MessageListItem:

private MessageListItem convertMessage(OpenAI.Message msg) {
    return new MessageListItem(msg.getContent(),
                            msg.getTime(),
                            formatName(msg.getRole()));
}
Enter fullscreen mode Exit fullscreen mode

Chatting asynchronously

To make the chat behave more real-time, few steps are needed
Enable websockets by adding @Push in Application class and to
use OpenAI.sendAsync instead to return a CompletableFuture

Note: to update the UI in the asynchronous callback, wrapping it into a UI.access call.

@Push
public class Application implements AppShellConfigurator {
    // ...
}
Enter fullscreen mode Exit fullscreen mode
private void onSubmit(MessageInput.SubmitEvent submitEvent) {
openAI.sendAsync(submitEvent.getValue())
    .whenComplete((messages, t) -> {
        // Lock the Vaadin UI for updates
        getUI().get().access(() -> {
            chat.setItems(messages.stream()
                        .map(this::convertMessage)
                        .collect(Collectors.toList()));
        });
    });
Enter fullscreen mode Exit fullscreen mode

Conclusion

That was nice. Vaadin components provide a nice familiar look and feel, and you can easily customize them to your own needs. The complete source code is again available on GitHub for your (and AI's) inspiration: github.com/samie/vaadin-openai-chat

Just update the openai.apikey in the application.properties and you are good to try it yourself.

Continuing from here, you can guess where this is heading if you already saw my voice activation tips for Vaadin.

Top comments (0)