Skip to content

Feature ai chat regenerate button #12794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,20 @@ public AiChatComponent(AiService aiService,
aiService.getIngestionService().ingest(name, ListUtil.getLinkedFiles(entries).toList(), bibDatabaseContext);

ViewLoader.view(this)
.root(this)
.load();
.root(this)
.load();
}

@FXML
public void initialize() {
uiChatHistory.setItems(aiChatLogic.getChatHistory());
uiChatHistory.setRegenerateCallback(userPrompt -> {
// Remove the last two messages: first the AI response, then the corresponding user message
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment duplicated in two places and simply restates what the code does without providing additional insight or reasoning about the implementation.

deleteLastMessage();
deleteLastMessage();
Comment on lines +96 to +97
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling deleteLastMessage() twice creates code duplication. Consider creating a new method like deleteLastTwoMessages() to encapsulate this logic and improve maintainability.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or add a Java comment above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive in this case.

chatPrompt.switchToNormalState();
onSendMessage(userPrompt);
});
initializeChatPrompt();
initializeNotice();
initializeNotifications();
Expand Down Expand Up @@ -122,6 +129,7 @@ private void initializeChatPrompt() {
chatPrompt.setCancelCallback(() -> chatPrompt.switchToNormalState());

chatPrompt.setRetryCallback(userMessage -> {
// Remove the last two messages: first the AI response, then the corresponding user message
deleteLastMessage();
deleteLastMessage();
chatPrompt.switchToNormalState();
Expand Down Expand Up @@ -174,8 +182,8 @@ private List<Notification> updateNotificationsForEntry(BibEntry entry) {
entry.getFiles().stream().map(file -> aiService.getIngestionService().ingest(file, bibDatabaseContext)).forEach(ingestionStatus -> {
switch (ingestionStatus.getState()) {
case PROCESSING -> notifications.add(new Notification(
Localization.lang("File %0 is currently being processed", ingestionStatus.getObject().getLink()),
Localization.lang("After the file will be ingested, you will be able to chat with it.")
Localization.lang("File %0 is currently being processed", ingestionStatus.getObject().getLink()),
Localization.lang("After the file will be ingested, you will be able to chat with it.")
));

case ERROR -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jabref.gui.ai.components.aichat.chathistory;

import java.util.function.Consumer;

import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
Expand All @@ -10,11 +12,15 @@
import org.jabref.gui.util.UiTaskExecutor;

import com.airhacks.afterburner.views.ViewLoader;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;

public class ChatHistoryComponent extends ScrollPane {
@FXML private VBox vBox;

private Consumer<String> regenerateCallback;

public ChatHistoryComponent() {
ViewLoader.view(this)
.root(this)
Expand All @@ -35,14 +41,35 @@ public void setItems(ObservableList<ChatMessage> items) {
items.addListener((ListChangeListener<? super ChatMessage>) obs -> fill(items));
}

public void setRegenerateCallback(Consumer<String> regenerateCallback) {
this.regenerateCallback = regenerateCallback;
}

private void fill(ObservableList<ChatMessage> items) {
UiTaskExecutor.runInJavaFXThread(() -> {
vBox.getChildren().clear();
items.forEach(chatMessage ->
vBox.getChildren().add(new ChatMessageComponent(chatMessage, chatMessageComponent -> {
int index = vBox.getChildren().indexOf(chatMessageComponent);
items.remove(index);
})));
for (ChatMessage chatMessage : items) {
ChatMessageComponent component = new ChatMessageComponent(chatMessage, comp -> {
items.remove(chatMessage);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you removed this code:

int index = vBox.getChildren().indexOf(chatMessageComponent);
items.remove(index);

});
if (chatMessage instanceof AiMessage) {
component.setOnRegenerate(comp -> {
if (items.size() > 1 && items.getLast() == chatMessage) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to move out this code to AiChatComponent? So that ChatHistory component would be responsible only for history?

ChatMessage previous = items.get(items.size() - 2);
Comment on lines +57 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code doesn't follow fail-fast principle. The conditions should be checked separately with early returns to avoid deep nesting and improve readability.

if (previous instanceof UserMessage message) {
String userText = message.singleText();
// Remove the last two messages: first the AI message, then the corresponding user message
items.removeLast();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

items.removeLast();
if (regenerateCallback != null) {
regenerateCallback.accept(userText);
}
}
}
});
}
vBox.getChildren().add(component);
}
});
}

Expand All @@ -51,3 +78,4 @@ public void scrollDown() {
this.setVvalue(this.getVmax());
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
<Tooltip text="%Delete message from chat history" />
</tooltip>
</Button>
<Button onAction="#onRegenerateClick" styleClass="icon-button,narrow" textAlignment="CENTER">
<graphic>
<JabRefIconView glyph="REFRESH"/>
</graphic>
<tooltip>
<Tooltip text="%Regenerate AI response" />
</tooltip>
</Button>
</VBox>
</HBox>
</fx:root>

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class ChatMessageComponent extends HBox {

private final ObjectProperty<ChatMessage> chatMessage = new SimpleObjectProperty<>();
private final ObjectProperty<Consumer<ChatMessageComponent>> onDelete = new SimpleObjectProperty<>();
private final ObjectProperty<Consumer<ChatMessageComponent>> onRegenerate = new SimpleObjectProperty<>();

@FXML private HBox wrapperHBox;
@FXML private VBox vBox;
Expand Down Expand Up @@ -63,6 +64,10 @@ public void setOnDelete(Consumer<ChatMessageComponent> onDeleteCallback) {
this.onDelete.set(onDeleteCallback);
}

public void setOnRegenerate(Consumer<ChatMessageComponent> callback) {
this.onRegenerate.set(callback);
}

private void loadChatMessage() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also make that "Regenerate AI response" button is shown only if the message is an AiMessage or ErrorMessage?

switch (chatMessage.get()) {
case UserMessage userMessage -> {
Expand All @@ -87,7 +92,7 @@ private void loadChatMessage() {
}

default ->
LOGGER.error("ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", chatMessage.get().type().name());
LOGGER.error("ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", chatMessage.get().type().name());
}
}

Expand All @@ -103,7 +108,15 @@ private void onDeleteClick() {
}
}

@FXML
private void onRegenerateClick() {
if (onRegenerate.get() != null) {
onRegenerate.get().accept(this);
}
}

private void setColor(String fillColor, String borderColor) {
vBox.setStyle("-fx-background-color: " + fillColor + "; -fx-border-radius: 10; -fx-background-radius: 10; -fx-border-color: " + borderColor + "; -fx-border-width: 3;");
}
}

1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2649,6 +2649,7 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both
An\ API\ key\ has\ to\ be\ provided=An API key has to be provided
Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Current AI model: %0. The AI may generate inaccurate or inappropriate responses. Please verify any information provided.
Delete\ message\ from\ chat\ history=Delete message from chat history
Regenerate\ AI\ response=Regenerate AI response
Generated\ at\ %0\ by\ %1=Generated at %0 by %1
Retry=Retry
Updating\ local\ embedding\ model...=Updating local embedding model...
Expand Down