diff --git a/pom.xml b/pom.xml
index c9c37a2..e7f02e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
org.vaadin.addons.flowingcode
chat-assistant-addon
- 3.0.1-SNAPSHOT
+ 4.0.0-SNAPSHOT
Chat Assistant Add-on
Chat Assistant Add-on for Vaadin Flow
@@ -18,7 +18,7 @@
${project.basedir}/drivers
11.0.12
3.10.0
- 1.1.0
+ 2.0.1
true
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 10315ee..d44e773 100644
--- a/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistant.java
+++ b/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistant.java
@@ -45,7 +45,6 @@
import com.vaadin.flow.data.renderer.Renderer;
import com.vaadin.flow.function.SerializableSupplier;
import com.vaadin.flow.shared.Registration;
-
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -70,7 +69,6 @@
public class ChatAssistant extends ReactAdapterComponent implements ClickNotifier> {
private static final String CHAT_HEADER_CLASS_NAME = "chat-header";
- private static final String PADDING_SMALL = "0.5em";
private Component headerComponent;
private VerticalLayout container;
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 2748131..f1e4582 100644
--- a/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatMessage.java
+++ b/src/main/java/com/flowingcode/vaadin/addons/chatassistant/ChatMessage.java
@@ -20,7 +20,6 @@
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;
@@ -46,6 +45,7 @@ public class ChatMessage extends Component implements HasComp
private T message;
private boolean markdownEnabled;
private Div loader;
+ private MarkdownViewer markdownViewer;
/**
* Creates a new ChatMessage based on the supplied message without markdown support.
@@ -64,6 +64,14 @@ public ChatMessage(T message) {
*/
public ChatMessage(T message, boolean markdownEnabled) {
this.markdownEnabled = markdownEnabled;
+ loader = new Div(new Div(),new Div(), new Div(), new Div());
+ loader.setClassName("lds-ellipsis");
+ loader.setVisible(false);
+ this.add(loader);
+ if (markdownEnabled) {
+ markdownViewer = new MarkdownViewer(message.getContent());
+ this.add(markdownViewer);
+ }
setMessage(message);
}
@@ -74,7 +82,7 @@ public ChatMessage(T message, boolean markdownEnabled) {
*/
public void setMessage(T message) {
this.message = message;
- updateLoadingState(message);
+ updateMessage(message);
if (message.getName()!=null) {
this.setUserName(message.getName());
if (message.getAvatar()!=null) {
@@ -87,21 +95,17 @@ public void setMessage(T message) {
}
}
- private void updateLoadingState(T message) {
- if (message.isLoading()) {
- loader = new Div(new Div(),new Div(), new Div(), new Div());
- loader.setClassName("lds-ellipsis");
- this.add(loader);
- } else {
- if (loader!=null) {
- this.remove(loader);
- loader = null;
- }
+ /**
+ * Updates the displayed message content and loading state.
+ * @param message
+ */
+ private void updateMessage(T message) {
+ loader.setVisible(message.isLoading());
+ if (!message.isLoading()) {
if (markdownEnabled) {
- MarkdownViewer mdv = new MarkdownViewer(message.getContent());
- mdv.setDataColorMode(DataColorMode.LIGHT);
- this.add(mdv);
+ markdownViewer.setContent(message.getContent());
} else {
+ this.getElement().executeJs("[...this.childNodes].forEach(node => node.nodeType === 3 && this.removeChild(node));");
this.getElement().executeJs("this.appendChild(document.createTextNode($0));", message.getContent());
}
}
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 8c31721..c4d151b 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantDemoView.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantDemoView.java
@@ -35,6 +35,7 @@ public ChatAssistantDemoView() {
addDemo(ChatAssistantDemo.class);
addDemo(ChatAssistantLazyLoadingDemo.class);
addDemo(ChatAssistantMarkdownDemo.class);
+ addDemo(ChatAssistantGenerativeDemo.class);
setSizeFull();
}
}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantGenerativeDemo.java b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantGenerativeDemo.java
new file mode 100644
index 0000000..aa18f3e
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/chatassistant/ChatAssistantGenerativeDemo.java
@@ -0,0 +1,143 @@
+/*-
+ * #%L
+ * Chat Assistant Add-on
+ * %%
+ * Copyright (C) 2023 - 2025 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.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.avatar.Avatar;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.dependency.CssImport;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.component.textfield.TextArea;
+import com.vaadin.flow.data.renderer.ComponentRenderer;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+@DemoSource(sourcePosition = SourcePosition.PRIMARY)
+@PageTitle("Generative Answer Demo")
+@SuppressWarnings("serial")
+@Route(value = "chat-assistant/generative-demo", layout = ChatAssistantDemoView.class)
+@CssImport("./styles/chat-assistant-styles-demo.css")
+public class ChatAssistantGenerativeDemo extends VerticalLayout {
+
+ public ChatAssistantGenerativeDemo() {
+ String sampleText = "Hi, I'm an advanced language model. I'm here to help you demonstrate"
+ + " how a text-streaming chat component works in Vaadin. As you can see, each word appears"
+ + " with a slight pause, simulating the time it would take me to \"think\" and generate"
+ + " the next word. I hope this is useful for your demonstration!";
+
+ ChatAssistant chatAssistant = new ChatAssistant<>();
+ chatAssistant.setAvatarProvider(()->new Avatar("Chat Assistant","chatbot.png"));
+ TextArea message = new TextArea();
+ message.setLabel("Enter a message from the assistant");
+ message.setSizeFull();
+ message.setValue(sampleText);
+ message.addKeyPressListener(ev->{
+ if (Strings.isNullOrEmpty(chatAssistant.getWhoIsTyping())) {
+ chatAssistant.setWhoIsTyping("Assistant is generating an answer ...");
+ }
+ });
+ message.addBlurListener(ev->chatAssistant.clearWhoIsTyping());
+ chatAssistant.setMessagesRenderer(new ComponentRenderer(m -> {
+ return new CustomChatMessage(m);
+ },
+ (component, m) -> {
+ ((CustomChatMessage) component).setMessage(m);
+ return component;
+ }));
+ chatAssistant.setSubmitListener(se -> {
+ chatAssistant.sendMessage(CustomMessage.builder().messageTime(LocalDateTime.now())
+ .name("User").content(se.getValue()).tagline("Generated by user").build());
+ });
+
+ Button chat = new Button("Chat");
+ chat.addClickListener(ev -> {
+ CustomMessage m = CustomMessage.builder().content(message.getValue()).messageTime(LocalDateTime.now())
+ .name("Assistant").avatar("chatbot.png").tagline("Generated by assistant").build();
+
+ chatAssistant.sendMessage(m);
+ message.clear();
+ });
+ Button chatWithThinking = new Button("Chat With Generative Thinking");
+ chatWithThinking.addClickListener(ev -> {
+ String messageToSend = message.getValue();
+ CustomMessage delayedMessage = CustomMessage.builder().loading(true).content("")
+ .messageTime(LocalDateTime.now())
+ .name("Assistant").avatar("chatbot.png").tagline("Generated by assistant").build();
+
+ UI currentUI = UI.getCurrent();
+ chatAssistant.sendMessage(delayedMessage);
+
+ CompletableFuture.runAsync(() -> {
+ try {
+ TimeUnit.MILLISECONDS.sleep(500);
+ currentUI.access(() -> {
+ delayedMessage.setLoading(false);
+ });
+ streamWords(messageToSend)
+ .forEach(item -> {
+ currentUI.access(() -> {
+ delayedMessage.setContent(delayedMessage.getContent() + item);
+ chatAssistant.updateMessage(delayedMessage);
+ });
+ });
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+
+ message.clear();
+ });
+ chatAssistant.sendMessage(CustomMessage.builder().content("Hello, I am here to assist you")
+ .messageTime(LocalDateTime.now())
+ .name("Assistant").avatar("chatbot.png").tagline("Generated by assistant").build());
+
+ add(message, chat, chatWithThinking, chatAssistant);
+ }
+
+ public Stream streamWords(String fullText) {
+ if (fullText == null || fullText.isEmpty()) {
+ return Stream.empty();
+ }
+
+ String[] words = fullText.split("\\s+");
+
+ return Arrays.stream(words)
+ .map(word -> {
+ try {
+ long delay = ThreadLocalRandom.current().nextLong(50, 250);
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ return word + " ";
+ });
+}
+
+}