diff --git a/articles/components/message-input/index.adoc b/articles/components/message-input/index.adoc index f186cf490c..65c4b82be8 100644 --- a/articles/components/message-input/index.adoc +++ b/articles/components/message-input/index.adoc @@ -70,6 +70,25 @@ include::{root}/frontend/demo/component/messages/react/message-basic.tsx[render, endif::[] -- +== File Attachments + +Use the <<../upload/modular-upload#,Modular Upload>> components to add file attachment support to Message Input. Place an [classname]`UploadButton` alongside the input and an [classname]`UploadFileList` above it to show pending attachments. When the user submits a message, clear the file list using [methodname]`clearFileList()` on the [classname]`UploadManager`. + +[.example,themes="lumo,aura"] +-- +ifdef::flow[] +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/component/messages/MessageInputUpload.java[render,tags=snippet,indent=0,group=Flow] +---- +endif::[] +-- + +The upload button opens a file picker. Selected files appear in the file list above the input. When the user sends the message, the submit listener processes the pending files and clears the attachment list. + +For AI-powered chat applications, see <<{articles}/flow/ai-support/file-attachments#,File Attachments in AI Support>>, which automates this integration using the [classname]`AIOrchestrator`. + + == Related Components [cols="1,2"] @@ -78,6 +97,8 @@ endif::[] |<<../message-list#,Message List>>|Show a list of messages. +|<<../upload/modular-upload#,Modular Upload>>|Add file attachment support to the input. + |=== diff --git a/articles/components/message-list/index.adoc b/articles/components/message-list/index.adoc index fd5bc19d87..864e38f5d0 100644 --- a/articles/components/message-list/index.adoc +++ b/articles/components/message-list/index.adoc @@ -110,6 +110,8 @@ endif::[] Each attachment has a name, a URL, and a MIME type. The MIME type determines how the attachment is rendered: types starting with `image/` are shown as thumbnail previews, while all other types are shown as file icons. You can listen for attachment click events to handle downloads or other actions. +To let users upload file attachments, use <<../upload/modular-upload#,Modular Upload>> components with <<../message-input#file-attachments,Message Input>>. + == Usage for AI Chats @@ -173,6 +175,7 @@ include::{root}/frontend/themes/docs/screen-reader-only.css[] |Component |Usage recommendations |<<../message-input#,Message Input>>|Allow users to author and send messages. +|<<../upload/modular-upload#,Modular Upload>>|Add file upload support for attachments. |<<../markdown#,Markdown>>|Standalone component for rendering Markdown content. |=== diff --git a/articles/components/upload/modular-upload.adoc b/articles/components/upload/modular-upload.adoc index 2a5c2731d0..4ff8a188f0 100644 --- a/articles/components/upload/modular-upload.adoc +++ b/articles/components/upload/modular-upload.adoc @@ -135,6 +135,23 @@ endif::[] -- +== Chat Input with Attachments + +The modular design makes it straightforward to integrate upload functionality into a <<../message-input#,Message Input>> component for a chat-with-attachments experience. Place an [classname]`UploadButton` next to the input and an [classname]`UploadFileList` above it. On submit, process the pending files and call [methodname]`clearFileList()` to reset. + +[.example,themes="lumo,aura"] +-- +ifdef::flow[] +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/component/messages/MessageInputUpload.java[render,tags=snippet,indent=0,group=Flow] +---- +endif::[] +-- + +For AI-powered chat, the <<{articles}/flow/ai-support/file-attachments#,AIOrchestrator>> can automate this wiring. + + == Configuration The [classname]`UploadManager` accepts an <> for processing files, and shares most of its configuration API with the standard <> component -- including <> (file count, size, and format), <>, and <>. @@ -207,6 +224,9 @@ endif::[] |<> |The default Upload component with built-in drop zone, button, and file list. +|<<../message-input#,Message Input>> +|Compose with upload components for chat-with-attachments. + |<<../progress-bar#,Progress Bar>> |Component for showing task completion progress. diff --git a/articles/flow/ai-support/file-attachments.adoc b/articles/flow/ai-support/file-attachments.adoc index c422c28c91..9fbde59dbc 100644 --- a/articles/flow/ai-support/file-attachments.adoc +++ b/articles/flow/ai-support/file-attachments.adoc @@ -59,4 +59,6 @@ var orchestrator = AIOrchestrator .build(); ---- +For a manual approach without the [classname]`AIOrchestrator`, see <<{articles}/components/message-input#file-attachments,File Attachments in Message Input>>. + endif::flow[] diff --git a/src/main/java/com/vaadin/demo/component/messages/MessageInputUpload.java b/src/main/java/com/vaadin/demo/component/messages/MessageInputUpload.java new file mode 100644 index 0000000000..8d3bfe2fb2 --- /dev/null +++ b/src/main/java/com/vaadin/demo/component/messages/MessageInputUpload.java @@ -0,0 +1,90 @@ +package com.vaadin.demo.component.messages; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.vaadin.demo.DemoExporter; // hidden-source-line +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-input-upload") +public class MessageInputUpload extends Div { + + // Pending files, keyed by file name + private final Map pendingFiles = new LinkedHashMap<>(); + + public MessageInputUpload() { + // tag::snippet[] + 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 + + // Remove files from the pending map when the user removes them + manager.addFileRemovedListener(event -> { + pendingFiles.remove(event.getFileName()); + }); + + var uploadButton = new UploadButton(manager); + + var fileList = new UploadFileList(manager); + fileList.addThemeVariants(UploadFileListVariant.THUMBNAILS); + fileList.setWidthFull(); + + var messageInput = new MessageInput(); + var messageList = new MessageList(); + + messageInput.addSubmitListener(event -> { + var message = new MessageListItem(event.getValue(), + Instant.now(), "You"); + message.setUserColorIndex(1); + + // Add pending files as attachments + for (var entry : pendingFiles.entrySet()) { + message.addAttachment(new MessageListItem.Attachment( + entry.getKey(), "#", "application/octet-stream")); + } + + var items = new ArrayList<>(messageList.getItems()); + items.add(message); + messageList.setItems(items); + + // Clear pending attachments + pendingFiles.clear(); + manager.clearFileList(); + }); + + var inputLayout = new HorizontalLayout(uploadButton, messageInput); + inputLayout.setWidthFull(); + inputLayout.expand(messageInput); + inputLayout.setAlignItems(FlexComponent.Alignment.END); + + var layout = new VerticalLayout(messageList, fileList, inputLayout); + layout.expand(messageList); + layout.setSizeFull(); + // end::snippet[] + + setSizeFull(); // hidden-source-line + add(layout); + } + + public static class Exporter // hidden-source-line + extends DemoExporter { // hidden-source-line + } // hidden-source-line +}