From aed96f02507014f85a8db0fe610eeb6744b9f2bc Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 14 Apr 2026 09:01:58 +0000 Subject: [PATCH] docs: Add modular upload to the AI chat example Update MessageListAiChat to include UploadManager, UploadButton, and UploadFileList so the AI chat demo shows file attachment support. Pending files are added as attachments to the user message on submit. --- articles/components/message-list/index.adoc | 1 + .../component/messages/MessageListAiChat.java | 50 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/articles/components/message-list/index.adoc b/articles/components/message-list/index.adoc index fd5bc19d87..e3eb09525a 100644 --- a/articles/components/message-list/index.adoc +++ b/articles/components/message-list/index.adoc @@ -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: diff --git a/src/main/java/com/vaadin/demo/component/messages/MessageListAiChat.java b/src/main/java/com/vaadin/demo/component/messages/MessageListAiChat.java index a807b2238d..26529cba7b 100644 --- a/src/main/java/com/vaadin/demo/component/messages/MessageListAiChat.java +++ b/src/main/java/com/vaadin/demo/component/messages/MessageListAiChat.java @@ -1,6 +1,8 @@ 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; @@ -8,11 +10,21 @@ 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 pendingFiles = new LinkedHashMap<>(); + private MessageListItem createItem(String text, boolean assistant) { MessageListItem item = new MessageListItem(text, assistant ? "Assistant" : "User"); @@ -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"); @@ -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); @@ -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 }