Skip to content

Commit

Permalink
feat: support copying user and response messages (closes #791)
Browse files Browse the repository at this point in the history
  • Loading branch information
carlrobertoh committed Dec 17, 2024
1 parent 0d52dc5 commit be9ae08
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 371 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.CopyAction;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatToolWindowScrollablePanel;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
import ee.carlrobert.codegpt.toolwindow.chat.ui.UserMessagePanel;
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensDetails;
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel;
import ee.carlrobert.codegpt.toolwindow.ui.ChatToolWindowLandingPanel;
import ee.carlrobert.codegpt.toolwindow.ui.ResponseMessagePanel;
import ee.carlrobert.codegpt.toolwindow.ui.UserMessagePanel;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
Expand Down Expand Up @@ -174,12 +175,14 @@ public void sendMessage(Message message, ConversationType conversationType) {
totalTokensPanel.updateReferencedFilesTokens(callParameters.getReferencedFiles());
}

var responsePanel = createResponsePanel(callParameters);
var userMessagePanel = createUserMessagePanel(message, callParameters);
var responseMessagePanel = createResponseMessagePanel(callParameters);

var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
messagePanel.add(new UserMessagePanel(project, message, this));
messagePanel.add(responsePanel);
messagePanel.add(userMessagePanel);
messagePanel.add(responseMessagePanel);

call(callParameters, responsePanel);
call(callParameters, responseMessagePanel, userMessagePanel);
});
}

Expand All @@ -193,29 +196,39 @@ private boolean hasReferencedFilePaths(Conversation conversation) {
it -> it.getReferencedFilePaths() != null && !it.getReferencedFilePaths().isEmpty());
}

