Skip to content
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

fiexd the issue (added the sample questions in html file). #12756

Closed
wants to merge 1 commit into from
Closed
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
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"java.configuration.updateBuildConfiguration": "interactive",
"java.format.settings.url": "/config/VSCode Code Style.xml",
"java.checkstyle.configuration": "${workspaceFolder}/config/checkstyle/checkstyle_reviewdog.xml",
"java.checkstyle.version": "10.21.0"
"java.checkstyle.version": "10.21.0",
"java.compile.nullAnalysis.mode": "automatic"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
<ChatHistoryComponent fx:id="uiChatHistory" VBox.vgrow="ALWAYS" fitToWidth="true" />
</Loadable>


<HBox spacing="10" alignment="CENTER">
<Hyperlink text="What is the goal of the paper?" onAction="#onQuestionClicked"/>
<Hyperlink text="Which methods were used in the research?" onAction="#onQuestionClicked"/>
<Hyperlink text="What are the key findings?" onAction="#onQuestionClicked"/>
</HBox>

<HBox spacing="10">
<Button alignment="CENTER"
fx:id="notificationsButton"
Expand Down
144 changes: 74 additions & 70 deletions src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@
import java.util.List;
import java.util.stream.Stream;

import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;

import org.controlsfx.control.PopOver;
import org.jabref.gui.DialogService;
import org.jabref.gui.ai.components.aichat.chathistory.ChatHistoryComponent;
import org.jabref.gui.ai.components.aichat.chatprompt.ChatPromptComponent;
Expand All @@ -34,16 +26,27 @@
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.util.ListUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.airhacks.afterburner.views.ViewLoader;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import org.controlsfx.control.PopOver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;

public class AiChatComponent extends VBox {

private static final Logger LOGGER = LoggerFactory.getLogger(AiChatComponent.class);

private final AiService aiService;
Expand All @@ -70,8 +73,7 @@ public AiChatComponent(AiService aiService,
BibDatabaseContext bibDatabaseContext,
AiPreferences aiPreferences,
DialogService dialogService,
TaskExecutor taskExecutor
) {
TaskExecutor taskExecutor) {
this.aiService = aiService;
this.entries = entries;
this.bibDatabaseContext = bibDatabaseContext;
Expand All @@ -84,8 +86,8 @@ public AiChatComponent(AiService aiService,
aiService.getIngestionService().ingest(name, ListUtil.getLinkedFiles(entries).toList(), bibDatabaseContext);

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

@FXML
Expand All @@ -97,25 +99,22 @@ public void initialize() {
}

private void initializeNotifications() {
ListUtil.getLinkedFiles(entries).forEach(file ->
aiService.getIngestionService().ingest(file, bibDatabaseContext).stateProperty().addListener(obs -> updateNotifications()));
ListUtil.getLinkedFiles(entries).forEach(file -> aiService.getIngestionService().ingest(file, bibDatabaseContext).stateProperty().addListener(obs -> updateNotifications()));

updateNotifications();
}

private void initializeNotice() {
String newNotice = noticeText
.getText()
.replaceAll("%0", aiPreferences.getAiProvider().getLabel() + " " + aiPreferences.getSelectedChatModel());
.getText()
.replaceAll("%0", aiPreferences.getAiProvider().getLabel() + " " + aiPreferences.getSelectedChatModel());

noticeText.setText(newNotice);
}

private void initializeChatPrompt() {
notificationsButton.setOnAction(event ->
new PopOver(new NotificationsComponent(notifications))
.show(notificationsButton)
);
notificationsButton.setOnAction(event -> new PopOver(new NotificationsComponent(notifications))
.show(notificationsButton));

chatPrompt.setSendCallback(this::onSendMessage);

Expand Down Expand Up @@ -151,43 +150,43 @@ private List<Notification> updateNotificationsForEntry(BibEntry entry) {
if (entries.size() == 1) {
if (entry.getCitationKey().isEmpty()) {
notifications.add(new Notification(
Localization.lang("No citation key for %0", entry.getAuthorTitleYear()),
Localization.lang("The chat history will not be stored in next sessions")
));
Localization.lang("No citation key for %0", entry.getAuthorTitleYear()),
Localization.lang("The chat history will not be stored in next sessions")));
} else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) {
notifications.add(new Notification(
Localization.lang("Invalid citation key for %0 (%1)", entry.getCitationKey().get(), entry.getAuthorTitleYear()),
Localization.lang("The chat history will not be stored in next sessions")
));
Localization.lang("Invalid citation key for %0 (%1)", entry.getCitationKey().get(), entry.getAuthorTitleYear()),
Localization.lang("The chat history will not be stored in next sessions")));
}
}

