-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add Markdown rendering to AI Chat #13194
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
base: main
Are you sure you want to change the base?
Changes from 12 commits
e04f0de
ebcd900
ae2e2fa
2b2fa66
be60b42
3336504
17becfb
abd52c0
99a6c3d
9e4ccfc
9c31517
4e2c604
abdc1d3
22ae09c
d9eaa0b
2017cc4
51e63d3
8348525
aed3c58
8edce1c
fda54f7
699d3eb
e3fa14f
2eca1a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,19 +2,24 @@ | |
|
||
import java.util.function.Consumer; | ||
|
||
import javafx.beans.binding.Bindings; | ||
import javafx.beans.binding.NumberBinding; | ||
import javafx.beans.property.ObjectProperty; | ||
import javafx.beans.property.SimpleObjectProperty; | ||
import javafx.fxml.FXML; | ||
import javafx.geometry.NodeOrientation; | ||
import javafx.geometry.Pos; | ||
import javafx.scene.control.Label; | ||
import javafx.scene.layout.HBox; | ||
import javafx.scene.layout.Pane; | ||
import javafx.scene.layout.Priority; | ||
import javafx.scene.layout.VBox; | ||
|
||
import org.jabref.gui.util.MarkdownTextFlow; | ||
import org.jabref.logic.ai.util.ErrorMessage; | ||
import org.jabref.logic.l10n.Localization; | ||
|
||
import com.airhacks.afterburner.views.ViewLoader; | ||
import com.dlsc.gemsfx.ExpandingTextArea; | ||
import dev.langchain4j.data.message.AiMessage; | ||
import dev.langchain4j.data.message.ChatMessage; | ||
import dev.langchain4j.data.message.UserMessage; | ||
|
@@ -27,18 +32,25 @@ public class ChatMessageComponent extends HBox { | |
private final ObjectProperty<ChatMessage> chatMessage = new SimpleObjectProperty<>(); | ||
private final ObjectProperty<Consumer<ChatMessageComponent>> onDelete = new SimpleObjectProperty<>(); | ||
|
||
@FXML private HBox wrapperHBox; | ||
@FXML private VBox vBox; | ||
@FXML private Label sourceLabel; | ||
@FXML private ExpandingTextArea contentTextArea; | ||
@FXML private VBox buttonsVBox; | ||
@FXML | ||
private HBox wrapperHBox; | ||
@FXML | ||
private VBox vBox; | ||
@FXML | ||
private Label sourceLabel; | ||
@FXML | ||
private Pane markdownContentPane; | ||
@FXML | ||
private VBox buttonsVBox; | ||
Yubo-Cao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private MarkdownTextFlow markdownTextFlow; | ||
|
||
public ChatMessageComponent() { | ||
ViewLoader.view(this) | ||
.root(this) | ||
.load(); | ||
|
||
chatMessage.addListener((observable, oldValue, newValue) -> { | ||
chatMessage.addListener((_, _, newValue) -> { | ||
if (newValue != null) { | ||
loadChatMessage(); | ||
} | ||
|
@@ -68,32 +80,46 @@ private void loadChatMessage() { | |
case UserMessage userMessage -> { | ||
setColor("-jr-ai-message-user", "-jr-ai-message-user-border"); | ||
setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); | ||
wrapperHBox.setAlignment(Pos.TOP_RIGHT); | ||
sourceLabel.setText(Localization.lang("User")); | ||
contentTextArea.setText(userMessage.singleText()); | ||
markdownTextFlow.setMarkdown(userMessage.singleText()); | ||
} | ||
|
||
case AiMessage aiMessage -> { | ||
setColor("-jr-ai-message-ai", "-jr-ai-message-ai-border"); | ||
setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); | ||
wrapperHBox.setAlignment(Pos.TOP_LEFT); | ||
sourceLabel.setText(Localization.lang("AI")); | ||
contentTextArea.setText(aiMessage.text()); | ||
markdownTextFlow.setMarkdown(aiMessage.text()); | ||
} | ||
|
||
case ErrorMessage errorMessage -> { | ||
setColor("-jr-ai-message-error", "-jr-ai-message-error-border"); | ||
setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); | ||
sourceLabel.setText(Localization.lang("Error")); | ||
contentTextArea.setText(errorMessage.getText()); | ||
markdownTextFlow.setMarkdown(errorMessage.getText()); | ||
} | ||
|
||
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()); | ||
} | ||
} | ||
|
||
@FXML | ||
private void initialize() { | ||
buttonsVBox.visibleProperty().bind(wrapperHBox.hoverProperty()); | ||
markdownTextFlow = new MarkdownTextFlow(markdownContentPane); | ||
Yubo-Cao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
markdownContentPane.getChildren().add(markdownTextFlow); | ||
markdownContentPane.minHeightProperty().bind(markdownTextFlow.heightProperty()); | ||
markdownContentPane.prefHeightProperty().bind(markdownTextFlow.heightProperty()); | ||
NumberBinding maxUsableWidth = Bindings.createDoubleBinding( | ||
() -> getScene() == null ? 600 : getScene().getWidth() - 20, sceneProperty(), | ||
getScene() == null ? widthProperty() : getScene().widthProperty() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to add a comment in which cases Scene is null? Or is it "magically" null and we don't know - then maybe also comment that it is unclar, but we need to check Reason --> Could help to create an MWE and mayb file a JavaFX issue - its not always on our side that something is wrong - see e.g., JabRef#713 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I proposed my fix, I used this answer of StackOverflow (warning: non-English): I think the author gives a good, if you let me say this, "idiom" of handling this |
||
); | ||
markdownTextFlow.maxWidthProperty().bind(maxUsableWidth); | ||
wrapperHBox.maxWidthProperty().bind(maxUsableWidth); | ||
setMaxWidth(Double.MAX_VALUE); | ||
HBox.setHgrow(this, Priority.ALWAYS); | ||
} | ||
|
||
@FXML | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package org.jabref.gui.keyboard; | ||
|
||
import javafx.scene.Scene; | ||
import javafx.scene.input.KeyEvent; | ||
|
||
import org.jabref.gui.util.SelectableTextFlow; | ||
|
||
public class SelectableTextFlowKeyBindings { | ||
public static void call(Scene scene, KeyEvent event, KeyBindingRepository keyBindingRepository) { | ||
if (scene.focusOwnerProperty().get() instanceof SelectableTextFlow selectableTextFlow) { | ||
keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { | ||
switch (binding) { | ||
case COPY -> { | ||
selectableTextFlow.copySelectedText(); | ||
event.consume(); | ||
} | ||
case SELECT_ALL -> { | ||
selectableTextFlow.selectAll(); | ||
event.consume(); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.