Add ability to create/override actions (#20, #22)

This commit is contained in:
Carl-Robert Linnupuu 2023-03-15 00:06:10 +00:00
parent 4418c6dd46
commit 139bc7128f
18 changed files with 272 additions and 79 deletions

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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<String, String> 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<String, String> 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<String, String> 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)));
}
}));
}
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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(

View file

@ -20,7 +20,7 @@ public class SettingsState implements PersistentStateComponent<SettingsState> {
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;

View file

@ -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<String, String> getTableData() {
var model = getModel();
Map<String, String> 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<String, String> 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);
}
}
}

View file

@ -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;
}
}

View file

@ -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<ConfigurationState> {
public Map<String, String> 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);
}
}