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);
+  }
+}