Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions articles/components/message-list/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Combine Message List with Message Input to create effective AI chat interfaces.

* Message List with Markdown formatting to display conversation history
* Message Input for user interactions
* <<../upload/modular-upload#,Modular Upload>> components for file attachments
* Backend service to handle AI interactions

Follow these best practices for a smooth user experience:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package com.vaadin.demo.component.messages;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.vaadin.demo.DemoExporter; // hidden-source-line
import com.vaadin.demo.component.messages.LLMClient.Message;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.messages.MessageInput;
import com.vaadin.flow.component.messages.MessageList;
import com.vaadin.flow.component.messages.MessageListItem;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.upload.UploadButton;
import com.vaadin.flow.component.upload.UploadFileList;
import com.vaadin.flow.component.upload.UploadFileListVariant;
import com.vaadin.flow.component.upload.UploadManager;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.streams.UploadHandler;

@Route("message-list-ai-chat")
public class MessageListAiChat extends Div {

private final Map<String, byte[]> pendingFiles = new LinkedHashMap<>();

private MessageListItem createItem(String text, boolean assistant) {
MessageListItem item = new MessageListItem(text,
assistant ? "Assistant" : "User");
Expand All @@ -28,6 +40,21 @@ public MessageListAiChat() {
// end::snippet[]
MessageInput input = new MessageInput();

// Modular upload for file attachments
var handler = UploadHandler.inMemory((metadata, data) -> {
pendingFiles.put(metadata.fileName(), data);
});
var manager = new UploadManager(this, handler);
manager.setMaxFiles(5);
manager.setMaxFileSize(10L * 1024 * 1024); // 10 MB
manager.addFileRemovedListener(
event -> pendingFiles.remove(event.getFileName()));

var uploadButton = new UploadButton(manager);
var fileList = new UploadFileList(manager);
fileList.addThemeVariants(UploadFileListVariant.THUMBNAILS);
fileList.setWidthFull();

// Live region for screen reader announcements
Div liveRegion = new Div();
liveRegion.getElement().setAttribute("aria-live", "polite");
Expand All @@ -43,8 +70,17 @@ public MessageListAiChat() {
input.addSubmitListener(e -> {
String userInput = e.getValue();

// Add the user message to the list
list.addItem(createItem(userInput, false));
// Add the user message with any pending attachments
MessageListItem userMessage = createItem(userInput, false);
for (var entry : pendingFiles.entrySet()) {
userMessage.addAttachment(new MessageListItem.Attachment(
entry.getKey(), "#", "application/octet-stream"));
}
list.addItem(userMessage);

// Clear pending attachments
pendingFiles.clear();
manager.clearFileList();

// Add the Assistant message to the list
MessageListItem newAssistantMessage = createItem("", true);
Expand Down Expand Up @@ -78,7 +114,15 @@ public MessageListAiChat() {
});
});

add(list, input);
var inputLayout = new HorizontalLayout(uploadButton, input);
inputLayout.setWidthFull();
inputLayout.expand(input);
inputLayout.setAlignItems(FlexComponent.Alignment.END);

var chatLayout = new VerticalLayout(list, fileList, inputLayout);
chatLayout.expand(list);
chatLayout.setSizeFull();
add(chatLayout);

com.vaadin.demo.component.messages.LLMClient.initPolling(list); // hidden-source-line
}
Expand Down
Loading