Skip to content

Commit

Permalink
feature completed
Browse files Browse the repository at this point in the history
Issue #610
  • Loading branch information
rsoika committed Oct 14, 2024
1 parent 77ef9d9 commit c285e37
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ type=Typ
summary=Zusammenfassung
ai=KI
ai.prompt=Wie kann ich Ihnen helfen?
ai.question=Frage
ai.answer=Antwort
ai.ask_question=Frage stellen...

####################
# Month
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ type=Type
summary=Summary
ai=AI
ai.prompt=How can I help you?
ai.question=Question
ai.answer=Answer
ai.ask_question=Ask question...

####################
# Month
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,66 @@
}


.imixs-ai-chat-history {
display: flex;
flex-direction: column;
gap: 20px;
max-width: 800px;
margin: 0 auto;
}

.imixs-ai-chat-entry {
display: flex;
flex-direction: column;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}

.imixs-ai-chat-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
padding: 10px 15px;
font-size: 0.9em;
color: #555;
}

.imixs-ai-chat-content {
display: flex;
flex-direction: column;
padding: 15px;
gap: 10px;
}

.imixs-ai-question,
.imixs-ai-answer {
display: flex;
flex-direction: column;
}

.imixs-ai-label {
font-weight: bold;
margin-bottom: 5px;
}

.imixs-ai-message {
background-color: #fff;
border-radius: 4px;
padding: 10px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-x: auto;
}

.user-question .imixs-ai-message {
background-color: #e6f3ff;
}

.imixs-ai-answer .message {
background-color: #f0f0f0;
}

