diff --git a/CHANGELOG.md b/CHANGELOG.md index de4f00f3f07..2e628a0b6a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added "Close library" to the File menu. [#14381](https://github.com/JabRef/jabref/issues/14381) - We added a "Regenerate" button for the AI chat allowing the user to make the language model reformulate its response to the previous prompt. [#12191](https://github.com/JabRef/jabref/issues/12191) - We added support for transliteration of fields to English and automatic transliteration of generated citation key. [#11377](https://github.com/JabRef/jabref/issues/11377) +- We added the generation of follow-up questions in AI chat. [#12243](https://github.com/JabRef/jabref/issues/12243) - We added support for "Search Google Scholar" and "Search Semantic Scholar" to quickly search for a selected entry's title in Google Scholar or Semantic Scholar directly from the main table's context menu [#12268](https://github.com/JabRef/jabref/issues/12268) - We introduced a new "Search Engine URL Template" setting in Preferences to allow users to customize their search engine URL templates [#12268](https://github.com/JabRef/jabref/issues/12268) diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java index ea518de18ae..11f35c44172 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -15,6 +15,7 @@ import javafx.scene.control.Button; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; @@ -78,6 +79,7 @@ public class AiChatComponent extends VBox { @FXML private Hyperlink exQuestion2; @FXML private Hyperlink exQuestion3; @FXML private HBox exQuestionBox; + @FXML private HBox followUpQuestionsBox; private String noticeTemplate; @@ -114,6 +116,7 @@ public void initialize() { initializeNotifications(); sendExampleQuestions(); initializeExampleQuestions(); + initializeFollowUpQuestions(); } private void initializeNotifications() { @@ -214,6 +217,42 @@ private void initializeChatPrompt() { updatePromptHistory(); } + private void initializeFollowUpQuestions() { + aiChatLogic.getFollowUpQuestions().addListener((javafx.collections.ListChangeListener) change -> { + updateFollowUpQuestions(); + }); + } + + private void updateFollowUpQuestions() { + List questions = new ArrayList<>(aiChatLogic.getFollowUpQuestions()); + + UiTaskExecutor.runInJavaFXThread(() -> { + followUpQuestionsBox.getChildren().removeIf(node -> node instanceof Hyperlink); + + if (questions.isEmpty()) { + followUpQuestionsBox.setVisible(false); + followUpQuestionsBox.setManaged(false); + exQuestionBox.setVisible(true); + exQuestionBox.setManaged(true); + } else { + followUpQuestionsBox.setVisible(true); + followUpQuestionsBox.setManaged(true); + exQuestionBox.setVisible(false); + exQuestionBox.setManaged(false); + + for (String question : questions) { + Hyperlink link = new Hyperlink(question); + link.getStyleClass().add("exampleQuestionStyle"); + link.setTooltip(new Tooltip(question)); + link.setOnAction(event -> { + onSendMessage(question); + }); + followUpQuestionsBox.getChildren().add(link); + } + } + }); + } + private void updateNotifications() { notifications.clear(); notifications.addAll(entries.stream().map(this::updateNotificationsForEntry).flatMap(List::stream).toList()); @@ -278,6 +317,13 @@ private List updateNotificationsForEntry(BibEntry entry) { } private void onSendMessage(String userPrompt) { + aiChatLogic.getFollowUpQuestions().clear(); + + UiTaskExecutor.runInJavaFXThread(() -> { + exQuestionBox.setVisible(false); + exQuestionBox.setManaged(false); + }); + UserMessage userMessage = new UserMessage(userPrompt); updatePromptHistory(); setLoading(true); diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java index 5da5cb8ccba..50aaee237bf 100644 --- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java @@ -198,4 +198,3 @@ private void showFailures(List failures) { ); } } - diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java index 1f708435c3f..1a461280411 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java @@ -9,6 +9,8 @@ import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextArea; @@ -40,6 +42,11 @@ public class AiTab extends AbstractPreferenceTabView implements @FXML private CheckBox enableAi; @FXML private CheckBox autoGenerateEmbeddings; @FXML private CheckBox autoGenerateSummaries; + @FXML private CheckBox generateFollowUpQuestions; + @FXML private Spinner followUpQuestionsCountSpinner; + @FXML private Tab followUpQuestionsTab; + @FXML private TextArea followUpQuestionsTextArea; + @FXML private Label followUpQuestionsCountLabel; @FXML private ComboBox aiProviderComboBox; @FXML private ComboBox chatModelComboBox; @@ -117,7 +124,7 @@ private void initializeTemplates() { summarizationCombineUserMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_COMBINE_USER_MESSAGE)); citationParsingSystemMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE)); citationParsingUserMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CITATION_PARSING_USER_MESSAGE)); - + followUpQuestionsTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.FOLLOW_UP_QUESTIONS)); templatesTabPane.getSelectionModel().selectedItemProperty().addListener(_ -> viewModel.selectedTemplateProperty().set(getAiTemplate())); } @@ -248,6 +255,11 @@ private void initializeEnableAi() { autoGenerateSummaries.disableProperty().bind(viewModel.disableAutoGenerateSummaries()); autoGenerateEmbeddings.selectedProperty().bindBidirectional(viewModel.autoGenerateEmbeddings()); autoGenerateEmbeddings.disableProperty().bind(viewModel.disableAutoGenerateEmbeddings()); + generateFollowUpQuestions.selectedProperty().bindBidirectional(viewModel.generateFollowUpQuestions()); + followUpQuestionsCountSpinner.setValueFactory(AiTabViewModel.followUpQuestionsCountValueFactory); + followUpQuestionsCountSpinner.getValueFactory().valueProperty().bindBidirectional(viewModel.followUpQuestionsCountProperty().asObject()); + followUpQuestionsCountSpinner.disableProperty().bind(generateFollowUpQuestions.selectedProperty().not()); + followUpQuestionsCountLabel.disableProperty().bind(generateFollowUpQuestions.selectedProperty().not()); } @Override @@ -292,6 +304,8 @@ public Optional getAiTemplate() { return Optional.of(AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE); } else if (selectedTab == citationParsingUserMessageTab) { return Optional.of(AiTemplate.CITATION_PARSING_USER_MESSAGE); + } else if (selectedTab == followUpQuestionsTab) { + return Optional.of(AiTemplate.FOLLOW_UP_QUESTIONS); } return Optional.empty(); diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java index c6308a77f29..0015c74f78c 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java @@ -18,6 +18,7 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; +import javafx.scene.control.SpinnerValueFactory; import org.jabref.gui.preferences.PreferenceTabViewModel; import org.jabref.logic.ai.AiDefaultPreferences; @@ -37,6 +38,8 @@ import de.saxsys.mvvmfx.utils.validation.Validator; public class AiTabViewModel implements PreferenceTabViewModel { + protected static SpinnerValueFactory followUpQuestionsCountValueFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 5, 3); + private final Locale oldLocale; private final BooleanProperty enableAi = new SimpleBooleanProperty(); @@ -44,6 +47,8 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final BooleanProperty disableAutoGenerateEmbeddings = new SimpleBooleanProperty(); private final BooleanProperty autoGenerateSummaries = new SimpleBooleanProperty(); private final BooleanProperty disableAutoGenerateSummaries = new SimpleBooleanProperty(); + private final BooleanProperty generateFollowUpQuestions = new SimpleBooleanProperty(); + private final IntegerProperty followUpQuestionsCount = new SimpleIntegerProperty(); private final ListProperty aiProvidersList = new SimpleListProperty<>(FXCollections.observableArrayList(AiProvider.values())); @@ -91,7 +96,8 @@ AiTemplate.SUMMARIZATION_CHUNK_USER_MESSAGE, new SimpleStringProperty(), AiTemplate.SUMMARIZATION_COMBINE_SYSTEM_MESSAGE, new SimpleStringProperty(), AiTemplate.SUMMARIZATION_COMBINE_USER_MESSAGE, new SimpleStringProperty(), AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE, new SimpleStringProperty(), - AiTemplate.CITATION_PARSING_USER_MESSAGE, new SimpleStringProperty() + AiTemplate.CITATION_PARSING_USER_MESSAGE, new SimpleStringProperty(), + AiTemplate.FOLLOW_UP_QUESTIONS, new SimpleStringProperty() ); private final OptionalObjectProperty selectedTemplate = OptionalObjectProperty.empty(); @@ -341,6 +347,8 @@ public void setValues() { enableAi.setValue(aiPreferences.getEnableAi()); autoGenerateSummaries.setValue(aiPreferences.getAutoGenerateSummaries()); autoGenerateEmbeddings.setValue(aiPreferences.getAutoGenerateEmbeddings()); + generateFollowUpQuestions.setValue(aiPreferences.getGenerateFollowUpQuestions()); + followUpQuestionsCount.setValue(aiPreferences.getFollowUpQuestionsCount()); selectedAiProvider.setValue(aiPreferences.getAiProvider()); @@ -364,6 +372,8 @@ public void storeSettings() { aiPreferences.setEnableAi(enableAi.get()); aiPreferences.setAutoGenerateEmbeddings(autoGenerateEmbeddings.get()); aiPreferences.setAutoGenerateSummaries(autoGenerateSummaries.get()); + aiPreferences.setGenerateFollowUpQuestions(generateFollowUpQuestions.get()); + aiPreferences.setFollowUpQuestionsCount(followUpQuestionsCount.get()); aiPreferences.setAiProvider(selectedAiProvider.get()); @@ -414,6 +424,7 @@ public void resetExpertSettings() { documentSplitterOverlapSize.set(AiDefaultPreferences.DOCUMENT_SPLITTER_OVERLAP); ragMaxResultsCount.set(AiDefaultPreferences.RAG_MAX_RESULTS_COUNT); ragMinScore.set(LocalizedNumbers.doubleToString(AiDefaultPreferences.RAG_MIN_SCORE)); + followUpQuestionsCount.set(AiDefaultPreferences.FOLLOW_UP_QUESTIONS_COUNT); } public void resetTemplates() { @@ -487,6 +498,18 @@ public BooleanProperty disableAutoGenerateSummaries() { return disableAutoGenerateSummaries; } + public BooleanProperty generateFollowUpQuestions() { + return generateFollowUpQuestions; + } + + public IntegerProperty followUpQuestionsCountProperty() { + return followUpQuestionsCount; + } + + public StringProperty followUpQuestionsTemplateProperty() { + return aiPreferences.templateProperty(AiTemplate.FOLLOW_UP_QUESTIONS); + } + public ReadOnlyListProperty aiProvidersProperty() { return aiProvidersList; } diff --git a/jabgui/src/main/resources/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml b/jabgui/src/main/resources/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml index 49a9123084a..2753d758937 100644 --- a/jabgui/src/main/resources/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml @@ -25,12 +25,16 @@ - + + +