Updated in August 2024.
Want to build a better version of ChatGPT? Learn how to integrate OpenAI's Chat Completions API to add an intelligent chatbot to your Vaadin Flow chat application.
Create a new chat app with OpenAI chatbot integration
Using the service at start.vaadin.com, create a new Vaadin application. A complete chat example template is available, but the "hello world" sample is enough.
Create a simple chat UI
Using MessageList and MessageInput from Vaadin Flow, 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);
}
}
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");
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. Just add a submit listener to the MessageInput
:
input.addSubmitListener(this::onSubmit);
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()));
}
Combine the above with a straight-forward conversion from Message
to MessageListItem
:
@Push
public class Application implements AppShellConfigurator {
// ...
}
Chatting asynchronously
To make the chat behave more real-time, a few steps are needed
Enable WebSockets by adding @Push in the Application class and
use OpenAI.sendAsync
to return a CompletableFuture.
Note: to update the UI in the asynchronous callback, wrap it into a UI.access call.
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()));
});
});
Final thoughts
Vaadin components provide a nice, familiar look and feel, and you can easily customize them to your own needs. The complete source code is available on GitHub for your (and AI's) inspiration: github.com/samie/vaadin-openai-chat
Just update the openai.apikey in application.properties
, and you are good to try it yourself.
Continuing from here, you can guess where this is heading if you have already seen my voice activation tips for Vaadin.