feat: add another component that can render md

This commit is contained in:
Markus Hofstetter 2025-04-29 08:39:25 +02:00
parent af32f2d02f
commit 1033f49c8e
8 changed files with 266 additions and 28 deletions

84
.m2/settings.xml Normal file
View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" ?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<interactiveMode>false</interactiveMode>
<servers>
<server>
<id>dvb</id>
<username>${env.NEXUS2_DEPLOYMENT_USER}</username>
<password>${env.NEXUS2_DEPLOYMENT_PASSWORD}</password>
</server>
<server>
<id>dvb.snapshots</id>
<username>${env.NEXUS2_DEPLOYMENT_USER}</username>
<password>${env.NEXUS2_DEPLOYMENT_PASSWORD}</password>
</server>
</servers>
<mirrors>
<mirror>
<id>dvbern.nexus</id>
<mirrorOf>*</mirrorOf>
<name>DV Bern NEXUS Repository Mirror</name>
<url>https://nexus.dvbern.ch/nexus/content/groups/allrepos/</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>dvbern.nexus</id>
<properties>
<maven.repo.releases.url>https://nexus.dvbern.ch/nexus/content/repositories/dvb/</maven.repo.releases.url>
<maven.repo.snapshots.url>https://nexus.dvbern.ch/nexus/content/repositories/dvb.snapshots/</maven.repo.snapshots.url>
</properties>
<repositories>
<repository>
<id>central</id>
<url>http://central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>mvnpm-repo</id>
<repositories>
<repository>
<id>central</id>
<name>central</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>mvnpm.org</id>
<name>mvnpm</name>
<url>https://repo.mvnpm.org/maven2</url>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>mvnpm-repo</activeProfile>
<!-- <activeProfile>dvbern.nexus</activeProfile>-->
</activeProfiles>
</settings>

View File

@ -76,6 +76,12 @@
<version>0.2.1</version> <version>0.2.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- <dependency>-->
<!-- <groupId>org.mvnpm</groupId>-->
<!-- <artifactId>deep-chat</artifactId>-->
<!-- <version>2.1.1</version>-->
<!-- <scope>runtime</scope>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>io.quarkiverse.langchain4j</groupId> <groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-easy-rag</artifactId> <artifactId>quarkus-langchain4j-easy-rag</artifactId>

View File

@ -0,0 +1,18 @@
package dev.langchain4j.quarkus.workshop.deepchat;
import dev.langchain4j.service.SystemMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import io.smallrye.mutiny.Multi;
import jakarta.enterprise.context.SessionScoped;
@SessionScoped
@RegisterAiService
public interface DeepChatSupportAgent {
@SystemMessage("""
You are a customer support agent of the vaccination application 'vacme'.
You are friendly, polite and concise.
If the question is unrelated to the login process or the usage of the vacme page, you should politely redirect the customer to the support email.
""")
Multi<String> chat(String userMessage);
}

View File

@ -0,0 +1,28 @@
package dev.langchain4j.quarkus.workshop.deepchat;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
import io.smallrye.mutiny.Multi;
import jakarta.inject.Inject;
@WebSocket(path = "/deep-chat-support-agent")
public class DeepChatSupportAgentWebSocket {
private final DeepChatSupportAgent deepChatSupportAgent;
@Inject
public DeepChatSupportAgentWebSocket(DeepChatSupportAgent deepChatSupportAgent) {
this.deepChatSupportAgent = deepChatSupportAgent;
}
@OnOpen
public DeepChatTextResponse onOpen() {
return new DeepChatTextResponse("Willkommen beim Vacme Support! Wie kann ich Ihnen Heute helfen?", false);
}
@OnTextMessage
public Multi<DeepChatTextResponse> onTextMessage(String message) {
return deepChatSupportAgent.chat(message).map(text -> new DeepChatTextResponse(text, false));
}
}

View File

@ -0,0 +1,8 @@
package dev.langchain4j.quarkus.workshop.deepchat;
public record DeepChatTextResponse(String text, boolean overwrite) {
public DeepChatTextResponse(String text, boolean overwrite) {
this.text = text;
this.overwrite = overwrite;
}
}

View File

