Skip to content

Commit

Permalink
Merge pull request #196 from jmartisk/devui-chat
Browse files Browse the repository at this point in the history
Dev UI chatting capability
  • Loading branch information
geoand authored Jan 3, 2024
2 parents 1df2e89 + 36b7001 commit a67a589
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import io.quarkiverse.langchain4j.deployment.EmbeddingModelBuildItem;
import io.quarkiverse.langchain4j.deployment.EmbeddingStoreBuildItem;
import io.quarkiverse.langchain4j.deployment.ToolsMetadataBuildItem;
import io.quarkiverse.langchain4j.deployment.items.ChatModelProviderCandidateBuildItem;
import io.quarkiverse.langchain4j.runtime.devui.ChatJsonRPCService;
import io.quarkiverse.langchain4j.runtime.devui.EmbeddingStoreJsonRPCService;
import io.quarkiverse.langchain4j.runtime.tool.ToolMethodCreateInfo;
import io.quarkus.deployment.IsDevelopment;
Expand All @@ -23,7 +25,8 @@ public class Langchain4jDevUIProcessor {
CardPageBuildItem cardPage(List<DeclarativeAiServiceBuildItem> aiServices,
ToolsMetadataBuildItem toolsMetadataBuildItem,
List<EmbeddingModelBuildItem> embeddingModelBuildItem,
List<EmbeddingStoreBuildItem> embeddingStoreBuildItem) {
List<EmbeddingStoreBuildItem> embeddingStoreBuildItem,
List<ChatModelProviderCandidateBuildItem> chatModelCandidates) {
CardPageBuildItem card = new CardPageBuildItem();
addAiServicesPage(card, aiServices);
if (toolsMetadataBuildItem != null) {
Expand All @@ -34,6 +37,9 @@ CardPageBuildItem cardPage(List<DeclarativeAiServiceBuildItem> aiServices,
if (embeddingModelBuildItem.size() == 1 && embeddingStoreBuildItem.size() == 1) {
addEmbeddingStorePage(card);
}
if (!chatModelCandidates.isEmpty()) {
addChatPage(card);
}
return card;
}

Expand Down Expand Up @@ -74,14 +80,23 @@ private void addToolsPage(CardPageBuildItem card, ToolsMetadataBuildItem metadat
.icon("font-awesome-solid:toolbox"));
}

private void addChatPage(CardPageBuildItem card) {
card.addPage(Page.webComponentPageBuilder().title("Chat")
.componentLink("qwc-chat.js")
.icon("font-awesome-solid:comments"));
}

@BuildStep(onlyIf = IsDevelopment.class)
void jsonRpcProviders(BuildProducer<JsonRPCProvidersBuildItem> producers,
List<EmbeddingModelBuildItem> embeddingModelBuildItem,
List<EmbeddingStoreBuildItem> embeddingStoreBuildItem) {
List<EmbeddingStoreBuildItem> embeddingStoreBuildItem,
List<ChatModelProviderCandidateBuildItem> chatModelCandidates) {
if (embeddingModelBuildItem.size() == 1 && embeddingStoreBuildItem.size() == 1) {
producers.produce(new JsonRPCProvidersBuildItem(EmbeddingStoreJsonRPCService.class));
}

if (!chatModelCandidates.isEmpty()) {
producers.produce(new JsonRPCProvidersBuildItem(ChatJsonRPCService.class));
}
}

}
89 changes: 89 additions & 0 deletions core/deployment/src/main/resources/dev-ui/qwc-chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {html, LitElement} from 'lit';
import '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-column.js';
import '@vaadin/text-area';
import '@vaadin/button';
import { JsonRpc } from 'jsonrpc';
import { columnBodyRenderer } from '@vaadin/grid/lit.js';

export class QwcChat extends LitElement {

jsonRpc = new JsonRpc(this);

static properties = {
"_chatHistory": {state: true}
}

constructor() {
super();
this._chatHistory = [];
}

render() {
return html`
<h3>Chat</h3>
<vaadin-text-area id="system-message" label="(Optional) System message. To apply
a new system message, you have to use the New conversation button." style="width:90%"></vaadin-text-area><br/>
<vaadin-text-area id="chat-message" label="Chat message" style="width:90%"></vaadin-text-area><br/>
<vaadin-button id="chat-button" @click=${() => this._doChat(
this.shadowRoot.getElementById('chat-message').value
)}>Submit
</vaadin-button>
<br/>
<vaadin-button @click=${() => this._reset()}>New conversation</vaadin-button>
<br/>
${this._renderHistory()}
`;
}

_doChat(message) {
// if no chat history exists, start a new conversation
if(this._chatHistory.length === 0) {
var systemMessage = this.shadowRoot.getElementById('system-message').value;
this._chatHistory = [{message: message, type:"User"}, {type: "System", message: systemMessage}];
this.shadowRoot.getElementById('chat-message').value = "";
this.shadowRoot.getElementById('chat-button').disabled = true;
this.requestUpdate();
this.jsonRpc.newConversation({message: message, systemMessage: systemMessage}).then(jsonRpcResponse => {
this._chatHistory = [{message: jsonRpcResponse.result, type:"AI"}].concat(this._chatHistory);
this.shadowRoot.getElementById('chat-button').disabled = null;
this.requestUpdate();
});
} else {
this._chatHistory = [{message: message, type: "User"}].concat(this._chatHistory);
this.requestUpdate();
this.shadowRoot.getElementById('chat-message').value = "";
this.shadowRoot.getElementById('chat-button').disabled = true;
this.jsonRpc.chat({message: message}).then(jsonRpcResponse => {
this._chatHistory = [{message: jsonRpcResponse.result, type: "AI"}].concat(this._chatHistory);
this.shadowRoot.getElementById('chat-button').disabled = null;
this.requestUpdate();
});
}
}

_reset() {
var systemMessage = this.shadowRoot.getElementById('system-message').value;
if(systemMessage) {
this._chatHistory = [{type: "System", message: systemMessage}];
}
this.shadowRoot.getElementById('chat-button').disabled = null;
this.jsonRpc.reset({systemMessage: systemMessage});
}

_renderHistory() {
return html`
<vaadin-grid .items="${this._chatHistory}" theme="wrap-cell-content">
<vaadin-grid-column width="10ch" resizable flex-grow="0"
path="type"
header="Type">
</vaadin-grid-column>
<vaadin-grid-column path="message"
header="Message">
</vaadin-grid-column>
</vaadin-grid>`;
}

}

customElements.define('qwc-chat', QwcChat);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.quarkiverse.langchain4j.runtime.devui;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;

import jakarta.inject.Inject;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.output.Response;

public class ChatJsonRPCService {

@Inject
ChatLanguageModel model;

@Inject
ChatMemoryProvider memoryProvider;

private AtomicReference<ChatMemory> currentMemory = new AtomicReference<>();

public String reset(String systemMessage) {
if (currentMemory.get() != null) {
currentMemory.get().clear();
}
ChatMemory memory = memoryProvider.get(ThreadLocalRandom.current().nextLong());
currentMemory.set(memory);
if (systemMessage != null && !systemMessage.isEmpty()) {
memory.add(new SystemMessage(systemMessage));
}
return "OK";
}

public String newConversation(String systemMessage, String message) {
reset(systemMessage);
return chat(message);
}

public String chat(String message) {
ChatMemory memory = currentMemory.get();
memory.add(new UserMessage(message));
Response<AiMessage> response = model.generate(memory.messages());
memory.add(response.content());
return response.content().text();
}

}

0 comments on commit a67a589

Please sign in to comment.