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.
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);
}
}
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, 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()));
}
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()));
}
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 {
// ...
}
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()));
});
});
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)