private ResponsePanel createResponsePanel(ChatCompletionParameters callParameters) {
private UserMessagePanel createUserMessagePanel(
Message message,
ChatCompletionParameters callParameters) {
var panel = new UserMessagePanel(project, message, this);
panel.addCopyAction(() -> CopyAction.copyToClipboard(message.getPrompt()));
panel.addReloadAction(() -> reloadMessage(callParameters, panel));
panel.addDeleteAction(() -> removeMessage(message.getId(), conversation));
return panel;
}

private ResponseMessagePanel createResponseMessagePanel(ChatCompletionParameters callParameters) {
var message = callParameters.getMessage();
var fileContextIncluded =
hasReferencedFilePaths(message) || hasReferencedFilePaths(conversation);

return new ResponsePanel()
.withReloadAction(() -> reloadMessage(callParameters))
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
.addContent(
new ChatMessageResponseBody(
project,
true,
false,
message.isWebSearchIncluded(),
fileContextIncluded || message.getDocumentationDetails() != null,
this));
var panel = new ResponseMessagePanel();
panel.addCopyAction(() -> CopyAction.copyToClipboard(message.getResponse()));
panel.addContent(new ChatMessageResponseBody(
project,
true,
false,
message.isWebSearchIncluded(),
fileContextIncluded || message.getDocumentationDetails() != null,
this));
return panel;
}

private void reloadMessage(ChatCompletionParameters prevParameters) {
private void reloadMessage(ChatCompletionParameters prevParameters,
UserMessagePanel userMessagePanel) {
var prevMessage = prevParameters.getMessage();
ResponsePanel responsePanel = null;
ResponseMessagePanel responsePanel = null;
try {
responsePanel = toolWindowScrollablePanel.getMessageResponsePanel(prevMessage.getId());
responsePanel = toolWindowScrollablePanel.getResponseMessagePanel(prevMessage.getId());
((ChatMessageResponseBody) responsePanel.getContent()).clear();
toolWindowScrollablePanel.update();
} catch (Exception e) {
Expand All @@ -226,7 +239,7 @@ private void reloadMessage(ChatCompletionParameters prevParameters) {
if (responsePanel != null) {
prevMessage.setResponse("");
conversationService.saveMessage(conversation, prevMessage);
call(prevParameters.toBuilder().retry(true).build(), responsePanel);
call(prevParameters.toBuilder().retry(true).build(), responsePanel, userMessagePanel);
}

totalTokensPanel.updateConversationTokens(conversation);
Expand All @@ -253,8 +266,11 @@ private void clearWindow() {
totalTokensPanel.updateConversationTokens(conversation);
}

private void call(ChatCompletionParameters callParameters, ResponsePanel responsePanel) {
var responseContainer = (ChatMessageResponseBody) responsePanel.getContent();
private void call(
ChatCompletionParameters callParameters,
ResponseMessagePanel responseMessagePanel,
UserMessagePanel userMessagePanel) {
var responseContainer = (ChatMessageResponseBody) responseMessagePanel.getContent();

if (!CompletionRequestService.isRequestAllowed()) {
responseContainer.displayMissingCredential();
Expand All @@ -264,15 +280,18 @@ private void call(ChatCompletionParameters callParameters, ResponsePanel respons
requestHandler = new ToolwindowChatCompletionRequestHandler(
new ToolWindowCompletionResponseEventListener(
conversationService,
responsePanel,
userMessagePanel,
responseMessagePanel,
totalTokensPanel,
userInputPanel) {
@Override
public void handleTokensExceededPolicyAccepted() {
call(callParameters, responsePanel);
call(callParameters, responseMessagePanel, userMessagePanel);
}
});
userInputPanel.setSubmitEnabled(false);
userMessagePanel.disableActions(List.of("RELOAD", "DELETE"));
responseMessagePanel.disableActions(List.of("COPY"));

requestHandler.call(callParameters);
}
Expand Down Expand Up @@ -337,28 +356,39 @@ private JComponent getLandingView() {
private void displayConversation() {
clearWindow();
conversation.getMessages().forEach(message -> {
var response = message.getResponse() == null ? "" : message.getResponse();
var messageResponseBody =
new ChatMessageResponseBody(project, this).withResponse(response);
var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
messagePanel.add(getUserMessagePanel(message));
messagePanel.add(getResponseMessagePanel(message));
});
}

messageResponseBody.hideCaret();
private UserMessagePanel getUserMessagePanel(Message message) {
var userMessagePanel = new UserMessagePanel(project, message, this);
userMessagePanel.addCopyAction(() -> CopyAction.copyToClipboard(message.getPrompt()));
userMessagePanel.addReloadAction(() -> reloadMessage(
ChatCompletionParameters.builder(conversation, message)
.conversationType(ConversationType.DEFAULT)
.build(),
userMessagePanel));
userMessagePanel.addDeleteAction(() -> removeMessage(message.getId(), conversation));
var imageFilePath = message.getImageFilePath();
if (imageFilePath != null && !imageFilePath.isEmpty()) {
userMessagePanel.displayImage(imageFilePath);
}
return userMessagePanel;
}

var userMessagePanel = new UserMessagePanel(project, message, this);
var imageFilePath = message.getImageFilePath();
if (imageFilePath != null && !imageFilePath.isEmpty()) {
userMessagePanel.displayImage(imageFilePath);
}
private ResponseMessagePanel getResponseMessagePanel(Message message) {
var response = message.getResponse() == null ? "" : message.getResponse();
var messageResponseBody =
new ChatMessageResponseBody(project, this).withResponse(response);

var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
messagePanel.add(userMessagePanel);
messagePanel.add(new ResponsePanel()
.withReloadAction(() -> reloadMessage(
ChatCompletionParameters.builder(conversation, message)
.conversationType(ConversationType.DEFAULT)
.build()))
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
.addContent(messageResponseBody));
});
messageResponseBody.hideCaret();

var responseMessagePanel = new ResponseMessagePanel();
responseMessagePanel.addContent(messageResponseBody);
responseMessagePanel.addCopyAction(() -> CopyAction.copyToClipboard(message.getResponse()));
return responseMessagePanel;
}

private JPanel createRootPanel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ private void renderCodeBlock(CodeBlock node, NodeRendererContext context, HtmlWr
}

private void renderHeading(Heading node, NodeRendererContext context, HtmlWriter html) {
if (node.getLevel() == 3) {
html.attr("style", "margin-top: 4px; margin-bottom: 4px;");
}
html.attr("style", "margin-top: 8px; margin-bottom: 4px;");
context.delegateRender();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import ee.carlrobert.codegpt.events.CodeGPTEvent;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel;
import ee.carlrobert.codegpt.toolwindow.ui.ResponseMessagePanel;
import ee.carlrobert.codegpt.toolwindow.ui.UserMessagePanel;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
Expand All @@ -31,7 +32,8 @@ abstract class ToolWindowCompletionResponseEventListener implements
private final StringBuilder messageBuilder = new StringBuilder();
private final EncodingManager encodingManager;
private final ConversationService conversationService;
private final ResponsePanel responsePanel;
private final ResponseMessagePanel responsePanel;
private final UserMessagePanel userMessagePanel;
private final ChatMessageResponseBody responseContainer;
private final TotalTokensPanel totalTokensPanel;
private final UserInputPanel textArea;
Expand All @@ -43,11 +45,13 @@ abstract class ToolWindowCompletionResponseEventListener implements

public ToolWindowCompletionResponseEventListener(
ConversationService conversationService,
ResponsePanel responsePanel,
UserMessagePanel userMessagePanel,
ResponseMessagePanel responsePanel,
TotalTokensPanel totalTokensPanel,
UserInputPanel textArea) {
this.encodingManager = EncodingManager.getInstance();
this.conversationService = conversationService;
this.userMessagePanel = userMessagePanel;
this.responsePanel = responsePanel;
this.responseContainer = (ChatMessageResponseBody) responsePanel.getContent();
this.totalTokensPanel = totalTokensPanel;
Expand Down Expand Up @@ -89,7 +93,7 @@ public void handleError(ErrorDetails error, Throwable ex) {
}
} finally {
LOG.error(error.getMessage(), ex);
responsePanel.enableActions();
responsePanel.enableAllActions(true);
stopStreaming(responseContainer);
}
});
Expand Down Expand Up @@ -119,7 +123,7 @@ public void handleCompleted(String fullMessage, ChatCompletionParameters callPar

ApplicationManager.getApplication().invokeLater(() -> {
try {
responsePanel.enableActions();
responsePanel.enableAllActions(true);
if (!streamResponseReceived && !fullMessage.isEmpty()) {
responseContainer.withResponse(fullMessage);
}
Expand Down Expand Up @@ -156,6 +160,8 @@ private void processBufferedMessages() {
private void stopStreaming(ChatMessageResponseBody responseContainer) {
stopped = true;
textArea.setSubmitEnabled(true);
userMessagePanel.enableAllActions(true);
responsePanel.enableAllActions(true);
responseContainer.hideCaret();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import ee.carlrobert.codegpt.actions.TrackableAction;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseEvent;
import org.jetbrains.annotations.NotNull;
Expand All @@ -19,26 +18,33 @@ public class CopyAction extends TrackableAction {

public CopyAction(@NotNull Editor toolwindowEditor) {
super(
CodeGPTBundle.get("shared.copy"),
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.description"),
CodeGPTBundle.get("shared.copyCode"),
CodeGPTBundle.get("shared.copyToClipboard"),
Actions.Copy,
ActionType.COPY_CODE);
this.toolwindowEditor = toolwindowEditor;
}

@Override
public void handleAction(@NotNull AnActionEvent event) {
StringSelection stringSelection = new StringSelection(toolwindowEditor.getDocument().getText());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
copyToClipboard(toolwindowEditor.getDocument().getText());
showCopyBalloon(event);
}

public static void copyToClipboard(String text) {
Toolkit.getDefaultToolkit()
.getSystemClipboard()
.setContents(new StringSelection(text), null);
}

public static void showCopyBalloon(AnActionEvent event) {
var mouseEvent = (MouseEvent) event.getInputEvent();
if (mouseEvent != null) {
var locationOnScreen = mouseEvent.getLocationOnScreen();
locationOnScreen.y = locationOnScreen.y - 16;

OverlayUtil.showInfoBalloon(
CodeGPTBundle.get("toolwindow.chat.editor.action.copy.success"),
CodeGPTBundle.get("shared.copiedToClipboard"),
locationOnScreen);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.intellij.icons.AllIcons.General;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
Expand Down Expand Up @@ -37,12 +38,15 @@
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
import ee.carlrobert.codegpt.toolwindow.chat.editor.ResponseEditorPanel;
import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.CopyAction;
import ee.carlrobert.codegpt.toolwindow.ui.ResponseBodyProgressPanel;
import ee.carlrobert.codegpt.toolwindow.ui.WebpageList;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.ui.UIUtil;
import ee.carlrobert.codegpt.util.EditorUtil;
import ee.carlrobert.codegpt.util.MarkdownUtil;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.util.Objects;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
Expand Down Expand Up @@ -331,9 +335,16 @@ private void installPopupMenu(JTextPane textPane) {
CodeGPTBundle.get("shared.copy"),
CodeGPTBundle.get("shared.copyToClipboard"),
AllIcons.Actions.Copy) {

@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.EDT;
}

@Override
public void actionPerformed(@NotNull AnActionEvent e) {
public void actionPerformed(@NotNull AnActionEvent event) {
textPane.copy();
CopyAction.showCopyBalloon(event);
}

@Override
Expand Down
Loading

0 comments on commit be9ae08

Please sign in to comment.