From 139bc7128fc4e292caf9b40b7a946ade61273c72 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Wed, 15 Mar 2023 00:06:10 +0000 Subject: [PATCH] Add ability to create/override actions (#20, #22) --- .../carlrobert/codegpt/client/BaseModel.java | 4 +- .../codegpt/ide/PluginStartupActivity.java | 16 ++++ .../codegpt/ide/action/ActionsUtil.java | 51 ++++++++++ .../codegpt/ide/action/AskAction.java | 5 + .../codegpt/ide/action/BaseAction.java | 8 +- .../ide/action/CustomPromptAction.java | 5 + .../codegpt/ide/action/ExplainAction.java | 11 --- .../codegpt/ide/action/FindBugsAction.java | 11 --- .../codegpt/ide/action/OptimizeAction.java | 11 --- .../codegpt/ide/action/RefactorAction.java | 11 --- .../codegpt/ide/action/WriteTestsAction.java | 11 --- .../ide/settings/SettingsComponent.java | 4 +- .../codegpt/ide/settings/SettingsState.java | 2 +- .../configuration/ConfigurationComponent.java | 95 +++++++++++++++++++ .../ConfigurationConfigurable.java | 50 ++++++++++ .../configuration/ConfigurationState.java | 35 +++++++ src/main/resources/META-INF/plugin.xml | 13 +-- .../messages/BasicActionsBundle.properties | 8 -- 18 files changed, 272 insertions(+), 79 deletions(-) create mode 100644 src/main/java/ee/carlrobert/codegpt/ide/PluginStartupActivity.java create mode 100644 src/main/java/ee/carlrobert/codegpt/ide/action/ActionsUtil.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/ide/action/ExplainAction.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/ide/action/FindBugsAction.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/ide/action/OptimizeAction.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/ide/action/RefactorAction.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/ide/action/WriteTestsAction.java create mode 100644 src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationComponent.java create mode 100644 src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationConfigurable.java create mode 100644 src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationState.java diff --git a/src/main/java/ee/carlrobert/codegpt/client/BaseModel.java b/src/main/java/ee/carlrobert/codegpt/client/BaseModel.java index 1c0cb482..c64c63c9 100644 --- a/src/main/java/ee/carlrobert/codegpt/client/BaseModel.java +++ b/src/main/java/ee/carlrobert/codegpt/client/BaseModel.java @@ -6,8 +6,8 @@ public enum BaseModel { BABBAGE("text-babbage-001", "Babbage - Powerful"), CURIE("text-curie-001", "Curie - Fast and efficient"), DAVINCI("text-davinci-003", "Davinci - Most powerful (Default)"), - CHATGPT("gpt-3.5-turbo", "ChatGPT - Most recent and capable model (Default)"), - CHATGPT_SNAPSHOT("gpt-3.5-turbo-0301", "ChatGPT - Snapshot of gpt-3.5-turbo from March 1st 2023"), + CHATGPT_3_5("gpt-3.5-turbo", "ChatGPT - Most recent and capable model (Default)"), + CHATGPT_3_5_SNAPSHOT("gpt-3.5-turbo-0301", "ChatGPT - Snapshot of gpt-3.5-turbo from March 1st 2023"), UNOFFICIAL_CHATGPT("text-davinci-002-render-sha", "Unofficial ChatGPT"); private final String code; diff --git a/src/main/java/ee/carlrobert/codegpt/ide/PluginStartupActivity.java b/src/main/java/ee/carlrobert/codegpt/ide/PluginStartupActivity.java new file mode 100644 index 00000000..ae1e9d8e --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/ide/PluginStartupActivity.java @@ -0,0 +1,16 @@ +package ee.carlrobert.codegpt.ide; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupActivity; +import ee.carlrobert.codegpt.ide.action.ActionsUtil; +import ee.carlrobert.codegpt.ide.settings.configuration.ConfigurationState; +import org.jetbrains.annotations.NotNull; + +public class PluginStartupActivity implements StartupActivity { + + @Override + public void runActivity(@NotNull Project project) { + ConfigurationState cfgState = ConfigurationState.getInstance(); + ActionsUtil.refreshActions(cfgState.tableData); + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/ActionsUtil.java b/src/main/java/ee/carlrobert/codegpt/ide/action/ActionsUtil.java new file mode 100644 index 00000000..0ad6abff --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/ide/action/ActionsUtil.java @@ -0,0 +1,51 @@ +package ee.carlrobert.codegpt.ide.action; + +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ActionsUtil { + + public static Map DEFAULT_ACTIONS = new LinkedHashMap<>(Map.of( + "Find Bugs", "Find bugs in the following code: {{selectedCode}}", + "Write Tests", "Write Tests for the following code: {{selectedCode}}", + "Explain", "Explain the following code: {{selectedCode}}", + "Refactor", "Refactor the following code: {{selectedCode}}", + "Optimize", "Optimize the following code: {{selectedCode}}")); + + public static String[][] DEFAULT_ACTIONS_ARRAY = toArray(DEFAULT_ACTIONS); + + public static String[][] toArray(Map actionsMap) { + return actionsMap.entrySet() + .stream() + .map((entry) -> new String[] {entry.getKey(), entry.getValue()}) + .collect(toList()) + .toArray(new String[0][0]); + } + + public static void refreshActions(Map tableData) { + ActionManager actionManager = ActionManager.getInstance(); + AnAction existingActionGroup = actionManager.getAction("ActionGroup"); + if (existingActionGroup instanceof DefaultActionGroup) { + DefaultActionGroup group = (DefaultActionGroup) existingActionGroup; + group.removeAll(); + group.add(new AskAction()); + group.addSeparator(); + group.add(new CustomPromptAction()); + group.addSeparator(); + tableData.forEach((action, prompt) -> group.add(new BaseAction(action) { + @Override + protected void actionPerformed(Project project, Editor editor, String selectedText) { + sendMessage(project, prompt.replace("{{selectedCode}}", format("\n\n%s", selectedText))); + } + })); + } + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/AskAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/AskAction.java index 2f3411c9..c9db7e34 100644 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/AskAction.java +++ b/src/main/java/ee/carlrobert/codegpt/ide/action/AskAction.java @@ -1,5 +1,6 @@ package ee.carlrobert.codegpt.ide.action; +import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import ee.carlrobert.codegpt.ide.conversations.ConversationsState; @@ -9,6 +10,10 @@ import org.jetbrains.annotations.NotNull; public class AskAction extends AnAction { + public AskAction() { + super("Ask ChatGPT", "Ask ChatGPT description", AllIcons.Actions.Find); + } + @Override public void update(@NotNull AnActionEvent event) { event.getPresentation().setEnabled(event.getProject() != null); diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/BaseAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/BaseAction.java index 34736a05..abe0a7e4 100644 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/BaseAction.java +++ b/src/main/java/ee/carlrobert/codegpt/ide/action/BaseAction.java @@ -9,13 +9,17 @@ import com.intellij.openapi.util.NlsActions; import ee.carlrobert.codegpt.ide.conversations.ConversationsState; import ee.carlrobert.codegpt.ide.toolwindow.ContentManagerService; import ee.carlrobert.codegpt.ide.toolwindow.ToolWindowService; +import javax.swing.Icon; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public abstract class BaseAction extends AnAction { - public BaseAction() { - super(); + public BaseAction( + @Nullable @NlsActions.ActionText String text, + @Nullable @NlsActions.ActionDescription String description, + @Nullable Icon icon) { + super(text, description, icon); } public BaseAction(@Nullable @NlsActions.ActionText String text) { diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/CustomPromptAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/CustomPromptAction.java index a4028ca0..9f1f869c 100644 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/CustomPromptAction.java +++ b/src/main/java/ee/carlrobert/codegpt/ide/action/CustomPromptAction.java @@ -1,5 +1,6 @@ package ee.carlrobert.codegpt.ide.action; +import com.intellij.icons.AllIcons; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; @@ -9,6 +10,10 @@ import javax.swing.SwingUtilities; public class CustomPromptAction extends BaseAction { + public CustomPromptAction() { + super("Custom Prompt", "Custom prompt description", AllIcons.Actions.Run_anything); + } + private static String previousUserPrompt = ""; protected void actionPerformed(Project project, Editor editor, String selectedText) { diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/ExplainAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/ExplainAction.java deleted file mode 100644 index 935a6dc2..00000000 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/ExplainAction.java +++ /dev/null @@ -1,11 +0,0 @@ -package ee.carlrobert.codegpt.ide.action; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; - -public class ExplainAction extends BaseAction { - - protected void actionPerformed(Project project, Editor editor, String selectedText) { - sendMessage(project, "Explain the following code:\n\n" + selectedText); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/FindBugsAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/FindBugsAction.java deleted file mode 100644 index 3ace5b93..00000000 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/FindBugsAction.java +++ /dev/null @@ -1,11 +0,0 @@ -package ee.carlrobert.codegpt.ide.action; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; - -public class FindBugsAction extends BaseAction { - - protected void actionPerformed(Project project, Editor editor, String selectedText) { - sendMessage(project, "Find bugs in the following code:\n\n" + selectedText); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/OptimizeAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/OptimizeAction.java deleted file mode 100644 index f96abdb0..00000000 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/OptimizeAction.java +++ /dev/null @@ -1,11 +0,0 @@ -package ee.carlrobert.codegpt.ide.action; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; - -public class OptimizeAction extends BaseAction { - - protected void actionPerformed(Project project, Editor editor, String selectedText) { - sendMessage(project, "Optimize the following code:\n\n" + selectedText); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/RefactorAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/RefactorAction.java deleted file mode 100644 index 6b7201b2..00000000 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/RefactorAction.java +++ /dev/null @@ -1,11 +0,0 @@ -package ee.carlrobert.codegpt.ide.action; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; - -public class RefactorAction extends BaseAction { - - protected void actionPerformed(Project project, Editor editor, String selectedText) { - sendMessage(project, "Refactor the following code:\n\n" + selectedText); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/action/WriteTestsAction.java b/src/main/java/ee/carlrobert/codegpt/ide/action/WriteTestsAction.java deleted file mode 100644 index 55c2d06d..00000000 --- a/src/main/java/ee/carlrobert/codegpt/ide/action/WriteTestsAction.java +++ /dev/null @@ -1,11 +0,0 @@ -package ee.carlrobert.codegpt.ide.action; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; - -public class WriteTestsAction extends BaseAction { - - protected void actionPerformed(Project project, Editor editor, String selectedText) { - sendMessage(project, "Generate unit tests for the following code:\n\n" + selectedText); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsComponent.java b/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsComponent.java index ec8129fe..f3f68e54 100644 --- a/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsComponent.java @@ -48,8 +48,8 @@ public class SettingsComponent { apiKeyField = new JBTextField(settings.apiKey, 1); chatCompletionBaseModelComboBox = new BaseModelComboBox( new BaseModel[] { - BaseModel.CHATGPT, - BaseModel.CHATGPT_SNAPSHOT, + BaseModel.CHATGPT_3_5, + BaseModel.CHATGPT_3_5_SNAPSHOT, }, settings.textCompletionBaseModel); textCompletionBaseModelComboBox = new BaseModelComboBox( diff --git a/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsState.java b/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsState.java index b155af76..d2ca5e5b 100644 --- a/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/ide/settings/SettingsState.java @@ -20,7 +20,7 @@ public class SettingsState implements PersistentStateComponent { public String accessToken = ""; public String reverseProxyUrl = ""; public BaseModel textCompletionBaseModel = BaseModel.DAVINCI; - public BaseModel chatCompletionBaseModel = BaseModel.CHATGPT; + public BaseModel chatCompletionBaseModel = BaseModel.CHATGPT_3_5; public boolean isGPTOptionSelected = true; public boolean isChatGPTOptionSelected; public boolean isChatCompletionOptionSelected = true; diff --git a/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationComponent.java b/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationComponent.java new file mode 100644 index 00000000..f59392c7 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationComponent.java @@ -0,0 +1,95 @@ +package ee.carlrobert.codegpt.ide.settings.configuration; + +import static ee.carlrobert.codegpt.ide.action.ActionsUtil.DEFAULT_ACTIONS_ARRAY; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.TitledSeparator; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.table.JBTable; +import com.intellij.util.ui.FormBuilder; +import ee.carlrobert.codegpt.ide.action.ActionsUtil; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.table.DefaultTableModel; +import org.jetbrains.annotations.NotNull; + +public class ConfigurationComponent { + + private final JPanel mainPanel; + private final JBTable table; + + public ConfigurationComponent(ConfigurationState configuration) { + table = new JBTable(new DefaultTableModel( + ActionsUtil.toArray(configuration.tableData), + new String[] {"Action", "Prompt"})); + table.getColumnModel().getColumn(0).setPreferredWidth(140); + table.getColumnModel().getColumn(0).setMaxWidth(140); + table.getEmptyText().setText("No actions configured"); + + var tablePanel = createTablePanel(); + tablePanel.setBorder(BorderFactory.createTitledBorder("Action prompts")); + + mainPanel = FormBuilder.createFormBuilder() + .addComponent(new TitledSeparator("Configuration Preference")) + .addVerticalGap(8) + .addComponent(tablePanel) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); + } + + public JPanel getPanel() { + return mainPanel; + } + + public Map getTableData() { + var model = getModel(); + Map data = new LinkedHashMap<>(); + for (int count = 0; count < model.getRowCount(); count++) { + data.put( + model.getValueAt(count, 0).toString(), + model.getValueAt(count, 1).toString()); + } + return data; + } + + private JPanel createTablePanel() { + return ToolbarDecorator.createDecorator(table) + .setPreferredSize(new Dimension(table.getPreferredSize().width, 160)) + .setAddAction(anActionButton -> getModel().addRow(new Object[] {"", ""})) + .setRemoveAction(anActionButton -> getModel().removeRow(table.getSelectedRow())) + .disableUpAction() + .disableDownAction() + .addExtraAction(new RevertToDefaultsActionButton()) + .createPanel(); + } + + private DefaultTableModel getModel() { + return (DefaultTableModel) table.getModel(); + } + + public void setTableData(Map tableData) { + var model = getModel(); + model.setNumRows(0); + tableData.forEach((action, prompt) -> model.addRow(new Object[] {action, prompt})); + } + + class RevertToDefaultsActionButton extends AnActionButton { + + RevertToDefaultsActionButton() { + super("Revert to Defaults", AllIcons.Actions.Rollback); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + var model = getModel(); + model.setNumRows(0); + Arrays.stream(DEFAULT_ACTIONS_ARRAY).forEach(model::addRow); + } + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationConfigurable.java b/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationConfigurable.java new file mode 100644 index 00000000..75613f33 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationConfigurable.java @@ -0,0 +1,50 @@ +package ee.carlrobert.codegpt.ide.settings.configuration; + +import com.intellij.openapi.options.Configurable; +import ee.carlrobert.codegpt.ide.action.ActionsUtil; +import javax.swing.JComponent; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.Nullable; + +public class ConfigurationConfigurable implements Configurable { + + private ConfigurationComponent configurationComponent; + + @Nls(capitalization = Nls.Capitalization.Title) + @Override + public String getDisplayName() { + return "CodeGPT: Configuration"; + } + + @Nullable + @Override + public JComponent createComponent() { + var configuration = ConfigurationState.getInstance(); + configurationComponent = new ConfigurationComponent(configuration); + return configurationComponent.getPanel(); + } + + @Override + public boolean isModified() { + var configuration = ConfigurationState.getInstance(); + return !configurationComponent.getTableData().equals(configuration.tableData); + } + + @Override + public void apply() { + var configuration = ConfigurationState.getInstance(); + configuration.tableData = configurationComponent.getTableData(); + ActionsUtil.refreshActions(configuration.tableData); + } + + @Override + public void reset() { + var configuration = ConfigurationState.getInstance(); + configurationComponent.setTableData(configuration.tableData); + } + + @Override + public void disposeUIResources() { + configurationComponent = null; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationState.java b/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationState.java new file mode 100644 index 00000000..329bcbff --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/ide/settings/configuration/ConfigurationState.java @@ -0,0 +1,35 @@ +package ee.carlrobert.codegpt.ide.settings.configuration; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.util.xmlb.XmlSerializerUtil; +import ee.carlrobert.codegpt.ide.action.ActionsUtil; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@State( + name = "ee.carlrobert.codegpt.ide.settings.configuration.ConfigurationState", + storages = @Storage("CodeGPTConfigurationTemp.xml") +) +public class ConfigurationState implements PersistentStateComponent { + + public Map tableData = ActionsUtil.DEFAULT_ACTIONS; + + public static ConfigurationState getInstance() { + return ApplicationManager.getApplication().getService(ConfigurationState.class); + } + + @Nullable + @Override + public ConfigurationState getState() { + return this; + } + + @Override + public void loadState(@NotNull ConfigurationState state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index a66d850c..39099c6c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -107,9 +107,13 @@ + + + @@ -122,15 +126,6 @@ - - - - - - - - - diff --git a/src/main/resources/messages/BasicActionsBundle.properties b/src/main/resources/messages/BasicActionsBundle.properties index 66ca64cf..d5b22156 100644 --- a/src/main/resources/messages/BasicActionsBundle.properties +++ b/src/main/resources/messages/BasicActionsBundle.properties @@ -1,9 +1 @@ group.ActionGroup.text=CodeGPT - -action.AskAction.text=Ask ChatGPT -action.ExplainAction.text=Explain -action.RefactorAction.text=Refactor -action.FindBugsAction.text=Find Bugs -action.OptimizeAction.text=Optimize -action.WriteTestsAction.text=Write Tests -action.CustomPromptAction.text=Custom Prompt