1.1.6 - Ability to execute custom prompts, add block caret when typing, other improvements

This commit is contained in:
Carl-Robert Linnupuu 2023-03-01 19:00:34 +00:00
parent 484511294d
commit fa1e2a486b
14 changed files with 192 additions and 67 deletions

View file

@ -1,6 +1,8 @@
package ee.carlrobert.chatgpt.client.chatgpt;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import ee.carlrobert.chatgpt.client.Subscriber;
import ee.carlrobert.chatgpt.client.chatgpt.response.ChatGPTResponse;
@ -27,11 +29,11 @@ public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
}
protected void onErrorOccurred() {
responseConsumer.accept("Something went wrong. Please try again later.");
responseConsumer.accept("\nSomething went wrong. Please try again later.");
}
protected void send(String responsePayload, String token) {
if (!responsePayload.isEmpty()) {
if (!responsePayload.isEmpty() && isValidJson(responsePayload)) {
try {
var response = objectMapper.readValue(responsePayload, ChatGPTResponse.class);
var author = response.getMessage().getAuthor();
@ -47,11 +49,13 @@ public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
throw new RuntimeException("Unable to deserialize the payload", e);
}
} else {
try {
var response = objectMapper.readValue(token, ChatGPTResponseDetail.class);
this.responseConsumer.accept(response.getDetail());
} catch (JsonProcessingException e) {
tryProcessingErrorResponse(token);
if (token != null && !token.isEmpty() && isValidJson(token)) {
try {
var response = objectMapper.readValue(token, ChatGPTResponseDetail.class);
this.responseConsumer.accept(response.getDetail());
} catch (JsonProcessingException e) {
tryProcessingErrorResponse(token);
}
}
}
}
@ -67,4 +71,15 @@ public class ChatGPTBodySubscriber extends Subscriber<ChatGPTResponse> {
future.completeExceptionally(e);
}
}
private boolean isValidJson(String json) {
ObjectMapper mapper = new ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
try {
mapper.readTree(json);
} catch (JacksonException e) {
return false;
}
return true;
}
}

View file

@ -4,6 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import ee.carlrobert.chatgpt.client.ClientFactory;
import ee.carlrobert.chatgpt.ide.toolwindow.ToolWindowService;
@ -11,26 +13,29 @@ import org.jetbrains.annotations.NotNull;
public abstract class BaseAction extends AnAction {
protected abstract String getPrompt(String selectedText);
protected abstract void initToolWindow(ToolWindow toolWindow);
protected abstract void actionPerformed(Project project, Editor editor, String selectedText);
public void actionPerformed(@NotNull AnActionEvent event) {
var project = event.getProject();
var editor = event.getData(PlatformDataKeys.EDITOR);
if (editor != null && project != null) {
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
var selectedText = editor.getSelectionModel().getSelectedText();
new ClientFactory().getClient().clearPreviousSession();
initToolWindow(toolWindowService.getToolWindow(project));
toolWindowService.removeAll();
toolWindowService.paintUserMessage(selectedText);
toolWindowService.sendMessage(getPrompt(selectedText), project, null);
actionPerformed(project, editor, editor.getSelectionModel().getSelectedText());
}
}
public void update(AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(e.getProject() != null);
}
protected void sendMessage(Project project, String prompt) {
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
new ClientFactory().getClient().clearPreviousSession();
initToolWindow(toolWindowService.getToolWindow(project));
toolWindowService.removeAll();
toolWindowService.paintUserMessage(prompt);
toolWindowService.sendMessage(prompt, project, null);
}
}

View file

@ -0,0 +1,37 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
public class CustomPromptAction extends BaseAction {
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Custom Prompt");
toolWindow.show();
}
protected void actionPerformed(Project project, Editor editor, String selectedText) {
if (selectedText != null && !selectedText.isEmpty()) {
var fileExtension = getFileExtension(((EditorImpl) editor).getVirtualFile().getName());
var dialog = new CustomPromptDialog(selectedText, fileExtension);
if (dialog.showAndGet()) {
SwingUtilities.invokeLater(() -> sendMessage(project, dialog.getPrompt()));
}
}
}
private String getFileExtension(String filename) {
Pattern pattern = Pattern.compile("[^.]+$");
Matcher matcher = pattern.matcher(filename);
if (matcher.find()) {
return matcher.group();
}
return null;
}
}

View file

