chore: Add README.md files

This commit is contained in:
Roque Caballero 2025-03-28 17:36:22 +01:00
commit 4ceec902a3
Signed by: roque.caballero
SSH key fingerprint: SHA256:+oco2mi9KAXp5fmBGQyUMk3bBo0scA4b8sL7Gf2pEwo
155 changed files with 19124 additions and 0 deletions

7
demo-03/README.md Normal file
View file

@ -0,0 +1,7 @@
Demo 03 - Streaming
===============================================
LLM responses can be long
# Chunks
`Multi<String> chat(String userMessage)` ndicate that the response will be streamed instead of returned synchronously.

199
demo-03/pom.xml Normal file
View file

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenixtechnologies</groupId>
<artifactId>ai-lc4j-workshop</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>demo-03</artifactId>
<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Plugin versions -->
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<enforcer-plugin.version>3.5.0</enforcer-plugin.version>
<surefire-plugin.version>3.5.2</surefire-plugin.version>
<spotless-maven-plugin.version>2.44.3</spotless-maven-plugin.version>
<palantir-java-format.version>2.50.0</palantir-java-format.version>
<release-plugin.version>3.1.1</release-plugin.version>
<!-- Quarkus version -->
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.19.2</quarkus.platform.version>
<!-- LangChain4j-->
<quarkus-langchain4j.version>0.25.0</quarkus-langchain4j.version>
<!-- Test dependency versions -->
<assertj.version>3.27.3</assertj.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-bom</artifactId>
<version>${quarkus-langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-openai</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
<!-- UI -->
<dependency>
<groupId>org.mvnpm</groupId>
<artifactId>importmap</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>org.mvnpm.at.mvnpm</groupId>
<artifactId>vaadin-webcomponents</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mvnpm</groupId>
<artifactId>es-module-shims</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mvnpm</groupId>
<artifactId>wc-chatbot</artifactId>
<version>0.2.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<release>${java.version}</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${enforcer-plugin.version}</version>
<executions>
<execution>
<id>ban-bad-log4j-versions</id>
<phase>validate</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>org.apache.logging.log4j:log4j-core:(,2.17.1)</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>${spotless-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<java>
<removeUnusedImports />
<palantirJavaFormat>
<version>${palantir-java-format.version}</version>
<style>PALANTIR</style>
<formatJavadoc>false</formatJavadoc>
</palantirJavaFormat>
</java>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>${release-plugin.version}</version>
<configuration>
<tagNameFormat>@{project.version}</tagNameFormat>
<checkModificationExcludes>mvnw</checkModificationExcludes>
<scmReleaseCommitComment>chore: release @{releaseLabel}</scmReleaseCommitComment>
<scmDevelopmentCommitComment>chore: prepare for next development iteration [skip ci]</scmDevelopmentCommitComment>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,11 @@
package ch.phoenixtechnologies.lc4j.workshop;
import io.quarkiverse.langchain4j.RegisterAiService;
import io.smallrye.mutiny.Multi;
import jakarta.enterprise.context.SessionScoped;
@SessionScoped
@RegisterAiService
public interface CustomerSupportAgent {
Multi<String> chat(String userMessage);
}

View file

@ -0,0 +1,25 @@
package ch.phoenixtechnologies.lc4j.workshop;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
import io.smallrye.mutiny.Multi;
@WebSocket(path = "/customer-support-agent")
public class CustomerSupportAgentWebSocket {
private final CustomerSupportAgent agent;
public CustomerSupportAgentWebSocket(CustomerSupportAgent agent) {
this.agent = agent;
}
@OnOpen
public String onOpen() {
return "Welcome to Phoenix Technologies AI!";
}
@OnTextMessage
public Multi<String> onTextMessage(String message) {
return agent.chat(message);
}
}

View file

@ -0,0 +1,45 @@
package ch.phoenixtechnologies.lc4j.workshop;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import org.mvnpm.importmap.Aggregator;
@ApplicationScoped
@Path("/_importmap")
public class ImportmapResource {
private static final String JAVASCRIPT_CODE =
"""
const im = document.createElement('script');
im.type = 'importmap';
im.textContent = JSON.stringify(%s);
document.currentScript.after(im);
""";
private String importmap;
@PostConstruct
void init() {
Aggregator aggregator = new Aggregator();
// Add our own mappings
aggregator.addMapping("icons/", "/icons/");
aggregator.addMapping("components/", "/components/");
aggregator.addMapping("fonts/", "/fonts/");
this.importmap = aggregator.aggregateAsJson();
}
@GET
@Path("/dynamic.importmap")
@Produces("application/importmap+json")
public String importMap() {
return this.importmap;
}
@GET
@Path("/dynamic-importmap.js")
@Produces("application/javascript")
public String importMapJson() {
return JAVASCRIPT_CODE.formatted(this.importmap);
}
}

View file

@ -0,0 +1,74 @@
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 chatBot = document.getElementsByTagName("chat-bot")[0];
const protocol = (window.location.protocol === 'https:') ? 'wss' : 'ws';
const socket = new WebSocket(protocol + '://' + window.location.host + '/customer-support-agent');
const that = this;
socket.onmessage = function (event) {
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"
}
});
}
}
chatBot.addEventListener("sent", function (e) {
if (e.detail.message.sender.name !== "Bot") {
// User message
const msg = that._stripHtml(e.detail.message.message);
socket.send(msg);
chatBot.sendMessage("", {
right: false,
loading: true
});
}
});
}
}
customElements.define('demo-chat', DemoChat);

