From 598ee95af11ccb00fd0fb83bd5cd7b3b9482302c Mon Sep 17 00:00:00 2001 From: Martin Lopez <mlopez@flowingcode.com> Date: Thu, 3 Oct 2024 19:47:27 -0300 Subject: [PATCH] feat: add markdown support via markdown-editor-add-on Closes #23 --- pom.xml | 6 ++ .../addons/chatassistant/ChatAssistant.java | 16 +++- .../addons/chatassistant/ChatMessage.java | 24 +++++- .../frontend/styles/chat-message-styles.css | 15 ++++ .../chatassistant/ChatAssistantDemoView.java | 1 + .../ChatAssistantMarkdownDemo.java | 73 +++++++++++++++++++ 6 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantMarkdownDemo.java diff --git a/pom.xml b/pom.xml index af12d13..db4a615 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ <drivers.dir>${project.basedir}/drivers</drivers.dir> <jetty.version>11.0.12</jetty.version> <flowingcode.commons.demo.version>3.10.0</flowingcode.commons.demo.version> + <markdown-editor.version>1.0.0</markdown-editor.version> <frontend.hotdeploy>true</frontend.hotdeploy> </properties> @@ -127,6 +128,11 @@ <artifactId>vaadin-core</artifactId> <optional>true</optional> </dependency> + <dependency> + <groupId>com.flowingcode.vaadin.addons</groupId> + <artifactId>markdown-editor-addon</artifactId> + <version>${markdown-editor.version}</version> + </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> diff --git a/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistant.java b/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistant.java index 21ce47a..30b2881 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistant.java +++ b/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistant.java @@ -74,21 +74,31 @@ public class ChatAssistant extends Div { * Default constructor. Creates a ChatAssistant with no messages. */ public ChatAssistant() { - this(new ArrayList<>()); + this(new ArrayList<>(), false); } + /** + * Creates a ChatAssistant with no messages. + * + * @param markdownEnabled flag to enable or disable markdown support + */ + public ChatAssistant(boolean markdownEnabled) { + this(new ArrayList<>(), markdownEnabled); + } + /** * Creates a ChatAssistant with the given list of messages. * * @param messages the list of messages + * @param markdownEnabled flag to enable or disable markdown support */ - public ChatAssistant(List<Message> messages) { + public ChatAssistant(List<Message> messages, boolean markdownEnabled) { this.messages = messages; content.getElement().setAttribute("slot", "content"); content.setItems(messages); content.setRenderer(new ComponentRenderer<ChatMessage, Message>( - message -> new ChatMessage(message), (component, message) -> { + message -> new ChatMessage(message, markdownEnabled), (component, message) -> { ((ChatMessage) component).setMessage(message); return component; })); diff --git a/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatMessage.java b/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatMessage.java index 408776c..8d124d9 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatMessage.java +++ b/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatMessage.java @@ -20,6 +20,8 @@ package com.flowingcode.vaadin.addons.chatassistant; import com.flowingcode.vaadin.addons.chatassistant.model.Message; +import com.flowingcode.vaadin.addons.markdown.BaseMarkdownComponent.DataColorMode; +import com.flowingcode.vaadin.addons.markdown.MarkdownViewer; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.HasComponents; import com.vaadin.flow.component.Tag; @@ -42,14 +44,26 @@ public class ChatMessage extends Component implements HasComponents { private Message message; + private boolean markdownEnabled; private Div loader; /** - * Creates a new ChatMessage based on the supplied message. + * Creates a new ChatMessage based on the supplied message without markdown support. * * @param message message used to populate the ChatMessage instance */ public ChatMessage(Message message) { + this(message, false); + } + + /** + * Creates a new ChatMessage based on the supplied message. + * + * @param message message used to populate the ChatMessage instance + * @param markdownEnabled whether the message supports markdown or not + */ + public ChatMessage(Message message, boolean markdownEnabled) { + this.markdownEnabled = markdownEnabled; setMessage(message); } @@ -83,7 +97,13 @@ private void updateLoadingState(Message message) { this.remove(loader); loader = null; } - this.getElement().executeJs("this.appendChild(document.createTextNode($0));", message.getContent()); + if (markdownEnabled) { + MarkdownViewer mdv = new MarkdownViewer(message.getContent()); + mdv.setDataColorMode(DataColorMode.LIGTH); + this.add(mdv); + } else { + this.getElement().executeJs("this.appendChild(document.createTextNode($0));", message.getContent()); + } } } diff --git a/src/main/resources/META-INF/frontend/styles/chat-message-styles.css b/src/main/resources/META-INF/frontend/styles/chat-message-styles.css index 53e5604..6c24929 100644 --- a/src/main/resources/META-INF/frontend/styles/chat-message-styles.css +++ b/src/main/resources/META-INF/frontend/styles/chat-message-styles.css @@ -88,3 +88,18 @@ font-family: var(--lumo-font-family); color: var(--lumo-secondary-text-color); } + +.wmde-markdown { + height: 100%; + margin-top: 0px; + margin-bottom: 0px; + padding-top: 0px; + padding-bottom: 0px; + display: flex; + flex-direction: column; + background: none !important; +} + +.language-mermaid { + padding: 0px !important; +} \ No newline at end of file diff --git a/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantDemoView.java b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantDemoView.java index cdf34e1..8c31721 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantDemoView.java +++ b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantDemoView.java @@ -34,6 +34,7 @@ public class ChatAssistantDemoView extends TabbedDemo { public ChatAssistantDemoView() { addDemo(ChatAssistantDemo.class); addDemo(ChatAssistantLazyLoadingDemo.class); + addDemo(ChatAssistantMarkdownDemo.class); setSizeFull(); } } diff --git a/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantMarkdownDemo.java b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantMarkdownDemo.java new file mode 100644 index 0000000..15854f3 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantMarkdownDemo.java @@ -0,0 +1,73 @@ +/*- + * #%L + * Chat Assistant Add-on + * %% + * Copyright (C) 2023 - 2024 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.chatassistant; + +import com.flowingcode.vaadin.addons.chatassistant.model.Message; +import com.flowingcode.vaadin.addons.demo.DemoSource; +import com.flowingcode.vaadin.addons.demo.SourcePosition; +import com.google.common.base.Strings; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.dependency.CssImport; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextArea; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import java.time.LocalDateTime; +import java.util.Timer; + +@DemoSource(sourcePosition = SourcePosition.PRIMARY) +@PageTitle("Markdown Demo") +@SuppressWarnings("serial") +@Route(value = "chat-assistant/markdown-demo", layout = ChatAssistantDemoView.class) +@CssImport("./styles/chat-assistant-styles-demo.css") +public class ChatAssistantMarkdownDemo extends VerticalLayout { + + public ChatAssistantMarkdownDemo() { + ChatAssistant chatAssistant = new ChatAssistant(true); + TextArea message = new TextArea(); + message.setLabel("Enter a message from the assistant (try using Markdown)"); + message.setSizeFull(); + message.addKeyPressListener(ev->{ + if (Strings.isNullOrEmpty(chatAssistant.getWhoIsTyping())) { + chatAssistant.setWhoIsTyping("Assistant is generating an answer ..."); + } + }); + message.addBlurListener(ev->chatAssistant.clearWhoIsTyping()); + + Button chat = new Button("Chat"); + chat.addClickListener(ev -> { + Message m = Message.builder().content(message.getValue()).messageTime(LocalDateTime.now()) + .name("Assistant").avatar("chatbot.png").build(); + + chatAssistant.sendMessage(m); + message.clear(); + }); + chatAssistant.sendMessage(Message.builder().content("**Hello, I am here to assist you**") + .messageTime(LocalDateTime.now()) + .name("Assistant").avatar("chatbot.png").build()); + + add(message, chat, chatAssistant); + } +}