entry.getFiles().forEach(file -> {
if (!FileUtil.isPDFFile(Path.of(file.getLink()))) {
notifications.add(new Notification(
Localization.lang("File %0 is not a PDF file", file.getLink()),
Localization.lang("Only PDF files can be used for chatting")
));
Localization.lang("File %0 is not a PDF file", file.getLink()),
Localization.lang("Only PDF files can be used for chatting")));
}
});

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 -> {
assert ingestionStatus.getException().isPresent(); // When the state is ERROR, the exception must be present.

notifications.add(new Notification(
Localization.lang("File %0 could not be ingested", ingestionStatus.getObject().getLink()),
ingestionStatus.getException().get().getLocalizedMessage()
));
Localization.lang("File %0 could not be ingested", ingestionStatus.getObject().getLink()),
ingestionStatus.getException().get().getLocalizedMessage()));
}

case SUCCESS -> {
}

case SUCCESS -> { }
case STOPPED -> notifications.add(new Notification(
Localization.lang("Processing for file %0 has been stopped", ingestionStatus.getObject().getLink()),
Localization.lang("You may need to restart the process.")));
}
});

Expand All @@ -199,28 +198,27 @@ private void onSendMessage(String userPrompt) {
updatePromptHistory();
setLoading(true);

BackgroundTask<AiMessage> task =
BackgroundTask
.wrap(() -> aiChatLogic.execute(userMessage))
.showToUser(true)
.onSuccess(aiMessage -> {
setLoading(false);
chatPrompt.requestPromptFocus();
})
.onFailure(e -> {
LOGGER.error("Got an error while sending a message to AI", e);
setLoading(false);

// Typically, if user has entered an invalid API base URL, we get either "401 - null" or "404 - null" strings.
// Since there might be other strings returned from other API endpoints, we use startsWith() here.
if (e.getMessage().startsWith("404") || e.getMessage().startsWith("401")) {
addError(Localization.lang("API base URL setting appears to be incorrect. Please check it in AI expert settings."));
} else {
addError(e.getMessage());
}

chatPrompt.switchToErrorState(userPrompt);
});
BackgroundTask<AiMessage> task = BackgroundTask
.wrap(() -> aiChatLogic.execute(userMessage))
.showToUser(true)
.onSuccess(aiMessage -> {
setLoading(false);
chatPrompt.requestPromptFocus();
})
.onFailure(e -> {
LOGGER.error("Got an error while sending a message to AI", e);
setLoading(false);

// Typically, if user has entered an invalid API base URL, we get either "401 - null" or "404 - null" strings.
// Since there might be other strings returned from other API endpoints, we use startsWith() here.
if (e.getMessage().startsWith("404") || e.getMessage().startsWith("401")) {
addError(Localization.lang("API base URL setting appears to be incorrect. Please check it in AI expert settings."));
} else {
addError(e.getMessage());
}

chatPrompt.switchToErrorState(userPrompt);
});

task.titleProperty().set(Localization.lang("Waiting for AI reply..."));

Expand All @@ -239,24 +237,30 @@ private void updatePromptHistory() {

private Stream<UserMessage> getReversedUserMessagesStream() {
return aiChatLogic
.getChatHistory()
.reversed()
.stream()
.filter(message -> message instanceof UserMessage)
.map(UserMessage.class::cast);
.getChatHistory()
.reversed()
.stream()
.filter(message -> message instanceof UserMessage)
.map(UserMessage.class::cast);
}

private void setLoading(boolean loading) {
uiLoadableChatHistory.setLoading(loading);
chatPrompt.setDisableToButtons(loading);
}

@FXML
private void onQuestionClicked(ActionEvent event) {
Hyperlink clickedLink = (Hyperlink) event.getSource();
String question = clickedLink.getText();
onSendMessage(question); // Sends the question to AI chat
}

@FXML
private void onClearChatHistory() {
boolean agreed = dialogService.showConfirmationDialogAndWait(
Localization.lang("Clear chat history"),
Localization.lang("Are you sure you want to clear the chat history of this entry?")
);
Localization.lang("Clear chat history"),
Localization.lang("Are you sure you want to clear the chat history of this entry?"));

if (agreed) {
aiChatLogic.getChatHistory().clear();
Expand Down
Loading