View file

@ -0,0 +1,63 @@
import {LitElement, html, css} 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/grid';
import '@vaadin/grid/vaadin-grid-sort-column.js';
export class DemoTitle extends LitElement {
static styles = css`
h2 {
font-family: "Red Hat Mono", monospace;
font-size: 60px;
font-style: normal;
font-variant: normal;
font-weight: 700;
line-height: 26.4px;
color: var(--main-highlight-text-color);
}
.title {
text-align: center;
padding: 1em;
background: var(--main-bg-color);
}
.explanation {
margin-left: auto;
margin-right: auto;
width: 50%;
text-align: justify;
font-size: 20px;
}
.explanation img {
max-width: 60%;
display: block;
float:left;
margin-right: 2em;
margin-top: 1em;
}
`
render() {
return html`
<div class="title">
<h2>Phoenix Technologies</h2>
</div>
<div class="explanation">
<p>Welcome to Phoenix Technologies!</p>
<p>Please click the button on the bottom right to start the conversation
with an LLM-powered customer support agent.</p>
</div>
`
}
}
customElements.define('demo-title', DemoTitle);

View file

@ -0,0 +1 @@
@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg8z6hR4jNCH5Z.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg_T6hR4jNCA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg8z6hR4jNCH5Z.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg_T6hR4jNCA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg8z6hR4jNCH5Z.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg_T6hR4jNCA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQZqctMc-JPWCN.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQaKctMc-JPQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQZqctMc-JPWCN.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQaKctMc-JPQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}/*# sourceMappingURL=red-hat-font.css.map */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
// import './font-awesome-brands.js';
// import './font-awesome-regular.js';
import './font-awesome-solid.js';
// export * from './font-awesome-brands.js';
// export * from './font-awesome-regular.js';
export * from './font-awesome-solid.js';

View file

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" type="image/png" href="favicon.ico">
<script src="/_importmap/dynamic-importmap.js"></script>
<script type="module">
import 'icons/font-awesome.js';
import 'components/demo-title.js';
import 'components/demo-chat.js';
import 'wc-chatbot';
</script>
<link rel="stylesheet" href="fonts/red-hat-font.min.css">
<title>Miles of Smiles</title>
<style>
:root {
--main-bg-color: rgb(246, 242, 242);
--main-highlight-text-color: rgba(237, 98, 128);
}
body {
margin: 0;
width: 100%;
height: 100vh;
font-family: 'Red Hat Text', sans-serif;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
color: var(--lumo-body-text-color);
background: var(--main-bg-color);
}
chat-bot {
--chatbot-avatar-bg-color: rgba(237, 98, 128);
--chatbot-avatar-margin: 10%;
--chatbot-header-bg-color: rgba(237, 98, 128);
--chatbot-header-title-color: #FFFFFF;
--chatbot-body-bg-color: var(--main-bg-color);
--chatbot-send-button-color: rgba(237, 98, 128);
}
chat-bot::part(chat-bubble) {
--chat-bubble-avatar-color: rgba(237, 98, 128);
--chat-bubble-color: rgba(203, 232, 237, 0.71);
--chat-bubble-right-color: rgb(157, 238, 244);
--chat-bubble-font-color: #333;
--chat-bubble-font-right-color: #333;
--chat-bubble-delay: 0.2s;
}
.middle {
margin-top: 2em;
overflow: hidden;
width: 50%;
margin-left: auto;
margin-right: auto;
display: flex;
}
</style>
</head>
<body>
<demo-title></demo-title>
<div class="middle">
<demo-chat>
<chat-bot></chat-bot>
</demo-chat>
</div>
</body>
</html>

View file

@ -0,0 +1,14 @@
quarkus:
langchain4j:
openai:
api-key: #PUT_YOUR_TOKEN_HERE
base-url: https://inference-llama33-70b-maas.apps.ai.kvant.cloud/v1/
timeout: 60s
chat-model:
model-name: inference-llama33-70b
temperature: 1.0
#max-tokens: 1000
#frequency-penalty: 2
frequency-penalty: 0
log-requests: true
log-responses: true