From dfa3f9c0aad4d8d0d954c930572eb6422ec6168b Mon Sep 17 00:00:00 2001 From: xiaoMartintin Date: Fri, 21 Mar 2025 14:51:17 +0800 Subject: [PATCH 1/3] Add regenerate button to AI Chat tab in entry editor (fixes #12191) --- .../ai/components/aichat/AiChatComponent.java | 14 +++++-- .../chathistory/ChatHistoryComponent.java | 42 ++++++++++++++++--- .../chatmessage/ChatMessageComponent.fxml | 9 ++++ .../chatmessage/ChatMessageComponent.java | 15 ++++++- src/main/resources/l10n/JabRef_de.properties | 1 + src/main/resources/l10n/JabRef_en.properties | 1 + src/main/resources/l10n/JabRef_fr.properties | 1 + .../resources/l10n/JabRef_pt_BR.properties | 1 + 8 files changed, 74 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java index 25e52d294e4..33bf94043a3 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -84,13 +84,19 @@ 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 -> { + deleteLastMessage(); + deleteLastMessage(); + chatPrompt.switchToNormalState(); + onSendMessage(userPrompt); + }); initializeChatPrompt(); initializeNotice(); initializeNotifications(); @@ -174,8 +180,8 @@ private List 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 -> { diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java index c99e125b8e7..ecd6bbda058 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -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; @@ -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 regenerateCallback; + public ChatHistoryComponent() { ViewLoader.view(this) .root(this) @@ -35,14 +41,39 @@ public void setItems(ObservableList items) { items.addListener((ListChangeListener) obs -> fill(items)); } + public void setRegenerateCallback(Consumer regenerateCallback) { + this.regenerateCallback = regenerateCallback; + } + private void fill(ObservableList 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); - }))); + // Go through the message list, create message components, and set regenerate functionality for AI messages + for (ChatMessage chatMessage : items) { + ChatMessageComponent component = new ChatMessageComponent(chatMessage, comp -> { + // Directly remove the corresponding message based on the message instance instead of UI index lookup + items.remove(chatMessage); + }); + // If the message is an AI message, set the regenerate callback + if (chatMessage instanceof AiMessage) { + component.setOnRegenerate(comp -> { + // Check if the current AI message is the last one, and ensure there are at least 2 messages (a user message must exist) + if (!items.isEmpty() && items.get(items.size() - 1) == chatMessage && items.size() > 1) { + ChatMessage previous = items.get(items.size() - 2); + if (previous instanceof UserMessage) { + String userText = ((UserMessage) previous).singleText(); + // Remove the last 2 messages: first the AI message, then the corresponding user message + items.remove(items.size() - 1); + items.remove(items.size() - 1); + if (regenerateCallback != null) { + regenerateCallback.accept(userText); + } + } + } + }); + } + vBox.getChildren().add(component); + } }); } @@ -51,3 +82,4 @@ public void scrollDown() { this.setVvalue(this.getVmax()); } } + diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml b/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml index cf56ad0d56b..bd48eed8f5f 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml @@ -35,6 +35,15 @@ + + diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java index 511c3d06628..5cf1555eabe 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java @@ -26,6 +26,7 @@ public class ChatMessageComponent extends HBox { private final ObjectProperty chatMessage = new SimpleObjectProperty<>(); private final ObjectProperty> onDelete = new SimpleObjectProperty<>(); + private final ObjectProperty> onRegenerate = new SimpleObjectProperty<>(); @FXML private HBox wrapperHBox; @FXML private VBox vBox; @@ -63,6 +64,10 @@ public void setOnDelete(Consumer onDeleteCallback) { this.onDelete.set(onDeleteCallback); } + public void setOnRegenerate(Consumer callback) { + this.onRegenerate.set(callback); + } + private void loadChatMessage() { switch (chatMessage.get()) { case UserMessage userMessage -> { @@ -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()); } } @@ -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;"); } } + diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 7ee4adb5480..2354810c1bc 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2645,6 +2645,7 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both An\ API\ key\ has\ to\ be\ provided=Ein API-Schlüssel muss bereitgestellt werden Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Aktuelles KI-Modell\: %0. Die KI kann ungenaue oder unangemessene Antworten generieren. Bitte überprüfen Sie die bereitgestellten Informationen. Delete\ message\ from\ chat\ history=Nachricht aus dem Chatverlauf löschen +Regenerate\ AI\ response=KI-Antwort neu generieren Generated\ at\ %0\ by\ %1=Generiert am %0 von %1 Retry=Erneut versuchen Updating\ local\ embedding\ model...=Aktualisiere lokales Einbettungsmodell... diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index e7496463445..e99ce6e3ab1 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -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... diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 9569c1735f7..b5ffe14e7ab 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2645,6 +2645,7 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both An\ API\ key\ has\ to\ be\ provided=Une clé API doit être fournie Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Modèle actuel de l'IA \: %0. L'IA peut générer des réponses inexactes ou inappropriées. Veuillez vérifier toutes les informations fournies. Delete\ message\ from\ chat\ history=Supprimer le message de l'historique du tchat +Regenerate\ AI\ response=Régénérer la réponse de l'IA Generated\ at\ %0\ by\ %1=Généré à %0 par %1 Retry=Réessayer Updating\ local\ embedding\ model...=Mise à jour du modèle d'intégration local... diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index d574aad8e16..88c33789337 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2643,6 +2643,7 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both An\ API\ key\ has\ to\ be\ provided=Uma chave de API tem de ser fornecida Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Modelo de IA atual\: %0. A IA pode gerar respostas imprecisas ou inadequadas. Por favor, verifique todas as informações fornecidas. Delete\ message\ from\ chat\ history=Excluir mensagem do histórico de bate-papo +Regenerate\ AI\ response=Regenerar resposta da IA Generated\ at\ %0\ by\ %1=Gerado a %0 por %1 Retry=Tentar novamente Updating\ local\ embedding\ model...=Atualizando modelo local de incorporação... From eb3eeaa0dd250616895557bd46cdd288137baa88 Mon Sep 17 00:00:00 2001 From: xiaoMartintin Date: Fri, 21 Mar 2025 15:55:41 +0800 Subject: [PATCH 2/3] Add regenerate button to AI Chat tab in entry editor and fix codestyle (fixes #12191) --- .../aichat/chathistory/ChatHistoryComponent.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java index ecd6bbda058..b8c4e356ae1 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -48,23 +48,18 @@ public void setRegenerateCallback(Consumer regenerateCallback) { private void fill(ObservableList items) { UiTaskExecutor.runInJavaFXThread(() -> { vBox.getChildren().clear(); - // Go through the message list, create message components, and set regenerate functionality for AI messages for (ChatMessage chatMessage : items) { ChatMessageComponent component = new ChatMessageComponent(chatMessage, comp -> { - // Directly remove the corresponding message based on the message instance instead of UI index lookup items.remove(chatMessage); }); - // If the message is an AI message, set the regenerate callback if (chatMessage instanceof AiMessage) { component.setOnRegenerate(comp -> { - // Check if the current AI message is the last one, and ensure there are at least 2 messages (a user message must exist) - if (!items.isEmpty() && items.get(items.size() - 1) == chatMessage && items.size() > 1) { + if (!items.isEmpty() && items.getLast() == chatMessage && items.size() > 1) { ChatMessage previous = items.get(items.size() - 2); - if (previous instanceof UserMessage) { - String userText = ((UserMessage) previous).singleText(); - // Remove the last 2 messages: first the AI message, then the corresponding user message - items.remove(items.size() - 1); - items.remove(items.size() - 1); + if (previous instanceof UserMessage message) { + String userText = message.singleText(); + items.removeLast(); + items.removeLast(); if (regenerateCallback != null) { regenerateCallback.accept(userText); } From 5f844204f7d9b966e28185a0a8a75fa5de5ffa44 Mon Sep 17 00:00:00 2001 From: xiaoMartintin Date: Fri, 21 Mar 2025 16:31:25 +0800 Subject: [PATCH 3/3] Improve the codestyle and add a new key for "Regenerate AI response" (fixes #12191) --- .../org/jabref/gui/ai/components/aichat/AiChatComponent.java | 2 ++ .../ai/components/aichat/chathistory/ChatHistoryComponent.java | 3 ++- src/main/resources/l10n/JabRef_de.properties | 1 - src/main/resources/l10n/JabRef_fr.properties | 1 - src/main/resources/l10n/JabRef_pt_BR.properties | 1 - 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java index 33bf94043a3..5d1a4bce101 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -92,6 +92,7 @@ public AiChatComponent(AiService aiService, public void initialize() { uiChatHistory.setItems(aiChatLogic.getChatHistory()); uiChatHistory.setRegenerateCallback(userPrompt -> { + // Remove the last two messages: first the AI response, then the corresponding user message deleteLastMessage(); deleteLastMessage(); chatPrompt.switchToNormalState(); @@ -128,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(); diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java index b8c4e356ae1..cefa4fb90dc 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -54,10 +54,11 @@ private void fill(ObservableList items) { }); if (chatMessage instanceof AiMessage) { component.setOnRegenerate(comp -> { - if (!items.isEmpty() && items.getLast() == chatMessage && items.size() > 1) { + if (items.size() > 1 && items.getLast() == chatMessage) { ChatMessage previous = items.get(items.size() - 2); 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(); items.removeLast(); if (regenerateCallback != null) { diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index f7777471bf6..ae86687d6f6 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2644,7 +2644,6 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both An\ API\ key\ has\ to\ be\ provided=Ein API-Schlüssel muss bereitgestellt werden Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Aktuelles KI-Modell\: %0. Die KI kann ungenaue oder unangemessene Antworten generieren. Bitte überprüfen Sie die bereitgestellten Informationen. Delete\ message\ from\ chat\ history=Nachricht aus dem Chatverlauf löschen -Regenerate\ AI\ response=KI-Antwort neu generieren Generated\ at\ %0\ by\ %1=Generiert am %0 von %1 Retry=Erneut versuchen Updating\ local\ embedding\ model...=Aktualisiere lokales Einbettungsmodell... diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 30a54ac3e7b..2570db85cb8 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2644,7 +2644,6 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both An\ API\ key\ has\ to\ be\ provided=Une clé API doit être fournie Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Modèle actuel de l'IA \: %0. L'IA peut générer des réponses inexactes ou inappropriées. Veuillez vérifier toutes les informations fournies. Delete\ message\ from\ chat\ history=Supprimer le message de l'historique du tchat -Regenerate\ AI\ response=Régénérer la réponse de l'IA Generated\ at\ %0\ by\ %1=Généré à %0 par %1 Retry=Réessayer Updating\ local\ embedding\ model...=Mise à jour du modèle d'intégration local... diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 45afd1a5f9c..bcb3992f36f 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2647,7 +2647,6 @@ Additionally,\ we\ use\ Deep\ Java\ Library\ (DJL)\ embedding\ models\ for\ both An\ API\ key\ has\ to\ be\ provided=Uma chave de API tem de ser fornecida Current\ AI\ model\:\ %0.\ The\ AI\ may\ generate\ inaccurate\ or\ inappropriate\ responses.\ Please\ verify\ any\ information\ provided.=Modelo de IA atual\: %0. A IA pode gerar respostas imprecisas ou inadequadas. Por favor, verifique todas as informações fornecidas. Delete\ message\ from\ chat\ history=Excluir mensagem do histórico de bate-papo -Regenerate\ AI\ response=Regenerar resposta da IA Generated\ at\ %0\ by\ %1=Gerado a %0 por %1 Retry=Tentar novamente Updating\ local\ embedding\ model...=Atualizando modelo local de incorporação...