From ce46deeb576191fcf994ba200a81b36f334eb1cb Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Tue, 17 Dec 2024 10:01:59 +0000 Subject: [PATCH] feat: support copying user and response messages (closes #791) --- .../chat/ChatToolWindowTabPanel.java | 124 ++++++++----- .../toolwindow/chat/ResponseNodeRenderer.java | 4 +- ...WindowCompletionResponseEventListener.java | 16 +- .../chat/editor/actions/CopyAction.java | 20 +- .../chat/ui/ChatMessageResponseBody.java | 13 +- .../ui/ChatToolWindowScrollablePanel.java | 49 ++--- .../toolwindow/chat/ui/ResponsePanel.java | 150 --------------- .../toolwindow/chat/ui/UserMessagePanel.java | 127 ------------- .../conversations/ConversationPanel.java | 2 +- .../codegpt/ui/IconActionButton.java | 3 +- .../configuration/ConfigurationSettings.kt | 2 +- .../codegpt/toolwindow/ui/BaseMessagePanel.kt | 113 ++++++++++++ .../ui/ChatToolWindowLandingPanel.kt | 3 +- .../toolwindow/ui/ResponseMessagePanel.kt | 20 ++ .../codegpt/toolwindow/ui/UserMessagePanel.kt | 174 ++++++++++++++++++ .../codegpt/ui/textarea/UserInputPanel.kt | 6 +- .../resources/messages/codegpt.properties | 10 +- 17 files changed, 463 insertions(+), 373 deletions(-) delete mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ResponsePanel.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/BaseMessagePanel.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ResponseMessagePanel.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index 1ee1b392..bdedddaf 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -22,13 +22,14 @@ import ee.carlrobert.codegpt.conversations.Conversation; 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; @@ -174,12 +175,14 @@ public class ChatToolWindowTabPanel implements Disposable { totalTokensPanel.updateReferencedFilesTokens(callParameters.getReferencedFiles()); } - var responsePanel = createResponsePanel(callParameters); - var messagePanel = toolWindowScrollablePanel.addMessage(message.getId()); - messagePanel.add(new UserMessagePanel(project, message, this)); - messagePanel.add(responsePanel); + var userMessagePanel = createUserMessagePanel(message, callParameters); + var responseMessagePanel = createResponseMessagePanel(callParameters); - call(callParameters, responsePanel); + var messagePanel = toolWindowScrollablePanel.addMessage(message.getId()); + messagePanel.add(userMessagePanel); + messagePanel.add(responseMessagePanel); + + call(callParameters, responseMessagePanel, userMessagePanel); }); } @@ -193,29 +196,39 @@ public class ChatToolWindowTabPanel implements Disposable { 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) { @@ -226,7 +239,7 @@ public class ChatToolWindowTabPanel implements Disposable { 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); @@ -253,8 +266,11 @@ public class ChatToolWindowTabPanel implements Disposable { 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(); @@ -264,15 +280,18 @@ public class ChatToolWindowTabPanel implements Disposable { 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); } @@ -337,30 +356,41 @@ public class ChatToolWindowTabPanel implements Disposable { private void displayConversation() { clearWindow(); conversation.getMessages().forEach(message -> { - var response = message.getResponse() == null ? "" : message.getResponse(); - var messageResponseBody = - new ChatMessageResponseBody(project, this).withResponse(response); - - messageResponseBody.hideCaret(); - - var userMessagePanel = new UserMessagePanel(project, message, this); - var imageFilePath = message.getImageFilePath(); - if (imageFilePath != null && !imageFilePath.isEmpty()) { - userMessagePanel.displayImage(imageFilePath); - } - 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)); + messagePanel.add(getUserMessagePanel(message)); + messagePanel.add(getResponseMessagePanel(message)); }); } + 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; + } + + private ResponseMessagePanel getResponseMessagePanel(Message message) { + var response = message.getResponse() == null ? "" : message.getResponse(); + var messageResponseBody = + new ChatMessageResponseBody(project, this).withResponse(response); + + messageResponseBody.hideCaret(); + + var responseMessagePanel = new ResponseMessagePanel(); + responseMessagePanel.addContent(messageResponseBody); + responseMessagePanel.addCopyAction(() -> CopyAction.copyToClipboard(message.getResponse())); + return responseMessagePanel; + } + private JPanel createRootPanel() { var rootPanel = new JPanel(new BorderLayout()); rootPanel.add(createScrollPaneWithSmartScroller(toolWindowScrollablePanel), diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java index 45c2b071..d3c1eaf3 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java @@ -37,9 +37,7 @@ public class ResponseNodeRenderer implements NodeRenderer { } 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(); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java index ad9207e1..bd04e055 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java @@ -13,8 +13,9 @@ import ee.carlrobert.codegpt.conversations.message.Message; 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; @@ -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; @@ -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; @@ -89,7 +93,7 @@ abstract class ToolWindowCompletionResponseEventListener implements } } finally { LOG.error(error.getMessage(), ex); - responsePanel.enableActions(); + responsePanel.enableAllActions(true); stopStreaming(responseContainer); } }); @@ -119,7 +123,7 @@ abstract class ToolWindowCompletionResponseEventListener implements ApplicationManager.getApplication().invokeLater(() -> { try { - responsePanel.enableActions(); + responsePanel.enableAllActions(true); if (!streamResponseReceived && !fullMessage.isEmpty()) { responseContainer.withResponse(fullMessage); } @@ -156,6 +160,8 @@ abstract class ToolWindowCompletionResponseEventListener implements private void stopStreaming(ChatMessageResponseBody responseContainer) { stopped = true; textArea.setSubmitEnabled(true); + userMessagePanel.enableAllActions(true); + responsePanel.enableAllActions(true); responseContainer.hideCaret(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/CopyAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/CopyAction.java index 15618a32..b82059f8 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/CopyAction.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/actions/CopyAction.java @@ -8,7 +8,6 @@ import ee.carlrobert.codegpt.actions.ActionType; 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; @@ -19,8 +18,8 @@ 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; @@ -28,17 +27,24 @@ public class CopyAction extends TrackableAction { @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); } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java index a001c8b7..53065f5a 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java @@ -9,6 +9,7 @@ import com.intellij.icons.AllIcons; 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; @@ -37,12 +38,15 @@ import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable; 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; @@ -331,9 +335,16 @@ public class ChatMessageResponseBody extends JPanel { CodeGPTBundle.get("shared.copy"), CodeGPTBundle.get("shared.copyToClipboard"), AllIcons.Actions.Copy) { + @Override - public void actionPerformed(@NotNull AnActionEvent e) { + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.EDT; + } + + @Override + public void actionPerformed(@NotNull AnActionEvent event) { textPane.copy(); + CopyAction.showCopyBalloon(event); } @Override diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java index 81108fdd..ca017608 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java @@ -12,6 +12,7 @@ import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; import ee.carlrobert.codegpt.settings.GeneralSettings; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceConfigurable; +import ee.carlrobert.codegpt.toolwindow.ui.ResponseMessagePanel; import ee.carlrobert.codegpt.ui.UIUtil; import ee.carlrobert.codegpt.util.ApplicationUtil; import java.util.Arrays; @@ -36,35 +37,35 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel { if (GeneralSettings.isSelected(ServiceType.CODEGPT) && !CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.CODEGPT_API_KEY)) { - var panel = new ResponsePanel() - .addContent(UIUtil.createTextPane(""" - -

- It looks like you haven't configured your API key yet. Visit CodeGPT settings to do so. -

-

- Don't have an account? Sign up to get the most out of CodeGPT. -

- """, - false, - event -> { - if (ACTIVATED.equals(event.getEventType()) - && "#OPEN_SETTINGS".equals(event.getDescription())) { - ShowSettingsUtil.getInstance().showSettingsDialog( - ApplicationUtil.findCurrentProject(), - CodeGPTServiceConfigurable.class); - } else { - UIUtil.handleHyperlinkClicked(event); - } - })); + var panel = new ResponseMessagePanel(); + panel.addContent(UIUtil.createTextPane(""" + +

+ It looks like you haven't configured your API key yet. Visit CodeGPT settings to do so. +

+

+ Don't have an account? Sign up to get the most out of CodeGPT. +

+ """, + false, + event -> { + if (ACTIVATED.equals(event.getEventType()) + && "#OPEN_SETTINGS".equals(event.getDescription())) { + ShowSettingsUtil.getInstance().showSettingsDialog( + ApplicationUtil.findCurrentProject(), + CodeGPTServiceConfigurable.class); + } else { + UIUtil.handleHyperlinkClicked(event); + } + })); panel.setBorder(JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0)); add(panel); } } - public ResponsePanel getMessageResponsePanel(UUID messageId) { - return (ResponsePanel) Arrays.stream(visibleMessagePanels.get(messageId).getComponents()) - .filter(ResponsePanel.class::isInstance) + public ResponseMessagePanel getResponseMessagePanel(UUID messageId) { + return (ResponseMessagePanel) Arrays.stream(visibleMessagePanels.get(messageId).getComponents()) + .filter(ResponseMessagePanel.class::isInstance) .findFirst().orElseThrow(); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ResponsePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ResponsePanel.java deleted file mode 100644 index 235ec7d8..00000000 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ResponsePanel.java +++ /dev/null @@ -1,150 +0,0 @@ -package ee.carlrobert.codegpt.toolwindow.chat.ui; - -import com.intellij.icons.AllIcons.Actions; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.ui.components.JBLabel; -import com.intellij.util.ui.JBFont; -import com.intellij.util.ui.JBUI; -import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.Icons; -import ee.carlrobert.codegpt.ui.IconActionButton; -import java.awt.BorderLayout; -import java.awt.FlowLayout; -import javax.swing.Box; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.SwingConstants; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ResponsePanel extends JPanel { - - private final Header header; - private final Body body; - - public ResponsePanel() { - super(new BorderLayout()); - header = new Header(); - body = new Body(); - add(header, BorderLayout.NORTH); - add(body, BorderLayout.CENTER); - } - - public void enableActions() { - header.enableActions(true); - } - - public ResponsePanel withReloadAction(Runnable onReload) { - header.addReloadAction(onReload); - return this; - } - - public ResponsePanel withDeleteAction(Runnable onDelete) { - header.addDeleteAction(onDelete); - return this; - } - - public ResponsePanel addContent(JComponent content) { - body.addContent(content); - return this; - } - - public void updateContent(JComponent content) { - body.updateContent(content); - } - - public JComponent getContent() { - return body.getContent(); - } - - static class Header extends JPanel { - - private final JPanel iconsWrapper; - - Header() { - super(new BorderLayout()); - setBorder(JBUI.Borders.empty(12, 8, 4, 8)); - add(getIconLabel(), BorderLayout.LINE_START); - - iconsWrapper = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0)); - iconsWrapper.setBackground(getBackground()); - add(iconsWrapper, BorderLayout.LINE_END); - } - - public void enableActions(boolean enabled) { - for (var iconButton : iconsWrapper.getComponents()) { - iconButton.setEnabled(enabled); - } - } - - public void addReloadAction(Runnable onReload) { - addIconActionButton(new IconActionButton( - new AnAction( - CodeGPTBundle.get("toolwindow.chat.response.action.reloadResponse.text"), - CodeGPTBundle.get("toolwindow.chat.response.action.reloadResponse.description"), - Actions.Refresh) { - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - enableActions(false); - onReload.run(); - } - })); - } - - public void addDeleteAction(Runnable onDelete) { - addIconActionButton(new IconActionButton( - new AnAction( - CodeGPTBundle.get("toolwindow.chat.response.action.deleteResponse.text"), - CodeGPTBundle.get("toolwindow.chat.response.action.deleteResponse.description"), - Actions.GC) { - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - onDelete.run(); - } - })); - } - - private void addIconActionButton(IconActionButton iconActionButton) { - if (iconsWrapper.getComponents() != null && iconsWrapper.getComponents().length > 0) { - iconsWrapper.add(Box.createHorizontalStrut(8)); - } - iconsWrapper.add(iconActionButton); - } - - private JBLabel getIconLabel() { - return new JBLabel( - CodeGPTBundle.get("project.label"), - Icons.Default, - SwingConstants.LEADING) - .setAllowAutoWrapping(true) - .withFont(JBFont.label().asBold()); - } - } - - static class Body extends JPanel { - - private @Nullable JComponent content; - - Body() { - super(new BorderLayout()); - setBorder(JBUI.Borders.empty(4, 8)); - } - - public void addContent(JComponent content) { - this.content = content; - add(content); - } - - public void updateContent(JComponent content) { - removeAll(); - revalidate(); - repaint(); - addContent(content); - } - - public @Nullable JComponent getContent() { - return content; - } - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java deleted file mode 100644 index 1e019f8a..00000000 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java +++ /dev/null @@ -1,127 +0,0 @@ -package ee.carlrobert.codegpt.toolwindow.chat.ui; - -import com.intellij.icons.AllIcons.General; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.project.Project; -import com.intellij.ui.ColorUtil; -import com.intellij.ui.JBColor; -import com.intellij.ui.components.JBLabel; -import com.intellij.util.ui.JBFont; -import com.intellij.util.ui.JBUI; -import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.CodeGPTKeys; -import ee.carlrobert.codegpt.Icons; -import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.events.WebSearchEventDetails; -import ee.carlrobert.codegpt.settings.GeneralSettings; -import ee.carlrobert.codegpt.toolwindow.ui.WebpageList; -import java.awt.BorderLayout; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.UUID; -import javax.swing.DefaultListModel; -import javax.swing.JPanel; -import javax.swing.SwingConstants; -import org.jetbrains.annotations.Nullable; - -public class UserMessagePanel extends JPanel { - - public UserMessagePanel(Project project, Message message, Disposable parentDisposable) { - super(new BorderLayout()); - var headerPanel = new JPanel(new BorderLayout()); - headerPanel.setOpaque(false); - headerPanel.add(createDisplayNameLabel(), BorderLayout.LINE_START); - setBorder(JBUI.Borders.compound( - JBUI.Borders.customLine(JBColor.border(), 1, 0, 1, 0), - JBUI.Borders.empty(12, 8, 8, 8))); - setBackground(ColorUtil.brighter(getBackground(), 2)); - add(headerPanel, BorderLayout.NORTH); - - var additionalContextPanel = getAdditionalContextPanel(project, message); - if (additionalContextPanel != null) { - add(additionalContextPanel, BorderLayout.CENTER); - } - - if (message.getImageFilePath() != null && !message.getImageFilePath().isEmpty()) { - displayImage(message.getImageFilePath()); - } - - add(createResponseBody( - project, - message.getPrompt(), - parentDisposable), BorderLayout.SOUTH); - } - - public @Nullable JPanel getAdditionalContextPanel(Project project, Message message) { - var addedDocumentation = CodeGPTKeys.ADDED_DOCUMENTATION.get(project); - var referencedFilePaths = message.getReferencedFilePaths(); - if (addedDocumentation == null - && (referencedFilePaths == null - || referencedFilePaths.isEmpty())) { - return null; - } - - var panel = new JPanel(new BorderLayout()); - panel.setOpaque(false); - if (addedDocumentation != null) { - var listModel = new DefaultListModel(); - listModel.addElement( - new WebSearchEventDetails(UUID.randomUUID(), addedDocumentation.getName(), - addedDocumentation.getUrl(), addedDocumentation.getUrl())); - panel.add(createWebpageListPanel(new WebpageList(listModel)), BorderLayout.NORTH); - } - - if (referencedFilePaths != null && !referencedFilePaths.isEmpty()) { - panel.add(new SelectedFilesAccordion(project, referencedFilePaths), BorderLayout.NORTH); - } - return panel; - } - - public void displayImage(String imageFilePath) { - try { - var path = Paths.get(imageFilePath); - add(new ImageAccordion(path.getFileName().toString(), Files.readAllBytes(path))); - } catch (IOException e) { - add(new JBLabel( - "Unable to load image %s".formatted(imageFilePath), - General.Error, - SwingConstants.LEFT)); - } - } - - private ChatMessageResponseBody createResponseBody( - Project project, - String prompt, - Disposable parentDisposable) { - return new ChatMessageResponseBody(project, false, true, false, false, parentDisposable) - .withResponse(prompt); - } - - private JBLabel createDisplayNameLabel() { - return new JBLabel( - GeneralSettings.getCurrentState().getDisplayName(), - Icons.User, - SwingConstants.LEADING) - .setAllowAutoWrapping(true) - .withFont(JBFont.label().asBold()) - .withBorder(JBUI.Borders.emptyBottom(6)); - } - - private static JPanel createWebpageListPanel(WebpageList webpageList) { - var title = new JPanel(new BorderLayout()); - title.setOpaque(false); - title.setBorder(JBUI.Borders.empty(8, 0)); - title.add(new JBLabel(CodeGPTBundle.get("userMessagePanel.documentation.title")) - .withFont(JBUI.Fonts.miniFont()), BorderLayout.LINE_START); - var listPanel = new JPanel(new BorderLayout()); - listPanel.setOpaque(false); - listPanel.add(webpageList, BorderLayout.LINE_START); - - var panel = new JPanel(new BorderLayout()); - panel.setOpaque(false); - panel.add(title, BorderLayout.NORTH); - panel.add(listPanel, BorderLayout.CENTER); - return panel; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java index d3386062..3aa18978 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java @@ -87,7 +87,7 @@ class ConversationPanel extends JPanel { gbc.gridx = 1; gbc.weightx = 0; - headerPanel.add(new IconActionButton(new DeleteConversationAction(onDelete)), gbc); + headerPanel.add(new IconActionButton(new DeleteConversationAction(onDelete), "DELETE"), gbc); var bottomPanel = new JPanel(new BorderLayout()); bottomPanel.add(new JLabel(conversation.getUpdatedOn() diff --git a/src/main/java/ee/carlrobert/codegpt/ui/IconActionButton.java b/src/main/java/ee/carlrobert/codegpt/ui/IconActionButton.java index bb093e10..d5067ad0 100644 --- a/src/main/java/ee/carlrobert/codegpt/ui/IconActionButton.java +++ b/src/main/java/ee/carlrobert/codegpt/ui/IconActionButton.java @@ -8,11 +8,12 @@ import com.intellij.openapi.actionSystem.impl.ActionButton; public class IconActionButton extends ActionButton { - public IconActionButton(AnAction action) { + public IconActionButton(AnAction action, String actionCode) { super(action, getPresentation(action), ActionPlaces.TOOLWINDOW_CONTENT, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE); + putClientProperty("actionCode", actionCode); } private static Presentation getPresentation(AnAction action) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/configuration/ConfigurationSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/configuration/ConfigurationSettings.kt index 7c8ea4ea..0d8692c5 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/configuration/ConfigurationSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/configuration/ConfigurationSettings.kt @@ -26,7 +26,7 @@ class ConfigurationSettingsState : BaseState() { var maxTokens by property(2048) var temperature by property(0.1f) { max(0f, min(1f, it)) } var checkForPluginUpdates by property(true) - var checkForNewScreenshots by property(false) + var checkForNewScreenshots by property(true) var ignoreGitCommitTokenLimit by property(false) var methodNameGenerationEnabled by property(true) var captureCompileErrors by property(true) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/BaseMessagePanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/BaseMessagePanel.kt new file mode 100644 index 00000000..98237c95 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/BaseMessagePanel.kt @@ -0,0 +1,113 @@ +package ee.carlrobert.codegpt.toolwindow.ui + +import com.intellij.icons.AllIcons.Actions +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.impl.ActionButton +import com.intellij.ui.components.JBLabel +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.components.BorderLayoutPanel +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.CopyAction +import ee.carlrobert.codegpt.ui.IconActionButton +import java.awt.FlowLayout +import javax.swing.Box +import javax.swing.JComponent +import javax.swing.JPanel + +abstract class BaseMessagePanel : BorderLayoutPanel() { + + private val headerPanel = BorderLayoutPanel() + private val iconsWrapper = JPanel(FlowLayout(FlowLayout.RIGHT, 0, 0)) + protected val body: Body = Body() + + init { + setupUI() + } + + protected abstract fun createDisplayNameLabel(): JBLabel + + protected fun addIconActionButton(iconActionButton: IconActionButton) { + if (iconsWrapper.components.isNotEmpty()) { + iconsWrapper.add(Box.createHorizontalStrut(8)) + } + iconsWrapper.add(iconActionButton) + } + + fun enableAllActions(enabled: Boolean) { + iconsWrapper.components.forEach { it.isEnabled = enabled } + } + + fun disableActions(actionCodes: List) { + iconsWrapper.components + .filterIsInstance() + .filter { actionCodes.contains(it.getClientProperty("actionCode")) } + .forEach { it.isEnabled = false } + } + + fun addCopyAction(onCopy: Runnable) { + addIconActionButton( + IconActionButton( + object : AnAction( + CodeGPTBundle.get("shared.copyMessageContents"), + CodeGPTBundle.get("shared.copyToClipboard"), + Actions.Copy + ) { + override fun actionPerformed(event: AnActionEvent) { + onCopy.run() + CopyAction.showCopyBalloon(event) + } + }, + "COPY" + ) + ) + } + + fun addContent(content: JComponent) { + body.addContent(content) + } + + fun updateContent(content: JComponent) { + body.updateContent(content) + } + + fun getContent(): JComponent = body.content + + private fun setupUI() { + headerPanel.apply { + isOpaque = false + border = JBUI.Borders.empty(12, 8, 4, 8) + + this.addToLeft(createDisplayNameLabel()) + this.addToRight(iconsWrapper) + } + iconsWrapper.apply { + isOpaque = false + } + + addToTop(headerPanel) + addToCenter(body) + } + + protected class Body : BorderLayoutPanel() { + var content: JComponent = BorderLayoutPanel() + private set + + init { + border = JBUI.Borders.empty(4, 8, 8, 8) + isOpaque = false + } + + fun addContent(component: JComponent) { + content = component + addToCenter(component) + } + + fun updateContent(component: JComponent) { + removeAll() + revalidate() + repaint() + addContent(component) + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ChatToolWindowLandingPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ChatToolWindowLandingPanel.kt index 1c2b2731..8d9285ea 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ChatToolWindowLandingPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ChatToolWindowLandingPanel.kt @@ -5,7 +5,6 @@ import com.intellij.util.ui.JBUI import ee.carlrobert.codegpt.Icons import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.prompts.ChatActionsState -import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel import ee.carlrobert.codegpt.ui.UIUtil.createTextPane import java.awt.BorderLayout import java.awt.Point @@ -14,7 +13,7 @@ import javax.swing.Box import javax.swing.BoxLayout import javax.swing.JPanel -class ChatToolWindowLandingPanel(onAction: (LandingPanelAction, Point) -> Unit) : ResponsePanel() { +class ChatToolWindowLandingPanel(onAction: (LandingPanelAction, Point) -> Unit) : ResponseMessagePanel() { init { addContent(createContent(onAction)) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ResponseMessagePanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ResponseMessagePanel.kt new file mode 100644 index 00000000..69a969b9 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/ResponseMessagePanel.kt @@ -0,0 +1,20 @@ +package ee.carlrobert.codegpt.toolwindow.ui + +import com.intellij.ui.components.JBLabel +import com.intellij.util.ui.JBFont +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.Icons +import javax.swing.SwingConstants + +open class ResponseMessagePanel : BaseMessagePanel() { + + override fun createDisplayNameLabel(): JBLabel { + return JBLabel( + CodeGPTBundle.get("project.label"), + Icons.Default, + SwingConstants.LEADING + ) + .setAllowAutoWrapping(true) + .withFont(JBFont.label().asBold()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt new file mode 100644 index 00000000..57c22b47 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt @@ -0,0 +1,174 @@ +package ee.carlrobert.codegpt.toolwindow.ui + +import com.intellij.icons.AllIcons +import com.intellij.icons.AllIcons.Actions +import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.Project +import com.intellij.ui.ColorUtil +import com.intellij.ui.JBColor +import com.intellij.ui.components.JBLabel +import com.intellij.util.ui.JBFont +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.components.BorderLayoutPanel +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.CodeGPTKeys +import ee.carlrobert.codegpt.Icons +import ee.carlrobert.codegpt.conversations.message.Message +import ee.carlrobert.codegpt.events.WebSearchEventDetails +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody +import ee.carlrobert.codegpt.toolwindow.chat.ui.ImageAccordion +import ee.carlrobert.codegpt.toolwindow.chat.ui.SelectedFilesAccordion +import ee.carlrobert.codegpt.ui.IconActionButton +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* +import javax.swing.DefaultListModel +import javax.swing.JPanel +import javax.swing.SwingConstants + + +class UserMessagePanel( + private val project: Project, + private val message: Message, + private val parentDisposable: Disposable +) : BaseMessagePanel() { + + init { + border = JBUI.Borders.customLine(JBColor.border(), 1, 0, 1, 0) + background = ColorUtil.brighter(getBackground(), 2) + + setupAdditionalContext() + setupImageIfPresent() + setupResponseBody() + } + + override fun createDisplayNameLabel(): JBLabel { + return JBLabel( + GeneralSettings.getCurrentState().displayName, + Icons.User, + SwingConstants.LEADING + ) + .setAllowAutoWrapping(true) + .withFont(JBFont.label().asBold()) + } + + fun addReloadAction(onReload: Runnable) { + addIconActionButton( + IconActionButton( + object : AnAction( + CodeGPTBundle.get("shared.reload"), + CodeGPTBundle.get("shared.reloadDescription"), + Actions.Refresh + ) { + override fun actionPerformed(e: AnActionEvent) { + onReload.run() + } + }, + "RELOAD" + ) + ) + } + + fun addDeleteAction(onDelete: Runnable) { + addIconActionButton( + IconActionButton( + object : AnAction( + CodeGPTBundle.get("shared.delete"), + CodeGPTBundle.get("shared.deleteDescription"), + Actions.GC + ) { + override fun actionPerformed(e: AnActionEvent) { + onDelete.run() + } + }, + "DELETE" + ) + ) + } + + fun displayImage(imageFilePath: String) { + try { + val path = Paths.get(imageFilePath) + body.addToTop(ImageAccordion(path.fileName.toString(), Files.readAllBytes(path))) + } catch (e: IOException) { + body.addToTop( + JBLabel( + "Unable to load image $imageFilePath", + AllIcons.General.Error, + SwingConstants.LEFT + ) + ) + } + } + + private fun setupAdditionalContext() { + val additionalContextPanel = getAdditionalContextPanel(project, message) + if (additionalContextPanel != null) { + body.addToTop(additionalContextPanel) + } + } + + private fun setupImageIfPresent() { + message.imageFilePath?.let { imageFilePath -> + if (imageFilePath.isNotEmpty()) { + displayImage(imageFilePath) + } + } + } + + private fun setupResponseBody() { + addContent( + ChatMessageResponseBody(project, false, true, false, false, parentDisposable) + .withResponse(message.prompt) + ) + } + + private fun getAdditionalContextPanel(project: Project?, message: Message): JPanel? { + val addedDocumentation = CodeGPTKeys.ADDED_DOCUMENTATION[project] + val referencedFilePaths = message.referencedFilePaths ?: emptyList() + if (addedDocumentation == null && referencedFilePaths.isEmpty()) { + return null + } + + return BorderLayoutPanel().apply { + isOpaque = false + + if (addedDocumentation != null) { + val listModel = DefaultListModel() + listModel.addElement( + WebSearchEventDetails( + UUID.randomUUID(), addedDocumentation.name, + addedDocumentation.url, addedDocumentation.url + ) + ) + addToTop(createWebpageListPanel(WebpageList(listModel))) + } + + if (referencedFilePaths.isNotEmpty()) { + addToTop(SelectedFilesAccordion(project!!, referencedFilePaths)) + } + } + } + + private fun createWebpageListPanel(webpageList: WebpageList): JPanel { + return BorderLayoutPanel().apply { + isOpaque = false + addToTop(BorderLayoutPanel().apply { + isOpaque = false + border = JBUI.Borders.empty(8, 0) + addToLeft( + JBLabel(CodeGPTBundle.get("userMessagePanel.documentation.title")) + .withFont(JBUI.Fonts.miniFont()) + ) + }) + addToCenter(BorderLayoutPanel().apply { + isOpaque = false + addToLeft(webpageList) + }) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt index 9a598e43..2897bef0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt @@ -64,7 +64,8 @@ class UserInputPanel( handleSubmit(promptTextField.text) promptTextField.clear() } - } + }, + "SUBMIT" ) private val stopButton = IconActionButton( object : AnAction( @@ -75,7 +76,8 @@ class UserInputPanel( override fun actionPerformed(e: AnActionEvent) { onStop() } - } + }, + "STOP" ).apply { isEnabled = false } private val imageActionSupported = AtomicBooleanProperty(isImageActionSupported()) diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 12e8e5d8..08d032b4 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -172,9 +172,8 @@ dialog.continue=Continue editor.diff.title=CodeGPT Diff editor.diff.local.content.title=CodeGPT suggested code toolwindow.chat.editor.action.copy.description=Copy generated code -toolwindow.chat.editor.action.copy.success=Code copied! toolwindow.chat.editor.action.autoApply.title=Auto Apply -toolwindow.chat.editor.action.autoApply.disabledTitle=This action is only available with CodeGPT provider +toolwindow.chat.editor.action.autoApply.disabledTitle=Auto apply is only available with CodeGPT provider toolwindow.chat.editor.action.autoApply.description=Apply suggested changes automatically toolwindow.chat.editor.action.autoApply.noActiveFile=Active file not found toolwindow.chat.editor.action.autoApply.fileTooLarge=Active file too large to process @@ -234,8 +233,15 @@ shared.escToCancel=Esc to cancel shared.cancel=Cancel shared.confirm=Confirm shared.copy=Copy +shared.copyCode=Copy Code +shared.copyMessageContents=Copy Message Contents shared.copyToClipboard=Copy to clipboard +shared.copiedToClipboard=Copied to clipboard shared.configuration=Configuration +shared.delete=Delete Message +shared.deleteDescription=Delete message +shared.reload=Reload Message +shared.reloadDescription=Reload message shared.port=Port: shared.discard=Discard shared.notification.doNotShowAgain=Do not show again