.imixs-ai-loader {
border: 6px solid #f3f3f3;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
<!-- Integration of Imixs-AI
-->
<dl>

<dd>
<h:inputTextarea required="false" value="#{workflowController.workitem.item['ai.chat.prompt']}"
a:placeholder="#{message['ai.prompt']}" id="promptInput" style="height: 5em; width: 95%">
</h:inputTextarea>
<h:commandButton value="#{message.send}" actionListener="#{aiController.sendAsync}">
<h:commandButton value="#{message['ai.ask_question']}" actionListener="#{aiController.sendAsync}">
<f:ajax execute="promptInput" render="@none" onevent="handleSendEvent" />
</h:commandButton>
</dd>
Expand All @@ -25,34 +24,53 @@
</h:panelGroup>

<!-- Imixs AI Chat history-->
<h:panelGroup layout="block" id="imixs_ai_chatlist" style="margin-bottom:10px;">
<ui:repeat var="aiResultEntry" value="#{aiController.aiChatHistory}">
#{aiResultEntry}
<hr />

<hr />
<h:panelGroup layout="block" id="imixs_ai_chatlist" styleClass="imixs-ai-chat-history">
<ui:repeat var="aiChatHistoryEntry" value="#{aiController.chatHistory}">
<div class="imixs-ai-chat-entry">
<div class="imixs-ai-chat-header">
<h:outputText value="#{aiChatHistoryEntry.item['date']}">
<f:convertDateTime pattern="#{message.dateTimePatternLong}" timeZone="#{message.timeZone}"
type="date" />
</h:outputText>
<span>#{userController.getUserName(aiChatHistoryEntry.item['user'])}</span>
</div>
<div class="imixs-ai-chat-content">
<div class="imixs-ai-question">
<span class="imixs-ai-label">#{message['ai.question']}:</span>
<div class="imixs-ai-message">#{aiChatHistoryEntry.item['question']}</div>
</div>
<div class="imixs-ai-answer">
<span class="imixs-ai-label">#{message['ai.answer']}:</span>
<div class="imixs-ai-message">#{aiChatHistoryEntry.item['answer']}</div>
</div>
</div>
</div>
</ui:repeat>
</h:panelGroup>

<h:commandScript name="refreshAILifeResult" render="aiResultStream" onevent="handleRefreshEvent" />
<h:commandScript name="refreshAIChatHistory" render="imixs_ai_chatlist" />
<h:commandScript name="refreshAIChatHistory" render="imixs_ai_chatlist promptInput" />
<script type="text/javascript">
/*<![CDATA[*/
var refreshInterval;
function handleSendEvent(data) {
if (data.status === "begin") {
console.log('streaming started....');
//console.log('streaming started....');
startRefreshing();
}
}
function handleRefreshEvent(data) {
if (data.status === 'success') {
console.log('handle refreshEvent: ' + data.responseText);
//console.log('handle refreshEvent: ' + data.responseText);
if (data.responseText && data.responseText.indexOf('imixs.ai.stream.completed') > -1) {
stopRefreshing();
}
}
}
function startRefreshing() {
console.log('Start Refreshing...');
//console.log('Start Refreshing...');
refreshInterval = setInterval(refreshAILifeResult, 100);
// show imixs_ai_life_stream
lifeStreamBlock = document.querySelector('[id$=":imixs_ai_life_stream"]');
Expand All @@ -62,7 +80,7 @@

}
function stopRefreshing() {
console.log('Stop Refreshing!');
//console.log('Stop Refreshing!');
clearInterval(refreshInterval);
refreshAIChatHistory();
// hide imixs_ai_life_stream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.faces.data.WorkflowController;
import org.imixs.workflow.faces.data.WorkflowEvent;
import org.imixs.workflow.faces.util.LoginController;

import jakarta.enterprise.context.ConversationScoped;
import jakarta.enterprise.event.Observes;
Expand Down Expand Up @@ -74,12 +75,17 @@
public class AIController implements Serializable {
public static final String ERROR_PROMPT_TEMPLATE = "ERROR_PROMPT_TEMPLATE";
public static final String ERROR_PROMPT_INFERENCE = "ERROR_PROMPT_INFERENCE";
public static final String AI_CHAT_HISTORY = "ai.chat.history";

public static final String AI_STREAM_EOS = "<!-- imixs.ai.stream.completed -->";

private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(AIController.class.getName());

List<String> aiChatHistory;
List<ItemCollection> chatHistory;
String currentStreamResult = "";
String question = null;
String answer = null;

@Inject
protected WorkflowController workflowController;
Expand All @@ -90,28 +96,30 @@ public class AIController implements Serializable {
@Inject
protected UserController userController;

@Inject
protected LoginController loginController;

@Inject
OpenAIAPIService openAIAPIService;

private CompletableFuture<Void> streamingFuture;

/**
* This helper method is called during the WorkflowEvent.WORKITEM_CHANGED to
* update the chronicle view for the current workitem.
* Returns the chat history in reverse order. Used by the frontend component
*
* @return
*/
public void init() {
aiChatHistory = new ArrayList<String>();
}

public List<String> getAiChatHistory() {
return aiChatHistory;
public List<ItemCollection> getChatHistory() {
List<ItemCollection> reversedList = new ArrayList<>(chatHistory);
Collections.reverse(reversedList);
return reversedList;
}

/**
* WorkflowEvent listener
*
* If a new WorkItem was created or changed, the chronicle view will be
* initialized.
* If a new WorkItem was created or changed, the imixs.ai.chat.history view will
* be initialized or updated.
*
* @param workflowEvent
*/
Expand All @@ -121,9 +129,10 @@ public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) {
}
if (WorkflowEvent.WORKITEM_CREATED == workflowEvent.getEventType()
|| WorkflowEvent.WORKITEM_CHANGED == workflowEvent.getEventType()) {
// reset data...
init();
// read current imixs.ai.chat.history...
chatHistory = ChildItemController.explodeChildList(workflowController.getWorkitem(), AI_CHAT_HISTORY);
}

}

/**
Expand All @@ -134,12 +143,11 @@ public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) {
*/
public void sendAsync() throws PluginException {
ItemCollection workitem = workflowController.getWorkitem();
String input = workitem.getItemValueString("ai.chat.prompt");
logger.info("prompt...:" + input);
String prompt = buildContextPrompt(input);
question = workitem.getItemValueString("ai.chat.prompt");
logger.fine("question: " + question);
String prompt = buildContextPrompt(question);
JsonObject jsonPrompt = openAIAPIService.buildJsonPromptObject(prompt, true, null);

aiChatHistory.add("Question: " + input);
// starting async http request...
streamingFuture = CompletableFuture.runAsync(() -> {
try {
Expand All @@ -162,7 +170,7 @@ private void streamPromptCompletion(JsonObject jsonPromptObject) throws PluginEx

// Write the JSON object to the output stream
String jsonString = jsonPromptObject.toString();
logger.info("JSON Object=" + jsonString);
logger.fine("JSON Object=" + jsonString);

try (OutputStream os = conn.getOutputStream()) {
byte[] input = jsonString.getBytes(StandardCharsets.UTF_8);
Expand All @@ -184,12 +192,13 @@ private void streamPromptCompletion(JsonObject jsonPromptObject) throws PluginEx
String content = responseObject.getString("content");
boolean stop = responseObject.getBoolean("stop", false);
currentStreamResult = currentStreamResult + content;
logger.info("FullResponse: " + currentStreamResult);
logger.fine("FullResponse: " + currentStreamResult);
if (stop) {
logger.info("request completed - adding answer....");
aiChatHistory.add("Answer: " + currentStreamResult);
logger.fine("request completed - adding answer....");
answer = currentStreamResult.trim();

// reset stream result!
currentStreamResult = "<!-- imixs.ai.stream.completed -->";
currentStreamResult = AI_STREAM_EOS;
break;
}
}
Expand All @@ -212,7 +221,26 @@ public boolean isStreamingComplete() {
}

public String getStreamResult() {
if (AI_STREAM_EOS.equals(currentStreamResult)) {
updateChatHistory();
}
return currentStreamResult;

}

/**
* Helper method to update the ai.chat.history
*/
private void updateChatHistory() {
ItemCollection chatEntry = new ItemCollection();
chatEntry.setItemValue("question", question);
chatEntry.setItemValue("answer", answer);
chatEntry.setItemValue("date", new Date());
chatEntry.setItemValue("user", loginController.getUserPrincipal());
chatHistory.add(chatEntry);
// persist new imixs.ai.chat.history
ChildItemController.implodeChildList(workflowController.getWorkitem(), chatHistory, AI_CHAT_HISTORY);
workflowController.getWorkitem().removeItem("ai.chat.prompt");
}

/**
Expand Down
Loading

0 comments on commit c285e37

Please sign in to comment.