Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

- We added support for selecting citation fetcher in Citations Tab. [#14430](https://github.com/JabRef/jabref/issues/14430)
- In the "New Entry" dialog the identifier type is now automatically updated on typing. [#14660](https://github.com/JabRef/jabref/issues/14660)
- We added the ability to copy selected text from AI chat interface. [#14655](https://github.com/JabRef/jabref/issues/14655)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@
import javafx.fxml.FXML;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Pos;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;

import org.jabref.gui.clipboard.ClipBoardManager;
import org.jabref.gui.util.MarkdownTextFlow;
import org.jabref.logic.ai.util.ChatMessageUtils;
import org.jabref.logic.ai.util.ErrorMessage;
import org.jabref.logic.l10n.Localization;

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 jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static javafx.scene.input.MouseEvent.MOUSE_PRESSED;
Copy link
Member

Choose a reason for hiding this comment

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

Don't import static, import MouseEvent and use MouseEvent.MOUSE_PRESSED


public class ChatMessageComponent extends HBox {
private static final Logger LOGGER = LoggerFactory.getLogger(ChatMessageComponent.class);

Expand All @@ -37,6 +44,7 @@ public class ChatMessageComponent extends HBox {
@FXML private VBox buttonsVBox;

private final MarkdownTextFlow markdownTextFlow;
@Inject private ClipBoardManager clipBoardManager;

public ChatMessageComponent() {
ViewLoader.view(this)
Expand All @@ -53,6 +61,7 @@ public ChatMessageComponent() {
markdownContentPane.getChildren().add(markdownTextFlow);
markdownContentPane.minHeightProperty().bind(markdownTextFlow.heightProperty());
markdownContentPane.prefHeightProperty().bind(markdownTextFlow.heightProperty());
setupContextMenu();
}

public ChatMessageComponent(ChatMessage chatMessage, Consumer<ChatMessageComponent> onDeleteCallback) {
Expand All @@ -61,6 +70,36 @@ public ChatMessageComponent(ChatMessage chatMessage, Consumer<ChatMessageCompone
setOnDelete(onDeleteCallback);
}

private void setupContextMenu() {
ContextMenu contextMenu = new ContextMenu();
MenuItem copyItem = new MenuItem(Localization.lang("Copy"));
contextMenu.getItems().add(copyItem);

// 1. Capture and LOCK the selection state
markdownContentPane.addEventFilter(MOUSE_PRESSED, event -> {
if (event.isSecondaryButtonDown() && markdownTextFlow.isSelectionActive()) {
// Consume the event to prevent JavaFX from clearing the selection highlight
event.consume();
// Manually trigger the context menu since we consumed the event that usually triggers it
contextMenu.show(markdownContentPane, event.getScreenX(), event.getScreenY());
}
});

copyItem.setOnAction(_ -> {
if (markdownTextFlow.isSelectionActive()) {
markdownTextFlow.copySelectedText();
} else {
copyFullMessage();
}
});

markdownContentPane.setOnContextMenuRequested(event -> {
if (!markdownTextFlow.isSelectionActive()) {
contextMenu.show(markdownContentPane, event.getScreenX(), event.getScreenY());
}
});
}

public void setChatMessage(ChatMessage chatMessage) {
this.chatMessage.set(chatMessage);
}
Expand Down Expand Up @@ -119,4 +158,12 @@ private void onDeleteClick() {
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;");
}

private void copyFullMessage() {
ChatMessageUtils.getContent(chatMessage.get()).ifPresent(content -> {
if (!content.isEmpty()) {
clipBoardManager.setContent(content);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.jabref.logic.ai.util;

import java.util.Optional;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;

public class ChatMessageUtils {

private ChatMessageUtils() {
}

public static Optional<String> getContent(ChatMessage chatMessage) {
if (chatMessage == null) {
return Optional.empty();
}

String content = switch (chatMessage) {
case UserMessage user ->
user.singleText();
case AiMessage ai ->
ai.text();
case ErrorMessage err ->
err.getText();
default ->
null;
};

return Optional.ofNullable(content);
}
}
Loading