feat: fast code edits (#601)

* feat: initial implementation of direct code edits

* fix: popup model selection

* refactor: simplify code replacement logic

* feat: interactive code modifications

* refactor: remove junk
This commit is contained in:
Carl-Robert 2024-06-30 00:39:52 +03:00 committed by GitHub
parent cf5f38365d
commit 14a0d4085c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 690 additions and 181 deletions

View file

@ -11,7 +11,7 @@ import javax.swing.Icon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
abstract class BaseEditorAction extends AnAction {
public abstract class BaseEditorAction extends AnAction {
BaseEditorAction(
@Nullable @NlsActions.ActionText String text,

View file

@ -4,7 +4,6 @@ import static java.lang.String.format;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.util.ui.FormBuilder;

View file

@ -2,11 +2,11 @@ package ee.carlrobert.codegpt.actions.editor;
import static java.lang.String.format;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import ee.carlrobert.codegpt.CodeGPTKeys;
@ -47,6 +47,7 @@ public class EditorActionsUtil {
if (actionGroup instanceof DefaultActionGroup group) {
group.removeAll();
group.add(new AskAction());
group.add(new EditCodeAction(Actions.EditSource));
group.add(new CustomPromptAction());
group.addSeparator();

View file

@ -30,8 +30,8 @@ public class ReplaceCodeInMainEditorAction extends AnAction {
var project = event.getProject();
var toolWindowEditor = event.getData(PlatformDataKeys.EDITOR);
if (project != null && toolWindowEditor != null) {
EditorUtil.replaceMainEditorSelection(
project,
EditorUtil.replaceEditorSelection(
toolWindowEditor,
requireNonNull(toolWindowEditor.getSelectionModel().getSelectedText()));
}
}

View file

@ -122,6 +122,20 @@ public class CompletionRequestProvider {
.build();
}
public static OpenAIChatCompletionRequest buildEditCodeRequest(
String context,
String model) {
return new OpenAIChatCompletionRequest.Builder(
List.of(
new OpenAIChatCompletionStandardMessage(
"system",
getResourceContent("/prompts/edit-code.txt")),
new OpenAIChatCompletionStandardMessage("user", context)))
.setModel(model)
.setStream(true)
.build();
}
public static Request buildCustomOpenAICompletionRequest(String system, String context) {
return buildCustomOpenAIChatCompletionRequest(
ApplicationManager.getApplication().getService(CustomServiceSettings.class)

View file

@ -11,7 +11,7 @@ public class ConfigurationState {
private String systemPrompt = COMPLETION_SYSTEM_PROMPT;
private String commitMessagePrompt = GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT;
private int maxTokens = 1000;
private int maxTokens = 2048;
private double temperature = 0.1;
private boolean checkForPluginUpdates = true;
private boolean checkForNewScreenshots = false;

View file

@ -44,6 +44,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import java.util.function.Consumer;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
@ -274,7 +275,7 @@ public class ChatToolWindowTabPanel implements Disposable {
panel.add(JBUI.Panels.simplePanel(createUserPromptTextAreaHeader(
project,
selectedService,
() -> {
(provider) -> {
ConversationService.getInstance().startConversation();
contentManager.createNewTabPanel();
})), BorderLayout.NORTH);
@ -285,7 +286,7 @@ public class ChatToolWindowTabPanel implements Disposable {
private JPanel createUserPromptTextAreaHeader(
Project project,
ServiceType selectedService,
Runnable onModelChange) {
Consumer<ServiceType> onModelChange) {
return JBUI.Panels.simplePanel()
.withBorder(Borders.emptyBottom(8))
.andTransparent()

View file

@ -27,7 +27,7 @@ public class ReplaceSelectionAction extends TrackableAction {
public void handleAction(@NotNull AnActionEvent event) {
var project = requireNonNull(event.getProject());
if (EditorUtil.isMainEditorTextSelected(project)) {
EditorUtil.replaceMainEditorSelection(project, editor.getDocument().getText());
EditorUtil.replaceEditorSelection(editor, editor.getDocument().getText());
} else {
OverlayUtil.showSelectedEditorSelectionWarning(event);
}

View file

@ -1,7 +1,11 @@
package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC;
import static ee.carlrobert.codegpt.settings.service.ServiceType.AZURE;
import static ee.carlrobert.codegpt.settings.service.ServiceType.CODEGPT;
import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI;
import static ee.carlrobert.codegpt.settings.service.ServiceType.GOOGLE;
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA;
import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
import static java.lang.String.format;
@ -28,20 +32,35 @@ import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import javax.swing.Icon;
import javax.swing.JComponent;
import org.jetbrains.annotations.NotNull;
public class ModelComboBoxAction extends ComboBoxAction {
private final Runnable onModelChange;
private final Consumer<ServiceType> onModelChange;
private final Project project;
private final List<ServiceType> availableProviders;
public ModelComboBoxAction(Project project, Runnable onModelChange, ServiceType selectedService) {
public ModelComboBoxAction(
Project project,
Consumer<ServiceType> onModelChange,
ServiceType selectedService) {
this(project, onModelChange, selectedService, Arrays.asList(ServiceType.values()));
}
public ModelComboBoxAction(
Project project,
Consumer<ServiceType> onModelChange,
ServiceType selectedProvider,
List<ServiceType> availableProviders) {
this.project = project;
this.onModelChange = onModelChange;
updateTemplatePresentation(selectedService);
this.availableProviders = availableProviders;
updateTemplatePresentation(selectedProvider);
}
public JComponent createCustomComponent(@NotNull String place) {
@ -70,52 +89,71 @@ public class ModelComboBoxAction extends ComboBoxAction {
protected @NotNull DefaultActionGroup createPopupActionGroup(JComponent button) {
var presentation = ((ComboBoxButton) button).getPresentation();
var actionGroup = new DefaultActionGroup();
actionGroup.addSeparator("CodeGPT");
actionGroup.addAll(getCodeGPTModelActions(project, presentation));
actionGroup.addSeparator("OpenAI");
List.of(
OpenAIChatCompletionModel.GPT_4_O,
OpenAIChatCompletionModel.GPT_4_VISION_PREVIEW,
OpenAIChatCompletionModel.GPT_4_0125_128k,
OpenAIChatCompletionModel.GPT_3_5_0125_16k)
.forEach(model -> actionGroup.add(createOpenAIModelAction(model, presentation)));
actionGroup.addSeparator("Custom OpenAI");
actionGroup.add(createModelAction(
CUSTOM_OPENAI,
ApplicationManager.getApplication().getService(CustomServiceSettings.class)
.getState()
.getTemplate()
.getProviderName(),
Icons.OpenAI,
presentation));
actionGroup.addSeparator();
actionGroup.add(createModelAction(
ServiceType.ANTHROPIC,
"Anthropic (Claude)",
Icons.Anthropic,
presentation));
actionGroup.addSeparator();
actionGroup.add(
createModelAction(ServiceType.AZURE, "Azure OpenAI", Icons.Azure, presentation));
actionGroup.addSeparator();
actionGroup.add(createModelAction(
ServiceType.LLAMA_CPP,
getLlamaCppPresentationText(),
Icons.Llama,
presentation));
actionGroup.addSeparator("Ollama");
ApplicationManager.getApplication()
.getService(OllamaSettings.class)
.getState()
.getAvailableModels()
.forEach(model ->
actionGroup.add(createOllamaModelAction(model, presentation)));
actionGroup.addSeparator();
actionGroup.add(createModelAction(
ServiceType.GOOGLE,
"Google (Gemini)",
Icons.Google,
presentation));
if (availableProviders.contains(CODEGPT)) {
actionGroup.addSeparator("CodeGPT");
actionGroup.addAll(getCodeGPTModelActions(project, presentation));
}
if (availableProviders.contains(OPENAI)) {
actionGroup.addSeparator("OpenAI");
List.of(
OpenAIChatCompletionModel.GPT_4_O,
OpenAIChatCompletionModel.GPT_4_VISION_PREVIEW,
OpenAIChatCompletionModel.GPT_4_0125_128k,
OpenAIChatCompletionModel.GPT_3_5_0125_16k)
.forEach(model -> actionGroup.add(createOpenAIModelAction(model, presentation)));
}
if (availableProviders.contains(CUSTOM_OPENAI)) {
actionGroup.addSeparator("Custom OpenAI");
actionGroup.add(createModelAction(
CUSTOM_OPENAI,
ApplicationManager.getApplication().getService(CustomServiceSettings.class)
.getState()
.getTemplate()
.getProviderName(),
Icons.OpenAI,
presentation));
}
if (availableProviders.contains(ANTHROPIC)) {
actionGroup.addSeparator("Anthropic");
actionGroup.add(createModelAction(
ANTHROPIC,
"Anthropic (Claude)",
Icons.Anthropic,
presentation));
}
if (availableProviders.contains(AZURE)) {
actionGroup.addSeparator("Azure");
actionGroup.add(
createModelAction(AZURE, "Azure OpenAI", Icons.Azure, presentation));
}
if (availableProviders.contains(GOOGLE)) {
actionGroup.addSeparator("Google");
actionGroup.add(createModelAction(
GOOGLE,
"Google (Gemini)",
Icons.Google,
presentation));
}
if (availableProviders.contains(LLAMA_CPP)) {
actionGroup.addSeparator("LLaMA C/C++");
actionGroup.add(createModelAction(
LLAMA_CPP,
getLlamaCppPresentationText(),
Icons.Llama,
presentation));
}
if (availableProviders.contains(OLLAMA)) {
actionGroup.addSeparator("Ollama");
createOllamaModelAction("Default model", presentation);
ApplicationManager.getApplication()
.getService(OllamaSettings.class)
.getState()
.getAvailableModels()
.forEach(model ->
actionGroup.add(createOllamaModelAction(model, presentation)));
}
return actionGroup;
}
@ -211,7 +249,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
handleModelChange(serviceType, label, icon, comboBoxPresentation);
handleModelChange(serviceType, label, label, icon, comboBoxPresentation);
}
@Override
@ -224,12 +262,13 @@ public class ModelComboBoxAction extends ComboBoxAction {
private void handleModelChange(
ServiceType serviceType,
String label,
String code,
Icon icon,
Presentation comboBoxPresentation) {
GeneralSettings.getCurrentState().setSelectedService(serviceType);
comboBoxPresentation.setIcon(icon);
comboBoxPresentation.setText(label);
onModelChange.run();
onModelChange.accept(serviceType);
}
private AnAction createCodeGPTModelAction(CodeGPTModel model, Presentation comboBoxPresentation) {
@ -249,6 +288,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
handleModelChange(
CODEGPT,
model.getName(),
model.getCode(),
model.getIcon(),
comboBoxPresentation);
}
@ -280,6 +320,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
handleModelChange(
OLLAMA,
model,
model,
Icons.Ollama,
comboBoxPresentation);
}
@ -309,6 +350,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
handleModelChange(
OPENAI,
model.getDescription(),
model.getCode(),
Icons.OpenAI,
comboBoxPresentation);
}