@ -26,33 +26,8 @@ export class DemoChat extends LitElement {
const that = this; const that = this;
socket.onmessage = function (event) { socket.onmessage = function (event) {
chatBot.hideLastLoading(); that.addToWsChat(chatBot, event, that);
// LLM response // this.addToDeepchat()
let lastMessage;
if (chatBot.messages.length > 0) {
lastMessage = chatBot.messages[chatBot.messages.length - 1];
}
if (lastMessage && lastMessage.sender.name === "Bot" && ! lastMessage.loading) {
if (! lastMessage.msg) {
lastMessage.msg = "";
}
lastMessage.msg += event.data;
let bubbles = chatBot.shadowRoot.querySelectorAll("chat-bubble");
let bubble = bubbles.item(bubbles.length - 1);
if (lastMessage.message) {
bubble.innerHTML = that._stripHtml(lastMessage.message) + lastMessage.msg;
} else {
bubble.innerHTML = lastMessage.msg;
}
chatBot.body.scrollTo({ top: chatBot.body.scrollHeight, behavior: 'smooth' })
} else {
chatBot.sendMessage(event.data, {
right: false,
sender: {
name: "Bot"
}
});
}
} }
chatBot.addEventListener("sent", function (e) { chatBot.addEventListener("sent", function (e) {
@ -68,7 +43,35 @@ export class DemoChat extends LitElement {
}); });
} }
addToWsChat(chatBot, event, that) {
chatBot.hideLastLoading();
// LLM response
let lastMessage;
if (chatBot.messages.length > 0) {
lastMessage = chatBot.messages[chatBot.messages.length - 1];
}
if (lastMessage && lastMessage.sender.name === "Bot" && !lastMessage.loading) {
if (!lastMessage.msg) {
lastMessage.msg = "";
}
lastMessage.msg += event.data;
let bubbles = chatBot.shadowRoot.querySelectorAll("chat-bubble");
let bubble = bubbles.item(bubbles.length - 1);
if (lastMessage.message) {
bubble.innerHTML = that._stripHtml(lastMessage.message) + lastMessage.msg;
} else {
bubble.innerHTML = lastMessage.msg;
}
chatBot.body.scrollTo({top: chatBot.body.scrollHeight, behavior: 'smooth'})
} else {
chatBot.sendMessage(event.data, {
right: false,
sender: {
name: "Bot"
}
});
}
}
} }
customElements.define('demo-chat', DemoChat); customElements.define('demo-chat', DemoChat);

View File

@ -0,0 +1,66 @@
import {css, LitElement} from 'lit';
import '@vaadin/icon';
import '@vaadin/button';
import '@vaadin/text-field';
import '@vaadin/text-area';
import '@vaadin/form-layout';
import '@vaadin/progress-bar';
import '@vaadin/checkbox';
import '@vaadin/horizontal-layout';
import '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-sort-column.js';
export class DemoChat extends LitElement {
_stripHtml(html) {
const div = document.createElement("div");
div.innerHTML = html;
return div.textContent || div.innerText || "";
}
connectedCallback() {
const chatElementRef = document.getElementsByTagName("deep-chat")[0];
const protocol = (window.location.protocol === 'https:') ? 'wss' : 'ws';
const websocket = new WebSocket(protocol + '://' + window.location.host + '/customer-support-agent');
// this handler is invoked when the component is loaded
chatElementRef.onMessage = (body) => {
if (body.message && body.message.role === 'user') {
websocket.send(body.message.text);
}};
chatElementRef.connect = {
stream: true,
handler: (_, signals) => {
try {
signals.onOpen(); // enables the user to send messages
websocket.onmessage = (message) => {
signals.onResponse({text: message.data});
};
websocket.onclose = () => {
signals.onClose(); // stops the user from sending messages
};
websocket.onerror = () => {
// 'Connection error' is a special string that will also display in Deep Chat
signals.onResponse({error: 'Connection error'});
};
// triggered when the user sends a message
// signals.newUserMessage.listener = (body) => {
// websocket.send(JSON.stringify(body));
// };
} catch (e) {
signals.onResponse({error: 'error'}); // displays an error message
signals.onClose(); // stops the user from sending messages
}
},
};
}
}
customElements.define('demo-deep-chat', DemoChat);

View File

@ -6,11 +6,16 @@
<link rel="shortcut icon" type="image/png" href="favicon.ico"> <link rel="shortcut icon" type="image/png" href="favicon.ico">
<script src="/_importmap/dynamic-importmap.js"></script> <script src="/_importmap/dynamic-importmap.js"></script>
<script
type="module"
src="https://unpkg.com/deep-chat@2.1.1/dist/deepChat.bundle.js"
></script>
<script type="module"> <script type="module">
import 'icons/font-awesome.js'; import 'icons/font-awesome.js';
import 'components/demo-title.js'; import 'components/demo-title.js';
import 'components/demo-chat.js'; import 'components/demo-chat.js';
import 'components/demo-deep-chat.js';
import 'wc-chatbot'; import 'wc-chatbot';
</script> </script>
@ -71,12 +76,32 @@
<body> <body>
<!--<deep-chat stream="true" connect='{"url": "ws://localhost:8080/deep-chat-support-agent", "websocket": true}'></deep-chat>-->
<!--<deep-chat avatars="true"></deep-chat>-->
<!--<deep-chat connect='{"url": "http://localhost:8686/openai-chat-stream", "stream": true}'></deep-chat>-->
<!--<deep-chat connect='{"stream": {"simulation": 6}}'></deep-chat>-->
<!--<deep-chat-->
<!-- remarkable='{"html": true, "typographer": true}'-->
<!-- history='[-->
<!-- {"text": "Text containing <button>html</button>", "role": "user"},-->
<!-- {"text": "Typographic text: (c)", "role": "user"},-->
<!-- {"text": " # title \n- list1", "role": "user"}-->
<!--]'-->
<!-- style="border-radius: 8px"-->
<!-- demo="true"-->
<!--&gt;</deep-chat>-->
<demo-title></demo-title> <demo-title></demo-title>
<div class="middle"> <div class="middle">
<demo-chat> <demo-chat>
<chat-bot></chat-bot> <chat-bot></chat-bot>
</demo-chat> </demo-chat>
<demo-deep-chat>
<deep-chat></deep-chat>
</demo-deep-chat>
</div> </div>
</body> </body>