mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-21 11:05:59 +00:00
refactor: improve inlay action UI rendering
This commit is contained in:
parent
36ba3616de
commit
74608ec930
7 changed files with 183 additions and 171 deletions
|
|
@ -23,6 +23,7 @@ import ee.carlrobert.codegpt.conversations.Conversation;
|
|||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.actionprocessor.ActionProcessorFactory;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatToolWindowScrollablePanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
|
||||
|
|
@ -32,14 +33,7 @@ import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel;
|
|||
import ee.carlrobert.codegpt.toolwindow.ui.ChatToolWindowLandingPanel;
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedCodeActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedSuggestionActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
|
||||
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;
|
||||
import ee.carlrobert.codegpt.util.EditorUtil;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import java.awt.BorderLayout;
|
||||
|
|
@ -48,7 +42,6 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import kotlin.Unit;
|
||||
|
|
@ -272,162 +265,46 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
requestHandler.call(callParameters);
|
||||
}
|
||||
|
||||
private String processEditorSelection(Editor editor, Message message) {
|
||||
if (editor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SelectionModel selectionModel = editor.getSelectionModel();
|
||||
String selectedText = selectionModel.getSelectedText();
|
||||
if (selectedText == null || selectedText.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String fileExtension = FileUtil.getFileExtension(editor.getVirtualFile().getName());
|
||||
message.setPrompt(
|
||||
message.getPrompt() + String.format("%n```%s%n%s%n```", fileExtension, selectedText));
|
||||
selectionModel.removeSelection();
|
||||
return selectedText;
|
||||
}
|
||||
|
||||
private Unit handleSubmit(String text, List<? extends AppliedActionInlay> appliedInlayActions) {
|
||||
var message = new Message(text);
|
||||
var editor = EditorUtil.getSelectedEditor(project);
|
||||
String highlightedText = null;
|
||||
if (editor != null) {
|
||||
var selectionModel = editor.getSelectionModel();
|
||||
var selectedText = selectionModel.getSelectedText();
|
||||
if (selectedText != null && !selectedText.isEmpty()) {
|
||||
var fileExtension = FileUtil.getFileExtension(editor.getVirtualFile().getName());
|
||||
message = new Message(text + format("%n```%s%n%s%n```", fileExtension, selectedText));
|
||||
highlightedText = selectedText;
|
||||
selectionModel.removeSelection();
|
||||
}
|
||||
}
|
||||
message.setUserMessage(text);
|
||||
processAppliedInlayActions(message, appliedInlayActions, text, editor);
|
||||
sendMessage(message, ConversationType.DEFAULT, highlightedText);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
private void processAppliedInlayActions(
|
||||
Message message,
|
||||
List<? extends AppliedActionInlay> appliedInlayActions,
|
||||
String text,
|
||||
Editor editor) {
|
||||
for (var action : appliedInlayActions) {
|
||||
if (action instanceof AppliedSuggestionActionInlay) {
|
||||
processSuggestionActions(
|
||||
message,
|
||||
filterActions(appliedInlayActions, AppliedSuggestionActionInlay.class),
|
||||
text);
|
||||
} else if (action instanceof AppliedCodeActionInlay) {
|
||||
processCodeActions(
|
||||
message,
|
||||
filterActions(appliedInlayActions, AppliedCodeActionInlay.class),
|
||||
text,
|
||||
editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
var remainingText = new StringBuilder(text);
|
||||
var promptBuilder = new StringBuilder();
|
||||
|
||||
private <T extends AppliedActionInlay> List<T> filterActions(
|
||||
List<? extends AppliedActionInlay> actions,
|
||||
Class<T> actionClass) {
|
||||
return actions.stream()
|
||||
.filter(actionClass::isInstance)
|
||||
.map(actionClass::cast)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private boolean containsWebSearchActionInlay(List<AppliedSuggestionActionInlay> actions) {
|
||||
return actions.stream().anyMatch(it -> it.getSuggestion() instanceof WebSearchActionItem);
|
||||
}
|
||||
|
||||
private void processSuggestionActions(
|
||||
Message message,
|
||||
List<AppliedSuggestionActionInlay> actions,
|
||||
String text) {
|
||||
message.setWebSearchIncluded(containsWebSearchActionInlay(actions));
|
||||
processDocumentationAction(message, actions);
|
||||
processPersonaAction(message, actions);
|
||||
processGitCommitAction(message, actions, text);
|
||||
}
|
||||
|
||||
private void processDocumentationAction(
|
||||
Message message,
|
||||
List<AppliedSuggestionActionInlay> actions) {
|
||||
var addedDocumentation = CodeGPTKeys.ADDED_DOCUMENTATION.get(project);
|
||||
var appliedInlayExists = actions.stream().anyMatch(it -> {
|
||||
var suggestion = it.getSuggestion();
|
||||
return suggestion instanceof DocumentationActionItem
|
||||
|| suggestion instanceof CreateDocumentationActionItem;
|
||||
});
|
||||
|
||||
if (addedDocumentation != null && appliedInlayExists) {
|
||||
message.setDocumentationDetails(addedDocumentation);
|
||||
CodeGPTKeys.ADDED_DOCUMENTATION.set(project, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void processPersonaAction(Message message, List<AppliedSuggestionActionInlay> actions) {
|
||||
var addedPersona = CodeGPTKeys.ADDED_PERSONA.get(project);
|
||||
var personaInlayExists = actions.stream()
|
||||
.anyMatch(it -> it.getSuggestion() instanceof PersonaActionItem);
|
||||
|
||||
if (addedPersona != null && personaInlayExists) {
|
||||
message.setPersonaDetails(addedPersona);
|
||||
CodeGPTKeys.ADDED_PERSONA.set(project, null);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends AppliedActionInlay> void processActions(
|
||||
Message message,
|
||||
List<T> actions,
|
||||
String text,
|
||||
Function<T, String> codeExtractor,
|
||||
Function<T, String> languageExtractor) {
|
||||
var stringBuilder = new StringBuilder(text);
|
||||
var resultStringBuilder = new StringBuilder();
|
||||
int lastProcessedIndex = 0;
|
||||
|
||||
for (var actionInlay : actions) {
|
||||
for (var actionInlay : appliedInlayActions) {
|
||||
var inlayOffset = actionInlay.getInlay().getOffset();
|
||||
|
||||
resultStringBuilder
|
||||
.append(stringBuilder, lastProcessedIndex, Math.min(stringBuilder.length(), inlayOffset))
|
||||
.append('\n')
|
||||
.append(formatCodeBlock(languageExtractor.apply(actionInlay),
|
||||
codeExtractor.apply(actionInlay)))
|
||||
.append('\n');
|
||||
|
||||
lastProcessedIndex = inlayOffset;
|
||||
promptBuilder.append(remainingText, 0, Math.min(inlayOffset, remainingText.length()))
|
||||
.append("\n");
|
||||
ActionProcessorFactory.getProcessor(actionInlay)
|
||||
.process(message, actionInlay, editor, promptBuilder);
|
||||
remainingText.delete(0, inlayOffset);
|
||||
}
|
||||
promptBuilder.append(remainingText);
|
||||
|
||||
resultStringBuilder.append(stringBuilder, lastProcessedIndex, stringBuilder.length());
|
||||
message.setUserMessage(promptBuilder.toString());
|
||||
message.setPrompt(promptBuilder.toString());
|
||||
|
||||
var result = resultStringBuilder.toString();
|
||||
message.setUserMessage(result);
|
||||
message.setPrompt(result);
|
||||
}
|
||||
|
||||
private void processGitCommitAction(
|
||||
Message message,
|
||||
List<AppliedSuggestionActionInlay> actions,
|
||||
String text) {
|
||||
var gitCommitInlays = actions.stream()
|
||||
.filter(it -> it.getSuggestion() instanceof GitCommitActionItem)
|
||||
.toList();
|
||||
|
||||
if (!gitCommitInlays.isEmpty()) {
|
||||
processActions(
|
||||
message,
|
||||
gitCommitInlays,
|
||||
text,
|
||||
action -> ((GitCommitActionItem) action.getSuggestion()).getDiffString(),
|
||||
action -> "shell"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void processCodeActions(
|
||||
Message message,
|
||||
List<AppliedCodeActionInlay> actions,
|
||||
String text,
|
||||
Editor editor) {
|
||||
processActions(
|
||||
message,
|
||||
actions,
|
||||
text,
|
||||
AppliedCodeActionInlay::getCode,
|
||||
action -> FileUtil.getFileExtension(editor.getVirtualFile().getName())
|
||||
);
|
||||
}
|
||||
|
||||
private String formatCodeBlock(String fileExtension, String code) {
|
||||
return String.format("```%s\n%s\n```", fileExtension, code);
|
||||
sendMessage(message, ConversationType.DEFAULT, processEditorSelection(editor, message));
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
private Unit handleCancel() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.actionprocessor;
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
|
||||
|
||||
public interface ActionProcessor {
|
||||
|
||||
void process(Message message, AppliedActionInlay action, Editor editor,
|
||||
StringBuilder promptBuilder);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.actionprocessor;
|
||||
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedCodeActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedSuggestionActionInlay;
|
||||
|
||||
public class ActionProcessorFactory {
|
||||
|
||||
public static ActionProcessor getProcessor(AppliedActionInlay action) {
|
||||
if (action instanceof AppliedSuggestionActionInlay) {
|
||||
return new SuggestionActionProcessor();
|
||||
} else if (action instanceof AppliedCodeActionInlay) {
|
||||
return new CodeActionProcessor();
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown action type");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.actionprocessor;
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedCodeActionInlay;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
|
||||
public class CodeActionProcessor implements ActionProcessor {
|
||||
|
||||
@Override
|
||||
public void process(Message message, AppliedActionInlay action, Editor editor,
|
||||
StringBuilder promptBuilder) {
|
||||
if (!(action instanceof AppliedCodeActionInlay codeAction)) {
|
||||
throw new IllegalArgumentException("Invalid action type");
|
||||
}
|
||||
processCodeAction(codeAction, editor, promptBuilder);
|
||||
}
|
||||
|
||||
private void processCodeAction(AppliedCodeActionInlay action, Editor editor,
|
||||
StringBuilder promptBuilder) {
|
||||
promptBuilder
|
||||
.append("\n```%s\n".formatted(FileUtil.getFileExtension(editor.getVirtualFile().getName())))
|
||||
.append(action.getCode())
|
||||
.append("\n```\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.actionprocessor;
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public void process(Message message, AppliedActionInlay action, Editor editor,
|
||||
StringBuilder promptBuilder) {
|
||||
if (!(action instanceof AppliedSuggestionActionInlay suggestionAction)) {
|
||||
throw new IllegalArgumentException("Invalid action type");
|
||||
}
|
||||
processSuggestionAction(message, suggestionAction, editor, promptBuilder);
|
||||
}
|
||||
|
||||
private void processSuggestionAction(
|
||||
Message message,
|
||||
AppliedSuggestionActionInlay action,
|
||||
Editor editor,
|
||||
StringBuilder promptBuilder) {
|
||||
message.setWebSearchIncluded(action.getSuggestion() instanceof WebSearchActionItem);
|
||||
processDocumentationAction(message, action, editor);
|
||||
processPersonaAction(message, action, editor);
|
||||
processGitCommitAction(action, promptBuilder);
|
||||
}
|
||||
|
||||
private void processDocumentationAction(Message message, AppliedSuggestionActionInlay action,
|
||||
Editor editor) {
|
||||
var project = editor.getProject();
|
||||
var addedDocumentation = CodeGPTKeys.ADDED_DOCUMENTATION.get(project);
|
||||
var appliedInlayExists = action.getSuggestion() instanceof DocumentationActionItem
|
||||
|| action.getSuggestion() instanceof CreateDocumentationActionItem;
|
||||
|
||||
if (addedDocumentation != null && appliedInlayExists) {
|
||||
message.setDocumentationDetails(addedDocumentation);
|
||||
CodeGPTKeys.ADDED_DOCUMENTATION.set(project, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void processPersonaAction(Message message, AppliedSuggestionActionInlay action,
|
||||
Editor editor) {
|
||||
var project = editor.getProject();
|
||||
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) {
|
||||
if (action.getSuggestion() instanceof GitCommitActionItem gitCommitActionItem) {
|
||||
promptBuilder
|
||||
.append("\n```shell\n")
|
||||
.append(gitCommitActionItem.getDiffString())
|
||||
.append("\n```\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +114,8 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
|
||||
public ChatMessageResponseBody withResponse(String response) {
|
||||
for (var message : MarkdownUtil.splitCodeBlocks(response)) {
|
||||
currentlyProcessedEditorPanel = null;
|
||||
currentlyProcessedTextPane = null;
|
||||
processResponse(message, message.startsWith("```"), false);
|
||||
}
|
||||
|
||||
|
|
@ -250,12 +252,12 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
var codeBlock = ((FencedCodeBlock) child);
|
||||
var code = codeBlock.getContentChars().unescape();
|
||||
if (!code.isEmpty()) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
if (currentlyProcessedEditorPanel == null) {
|
||||
if (currentlyProcessedEditorPanel == null) {
|
||||
ApplicationManager.getApplication().invokeAndWait(() -> {
|
||||
prepareProcessingCode(code, codeBlock.getInfo().unescape());
|
||||
}
|
||||
EditorUtil.updateEditorDocument(currentlyProcessedEditorPanel.getEditor(), code);
|
||||
});
|
||||
});
|
||||
}
|
||||
EditorUtil.updateEditorDocument(currentlyProcessedEditorPanel.getEditor(), code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -283,9 +285,9 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
private void prepareProcessingCode(String code, String markdownLanguage) {
|
||||
hideCaret();
|
||||
currentlyProcessedTextPane = null;
|
||||
currentlyProcessedEditorPanel =
|
||||
new ResponseEditorPanel(project, code, markdownLanguage, readOnly, highlightedText,
|
||||
parentDisposable);
|
||||
currentlyProcessedEditorPanel = new ResponseEditorPanel(project, code, markdownLanguage,
|
||||
readOnly, highlightedText,
|
||||
parentDisposable);
|
||||
add(currentlyProcessedEditorPanel);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea.suggestion.item
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.vcs.changes.VcsIgnoreManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys
|
||||
import ee.carlrobert.codegpt.EncodingManager
|
||||
|
|
@ -121,12 +120,21 @@ class GitCommitActionItem(
|
|||
}
|
||||
|
||||
fun getDiffString(): String {
|
||||
return ReadAction.nonBlocking<String> {
|
||||
val repository = GitUtil.getProjectRepository(project) ?: return@nonBlocking ""
|
||||
val diff = GitUtil.getCommitDiff(project, repository, gitCommit.id.asString())
|
||||
.joinToString("\n")
|
||||
service<EncodingManager>().truncateText(diff, MAX_TOKENS, true)
|
||||
}.submit(AppExecutorUtil.getAppExecutorService()).get()
|
||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously<String, Exception>(
|
||||
{
|
||||
val repository = GitUtil.getProjectRepository(project)
|
||||
?: return@runProcessWithProgressSynchronously ""
|
||||
|
||||
val commitId = gitCommit.id.asString()
|
||||
val diff = GitUtil.getCommitDiff(project, repository, commitId)
|
||||
.joinToString("\n")
|
||||
|
||||
service<EncodingManager>().truncateText(diff, MAX_TOKENS, true)
|
||||
},
|
||||
"Getting Diff",
|
||||
true,
|
||||
project
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue