feat: consolidate all prompts into a single settings view (#775)

* feat: consolidate all prompts into a single configurable

* feat: implement prompts settings view

* feat: use prompts from PromptsSettings state

* feat: use startInNewWindow settings value

* fix: landing view action placeholder

* feat: update default chat prompts
This commit is contained in:
Carl-Robert Linnupuu 2024-11-22 10:02:13 +00:00 committed by GitHub
parent fdc4134aec
commit 89344346c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1439 additions and 3705 deletions

View file

@ -1,7 +1,7 @@
package ee.carlrobert.codegpt;
import com.intellij.openapi.util.Key;
import ee.carlrobert.codegpt.settings.persona.PersonaDetails;
import ee.carlrobert.codegpt.settings.prompts.PersonaDetails;
import ee.carlrobert.codegpt.ui.DocumentationDetails;
import ee.carlrobert.llm.client.codegpt.CodeGPTUserDetails;
import java.util.List;

View file

@ -20,7 +20,7 @@ import ee.carlrobert.codegpt.EncodingManager;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.completions.CommitMessageCompletionParameters;
import ee.carlrobert.codegpt.completions.CompletionRequestService;
import ee.carlrobert.codegpt.settings.configuration.CommitMessageTemplate;
import ee.carlrobert.codegpt.settings.prompts.CommitMessageTemplate;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.util.CommitWorkflowChanges;
import ee.carlrobert.codegpt.util.GitUtil;

View file

@ -5,6 +5,7 @@ import static java.lang.String.format;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
@ -12,6 +13,7 @@ import ee.carlrobert.codegpt.CodeGPTKeys;
import ee.carlrobert.codegpt.ReferencedFile;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings;
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
import ee.carlrobert.codegpt.util.file.FileUtil;
import java.util.Collection;
@ -44,31 +46,36 @@ public class EditorActionsUtil {
ActionManager.getInstance().getAction("CodeGPT.MyEditorActionsGroup");
if (actionGroup instanceof DefaultActionGroup group) {
group.removeAll();
var configuredActions = ConfigurationSettings.getState().getTableData();
configuredActions.forEach((label, prompt) -> {
// using label as action description to prevent com.intellij.diagnostic.PluginException
// https://github.com/carlrobertoh/CodeGPT/issues/95
var action = new BaseEditorAction(label, label) {
@Override
protected void actionPerformed(Project project, Editor editor, String selectedText) {
var toolWindowContentManager =
project.getService(ChatToolWindowContentManager.class);
toolWindowContentManager.getToolWindow().show();
ApplicationManager.getApplication().getService(PromptsSettings.class)
.getState()
.getChatActions()
.getPrompts()
.forEach((promptDetails) -> {
// using label as action description to prevent com.intellij.diagnostic.PluginException
// https://github.com/carlrobertoh/CodeGPT/issues/95
var action = new BaseEditorAction(promptDetails.getName(), promptDetails.getName()) {
@Override
protected void actionPerformed(Project project, Editor editor, String selectedText) {
var toolWindowContentManager =
project.getService(ChatToolWindowContentManager.class);
toolWindowContentManager.getToolWindow().show();
var fileExtension = FileUtil.getFileExtension(editor.getVirtualFile().getName());
var message = new Message(prompt.replace(
"{{selectedCode}}",
format("%n```%s%n%s%n```", fileExtension, selectedText)));
message.setReferencedFilePaths(
Stream.ofNullable(project.getUserData(CodeGPTKeys.SELECTED_FILES))
.flatMap(Collection::stream)
.map(ReferencedFile::getFilePath)
.toList());
toolWindowContentManager.sendMessage(message);
}
};
group.add(action);
});
var fileExtension = FileUtil.getFileExtension(editor.getVirtualFile().getName());
var prompt =
promptDetails.getInstructions() == null ? "" : promptDetails.getInstructions();
var message = new Message(prompt.replace(
"{SELECTION}",
format("%n```%s%n%s%n```", fileExtension, selectedText)));
message.setReferencedFilePaths(
Stream.ofNullable(project.getUserData(CodeGPTKeys.SELECTED_FILES))
.flatMap(Collection::stream)
.map(ReferencedFile::getFilePath)
.toList());
toolWindowContentManager.sendMessage(message);
}
};
group.add(action);
});
}
}

View file

@ -3,7 +3,6 @@ package ee.carlrobert.codegpt.conversations.message;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ee.carlrobert.codegpt.settings.persona.PersonaDetails;
import ee.carlrobert.codegpt.ui.DocumentationDetails;
import java.util.List;
import java.util.Objects;
@ -20,7 +19,6 @@ public class Message {
private @Nullable String imageFilePath;
private boolean webSearchIncluded;
private DocumentationDetails documentationDetails;
private PersonaDetails personaDetails;
public Message(String prompt, String response) {
this(prompt);
@ -85,14 +83,6 @@ public class Message {
this.documentationDetails = documentationDetails;
}
public PersonaDetails getPersonaDetails() {
return personaDetails;
}
public void setPersonaDetails(PersonaDetails personaDetails) {
this.personaDetails = personaDetails;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {

View file

@ -1,74 +1,39 @@
package ee.carlrobert.codegpt.settings.configuration;
import static ee.carlrobert.codegpt.actions.editor.EditorActionsUtil.DEFAULT_ACTIONS_ARRAY;
import com.intellij.icons.AllIcons;
import com.intellij.icons.AllIcons.Nodes;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.keymap.impl.ui.EditKeymapsDialog;
import com.intellij.openapi.ui.ComponentValidator;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.ui.AnActionButton;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.components.fields.IntegerField;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil;
import ee.carlrobert.codegpt.ui.UIUtil;
import java.awt.Dimension;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import org.jetbrains.annotations.NotNull;
public class ConfigurationComponent {
private final JPanel mainPanel;
private final JBTable table;
private final JBCheckBox checkForPluginUpdatesCheckBox;
private final JBCheckBox checkForNewScreenshotsCheckBox;
private final JBCheckBox openNewTabCheckBox;
private final JBCheckBox methodNameGenerationCheckBox;
private final JBCheckBox autoFormattingCheckBox;
private final JBCheckBox autocompletionPostProcessingCheckBox;
private final JBCheckBox autocompletionContextAwareCheckBox;
private final JBCheckBox autocompletionGitContextCheckBox;
private final JTextArea commitMessagePromptTextArea;
private final IntegerField maxTokensField;
private final JBTextField temperatureField;
public ConfigurationComponent(
Disposable parentDisposable,
ConfigurationSettingsState configuration) {
table = new JBTable(new DefaultTableModel(
EditorActionsUtil.toArray(configuration.getTableData()),
new String[]{
CodeGPTBundle.get("configurationConfigurable.table.header.actionColumnLabel"),
CodeGPTBundle.get("configurationConfigurable.table.header.promptColumnLabel")
}));
table.getColumnModel().getColumn(0).setPreferredWidth(60);
table.getColumnModel().getColumn(1).setPreferredWidth(240);
table.getEmptyText().setText(CodeGPTBundle.get("configurationConfigurable.table.emptyText"));
var tablePanel = createTablePanel();
tablePanel.setBorder(BorderFactory.createTitledBorder(
CodeGPTBundle.get("configurationConfigurable.table.title")));
temperatureField = new JBTextField(12);
temperatureField.setText(String.valueOf(configuration.getTemperature()));
@ -95,20 +60,12 @@ public class ConfigurationComponent {
maxTokensField.setColumns(12);
maxTokensField.setValue(configuration.getMaxTokens());
commitMessagePromptTextArea = new JTextArea(configuration.getCommitMessagePrompt(), 3, 60);
commitMessagePromptTextArea.setLineWrap(true);
commitMessagePromptTextArea.setWrapStyleWord(true);
commitMessagePromptTextArea.setBorder(JBUI.Borders.empty(8, 4));
checkForPluginUpdatesCheckBox = new JBCheckBox(
CodeGPTBundle.get("configurationConfigurable.checkForPluginUpdates.label"),
configuration.getCheckForPluginUpdates());
checkForNewScreenshotsCheckBox = new JBCheckBox(
CodeGPTBundle.get("configurationConfigurable.checkForNewScreenshots.label"),
configuration.getCheckForNewScreenshots());
openNewTabCheckBox = new JBCheckBox(
CodeGPTBundle.get("configurationConfigurable.openNewTabCheckBox.label"),
configuration.getCreateNewChatOnEachAction());
methodNameGenerationCheckBox = new JBCheckBox(
CodeGPTBundle.get("configurationConfigurable.enableMethodNameGeneration.label"),
configuration.getMethodNameGenerationEnabled());
@ -129,11 +86,8 @@ public class ConfigurationComponent {
);
mainPanel = FormBuilder.createFormBuilder()
.addComponent(tablePanel)
.addVerticalGap(4)
.addComponent(checkForPluginUpdatesCheckBox)
.addComponent(checkForNewScreenshotsCheckBox)
.addComponent(openNewTabCheckBox)
.addComponent(methodNameGenerationCheckBox)
.addComponent(autoFormattingCheckBox)
.addComponent(autocompletionPostProcessingCheckBox)
@ -143,9 +97,6 @@ public class ConfigurationComponent {
.addComponent(new TitledSeparator(
CodeGPTBundle.get("configurationConfigurable.section.assistant.title")))
.addComponent(createAssistantConfigurationForm())
.addComponent(new TitledSeparator(
CodeGPTBundle.get("configurationConfigurable.section.commitMessage.title")))
.addComponent(createCommitMessageConfigurationForm())
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
@ -156,13 +107,10 @@ public class ConfigurationComponent {
public ConfigurationSettingsState getCurrentFormState() {
var state = new ConfigurationSettingsState();
state.setTableData(getTableData());
state.setMaxTokens(maxTokensField.getValue());
state.setTemperature(Float.parseFloat(temperatureField.getText()));
state.setCommitMessagePrompt(commitMessagePromptTextArea.getText());
state.setCheckForPluginUpdates(checkForPluginUpdatesCheckBox.isSelected());
state.setCheckForNewScreenshots(checkForNewScreenshotsCheckBox.isSelected());
state.setCreateNewChatOnEachAction(openNewTabCheckBox.isSelected());
state.setMethodNameGenerationEnabled(methodNameGenerationCheckBox.isSelected());
state.setAutoFormattingEnabled(autoFormattingCheckBox.isSelected());
state.setAutocompletionPostProcessingEnabled(autocompletionPostProcessingCheckBox.isSelected());
@ -173,13 +121,10 @@ public class ConfigurationComponent {
public void resetForm() {
var configuration = ConfigurationSettings.getState();
setTableData(configuration.getTableData());
maxTokensField.setValue(configuration.getMaxTokens());
temperatureField.setText(String.valueOf(configuration.getTemperature()));
commitMessagePromptTextArea.setText(configuration.getCommitMessagePrompt());
checkForPluginUpdatesCheckBox.setSelected(configuration.getCheckForPluginUpdates());
checkForNewScreenshotsCheckBox.setSelected(configuration.getCheckForNewScreenshots());
openNewTabCheckBox.setSelected(configuration.getCreateNewChatOnEachAction());
methodNameGenerationCheckBox.setSelected(configuration.getMethodNameGenerationEnabled());
autoFormattingCheckBox.setSelected(configuration.getAutoFormattingEnabled());
autocompletionPostProcessingCheckBox.setSelected(
@ -191,34 +136,6 @@ public class ConfigurationComponent {
);
}
private 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, 140))
.setAddAction(anActionButton -> {
getModel().addRow(new Object[]{"", ""});
int lastRowIndex = getModel().getRowCount() - 1;
table.changeSelection(lastRowIndex, 0, false, false);
table.editCellAt(lastRowIndex, 0);
})
.setRemoveAction(anActionButton -> getModel().removeRow(table.getSelectedRow()))
.disableUpAction()
.disableDownAction()
.addExtraAction(new RevertToDefaultsActionButton())
.addExtraAction(new KeymapActionButton())
.createPanel();
}
// Formatted keys are not referenced in the messages bundle file
private void addAssistantFormLabeledComponent(
FormBuilder formBuilder,
@ -255,22 +172,6 @@ public class ConfigurationComponent {
return form;
}
private JPanel createCommitMessageConfigurationForm() {
return FormBuilder.createFormBuilder()
.setFormLeftIndent(16)
.addLabeledComponent(
new JBLabel(CodeGPTBundle.get(
"configurationConfigurable.section.commitMessage.systemPromptField.label"))
.withBorder(JBUI.Borders.emptyLeft(2)),
UI.PanelFactory.panel(commitMessagePromptTextArea)
.resizeX(false)
.withComment(CommitMessageTemplate.Companion.getHtmlDescription())
.createPanel(),
true
)
.getPanel();
}
private ComponentValidator createTemperatureInputValidator(
Disposable parentDisposable,
JBTextField component) {
@ -297,66 +198,4 @@ public class ConfigurationComponent {
validator.enableValidation();
return validator;
}
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(
CodeGPTBundle.get("configurationConfigurable.table.action.revertToDefaults.text"),
AllIcons.Actions.Rollback);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var model = getModel();
model.setRowCount(0);
Arrays.stream(DEFAULT_ACTIONS_ARRAY).forEach(model::addRow);
EditorActionsUtil.refreshActions();
}
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.EDT;
}
}
class KeymapActionButton extends AnActionButton {
KeymapActionButton() {
super(
CodeGPTBundle.get("configurationConfigurable.table.action.addKeymap.text"),
Nodes.KeymapEditor);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var actionId = "codegpt.AskChatgpt";
var selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
var label = getModel()
.getDataVector()
.get(selectedRow)
.get(0);
if (label != null && !label.toString().isEmpty()) {
actionId = EditorActionsUtil.convertToId(label.toString());
}
}
new EditKeymapsDialog(e.getProject(), actionId, false).show();
}
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.EDT;
}
}
}

View file

@ -2,6 +2,7 @@ package ee.carlrobert.codegpt.toolwindow.chat;
import static java.util.Objects.requireNonNull;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentContainer;
@ -16,7 +17,7 @@ import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
@ -38,8 +39,11 @@ public final class ChatToolWindowContentManager {
public void sendMessage(Message message, ConversationType conversationType) {
getToolWindow().show();
if (ConfigurationSettings.getState().getCreateNewChatOnEachAction()
|| ConversationsState.getCurrentConversation() == null) {
var startInNewWindow = ApplicationManager.getApplication().getService(PromptsSettings.class)
.getState()
.getChatActions()
.getStartInNewWindow();
if (startInNewWindow || ConversationsState.getCurrentConversation() == null) {
createNewTabPanel().sendMessage(message, conversationType);
return;
}

View file

@ -28,8 +28,8 @@ import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.settings.persona.PersonaSettings;
import ee.carlrobert.codegpt.settings.persona.PersonasConfigurable;
import ee.carlrobert.codegpt.settings.prompts.PromptsConfigurable;
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings;
import ee.carlrobert.codegpt.settings.service.ProviderChangeNotifier;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTUserDetailsNotifier;
@ -213,7 +213,7 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
@NotNull String place) {
var link = new ActionLink(getSelectedPersonaName(), (e) -> {
ShowSettingsUtil.getInstance()
.showSettingsDialog(project, PersonasConfigurable.class);
.showSettingsDialog(project, PromptsConfigurable.class);
});
link.setExternalLinkIcon();
link.setFont(JBUI.Fonts.smallFont());
@ -233,8 +233,9 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
}
private String getSelectedPersonaName() {
return ApplicationManager.getApplication().getService(PersonaSettings.class)
return ApplicationManager.getApplication().getService(PromptsSettings.class)
.getState()
.getPersonas()
.getSelectedPersona()
.getName();
}

View file

@ -155,9 +155,12 @@ public class ChatToolWindowTabPanel implements Disposable {
.sessionId(chatSession.getId())
.conversationType(conversationType)
.imageDetailsFromPath(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH.get(project))
.persona(CodeGPTKeys.ADDED_PERSONA.get(project))
.referencedFiles(getReferencedFiles())
.build();
CodeGPTKeys.ADDED_PERSONA.set(project, null);
var referencedFiles = callParameters.getReferencedFiles();
if ((referencedFiles != null && !referencedFiles.isEmpty())
|| callParameters.getImageDetails() != null) {
@ -324,7 +327,7 @@ public class ChatToolWindowTabPanel implements Disposable {
var fileExtension = FileUtil.getFileExtension(editor.getVirtualFile().getName());
var message = new Message(action.getPrompt().replace(
"{{selectedCode}}",
"{SELECTION}",
format("%n```%s%n%s%n```", fileExtension, editor.getSelectionModel().getSelectedText())));
sendMessage(message, ConversationType.DEFAULT);
return Unit.INSTANCE;

View file

@ -8,7 +8,6 @@ import ee.carlrobert.codegpt.ui.textarea.AppliedSuggestionActionInlay;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.CreateDocumentationActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.DocumentationActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.GitCommitActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.PersonaActionItem;
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.WebSearchActionItem;
public class SuggestionActionProcessor implements ActionProcessor {
@ -37,7 +36,6 @@ public class SuggestionActionProcessor implements ActionProcessor {
message.setWebSearchIncluded(action.getSuggestion() instanceof WebSearchActionItem);
processDocumentationAction(message, action);
processPersonaAction(message, action);
processGitCommitAction(action, promptBuilder);
}
@ -52,15 +50,6 @@ public class SuggestionActionProcessor implements ActionProcessor {
}
}
private void processPersonaAction(Message message, AppliedSuggestionActionInlay action) {
var addedPersona = CodeGPTKeys.ADDED_PERSONA.get(project);
var personaInlayExists = action.getSuggestion() instanceof PersonaActionItem;
if (addedPersona != null && personaInlayExists) {
message.setPersonaDetails(addedPersona);
CodeGPTKeys.ADDED_PERSONA.set(project, null);
}
}
private void processGitCommitAction(
AppliedSuggestionActionInlay action,
StringBuilder promptBuilder) {

View file

@ -21,7 +21,7 @@ import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.settings.persona.PersonaSettings;
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
@ -141,7 +141,7 @@ public class TotalTokensPanel extends JPanel {
List<ReferencedFile> includedFiles,
@Nullable String highlightedText) {
var tokenDetails = new TotalTokensDetails(
encodingManager.countTokens(PersonaSettings.getSystemPrompt()));
encodingManager.countTokens(PromptsSettings.getSelectedPersonaSystemPrompt()));
tokenDetails.setConversationTokens(encodingManager.countConversationTokens(conversation));
if (includedFiles != null) {
tokenDetails.setReferencedFilesTokens(includedFiles.stream()

View file

@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.completions
import ee.carlrobert.codegpt.ReferencedFile
import ee.carlrobert.codegpt.conversations.Conversation
import ee.carlrobert.codegpt.conversations.message.Message
import ee.carlrobert.codegpt.settings.prompts.PersonaDetails
import ee.carlrobert.codegpt.util.file.FileUtil
import java.nio.file.Files
import java.nio.file.Path
@ -17,7 +18,8 @@ class ChatCompletionParameters private constructor(
var sessionId: UUID?,
var retry: Boolean,
var imageDetails: ImageDetails?,
var referencedFiles: List<ReferencedFile>?
var referencedFiles: List<ReferencedFile>?,
var persona: PersonaDetails?,
) : CompletionParameters {
fun toBuilder(): Builder {
@ -27,6 +29,7 @@ class ChatCompletionParameters private constructor(
retry(this@ChatCompletionParameters.retry)
imageDetails(this@ChatCompletionParameters.imageDetails)
referencedFiles(this@ChatCompletionParameters.referencedFiles)
persona(this@ChatCompletionParameters.persona)
}
}
@ -36,6 +39,7 @@ class ChatCompletionParameters private constructor(
private var retry: Boolean = false
private var imageDetails: ImageDetails? = null
private var referencedFiles: List<ReferencedFile>? = null
private var persona: PersonaDetails? = null
fun sessionId(sessionId: UUID?) = apply { this.sessionId = sessionId }
fun conversationType(conversationType: ConversationType) =
@ -55,6 +59,8 @@ class ChatCompletionParameters private constructor(
fun referencedFiles(referencedFiles: List<ReferencedFile>?) =
apply { this.referencedFiles = referencedFiles }
fun persona(persona: PersonaDetails?) = apply { this.persona = persona }
fun build(): ChatCompletionParameters {
return ChatCompletionParameters(
conversation,
@ -63,7 +69,8 @@ class ChatCompletionParameters private constructor(
sessionId,
retry,
imageDetails,
referencedFiles
referencedFiles,
persona
)
}
}

View file

@ -1,8 +1,9 @@
package ee.carlrobert.codegpt.completions
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.EDIT_CODE_SYSTEM_PROMPT
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.GENERATE_METHOD_NAMES_SYSTEM_PROMPT
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.completions.factory.*
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.llm.completion.CompletionRequest
@ -32,7 +33,9 @@ interface CompletionRequestFactory {
abstract class BaseRequestFactory : CompletionRequestFactory {
override fun createEditCodeRequest(params: EditCodeCompletionParameters): CompletionRequest {
val prompt = "Code to modify:\n${params.selectedText}\n\nInstructions: ${params.prompt}"
return createBasicCompletionRequest(EDIT_CODE_SYSTEM_PROMPT, prompt, 8192, true)
return createBasicCompletionRequest(
service<PromptsSettings>().state.coreActions.editCode.instructions
?: CoreActionsState.DEFAULT_EDIT_CODE_PROMPT, prompt, 8192, true)
}
override fun createCommitMessageRequest(params: CommitMessageCompletionParameters): CompletionRequest {
@ -40,7 +43,12 @@ abstract class BaseRequestFactory : CompletionRequestFactory {
}
override fun createLookupRequest(params: LookupCompletionParameters): CompletionRequest {
return createBasicCompletionRequest(GENERATE_METHOD_NAMES_SYSTEM_PROMPT, params.prompt, 512)
return createBasicCompletionRequest(
service<PromptsSettings>().state.coreActions.generateNameLookups.instructions
?: CoreActionsState.DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT,
params.prompt,
512
)
}
abstract fun createBasicCompletionRequest(

View file

@ -3,19 +3,9 @@ package ee.carlrobert.codegpt.completions
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.ReferencedFile
import ee.carlrobert.codegpt.settings.IncludedFilesSettings
import ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent
import java.util.stream.Collectors
object CompletionRequestUtil {
val GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT =
getResourceContent("/prompts/generate-commit-message.txt")
val FIX_COMPILE_ERRORS_SYSTEM_PROMPT =
getResourceContent("/prompts/fix-compile-errors.txt")
val GENERATE_METHOD_NAMES_SYSTEM_PROMPT =
getResourceContent("/prompts/method-name-generator.txt")
val EDIT_CODE_SYSTEM_PROMPT =
getResourceContent("/prompts/edit-code.txt")
@JvmStatic
fun getPromptWithContext(
referencedFiles: List<ReferencedFile>,

View file

@ -5,6 +5,7 @@ import ee.carlrobert.codegpt.completions.BaseRequestFactory
import ee.carlrobert.codegpt.completions.ChatCompletionParameters
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings
import ee.carlrobert.llm.client.anthropic.completion.*
import ee.carlrobert.llm.completion.CompletionRequest
@ -16,7 +17,7 @@ class ClaudeRequestFactory : BaseRequestFactory() {
model = service<AnthropicSettings>().state.model
maxTokens = service<ConfigurationSettings>().state.maxTokens
isStream = true
system = PersonaSettings.getSystemPrompt()
system = PromptsSettings.getSelectedPersonaSystemPrompt()
messages = params.conversation.messages
.filter { it.response != null && it.response.isNotEmpty() }

View file

@ -4,12 +4,11 @@ import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.completions.BaseRequestFactory
import ee.carlrobert.codegpt.completions.ChatCompletionParameters
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.FIX_COMPILE_ERRORS_SYSTEM_PROMPT
import ee.carlrobert.codegpt.completions.ConversationType
import ee.carlrobert.codegpt.completions.TotalUsageExceededException
import ee.carlrobert.codegpt.conversations.ConversationsState
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.google.GoogleSettings
import ee.carlrobert.codegpt.util.file.FileUtil
import ee.carlrobert.llm.client.google.completion.GoogleCompletionContent
@ -100,7 +99,7 @@ class GoogleRequestFactory : BaseRequestFactory() {
messages.add(
GoogleCompletionContent(
"user",
listOf(PersonaSettings.getSystemPrompt())
listOf(PromptsSettings.getSelectedPersonaSystemPrompt())
)
)
messages.add(GoogleCompletionContent("model", listOf("Understood.")))
@ -108,7 +107,10 @@ class GoogleRequestFactory : BaseRequestFactory() {
ConversationType.FIX_COMPILE_ERRORS -> {
messages.add(
GoogleCompletionContent("user", listOf(FIX_COMPILE_ERRORS_SYSTEM_PROMPT))
GoogleCompletionContent(
"user",
listOf(service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions)
)
)
messages.add(GoogleCompletionContent("model", listOf("Understood.")))
}

View file

@ -3,12 +3,11 @@ package ee.carlrobert.codegpt.completions.factory
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.completions.BaseRequestFactory
import ee.carlrobert.codegpt.completions.ChatCompletionParameters
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.FIX_COMPILE_ERRORS_SYSTEM_PROMPT
import ee.carlrobert.codegpt.completions.ConversationType
import ee.carlrobert.codegpt.completions.llama.LlamaModel
import ee.carlrobert.codegpt.completions.llama.PromptTemplate
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings.Companion.getSystemPrompt
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest
@ -18,9 +17,9 @@ class LlamaRequestFactory : BaseRequestFactory() {
val promptTemplate = getPromptTemplate()
val systemPrompt =
if (params.conversationType == ConversationType.FIX_COMPILE_ERRORS)
FIX_COMPILE_ERRORS_SYSTEM_PROMPT
service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions
else
getSystemPrompt()
PromptsSettings.getSelectedPersonaSystemPrompt()
val prompt = promptTemplate.buildPrompt(
systemPrompt,
getPromptWithFilesContext(params),

View file

@ -3,13 +3,11 @@ package ee.carlrobert.codegpt.completions.factory
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.completions.*
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.EDIT_CODE_SYSTEM_PROMPT
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.FIX_COMPILE_ERRORS_SYSTEM_PROMPT
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.GENERATE_METHOD_NAMES_SYSTEM_PROMPT
import ee.carlrobert.codegpt.conversations.ConversationsState
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings.Companion.getState
import ee.carlrobert.codegpt.settings.persona.PersonaSettings.Companion.getSystemPrompt
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
import ee.carlrobert.codegpt.util.file.FileUtil.getImageMediaType
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel
@ -47,10 +45,12 @@ class OpenAIRequestFactory : CompletionRequestFactory {
override fun createEditCodeRequest(params: EditCodeCompletionParameters): OpenAIChatCompletionRequest {
val model = service<OpenAISettings>().state.model
val prompt = "Code to modify:\n${params.selectedText}\n\nInstructions: ${params.prompt}"
val systemPrompt = service<PromptsSettings>().state.coreActions.editCode.instructions
?: CoreActionsState.DEFAULT_EDIT_CODE_PROMPT
if (model == "o1-mini" || model == "o1-preview") {
return buildBasicO1Request(model, prompt, EDIT_CODE_SYSTEM_PROMPT)
return buildBasicO1Request(model, prompt, systemPrompt)
}
return createBasicCompletionRequest(EDIT_CODE_SYSTEM_PROMPT, prompt, model, true)
return createBasicCompletionRequest(systemPrompt, prompt, model, true)
}
override fun createCommitMessageRequest(params: CommitMessageCompletionParameters): OpenAIChatCompletionRequest {
@ -66,9 +66,17 @@ class OpenAIRequestFactory : CompletionRequestFactory {
val model = service<OpenAISettings>().state.model
val (prompt) = params
if (model == "o1-mini" || model == "o1-preview") {
return buildBasicO1Request(model, prompt, GENERATE_METHOD_NAMES_SYSTEM_PROMPT)
return buildBasicO1Request(
model,
prompt,
service<PromptsSettings>().state.coreActions.generateNameLookups.instructions
?: CoreActionsState.DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT
)
}
return createBasicCompletionRequest(GENERATE_METHOD_NAMES_SYSTEM_PROMPT, prompt, model)
return createBasicCompletionRequest(
service<PromptsSettings>().state.coreActions.generateNameLookups.instructions
?: CoreActionsState.DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT, prompt, model
)
}
companion object {
@ -142,10 +150,13 @@ class OpenAIRequestFactory : CompletionRequestFactory {
val role = if ("o1-mini" == model || "o1-preview" == model) "user" else "system"
if (callParameters.conversationType == ConversationType.DEFAULT) {
val sessionPersonaDetails = callParameters.message.personaDetails
if (callParameters.message.personaDetails == null) {
val sessionPersonaDetails = callParameters.persona
if (sessionPersonaDetails == null) {
messages.add(
OpenAIChatCompletionStandardMessage(role, getSystemPrompt())
OpenAIChatCompletionStandardMessage(
role,
PromptsSettings.getSelectedPersonaSystemPrompt()
)
)
} else {
messages.add(
@ -158,7 +169,10 @@ class OpenAIRequestFactory : CompletionRequestFactory {
}
if (callParameters.conversationType == ConversationType.FIX_COMPILE_ERRORS) {
messages.add(
OpenAIChatCompletionStandardMessage(role, FIX_COMPILE_ERRORS_SYSTEM_PROMPT)
OpenAIChatCompletionStandardMessage(
role,
service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions
)
)
}

View file

@ -2,7 +2,7 @@ package ee.carlrobert.codegpt.settings.configuration
import com.intellij.openapi.components.*
import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState
import kotlin.math.max
import kotlin.math.min
@ -22,12 +22,11 @@ class ConfigurationSettings :
}
class ConfigurationSettingsState : BaseState() {
var commitMessagePrompt by string(GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT)
var commitMessagePrompt by string(CoreActionsState.DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT)
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 createNewChatOnEachAction by property(false)
var ignoreGitCommitTokenLimit by property(false)
var methodNameGenerationEnabled by property(true)
var captureCompileErrors by property(true)

View file

@ -1,44 +1,23 @@
package ee.carlrobert.codegpt.settings.persona
import com.intellij.openapi.components.*
import ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent
val DEFAULT_PROMPT = getResourceContent("/prompts/default-completion.txt")
import ee.carlrobert.codegpt.settings.prompts.PersonasState
@Deprecated("Use PromptsSettings instead")
@Service
@State(
name = "CodeGPT_PersonaSettings",
storages = [Storage("CodeGPT_PersonaSettings.xml")]
)
class PersonaSettings :
SimplePersistentStateComponent<PersonaSettingsState>(PersonaSettingsState()) {
companion object {
@JvmStatic
fun getSystemPrompt(): String {
return service<PersonaSettings>().state.selectedPersona.instructions ?: ""
}
}
}
SimplePersistentStateComponent<PersonaSettingsState>(PersonaSettingsState())
class PersonaSettingsState : BaseState() {
var selectedPersona by property(PersonaDetailsState())
var userCreatedPersonas by list<PersonaDetailsState>()
}
class PersonaDetailsState : BaseState() {
var id by property(1L)
var name by string("CodeGPT Default")
var instructions by string(DEFAULT_PROMPT)
}
@JvmRecord
data class PersonaDetails(val id: Long, val name: String, val instructions: String)
fun PersonaDetails.toPersonaDetailsState(): PersonaDetailsState {
val newState = PersonaDetailsState()
newState.id = id
newState.name = name
newState.instructions = instructions
return newState
var instructions by string(PersonasState.DEFAULT_PERSONA_PROMPT)
}

View file

@ -1,308 +0,0 @@
package ee.carlrobert.codegpt.settings.persona
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.ui.DialogPanel
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.components.JBTextArea
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.LabelPosition
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.table.JBTable
import com.intellij.util.ui.JBUI
import ee.carlrobert.codegpt.util.ResourceUtil
import java.awt.Dimension
import javax.swing.UIManager
import javax.swing.event.DocumentEvent
import javax.swing.table.DefaultTableModel
import javax.swing.text.JTextComponent
class NonEditableTableModel(columnNames: Array<String>, rowCount: Int) :
DefaultTableModel(columnNames, rowCount) {
override fun isCellEditable(row: Int, column: Int) = false
}
class PersonasSettingsForm {
private val tableModel =
NonEditableTableModel(arrayOf("Id", "Name", "Instructions", "FromResource"), 0)
private val table = JBTable(tableModel).apply {
setupTableColumns()
selectionModel.addListSelectionListener { populateEditArea() }
}
private val nameField = JBTextField().apply {
addTextChangeListener { newText ->
updateTableModelIfRowSelected(1, newText)
}
}
private val instructionsTextArea = JBTextArea().apply {
lineWrap = true
wrapStyleWord = true
font = UIManager.getFont("TextField.font")
border = JBUI.Borders.empty(3, 6)
addTextChangeListener { newText ->
updateTableModelIfRowSelected(2, newText)
}
}
private val addedItems = mutableListOf<PersonaDetails>()
private val removedItemIds = mutableListOf<Long>()
init {
setupForm()
}
fun createPanel(): DialogPanel {
return panel {
row {
val toolbarDecorator = ToolbarDecorator.createDecorator(table)
.setAddAction { handleAddItem() }
.setRemoveAction { handleRemoveItem() }
.addExtraAction(object :
AnAction("Duplicate", "Duplicate persona", AllIcons.Actions.Copy) {
override fun actionPerformed(e: AnActionEvent) {
handleDuplicateItem()
}
})
.setRemoveActionUpdater {
val selectedRow = table.selectedRow
selectedRow != -1 && !(tableModel.getValueAt(selectedRow, 3) as Boolean)
}
.disableUpDownActions()
cell(toolbarDecorator.createPanel())
.align(Align.FILL)
.resizableColumn()
.applyToComponent {
preferredSize = Dimension(650, 250)
}
}
row {
cell(nameField)
.label("Name:", LabelPosition.TOP)
.align(Align.FILL)
}
row {
cell(JBScrollPane(instructionsTextArea).apply {
preferredSize = Dimension(650, 225)
})
.label("Instructions:", LabelPosition.TOP)
.align(Align.FILL)
.resizableColumn()
}
}
}
fun applyChanges() {
val persona = getSelectedPersona()
service<PersonaSettings>().state.run {
if (persona != null) {
selectedPersona = persona.toPersonaDetailsState()
}
userCreatedPersonas.removeIf { removedItemIds.contains(it.id) }
userCreatedPersonas.forEach {
if (it.id == persona?.id) {
it.name = persona.name
it.instructions = persona.instructions
}
}
userCreatedPersonas.addAll(findMatchingRows(addedItems.map { it.id }).map { it.toPersonaDetailsState() })
}
clear()
}
fun isModified(): Boolean {
service<PersonaSettings>().state.let {
val (id, name, description) = getSelectedPersona() ?: return false
return it.selectedPersona.id != id
|| it.selectedPersona.name != name
|| it.selectedPersona.instructions != description
|| removedItemIds.size > 0
|| addedItems.size > 0
}
}
fun resetChanges() {
clear()
tableModel.rowCount = 0
setupForm()
}
private fun populateEditArea() {
val selectedRow = table.selectedRow
if (selectedRow != -1) {
val userCreatedResource = !(tableModel.getValueAt(selectedRow, 3) as Boolean)
nameField.text = tableModel.getValueAt(selectedRow, 1) as String
nameField.isEnabled = userCreatedResource
instructionsTextArea.text = tableModel.getValueAt(selectedRow, 2) as String
instructionsTextArea.isEnabled = userCreatedResource
} else {
nameField.text = ""
instructionsTextArea.text = ""
}
}
private fun handleAddItem() {
addPersonaToTable(createNewPersona("New Persona", "New Prompt"))
}
private fun handleDuplicateItem() {
val selectedRow = table.selectedRow
val originalName = tableModel.getValueAt(selectedRow, 1) as String
val originalPrompt = tableModel.getValueAt(selectedRow, 2) as String
addPersonaToTable(createNewPersona("$originalName Copy", originalPrompt))
}
private fun createNewPersona(name: String, prompt: String): PersonaDetails {
return PersonaDetails(findMaxId() + 1, name, prompt)
}
private fun addPersonaToTable(persona: PersonaDetails) {
addedItems.add(persona)
tableModel.addRow(arrayOf(persona.id, persona.name, persona.instructions, false))
selectLastRowAndUpdateUI()
}
private fun findMaxId(): Long {
return (0 until table.rowCount).maxOf {
tableModel.getValueAt(it, 0) as Long
}
}
private fun selectLastRowAndUpdateUI() {
val lastRow = table.rowCount - 1
table.setRowSelectionInterval(lastRow, lastRow)
populateEditArea()
scrollToLastRow()
nameField.requestFocus()
}
private fun handleRemoveItem() {
val selectedRow = table.selectedRow
if (selectedRow != -1 && !(tableModel.getValueAt(selectedRow, 3) as Boolean)) {
val id = tableModel.getValueAt(selectedRow, 0) as Long
if (addedItems.none { it.id == id }) {
removedItemIds.add(id)
}
addedItems.filter { it.id != id }
tableModel.removeRow(selectedRow)
populateEditArea()
val newSelectedRow = if (selectedRow > 0) selectedRow - 1 else 0
if (table.rowCount > 0) {
table.setRowSelectionInterval(newSelectedRow, newSelectedRow)
}
}
}
private fun setupForm() {
service<PersonaSettings>().state.run {
userCreatedPersonas.forEachIndexed { index, persona ->
tableModel.addPersonaRow(
PersonaDetails(
persona.id,
persona.name ?: "",
persona.instructions ?: ""
),
selectedPersona.id,
index
)
}
ResourceUtil.getDefaultPersonas().forEachIndexed { index, persona ->
tableModel.addPersonaRow(persona, selectedPersona.id, index, true)
}
}
}
private fun DefaultTableModel.addPersonaRow(
persona: PersonaDetails,
selectedPersonaId: Long,
rowIndex: Int,
fromResource: Boolean = false
) {
val (id, name, instructions) = persona
addRow(arrayOf(id, name, instructions, fromResource))
if (selectedPersonaId == id) {
table.setRowSelectionInterval(rowIndex, rowIndex)
}
}
private fun scrollToLastRow() {
table.scrollRectToVisible(table.getCellRect(table.rowCount - 1, 0, true))
}
private fun JBTable.setupTableColumns() {
columnModel.apply {
getColumn(0).apply {
minWidth = 0
maxWidth = 0
preferredWidth = 0
resizable = false
}
getColumn(1).preferredWidth = 60
getColumn(2).preferredWidth = 240
getColumn(3).apply {
minWidth = 0
maxWidth = 0
preferredWidth = 0
resizable = false
}
}
}
private fun JBTable.getSelectedPersona(): PersonaDetails? {
if (selectedRow == -1) {
return null
}
return PersonaDetails(
tableModel.getValueAt(selectedRow, 0) as Long,
tableModel.getValueAt(selectedRow, 1) as String,
tableModel.getValueAt(selectedRow, 2) as String
)
}
private fun JTextComponent.addTextChangeListener(listener: (String) -> Unit) {
document.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
listener(e.document.getText(0, e.document.length))
}
})
}
private fun updateTableModelIfRowSelected(column: Int, newValue: Any) {
if (table.selectedRow != -1) {
tableModel.setValueAt(newValue, table.selectedRow, column)
}
}
private fun getSelectedPersona(): PersonaDetails? {
return table.getSelectedPersona()
}
private fun clear() {
addedItems.clear()
removedItemIds.clear()
}
private fun findMatchingRows(ids: List<Long>): List<PersonaDetails> {
val matchingRows = mutableListOf<PersonaDetails>()
for (rowIndex in 0 until tableModel.rowCount) {
val personaId = tableModel.getValueAt(rowIndex, 0) as Long
if (ids.contains(personaId)) {
val name = tableModel.getValueAt(rowIndex, 1) as String
val instructions = tableModel.getValueAt(rowIndex, 2) as String
matchingRows.add(PersonaDetails(personaId, name, instructions))
}
}
return matchingRows
}
}

View file

@ -1,11 +1,15 @@
package ee.carlrobert.codegpt.settings.configuration
package ee.carlrobert.codegpt.settings.prompts
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level.PROJECT
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import ee.carlrobert.codegpt.settings.configuration.BranchNamePlaceholderStrategy
import ee.carlrobert.codegpt.settings.configuration.DatePlaceholderStrategy
import ee.carlrobert.codegpt.settings.configuration.Placeholder
import ee.carlrobert.codegpt.settings.configuration.Placeholder.BRANCH_NAME
import ee.carlrobert.codegpt.settings.configuration.Placeholder.DATE_ISO_8601
import ee.carlrobert.codegpt.settings.configuration.PlaceholderStrategy
@Service(PROJECT)
class CommitMessageTemplate private constructor(project: Project) {
@ -17,12 +21,8 @@ class CommitMessageTemplate private constructor(project: Project) {
}
return buildString {
append("<html>\n")
append("<body>\n")
append("<p>Template for generating commit messages. Use the following placeholders to insert dynamic values:</p>\n")
append("<ul>$placeholderDescriptions</ul>\n")
append("</body>\n")
append("</html>")
}
}
}
@ -33,7 +33,7 @@ class CommitMessageTemplate private constructor(project: Project) {
)
fun getSystemPrompt(): String =
service<ConfigurationSettings>().state.commitMessagePrompt.let { template ->
service<PromptsSettings>().state.coreActions.generateCommitMessage.instructions.let { template ->
placeholderStrategyMapping.entries.fold(
template ?: ""
) { acc, (placeholder, strategy) ->

View file

@ -1,18 +1,19 @@
package ee.carlrobert.codegpt.settings.persona
package ee.carlrobert.codegpt.settings.prompts
import com.intellij.openapi.options.Configurable
import ee.carlrobert.codegpt.settings.prompts.form.PromptsForm
import javax.swing.JComponent
class PersonasConfigurable : Configurable {
class PromptsConfigurable : Configurable {
private lateinit var component: PersonasSettingsForm
private lateinit var component: PromptsForm
override fun getDisplayName(): String {
return "CodeGPT: Personas"
return "CodeGPT: Prompts"
}
override fun createComponent(): JComponent {
component = PersonasSettingsForm()
component = PromptsForm()
return component.createPanel()
}

View file

@ -0,0 +1,183 @@
package ee.carlrobert.codegpt.settings.prompts
import com.intellij.openapi.components.*
import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent
@Service
@State(
name = "CodeGPT_PromptsSettings",
storages = [Storage("CodeGPT_PromptsSettings.xml")]
)
class PromptsSettings :
SimplePersistentStateComponent<PromptsSettingsState>(PromptsSettingsState()) {
companion object {
@JvmStatic
fun getSelectedPersonaSystemPrompt(): String {
return service<PromptsSettings>().state.personas.selectedPersona.instructions ?: ""
}
}
}
class PromptsSettingsState : BaseState() {
var coreActions by property(CoreActionsState())
var chatActions by property(ChatActionsState())
var personas by property(PersonasState())
}
class CoreActionsState : BaseState() {
companion object {
val DEFAULT_EDIT_CODE_PROMPT = getResourceContent("/prompts/core/edit-code.txt")
val DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT =
getResourceContent("/prompts/core/generate-commit-message.txt")
val DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT =
getResourceContent("/prompts/core/generate-name-lookups.txt")
val DEFAULT_FIX_COMPILE_ERRORS_PROMPT =
getResourceContent("/prompts/core/fix-compile-errors.txt")
}
var editCode by property(CoreActionPromptDetailsState().apply {
name = "Edit Code"
code = "EDIT_CODE"
instructions = DEFAULT_EDIT_CODE_PROMPT
})
var fixCompileErrors by property(CoreActionPromptDetailsState().apply {
name = "Fix Compile Errors"
code = "FIX_COMPILE_ERRORS"
instructions = DEFAULT_FIX_COMPILE_ERRORS_PROMPT
})
var generateCommitMessage by property(CoreActionPromptDetailsState().apply {
name = "Generate Commit Message"
code = "GENERATE_COMMIT_MESSAGE"
instructions = service<ConfigurationSettings>().state.commitMessagePrompt
})
var generateNameLookups by property(CoreActionPromptDetailsState().apply {
name = "Generate Name Lookups"
code = "GENERATE_NAME_LOOKUPS"
instructions = DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT
})
}
class PersonasState : BaseState() {
companion object {
val DEFAULT_PERSONA_PROMPT = getResourceContent("/prompts/persona/default-persona.txt")
val DEFAULT_PERSONA = PersonaPromptDetailsState().apply {
id = 1L
name = "CodeGPT Default"
instructions = DEFAULT_PERSONA_PROMPT
}
}
var selectedPersona by property(DEFAULT_PERSONA)
var prompts by list<PersonaPromptDetailsState>()
init {
prompts.add(DEFAULT_PERSONA)
prompts.add(PersonaPromptDetailsState().apply {
id = 2L
name = "Rubber Duck"
instructions = getResourceContent("/prompts/persona/rubber-duck.txt")
})
// migrate old personas
var nextPersonaIndex = 3L
prompts.addAll(service<PersonaSettings>().state.userCreatedPersonas
.map {
val newState = PersonaPromptDetailsState().apply {
id = nextPersonaIndex
name = it.name
instructions = it.instructions
}
nextPersonaIndex++
newState
})
}
}
class ChatActionsState : BaseState() {
var prompts by list<ChatActionPromptDetailsState>()
var startInNewWindow by property(false)
companion object {
val DEFAULT_FIND_BUGS_PROMPT = getResourceContent("/prompts/chat/find-bugs.txt")
val DEFAULT_WRITE_TESTS_PROMPT = getResourceContent("/prompts/chat/write-tests.txt")
val DEFAULT_EXPLAIN_PROMPT = getResourceContent("/prompts/chat/explain.txt")
val DEFAULT_REFACTOR_PROMPT = getResourceContent("/prompts/chat/refactor.txt")
val DEFAULT_OPTIMIZE_PROMPT = getResourceContent("/prompts/chat/optimize.txt")
}
init {
prompts.add(ChatActionPromptDetailsState().apply {
id = 1L
code = "FIND_BUGS"
name = "Find Bugs"
instructions = DEFAULT_FIND_BUGS_PROMPT
})
prompts.add(ChatActionPromptDetailsState().apply {
id = 2L
code = "WRITE_TESTS"
name = "Write Tests"
instructions = DEFAULT_WRITE_TESTS_PROMPT
})
prompts.add(ChatActionPromptDetailsState().apply {
id = 3L
code = "EXPLAIN"
name = "Explain"
instructions = DEFAULT_EXPLAIN_PROMPT
})
prompts.add(ChatActionPromptDetailsState().apply {
id = 4L
code = "REFACTOR"
name = "Refactor"
instructions = DEFAULT_REFACTOR_PROMPT
})
prompts.add(ChatActionPromptDetailsState().apply {
id = 5L
code = "OPTIMIZE"
name = "Optimize"
instructions = DEFAULT_OPTIMIZE_PROMPT
})
// migrate old chat actions
var nextChatActionIndex = 6L
prompts.addAll(service<ConfigurationSettings>().state.tableData
.filterNot { entry ->
EditorActionsUtil.DEFAULT_ACTIONS.any { it.key == entry.key && it.value == entry.value }
}
.map {
val newState = ChatActionPromptDetailsState().apply {
id = nextChatActionIndex
name = it.key
instructions = it.value
}
nextChatActionIndex++
newState
})
}
}
abstract class PromptDetailsState : BaseState() {
var name by string()
var instructions by string()
}
class CoreActionPromptDetailsState : PromptDetailsState() {
var code by string()
}
class ChatActionPromptDetailsState : PromptDetailsState() {
var id by property(1L)
var code by string()
}
class PersonaPromptDetailsState : PromptDetailsState() {
var id by property(1L)
}
@JvmRecord
data class PersonaDetails(val id: Long, val name: String, val instructions: String)

View file

@ -0,0 +1,392 @@
package ee.carlrobert.codegpt.settings.prompts.form
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.service
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.treeStructure.SimpleTree
import com.intellij.util.ui.components.BorderLayoutPanel
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState
import ee.carlrobert.codegpt.settings.prompts.PersonasState
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.prompts.form.PromptsFormUtil.getFormState
import ee.carlrobert.codegpt.settings.prompts.form.PromptsFormUtil.toState
import ee.carlrobert.codegpt.settings.prompts.form.details.*
import java.awt.CardLayout
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreePath
import javax.swing.tree.TreeSelectionModel
enum class PromptCategory {
CORE_ACTIONS,
CHAT_ACTIONS,
PERSONAS,
}
class PromptDetailsTreeNode(
val details: FormPromptDetails,
val category: PromptCategory
) : DefaultMutableTreeNode() {
override fun toString(): String {
return details.name ?: ""
}
}
class PromptsForm {
private val cardLayout = CardLayout()
private val promptDetailsContainer = JPanel(cardLayout)
private val categoryPanels = mapOf(
PromptCategory.CORE_ACTIONS to CoreActionsDetailsPanel(),
PromptCategory.CHAT_ACTIONS to ChatActionsDetailsPanel(),
PromptCategory.PERSONAS to PersonasDetailsPanel { handleDefaultPersonaChanged(it) },
).onEach { (category, panel) ->
promptDetailsContainer.add(panel.getPanel(), category.name)
}
private val coreActionsNode = DefaultMutableTreeNode("Core Actions")
private val chatActionsNode = DefaultMutableTreeNode("Chat Actions")
private val personasNode = DefaultMutableTreeNode("Personas")
private val root = DefaultMutableTreeNode("Root").apply {
add(coreActionsNode)
add(chatActionsNode)
add(personasNode)
}
private val treeModel = DefaultTreeModel(root)
private val tree = SimpleTree(treeModel).apply {
isRootVisible = false
selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
cellRenderer = PromptsFormTreeCellRenderer(root)
setupChildNodes()
addTreeSelectionListener { e ->
val node = (e.newLeadSelectionPath?.lastPathComponent as? PromptDetailsTreeNode)
if (node == null || node.parent == root) {
return@addTreeSelectionListener
}
categoryPanels[node.category]?.updateData(node.details)
cardLayout.show(promptDetailsContainer, node.category.name)
}
}
init {
runInEdt {
expandAll()
selectFirstPersonaNode()
}
}
fun createPanel(): JComponent {
return BorderLayoutPanel(8, 0)
.addToLeft(createToolbarDecorator().createPanel())
.addToCenter(promptDetailsContainer)
}
fun isModified(): Boolean {
val settings = service<PromptsSettings>().state
return isCoreActionsModified(settings.coreActions) ||
isChatActionsModified(settings.chatActions) ||
isPersonasModified(settings.personas)
}
fun applyChanges() {
val settings = service<PromptsSettings>().state
val coreActionsFormState = getFormState<CoreActionPromptDetails>(coreActionsNode)
settings.coreActions.apply {
editCode = coreActionsFormState[0].toState()
fixCompileErrors = coreActionsFormState[1].toState()
generateCommitMessage = coreActionsFormState[2].toState()
generateNameLookups = coreActionsFormState[3].toState()
}
settings.chatActions.prompts = getFormState<ChatActionPromptDetails>(chatActionsNode)
.map { it.toState() }
.toMutableList()
val personasFormState = getFormState<PersonaPromptDetails>(personasNode)
settings.personas.prompts = personasFormState
.map { it.toState() }
.toMutableList()
settings.personas.selectedPersona =
personasFormState.find { it.selected.get() }?.toState() ?: PersonasState.DEFAULT_PERSONA
}
fun resetChanges() {
removeAllChildNodes()
setupChildNodes()
reloadTreeView()
}
private fun removeAllChildNodes() {
coreActionsNode.removeAllChildren()
chatActionsNode.removeAllChildren()
personasNode.removeAllChildren()
}
private fun reloadTreeView() {
treeModel.reload()
expandAll()
selectFirstPersonaNode()
}
private fun selectFirstPersonaNode() {
val defaultNode = personasNode.getFirstChild() as? PromptDetailsTreeNode
if (defaultNode != null) {
tree.selectionPath = TreePath(defaultNode.path)
}
}
private fun expandAll() {
tree.expandPaths(
listOf(
TreePath(coreActionsNode.path),
TreePath(personasNode.path),
TreePath(chatActionsNode.path)
)
)
}
private fun isCoreActionsModified(settingsState: CoreActionsState): Boolean {
val formState = getFormState<CoreActionPromptDetails>(coreActionsNode)
val stateActions = listOf(
settingsState.editCode,
settingsState.fixCompileErrors,
settingsState.generateCommitMessage,
settingsState.generateNameLookups
)
return !stateActions.all { action ->
formState.find { it.code == action.code }
?.let { details ->
details.name == action.name && details.instructions == action.instructions
} ?: false
}
}
private fun isChatActionsModified(settingsState: ChatActionsState): Boolean {
val formState = getFormState<ChatActionPromptDetails>(chatActionsNode)
if (formState.size != settingsState.prompts.size) {
return true
}
return !formState.zip(settingsState.prompts)
.all { (details, prompt) ->
details.id == prompt.id &&
details.name == prompt.name &&
details.instructions == prompt.instructions
}
}
private fun isPersonasModified(settingsState: PersonasState): Boolean {
val formState = getFormState<PersonaPromptDetails>(personasNode)
if (formState.size != settingsState.prompts.size) {
return true
}
val selectedDefaultPersona = formState.find { it.selected.get() }
if (selectedDefaultPersona?.id != settingsState.selectedPersona.id) {
return true
}
return !formState.zip(settingsState.prompts)
.all { (details, prompt) ->
details.id == prompt.id &&
details.name == prompt.name &&
details.instructions == prompt.instructions
}
}
private fun setupChildNodes() {
val settings = service<PromptsSettings>().state
listOf(
settings.coreActions.editCode,
settings.coreActions.fixCompileErrors,
settings.coreActions.generateCommitMessage,
settings.coreActions.generateNameLookups
).forEach {
coreActionsNode.add(
PromptDetailsTreeNode(CoreActionPromptDetails(it), PromptCategory.CORE_ACTIONS)
)
}
settings.chatActions.prompts.forEach {
chatActionsNode.add(
PromptDetailsTreeNode(ChatActionPromptDetails(it), PromptCategory.CHAT_ACTIONS)
)
}
settings.personas.prompts.forEach {
val formDetails = PersonaPromptDetails(it)
formDetails.selected.set(settings.personas.selectedPersona.id == it.id)
personasNode.add(
PromptDetailsTreeNode(formDetails, PromptCategory.PERSONAS)
)
}
}
private fun createToolbarDecorator(): ToolbarDecorator =
ToolbarDecorator.createDecorator(tree)
.setAddAction { handleAddAction() }
.setAddActionUpdater {
val selectedNode = tree.selectionPath?.lastPathComponent
if (selectedNode is PromptDetailsTreeNode) {
selectedNode.category != PromptCategory.CORE_ACTIONS
} else {
selectedNode is DefaultMutableTreeNode && selectedNode.userObject != "Core Actions"
}
}
.setRemoveAction { handleRemoveAction() }
.setRemoveActionUpdater {
val selectedNode = tree.selectionPath?.lastPathComponent
selectedNode is PromptDetailsTreeNode
&& selectedNode.category != PromptCategory.CORE_ACTIONS
&& selectedNode.details.name != "CodeGPT Default"
}
.addExtraAction(object :
AnAction("Duplicate", "Duplicate prompt", AllIcons.Actions.Copy) {
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun update(e: AnActionEvent) {
val selectedNode = tree.selectionPath?.lastPathComponent
e.presentation.isEnabled =
selectedNode is PromptDetailsTreeNode && selectedNode.category != PromptCategory.CORE_ACTIONS
}
override fun actionPerformed(e: AnActionEvent) {
handleDuplicateAction()
}
})
.disableUpDownActions()
private fun handleAddAction() {
val category = determineCategory(tree.selectionPath?.lastPathComponent) ?: return
when (category) {
PromptCategory.CHAT_ACTIONS -> {
val newNode = PromptDetailsTreeNode(
ChatActionPromptDetails(
"New Action",
"New Prompt",
chatActionsNode.childCount.toLong() + 1,
null
),
PromptCategory.CHAT_ACTIONS
)
insertAndSelectNode(newNode, chatActionsNode)
}
PromptCategory.PERSONAS -> {
val newNode = PromptDetailsTreeNode(
PersonaPromptDetails(
"New Persona",
"New Prompt",
personasNode.childCount.toLong() + 1
),
PromptCategory.PERSONAS
)
insertAndSelectNode(newNode, personasNode)
}
else -> throw IllegalStateException("Could not add new node for category $category")
}
}
private fun handleDuplicateAction() {
val selectedNode = tree.selectionPath?.lastPathComponent as? PromptDetailsTreeNode ?: return
when (selectedNode.details) {
is ChatActionPromptDetails -> {
val newNode = PromptDetailsTreeNode(
ChatActionPromptDetails(
"${selectedNode.details.name} Copy",
selectedNode.details.instructions,
chatActionsNode.childCount.toLong() + 1,
null
),
PromptCategory.CHAT_ACTIONS
)
insertAndSelectNode(newNode, chatActionsNode)
}
is PersonaPromptDetails -> {
val newNode = PromptDetailsTreeNode(
PersonaPromptDetails(
"${selectedNode.details.name} Copy",
selectedNode.details.instructions,
personasNode.childCount.toLong() + 1
),
PromptCategory.PERSONAS
)
insertAndSelectNode(newNode, personasNode)
}
else -> throw IllegalStateException("Unknown node $selectedNode")
}
}
private fun handleDefaultPersonaChanged(promptDetails: PersonaPromptDetails) {
val previousDefaultPersonaNode = findPromptDetailsNode {
it.details is PersonaPromptDetails && it.details.selected.get()
}
if (previousDefaultPersonaNode != null) {
(previousDefaultPersonaNode.details as PersonaPromptDetails).selected.set(false)
}
promptDetails.selected.set(true)
treeModel.reload(previousDefaultPersonaNode)
treeModel.reload(tree.selectionPath?.lastPathComponent as? PromptDetailsTreeNode)
}
private fun handleRemoveAction() {
val selectedNode = tree.selectionPath?.lastPathComponent as? PromptDetailsTreeNode ?: return
treeModel.removeNodeFromParent(selectedNode)
categoryPanels[selectedNode.category]?.remove(selectedNode.details)
if (selectedNode.details is PersonaPromptDetails && selectedNode.details.selected.get()) {
val defaultPersonaNode = findPromptDetailsNode {
it.details is PersonaPromptDetails && it.details.id == 1L
}
if (defaultPersonaNode != null) {
handleDefaultPersonaChanged(defaultPersonaNode.details as PersonaPromptDetails)
}
}
}
private fun findPromptDetailsNode(predicate: (PromptDetailsTreeNode) -> Boolean): PromptDetailsTreeNode? {
return personasNode.children().toList()
.filterIsInstance<PromptDetailsTreeNode>()
.find(predicate)
}
private fun insertAndSelectNode(
newNode: PromptDetailsTreeNode,
parentNode: DefaultMutableTreeNode
) {
treeModel.insertNodeInto(newNode, parentNode, parentNode.childCount)
tree.selectionPath = TreePath(newNode.path)
}
private fun determineCategory(component: Any?): PromptCategory? = when (component) {
is PromptDetailsTreeNode -> component.category
personasNode -> PromptCategory.PERSONAS
chatActionsNode -> PromptCategory.CHAT_ACTIONS
else -> null
}
}

View file

@ -0,0 +1,51 @@
package ee.carlrobert.codegpt.settings.prompts.form
import com.intellij.ui.render.LabelBasedRenderer
import ee.carlrobert.codegpt.Icons
import ee.carlrobert.codegpt.settings.prompts.form.details.PersonaPromptDetails
import java.awt.Component
import java.awt.Font
import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode
class PromptsFormTreeCellRenderer(
private val root: DefaultMutableTreeNode
) : LabelBasedRenderer.Tree() {
override fun getTreeCellRendererComponent(
tree: JTree,
value: Any?,
selected: Boolean,
expanded: Boolean,
leaf: Boolean,
row: Int,
focused: Boolean
): Component {
super.getTreeCellRendererComponent(
tree,
value,
selected,
expanded,
leaf,
row,
focused
)
val node = value as? DefaultMutableTreeNode
font = if (node?.parent == root) {
font.deriveFont(Font.BOLD)
} else {
font.deriveFont(Font.PLAIN)
}
if (node is PromptDetailsTreeNode && node.category == PromptCategory.PERSONAS) {
val personaDetails = node.details as PersonaPromptDetails
icon = if (personaDetails.selected.get()) Icons.GreenCheckmark else null
iconTextGap = 6
} else {
icon = null
}
horizontalTextPosition = LEFT
return this
}
}

View file

@ -0,0 +1,47 @@
package ee.carlrobert.codegpt.settings.prompts.form
import ee.carlrobert.codegpt.settings.prompts.ChatActionPromptDetailsState
import ee.carlrobert.codegpt.settings.prompts.CoreActionPromptDetailsState
import ee.carlrobert.codegpt.settings.prompts.PersonaPromptDetailsState
import ee.carlrobert.codegpt.settings.prompts.form.details.ChatActionPromptDetails
import ee.carlrobert.codegpt.settings.prompts.form.details.CoreActionPromptDetails
import ee.carlrobert.codegpt.settings.prompts.form.details.FormPromptDetails
import ee.carlrobert.codegpt.settings.prompts.form.details.PersonaPromptDetails
import javax.swing.tree.DefaultMutableTreeNode
object PromptsFormUtil {
fun CoreActionPromptDetails.toState(): CoreActionPromptDetailsState {
val state = CoreActionPromptDetailsState()
state.code = this.code
state.name = this.name
state.instructions = this.instructions
return state
}
fun ChatActionPromptDetails.toState(): ChatActionPromptDetailsState {
val state = ChatActionPromptDetailsState()
state.id = this.id
state.code = this.code
state.name = this.name
state.instructions = this.instructions
return state
}
fun PersonaPromptDetails.toState(): PersonaPromptDetailsState {
val state = PersonaPromptDetailsState()
state.id = this.id
state.name = this.name
state.instructions = this.instructions
return state
}
inline fun <reified T : FormPromptDetails> getFormState(
formNode: DefaultMutableTreeNode,
): List<T> {
return formNode.children().toList()
.filterIsInstance<PromptDetailsTreeNode>()
.map { it.details }
.filterIsInstance<T>()
}
}

View file

@ -0,0 +1,101 @@
package ee.carlrobert.codegpt.settings.prompts.form.details
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.TextRange
import com.intellij.ui.JBColor
import java.awt.Dimension
abstract class AbstractEditorPromptPanel(
private val details: FormPromptDetails,
highlightedPlaceholders: List<String> = emptyList()
) : Disposable {
protected val editor: Editor = createEditor()
private val highlighterMap = mutableMapOf<String, RangeHighlighter>()
init {
highlightedPlaceholders.forEach {
highlightPlaceholder(it)
}
}
private fun createEditor(): Editor {
return service<EditorFactory>()
.run {
createEditor(createDocument(details.instructions ?: ""))
}
.apply {
settings.additionalLinesCount = 0
settings.isWhitespacesShown = true
settings.isLineMarkerAreaShown = false
settings.isIndentGuidesShown = false
settings.isLineNumbersShown = false
settings.isFoldingOutlineShown = false
settings.isUseSoftWraps = true
settings.isAdditionalPageAtBottom = false
settings.isVirtualSpace = false
settings.setSoftMargins(emptyList<Int>())
component.preferredSize = Dimension(0, lineHeight * 16)
document.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
details.instructions = event.document.text
highlighterMap.forEach { (placeholder, highlighter) ->
val start = editor.document.text.indexOf(placeholder)
if (start == -1) {
if (highlighter.isValid) {
highlighter.dispose()
}
} else if (!highlighter.isValid) {
highlighterMap[placeholder] =
createHighlighter(TextRange.from(start, placeholder.length))
}
}
}
})
}
}
private fun createHighlighter(range: TextRange): RangeHighlighter {
val attributes = TextAttributes().apply {
foregroundColor = JBColor(0x00627A, 0xCC7832)
}
return editor.markupModel.addRangeHighlighter(
range.startOffset,
range.endOffset,
HighlighterLayer.SELECTION,
attributes,
HighlighterTargetArea.EXACT_RANGE
)
}
private fun highlightPlaceholder(placeholder: String) {
val start = editor.document.text.indexOf(placeholder)
if (start >= 0) {
highlighterMap[placeholder] =
createHighlighter(TextRange.from(start, placeholder.length))
}
}
protected fun updateEditorText(text: String?) {
runWriteAction {
editor.document.setText(text ?: "")
}
}
override fun dispose() {
EditorFactory.getInstance().releaseEditor(editor)
}
}

View file

@ -0,0 +1,122 @@
package ee.carlrobert.codegpt.settings.prompts.form.details
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.ui.CardLayoutPanel
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.layout.ComponentPredicate
import com.intellij.util.ui.components.BorderLayoutPanel
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState.Companion.DEFAULT_EXPLAIN_PROMPT
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState.Companion.DEFAULT_FIND_BUGS_PROMPT
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState.Companion.DEFAULT_OPTIMIZE_PROMPT
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState.Companion.DEFAULT_REFACTOR_PROMPT
import ee.carlrobert.codegpt.settings.prompts.ChatActionsState.Companion.DEFAULT_WRITE_TESTS_PROMPT
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import javax.swing.JComponent
import javax.swing.JPanel
class ChatActionsDetailsPanel : PromptDetailsPanel {
private val cardLayoutPanel =
object : CardLayoutPanel<ChatActionPromptDetails, ChatActionPromptDetails, JComponent>() {
override fun prepare(key: ChatActionPromptDetails) = key
override fun create(state: ChatActionPromptDetails): JComponent {
val defaultInstructions = when (state.code) {
"FIND_BUGS" -> DEFAULT_FIND_BUGS_PROMPT
"WRITE_TESTS" -> DEFAULT_WRITE_TESTS_PROMPT
"EXPLAIN" -> DEFAULT_EXPLAIN_PROMPT
"REFACTOR" -> DEFAULT_REFACTOR_PROMPT
"OPTIMIZE" -> DEFAULT_OPTIMIZE_PROMPT
else -> null
}
return ChatActionEditorPanel(state, defaultInstructions).getPanel()
}
}
init {
service<PromptsSettings>().state.chatActions.prompts.forEach {
cardLayoutPanel.select(ChatActionPromptDetails(it), true)
}
}
override fun getPanel() = cardLayoutPanel
override fun updateData(details: FormPromptDetails) {
if (details !is ChatActionPromptDetails) {
return
}
cardLayoutPanel.select(details, true)
}
override fun remove(details: FormPromptDetails) {
if (details !is ChatActionPromptDetails) {
return
}
cardLayoutPanel.remove(cardLayoutPanel.getValue(details, false))
}
private class ChatActionEditorPanel(
private val details: ChatActionPromptDetails,
private val defaultInstructions: String? = "",
) : AbstractEditorPromptPanel(details, listOf("{SELECTION}")) {
private val nameField = JBTextField(details.name)
private val startInNewChatCheckBox = JBCheckBox(
"Start in a new chat window",
service<PromptsSettings>().state.chatActions.startInNewWindow
)
fun getPanel(): JPanel = panel {
row {
cell(BorderLayoutPanel().addToTop(editor.component)).align(Align.FILL)
.resizableColumn()
}
row {
link("Revert to default") {
updateEditorText(defaultInstructions)
}
}.visibleIf(object : ComponentPredicate() {
override fun addListener(listener: (Boolean) -> Unit) {
editor.document.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
listener(invoke())
}
})
}
override fun invoke(): Boolean {
if (defaultInstructions == null) {
return false
}
return defaultInstructions != editor.document.text
}
})
row {
cell(nameField)
.label("Action name:")
.align(Align.FILL)
.onChanged { details.name = it.text }
}
row {
cell(startInNewChatCheckBox)
.comment(
"If not checked, the previous chat history will be sent along with each action",
60
)
.onChanged {
service<PromptsSettings>().state
.chatActions
.startInNewWindow = it.isSelected
}
}
}
}
}

View file

@ -0,0 +1,119 @@
package ee.carlrobert.codegpt.settings.prompts.form.details
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.ui.CardLayoutPanel
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.layout.ComponentPredicate
import com.intellij.util.ui.components.BorderLayoutPanel
import ee.carlrobert.codegpt.settings.prompts.CommitMessageTemplate
import ee.carlrobert.codegpt.settings.prompts.*
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_EDIT_CODE_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_FIX_COMPILE_ERRORS_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT
import javax.swing.JComponent
import javax.swing.JPanel
class CoreActionsDetailsPanel : PromptDetailsPanel {
private val cardLayoutPanel =
object : CardLayoutPanel<CoreActionPromptDetails, CoreActionPromptDetails, JComponent>() {
override fun prepare(key: CoreActionPromptDetails) = key
override fun create(details: CoreActionPromptDetails): JComponent {
val editorPanel = when (details.code) {
"EDIT_CODE" -> CoreActionEditorPanel(
details,
DEFAULT_EDIT_CODE_PROMPT,
"Template used for the 'Edit Code' feature in the main editor."
)
"FIX_COMPILE_ERRORS" -> CoreActionEditorPanel(
details,
DEFAULT_FIX_COMPILE_ERRORS_PROMPT,
"Template used for resolving compile errors in the code."
)
"GENERATE_COMMIT_MESSAGE" -> CoreActionEditorPanel(
details,
DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT,
CommitMessageTemplate.getHtmlDescription(),
listOf("{BRANCH_NAME}", "{DATE_ISO_8601}")
)
"GENERATE_NAME_LOOKUPS" -> CoreActionEditorPanel(
details,
DEFAULT_GENERATE_NAME_LOOKUPS_PROMPT,
"Template used for generating suitable lookup names for a given method or function body."
)
else -> null
}
return editorPanel?.getPanel() ?: JPanel()
}
}
init {
val settings = service<PromptsSettings>().state.coreActions
listOf(
settings.editCode,
settings.fixCompileErrors,
settings.generateCommitMessage,
settings.generateNameLookups
).forEach {
cardLayoutPanel.select(CoreActionPromptDetails(it), true)
}
}
override fun getPanel() = cardLayoutPanel
override fun updateData(details: FormPromptDetails) {
if (details !is CoreActionPromptDetails || details.code.isNullOrEmpty()) {
return
}
cardLayoutPanel.select(details, true)
}
override fun remove(details: FormPromptDetails) {
TODO("Not implemented")
}
private class CoreActionEditorPanel(
details: CoreActionPromptDetails,
private val defaultInstructions: String,
private val description: String,
highlightedPlaceholders: List<String> = emptyList()
) : AbstractEditorPromptPanel(details, highlightedPlaceholders) {
fun getPanel(): JPanel = panel {
row {
cell(BorderLayoutPanel().addToTop(editor.component))
.align(Align.FILL)
.resizableColumn()
}
row {
link("Revert to default") {
updateEditorText(defaultInstructions)
}
}.visibleIf(object : ComponentPredicate() {
override fun addListener(listener: (Boolean) -> Unit) {
editor.document.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
listener(invoke())
}
})
}
override fun invoke(): Boolean = defaultInstructions != editor.document.text
})
row {
comment(description, 60)
}
}
}
}

View file

@ -0,0 +1,57 @@
package ee.carlrobert.codegpt.settings.prompts.form.details
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import ee.carlrobert.codegpt.settings.prompts.ChatActionPromptDetailsState
import ee.carlrobert.codegpt.settings.prompts.CoreActionPromptDetailsState
import ee.carlrobert.codegpt.settings.prompts.PersonaPromptDetailsState
import javax.swing.JComponent
interface PromptDetailsPanel {
fun getPanel(): JComponent
fun updateData(details: FormPromptDetails)
fun remove(details: FormPromptDetails)
}
sealed class FormPromptDetails {
abstract var name: String?
abstract var instructions: String?
}
data class CoreActionPromptDetails(
override var name: String?,
override var instructions: String?,
val code: String?
) : FormPromptDetails() {
constructor(state: CoreActionPromptDetailsState) : this(
name = state.name,
instructions = state.instructions,
code = state.code
)
}
data class ChatActionPromptDetails(
override var name: String?,
override var instructions: String?,
val id: Long,
val code: String?
) : FormPromptDetails() {
constructor(state: ChatActionPromptDetailsState) : this(
name = state.name,
instructions = state.instructions,
id = state.id,
code = state.code
)
}
data class PersonaPromptDetails(
override var name: String?,
override var instructions: String?,
val id: Long,
var selected: AtomicBooleanProperty = AtomicBooleanProperty(false)
) : FormPromptDetails() {
constructor(state: PersonaPromptDetailsState) : this(
name = state.name,
instructions = state.instructions,
id = state.id
)
}

View file

@ -0,0 +1,105 @@
package ee.carlrobert.codegpt.settings.prompts.form.details
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.observable.util.not
import com.intellij.ui.CardLayoutPanel
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.TopGap
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.layout.ComponentPredicate
import com.intellij.util.ui.components.BorderLayoutPanel
import ee.carlrobert.codegpt.settings.prompts.PersonasState.Companion.DEFAULT_PERSONA_PROMPT
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import javax.swing.JComponent
import javax.swing.JPanel
class PersonasDetailsPanel(onSelected: (PersonaPromptDetails) -> Unit) : PromptDetailsPanel {
private val cardLayoutPanel =
object : CardLayoutPanel<PersonaPromptDetails, PersonaPromptDetails, JComponent>() {
override fun prepare(key: PersonaPromptDetails): PersonaPromptDetails = key
override fun create(state: PersonaPromptDetails): JComponent {
return PersonaEditorPanel(state, onSelected).getPanel()
}
}
init {
service<PromptsSettings>().state.personas.prompts.forEach {
cardLayoutPanel.select(PersonaPromptDetails(it), true)
}
}
override fun getPanel() = cardLayoutPanel
override fun updateData(details: FormPromptDetails) {
if (details !is PersonaPromptDetails) {
return
}
cardLayoutPanel.select(details, true)
}
override fun remove(details: FormPromptDetails) {
if (details !is PersonaPromptDetails) {
return
}
cardLayoutPanel.remove(cardLayoutPanel.getValue(details, false))
}
private class PersonaEditorPanel(
private val details: PersonaPromptDetails,
private val onSelected: (PersonaPromptDetails) -> Unit
) : AbstractEditorPromptPanel(details) {
private val nameField = JBTextField(details.name).apply {
isEnabled = details.id != 1L
}
fun getPanel(): JPanel = panel {
row {
cell(BorderLayoutPanel().addToTop(editor.component))
.align(Align.FILL)
.resizableColumn()
}
row {
link("Revert to default") {
updateEditorText(DEFAULT_PERSONA_PROMPT)
}
}.visibleIf(object : ComponentPredicate() {
override fun addListener(listener: (Boolean) -> Unit) {
editor.document.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
listener(invoke())
}
})
}
override fun invoke(): Boolean {
return details.id == 1L && DEFAULT_PERSONA_PROMPT != editor.document.text
}
})
row {
comment(
"Set of instructions that serve as the starting point when starting a new chat session. This defines things for the model and helps to focus its capabilities.",
60
)
}
row {
cell(nameField)
.label("Persona name:")
.align(Align.FILL)
.onChanged { details.name = it.text }
}
row {
button("Set as Default") {
onSelected(details)
}.enabledIf(details.selected.not())
}.topGap(TopGap.SMALL)
}
}
}

View file

@ -4,6 +4,7 @@ import com.intellij.ui.components.ActionLink
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
@ -81,17 +82,17 @@ enum class LandingPanelAction(
FIND_BUGS(
"Find Bugs",
"Find bugs in this code",
"Find bugs and output code with bugs fixed in the selected code: {{selectedCode}}"
ChatActionsState.DEFAULT_FIND_BUGS_PROMPT
),
WRITE_TESTS(
"Write Tests",
"Write unit tests for this code",
"Write unit tests for the selected code: {{selectedCode}}"
ChatActionsState.DEFAULT_WRITE_TESTS_PROMPT
),
EXPLAIN(
"Explain",
"Explain the selected code",
"Explain the selected code: {{selectedCode}}"
ChatActionsState.DEFAULT_EXPLAIN_PROMPT
)
}

View file

@ -12,8 +12,8 @@ import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.documentation.DocumentationSettings
import ee.carlrobert.codegpt.settings.documentation.DocumentationsConfigurable
import ee.carlrobert.codegpt.settings.persona.PersonaDetails
import ee.carlrobert.codegpt.settings.persona.PersonasConfigurable
import ee.carlrobert.codegpt.settings.prompts.PersonaDetails
import ee.carlrobert.codegpt.settings.prompts.PromptsConfigurable
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.codegpt.ui.AddDocumentationDialog
import ee.carlrobert.codegpt.ui.DocumentationDetails
@ -148,7 +148,7 @@ class CreatePersonaActionItem : SuggestionActionItem {
override fun execute(project: Project, textPane: PromptTextField) {
service<ShowSettingsUtil>().showSettingsDialog(
project,
PersonasConfigurable::class.java
PromptsConfigurable::class.java
)
}
}

View file

@ -11,12 +11,11 @@ import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.Icons
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.documentation.DocumentationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaDetails
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PersonaDetails
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.codegpt.ui.DocumentationDetails
import ee.carlrobert.codegpt.util.GitUtil
import ee.carlrobert.codegpt.util.ResourceUtil.getDefaultPersonas
import ee.carlrobert.codegpt.util.file.FileUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -84,12 +83,10 @@ class PersonaSuggestionGroupItem : SuggestionGroupItem {
override val icon = AllIcons.General.User
override suspend fun getSuggestions(searchText: String?): List<SuggestionActionItem> {
val userCreatedPersonas = service<PersonaSettings>().state.userCreatedPersonas
return service<PromptsSettings>().state.personas.prompts
.map {
PersonaDetails(it.id, it.name ?: "Unknown", it.instructions ?: "Unknown")
}
.toMutableList()
return (userCreatedPersonas + getDefaultPersonas())
.filter {
searchText.isNullOrEmpty() || it.name.contains(searchText, true)
}

View file

@ -8,8 +8,7 @@ import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.gridLayout.UnscaledGaps
import com.intellij.util.ui.JBUI
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.ui.textarea.PromptTextField
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.*
import ee.carlrobert.codegpt.ui.textarea.suggestion.renderer.SuggestionItemRendererTextUtils.highlightSearchText
@ -115,7 +114,7 @@ class DefaultItemRenderer(textPane: PromptTextField) : BaseItemRenderer(textPane
private fun getDescription(item: SuggestionItem) =
if (item is PersonaSuggestionGroupItem) {
service<PersonaSettings>().state.selectedPersona.name
service<PromptsSettings>().state.personas.selectedPersona.name
} else {
null
}

View file

@ -1,15 +0,0 @@
package ee.carlrobert.codegpt.util
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import ee.carlrobert.codegpt.settings.persona.PersonaDetails
import ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent
object ResourceUtil {
fun getDefaultPersonas(): MutableList<PersonaDetails> {
return ObjectMapper().readValue(
getResourceContent("/prompts.json"),
object : TypeReference<MutableList<PersonaDetails>>() {})
}
}

View file

@ -44,9 +44,9 @@
instance="ee.carlrobert.codegpt.settings.service.LlamaServiceConfigurable"/>
<applicationConfigurable id="settings.codegpt.services.ollama" parentId="settings.codegpt.services" displayName="Ollama (Local)"
instance="ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsConfigurable"/>
<applicationConfigurable id="settings.codegpt.personas" parentId="settings.codegpt" displayName="Personas"
instance="ee.carlrobert.codegpt.settings.persona.PersonasConfigurable"/>
<applicationConfigurable id="settings.codegpt.dcoumentations" parentId="settings.codegpt" displayName="Documentations"
<applicationConfigurable id="settings.codegpt.prompts" parentId="settings.codegpt" displayName="Prompts"
instance="ee.carlrobert.codegpt.settings.prompts.PromptsConfigurable"/>
<applicationConfigurable id="settings.codegpt.documentations" parentId="settings.codegpt" displayName="Documentations"
instance="ee.carlrobert.codegpt.settings.documentation.DocumentationsConfigurable"/>
<applicationConfigurable id="settings.codegpt.configuration" parentId="settings.codegpt" displayName="Configuration"
instance="ee.carlrobert.codegpt.settings.configuration.ConfigurationConfigurable"/>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
Your task is to provide a clear, concise explanation of what this code does. Focus on the main functionality and purpose of the code, avoiding unnecessary details. Explain any complex logic or algorithms if present.
Provide your explanation in a few sentences, using simple language that a junior programmer could understand. If there are any notable best practices or potential improvements, briefly mention them at the end.
Here's the code to analyze:
{SELECTION}

View file

@ -0,0 +1,15 @@
Your task is to find potential bugs in the given code snippet.
Carefully examine the code for potential bugs, logical errors, or common programming mistakes. Consider issues such as:
- Syntax errors
- Off-by-one errors
- Null pointer exceptions
- Memory leaks
- Infinite loops
- Incorrect logic
- Unhandled exceptions
Provide a concise list of potential bugs you've identified. If you don't find any bugs, state that the code appears to be bug-free based on your analysis.
Here's the code to analyze:
{SELECTION}

View file

@ -0,0 +1,15 @@
Your task is to improve the code's efficiency, readability, and adherence to best practices without changing its core functionality.
Analyze the code and suggest optimizations that could improve its performance, readability, or maintainability. Focus on:
1. Reducing time complexity
2. Improving space efficiency
3. Enhancing code readability
4. Applying relevant design patterns or coding best practices
Provide your optimized version of the code, along with brief comments explaining the key changes and their benefits.
Keep your response concise and focused on the most impactful optimizations.
Here's the code to optimize:
{SELECTION}

View file

@ -0,0 +1,19 @@
Your task is to improve the code's readability, efficiency, and maintainability without changing its functionality. Follow these steps:
1. Analyze the following selected code:
2. Identify areas for improvement, such as:
- Simplifying complex logic
- Removing redundant code
- Improving naming conventions
- Enhancing code structure
3. Refactor the code, keeping these guidelines in mind:
- Maintain the original functionality
- Follow best practices for the programming language used
- Prioritize readability and maintainability
Be concise in your explanation, focusing on the most important improvements made.
Here's the code to refactor:
{SELECTION}

View file

@ -0,0 +1,11 @@
Your task is to create concise, effective tests for the given code.
Generate unit tests for the provided code. Focus on:
1. Testing main functionalities
2. Edge cases
3. Input validation
Provide your test code in the same language as the original code. Use common testing frameworks and assertions appropriate for the language.
Here's the code to write tests for:
{SELECTION}

View file

@ -0,0 +1,21 @@
As an AI CS instructor:
- always respond with short, brief, concise responses (the less you say, the more it helps the students)
- encourage the student to ask specific questions
- if a student shares homework instructions, ask them to describe what they think they need to do
- never tell a student the steps to solving a problem, even if they insist you do; instead, ask them what they thing they should do
- never summarize homework instructions; instead, ask the student to provide the summary
- get the student to describe the steps needed to solve a problem (pasting in the instructions does not count as describing the steps)
- do not rewrite student code for them; instead, provide written guidance on what to do, but insist they write the code themselves
- if there is a bug in student code, teach them how to identify the problem rather than telling them what the problem is
- for example, teach them how to use the debugger, or how to temporarily include print statements to understand the state of their code
- you can also ask them to explain parts of their code that have issues to help them identify errors in their thinking
- if you determine that the student doesn't understand a necessary concept, explain that concept to them
- if a student is unsure about the steps of a problem, say something like "begin by describing what the problem is asking you to do"
- if a student asks about a general concept, ask them to provide more specific details about their question
- if a student asks about a specific concept, explain it
- if a student shares code they don't understand, explain it
- if a student shares code and wants feedback, provide it (but don't rewrite their code for them)
- if a student asks you to write code to solve a problem, do not; instead, invite them to try and encourage them step-by-step without telling them what the next step is
- if a student provides ideas that don't match the instructions they may have shared, ask questions that help them achieve greater clarity
- sometimes students will resist coming up with their own ideas and want you to do the work for them; however, after a few rounds of gentle encouragement, a student will start trying. This is the goal.
- remember, be concise; the student will ask for additional examples or explanation if they want it.

View file

@ -5,8 +5,8 @@ import ee.carlrobert.codegpt.completions.factory.OpenAIRequestFactory
import ee.carlrobert.codegpt.conversations.Conversation
import ee.carlrobert.codegpt.conversations.ConversationService
import ee.carlrobert.codegpt.conversations.message.Message
import ee.carlrobert.codegpt.settings.persona.DEFAULT_PROMPT
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PersonasState.Companion.DEFAULT_PERSONA_PROMPT
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.groups.Tuple
@ -16,7 +16,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
fun testChatCompletionRequestWithSystemPromptOverride() {
useOpenAIService(OpenAIChatCompletionModel.GPT_3_5.code)
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val conversation = ConversationService.getInstance().startConversation()
val firstMessage = createDummyMessage(500)
val secondMessage = createDummyMessage(250)
@ -42,7 +42,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
fun testChatCompletionRequestWithoutSystemPromptOverride() {
useOpenAIService(OpenAIChatCompletionModel.GPT_3_5.code)
service<PersonaSettings>().state.selectedPersona.instructions = DEFAULT_PROMPT
service<PromptsSettings>().state.personas.selectedPersona.instructions = DEFAULT_PERSONA_PROMPT
val conversation = ConversationService.getInstance().startConversation()
val firstMessage = createDummyMessage(500)
val secondMessage = createDummyMessage(250)
@ -57,7 +57,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
assertThat(request.messages)
.extracting("role", "content")
.containsExactly(
Tuple.tuple("system", DEFAULT_PROMPT),
Tuple.tuple("system", DEFAULT_PERSONA_PROMPT),
Tuple.tuple("user", "TEST_PROMPT"),
Tuple.tuple("assistant", firstMessage.response),
Tuple.tuple("user", "TEST_PROMPT"),
@ -68,7 +68,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
fun testChatCompletionRequestRetry() {
useOpenAIService(OpenAIChatCompletionModel.GPT_3_5.code)
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val conversation = ConversationService.getInstance().startConversation()
val firstMessage = createDummyMessage("FIRST_TEST_PROMPT", 500)
val secondMessage = createDummyMessage("SECOND_TEST_PROMPT", 250)
@ -92,7 +92,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
fun testReducedChatCompletionRequest() {
useOpenAIService(OpenAIChatCompletionModel.GPT_3_5.code)
service<PersonaSettings>().state.selectedPersona.instructions = DEFAULT_PROMPT
service<PromptsSettings>().state.personas.selectedPersona.instructions = DEFAULT_PERSONA_PROMPT
val conversation = Conversation()
conversation.addMessage(createDummyMessage(50))
conversation.addMessage(createDummyMessage(100))
@ -110,7 +110,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
assertThat(request.messages)
.extracting("role", "content")
.containsExactly(
Tuple.tuple("system", DEFAULT_PROMPT),
Tuple.tuple("system", DEFAULT_PERSONA_PROMPT),
Tuple.tuple("user", "TEST_PROMPT"),
Tuple.tuple("assistant", remainingMessage.response),
Tuple.tuple("user", "TEST_CHAT_COMPLETION_PROMPT")

View file

@ -6,6 +6,7 @@ import ee.carlrobert.codegpt.conversations.ConversationService
import ee.carlrobert.codegpt.conversations.message.Message
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.llm.client.http.RequestEntity
import ee.carlrobert.llm.client.http.exchange.NdJsonStreamHttpExchange
import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange
@ -18,7 +19,7 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() {
fun testOpenAIChatCompletionCall() {
useOpenAIService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val message = Message("TEST_PROMPT")
val conversation = ConversationService.getInstance().startConversation()
expectOpenAI(StreamHttpExchange { request: RequestEntity ->
@ -57,7 +58,7 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() {
fun testAzureChatCompletionCall() {
useAzureService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val conversationService = ConversationService.getInstance()
val prevMessage = Message("TEST_PREV_PROMPT")
prevMessage.response = "TEST_PREV_RESPONSE"
@ -103,7 +104,7 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() {
fun testLlamaChatCompletionCall() {
useLlamaService()
service<ConfigurationSettings>().state.maxTokens = 99
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val message = Message("TEST_PROMPT")
val conversation = ConversationService.getInstance().startConversation()
conversation.addMessage(Message("Ping", "Pong"))
@ -144,7 +145,7 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() {
fun testOllamaChatCompletionCall() {
useOllamaService()
service<ConfigurationSettings>().state.maxTokens = 99
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val message = Message("TEST_PROMPT")
val conversation = ConversationService.getInstance().startConversation()
expectOllama(NdJsonStreamHttpExchange { request: RequestEntity ->
@ -183,7 +184,7 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() {
fun testGoogleChatCompletionCall() {
useGoogleService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val message = Message("TEST_PROMPT")
val conversation = ConversationService.getInstance().startConversation()
expectGoogle(StreamHttpExchange { request: RequestEntity ->
@ -228,7 +229,7 @@ class DefaultToolwindowChatCompletionRequestHandlerTest : IntegrationTest() {
fun testCodeGPTServiceChatCompletionCall() {
useCodeGPTService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
val message = Message("TEST_PROMPT")
val conversation = ConversationService.getInstance().startConversation()
expectCodeGPT(StreamHttpExchange { request: RequestEntity ->

View file

@ -1,6 +1,8 @@
package ee.carlrobert.codegpt.settings.configuration
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.settings.prompts.CommitMessageTemplate
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import git4idea.commands.GitCommand
import org.assertj.core.api.Assertions.assertThat
import testsupport.VcsTestCase
@ -12,7 +14,7 @@ class CommitMessageTemplateTest : VcsTestCase() {
git(GitCommand.INIT)
git(GitCommand.CHECKOUT, listOf("-b", "feature/my-cool-feature"))
registerRepository()
service<ConfigurationSettings>().state.commitMessagePrompt = buildString {
service<PromptsSettings>().state.coreActions.generateCommitMessage.instructions = buildString {
append("Branch: {BRANCH_NAME}\n")
append("Date: {DATE_ISO_8601}")
}

View file

@ -4,14 +4,13 @@ import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.CodeGPTKeys
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.ReferencedFile
import ee.carlrobert.codegpt.completions.CompletionRequestUtil.FIX_COMPILE_ERRORS_SYSTEM_PROMPT
import ee.carlrobert.codegpt.completions.ConversationType
import ee.carlrobert.codegpt.completions.HuggingFaceModel
import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA
import ee.carlrobert.codegpt.conversations.ConversationService
import ee.carlrobert.codegpt.conversations.message.Message
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
import ee.carlrobert.llm.client.http.RequestEntity
import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange
@ -28,7 +27,8 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
fun testSendingOpenAIMessage() {
useOpenAIService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions =
"TEST_SYSTEM_PROMPT"
val message = Message("Hello!")
val conversation = ConversationService.getInstance().startConversation()
val panel = ChatToolWindowTabPanel(project, conversation)
@ -104,7 +104,8 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
)
)
useOpenAIService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions =
"TEST_SYSTEM_PROMPT"
val message = Message("TEST_MESSAGE")
message.referencedFilePaths =
listOf("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")
@ -206,7 +207,8 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
Objects.requireNonNull(javaClass.getResource("/images/test-image.png")).path
project.putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, testImagePath)
useOpenAIService("gpt-4-vision-preview")
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions =
"TEST_SYSTEM_PROMPT"
val message = Message("TEST_MESSAGE")
val conversation = ConversationService.getInstance().startConversation()
val panel = ChatToolWindowTabPanel(project, conversation)
@ -299,7 +301,8 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
)
)
useOpenAIService()
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions =
"TEST_SYSTEM_PROMPT"
val message = Message("TEST_MESSAGE")
message.referencedFilePaths =
listOf("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")
@ -317,7 +320,10 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
.containsExactly(
"gpt-4",
listOf(
mapOf("role" to "system", "content" to FIX_COMPILE_ERRORS_SYSTEM_PROMPT),
mapOf(
"role" to "system",
"content" to service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions
),
mapOf(
"role" to "user", "content" to """
Use the following context to answer question at the end:
@ -399,7 +405,8 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
fun testSendingLlamaMessage() {
useLlamaService()
val configurationState = service<ConfigurationSettings>().state
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
service<PromptsSettings>().state.personas.selectedPersona.instructions =
"TEST_SYSTEM_PROMPT"
configurationState.maxTokens = 1000
configurationState.temperature = 0.1f
val llamaSettings = LlamaSettings.getCurrentState()