@ -0,0 +1,57 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UI;
import ee.carlrobert.chatgpt.ide.toolwindow.components.SyntaxTextArea;
import javax.annotation.Nullable;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
public class CustomPromptDialog extends DialogWrapper {
private final String selectedText;
private final String fileExtension;
private final JTextArea prefixTextArea = new JTextArea();
private final SyntaxTextArea syntaxTextArea = new SyntaxTextArea(false, false, SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
public CustomPromptDialog(String selectedText, String fileExtension) {
super(true);
this.selectedText = selectedText;
this.fileExtension = fileExtension;
setTitle("Custom Prompt");
setSize(460, 320);
init();
}
@Nullable
@Override
protected JComponent createCenterPanel() {
prefixTextArea.setLineWrap(true);
prefixTextArea.setWrapStyleWord(true);
prefixTextArea.setMargin(JBUI.insets(5));
syntaxTextArea.setText(selectedText.trim());
// syntaxTextArea.setSyntaxEditingStyle(getSyntaxForFileExtension(fileExtension));
syntaxTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
return FormBuilder.createFormBuilder()
.addComponent(UI.PanelFactory.panel(prefixTextArea)
.withLabel("Prefix:")
.moveLabelOnTop()
.withComment(
"Example: Find bugs in the following code")
.createPanel())
.addVerticalGap(16)
.addComponent(new JBScrollPane(syntaxTextArea))
.getPanel();
}
public String getPrompt() {
return prefixTextArea.getText() + "\n\n" + syntaxTextArea.getText();
}
}

View file

@ -1,15 +1,17 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
public class ExplainAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Explain the following code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Explain Code");
toolWindow.show();
}
protected void actionPerformed(Project project, Editor editor, String selectedText) {
sendMessage(project, "Explain the following code:\n\n" + selectedText);
}
}

View file

@ -1,15 +1,17 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
public class FindBugsAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Find bugs in the following code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Find Bugs");
toolWindow.show();
}
protected void actionPerformed(Project project, Editor editor, String selectedText) {
sendMessage(project, "Find bugs in the following code:\n\n" + selectedText);
}
}

View file

@ -1,15 +1,17 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
public class OptimizeAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Optimize the following code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Optimize Code");
toolWindow.show();
}
protected void actionPerformed(Project project, Editor editor, String selectedText) {
sendMessage(project, "Optimize the following code:\n\n" + selectedText);
}
}

View file

@ -1,15 +1,17 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
public class RefactorAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Refactor the following code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Refactor Code");
toolWindow.show();
}
protected void actionPerformed(Project project, Editor editor, String selectedText) {
sendMessage(project, "Refactor the following code:\n\n" + selectedText);
}
}

View file

@ -1,15 +1,17 @@
package ee.carlrobert.chatgpt.ide.action;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
public class WriteTestsAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Generate unit tests for the following code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Write Tests");
toolWindow.show();
}
protected void actionPerformed(Project project, Editor editor, String selectedText) {
sendMessage(project, "Generate unit tests for the following code:\n\n" + selectedText);
}
}

View file

@ -29,6 +29,8 @@ import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.NotNull;
public class ToolWindowService implements LafManagerListener {
@ -65,20 +67,21 @@ public class ToolWindowService implements LafManagerListener {
} else if (settings.isChatGPTOptionSelected && settings.accessToken.isEmpty()) {
notifyMissingCredential(project, "Access token not provided.");
} else {
var textArea = new SyntaxTextArea();
var textArea = new SyntaxTextArea(true, true, SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
scrollablePanel.add(textArea);
textAreas.add(textArea);
var client = new ClientFactory().getClient();
client.getCompletionsAsync(prompt, message -> {
textArea.append(message);
if (scrollToBottom != null) {
scrollToBottom.run();
}
}, () -> {
client.getCompletionsAsync(prompt, message -> SwingUtilities.invokeLater(
() -> {
textArea.append(message);
if (scrollToBottom != null) {
scrollToBottom.run();
}
}
), () -> {
textArea.displayCopyButton();
textArea.enableSelection();
textArea.hideCaret();
});
}

View file

@ -11,14 +11,14 @@ import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import javax.swing.JButton;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.fife.ui.rtextarea.CaretStyle;
public class SyntaxTextArea extends RSyntaxTextArea {
public SyntaxTextArea() {
public SyntaxTextArea(boolean isReadOnly, boolean withBlockCaret, String syntax) {
super("");
setStyles();
setStyles(isReadOnly, withBlockCaret, syntax);
}
public void displayCopyButton() {
@ -27,9 +27,8 @@ public class SyntaxTextArea extends RSyntaxTextArea {
cb.install(this);
}
public void enableSelection() {
setEditable(false);
setEnabled(true);
public void hideCaret() {
getCaret().setVisible(false);
}
public void changeStyleViaThemeXml() {
@ -43,20 +42,24 @@ public class SyntaxTextArea extends RSyntaxTextArea {
}
}
private void setStyles() {
private void setStyles(boolean isReadOnly, boolean withBlockCaret, String syntax) {
setMargin(JBUI.insets(5));
setAntiAliasingEnabled(true);
setEnabled(false);
setEnabled(true);
setEditable(!isReadOnly);
setPaintTabLines(false);
setHighlightCurrentLine(false);
setLineWrap(true);
setWrapStyleWord(true);
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_MARKDOWN);
if (withBlockCaret) {
setCaretStyle(0, CaretStyle.BLOCK_STYLE);
getCaret().setVisible(true);
}
setSyntaxEditingStyle(syntax);
changeStyleViaThemeXml();
}
private void copyToClipboard() {
StringSelection stringSelection = new StringSelection(getText());
StringSelection stringSelection = new StringSelection(getText().trim());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
}