From f831a1facd5fc2e6664a3bada470dcd5f0970e38 Mon Sep 17 00:00:00 2001 From: Carl-Robert Date: Fri, 29 Dec 2023 16:41:47 +0200 Subject: [PATCH] feat: add support for auto resolving compilation errors (#318) --- build.gradle.kts | 2 +- .../codegpt.java-conventions.gradle.kts | 2 +- .../embedding/EmbeddingsService.java | 26 +- .../{CheckedFile.java => ReferencedFile.java} | 25 +- .../default-completion-system-prompt.txt | 15 + .../resources/prompts/fix-compile-errors.txt | 3 + .../generate-commit-message-system-prompt.txt | 3 + .../ee/carlrobert/codegpt/CodeGPTKeys.java | 4 +- .../ProjectCompilationStatusListener.java | 109 +++++++ .../GenerateGitCommitMessageAction.java | 33 +-- .../actions/IncludeFilesInContextAction.java | 18 +- .../IncludeFilesInContextNotifier.java | 4 +- .../actions/editor/EditorActionsUtil.java | 9 +- .../toolwindow/OpenInEditorAction.java | 5 +- .../codegpt/completions/CallParameters.java | 39 +++ .../completions/CompletionClientProvider.java | 4 +- .../completions/CompletionRequestHandler.java | 51 ++-- .../CompletionRequestProvider.java | 102 ++++--- .../completions/CompletionRequestService.java | 73 +++-- .../CompletionResponseEventListener.java | 19 +- .../codegpt/completions/ConversationType.java | 9 + .../completions/MethodNameLookupListener.java | 43 +-- .../codegpt/conversations/Conversation.java | 4 - .../conversations/ConversationService.java | 30 +- .../indexes/CodebaseIndexingAction.java | 2 +- .../codegpt/indexes/CodebaseIndexingTask.java | 12 +- .../indexes/FolderStructureTreePanel.java | 6 +- .../configuration/ConfigurationState.java | 13 +- .../service/LlamaModelPreferencesForm.java | 3 +- .../codegpt/settings/state/SettingsState.java | 4 +- .../chat/BaseChatToolWindowTabPanel.java | 276 ----------------- .../chat/ChatToolWindowTabPanel.java | 279 +++++++++++++++++- ...WindowCompletionResponseEventListener.java | 12 +- .../ContextualChatToolWindowLandingPanel.java | 3 +- .../ContextualChatToolWindowTabPanel.java | 7 +- .../StandardChatToolWindowContentManager.java | 9 +- .../standard/StandardChatToolWindowPanel.java | 6 +- .../StandardChatToolWindowTabPanel.java | 9 +- .../StandardChatToolWindowTabbedPane.java | 2 - .../chat/ui/SelectedFilesAccordion.java | 2 +- .../chat/ui/SelectedFilesNotification.java | 12 +- .../chat/ui/textarea/TotalTokensPanel.java | 7 +- .../codegpt/ui/checkbox/FileCheckboxTree.java | 4 +- .../ui/checkbox/PsiElementCheckboxTree.java | 7 +- .../ui/checkbox/VirtualFileCheckboxTree.java | 6 +- src/main/resources/META-INF/plugin-java.xml | 6 + src/main/resources/META-INF/plugin.xml | 2 + .../resources/messages/codegpt.properties | 2 + .../CompletionRequestProviderTest.java | 65 +++- .../DefaultCompletionRequestHandlerTest.java | 20 +- .../StandardChatToolWindowTabPanelTest.java | 106 ++++++- 51 files changed, 919 insertions(+), 595 deletions(-) rename codegpt-core/src/main/java/ee/carlrobert/embedding/{CheckedFile.java => ReferencedFile.java} (66%) create mode 100644 codegpt-core/src/main/resources/prompts/default-completion-system-prompt.txt create mode 100644 codegpt-core/src/main/resources/prompts/fix-compile-errors.txt create mode 100644 codegpt-core/src/main/resources/prompts/generate-commit-message-system-prompt.txt create mode 100644 src/main/java/ee/carlrobert/codegpt/ProjectCompilationStatusListener.java create mode 100644 src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java create mode 100644 src/main/java/ee/carlrobert/codegpt/completions/ConversationType.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java create mode 100644 src/main/resources/META-INF/plugin-java.xml diff --git a/build.gradle.kts b/build.gradle.kts index c9c3fddd..ceee47cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,7 +38,7 @@ intellij { pluginName.set(properties("pluginName")) version.set(properties("platformVersion")) type.set(properties("platformType")) - plugins.set(listOf()) + plugins.set(listOf("java")) } changelog { diff --git a/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts index ee5cb1d4..e5ac8d8f 100644 --- a/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts @@ -23,7 +23,7 @@ checkstyle { } dependencies { - implementation("ee.carlrobert:llm-client:0.1.3") + implementation("ee.carlrobert:llm-client:0.2.0") } tasks { diff --git a/codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java b/codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java index 91da7f5f..1d77beb2 100644 --- a/codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java +++ b/codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java @@ -64,16 +64,16 @@ public class EmbeddingsService { } public List> createEmbeddings( - List checkedFiles, + List referencedFiles, @Nullable ProgressIndicator indicator) { var words = new ArrayList>(); - for (int i = 0; i < checkedFiles.size(); i++) { + for (int i = 0; i < referencedFiles.size(); i++) { try { - var checkedFile = checkedFiles.get(i); - addEmbeddings(checkedFile, words); + var referencedFile = referencedFiles.get(i); + addEmbeddings(referencedFile, words); if (indicator != null) { - indicator.setFraction((double) i / checkedFiles.size()); + indicator.setFraction((double) i / referencedFiles.size()); } } catch (Throwable t) { // ignore @@ -101,24 +101,26 @@ public class EmbeddingsService { .getContent(); } - private void addEmbeddings(CheckedFile checkedFile, List> prevEmbeddings) { - var fileExtension = checkedFile.getFileExtension(); + private void addEmbeddings( + ReferencedFile referencedFile, + List> prevEmbeddings) { + var fileExtension = referencedFile.getFileExtension(); var codeSplitter = SplitterFactory.getCodeSplitter(fileExtension); if (codeSplitter != null) { var chunks = codeSplitter.split( - checkedFile.getFileName(), - checkedFile.getFileContent()); + referencedFile.getFileName(), + referencedFile.getFileContent()); var embeddings = openAIClient.getEmbeddings(chunks); for (int i = 0; i < chunks.size(); i++) { prevEmbeddings.add( - new Word(chunks.get(i), checkedFile.getFileName(), normalize(embeddings.get(i)))); + new Word(chunks.get(i), referencedFile.getFileName(), normalize(embeddings.get(i)))); } } else { - var chunks = splitText(checkedFile.getFileContent(), 400); + var chunks = splitText(referencedFile.getFileContent(), 400); var embeddings = getEmbeddings(chunks); for (int i = 0; i < chunks.size(); i++) { prevEmbeddings.add( - new Word(chunks.get(i), checkedFile.getFileName(), normalize(embeddings.get(i)))); + new Word(chunks.get(i), referencedFile.getFileName(), normalize(embeddings.get(i)))); } } } diff --git a/codegpt-core/src/main/java/ee/carlrobert/embedding/CheckedFile.java b/codegpt-core/src/main/java/ee/carlrobert/embedding/ReferencedFile.java similarity index 66% rename from codegpt-core/src/main/java/ee/carlrobert/embedding/CheckedFile.java rename to codegpt-core/src/main/java/ee/carlrobert/embedding/ReferencedFile.java index a5ea9460..e4ce8e29 100644 --- a/codegpt-core/src/main/java/ee/carlrobert/embedding/CheckedFile.java +++ b/codegpt-core/src/main/java/ee/carlrobert/embedding/ReferencedFile.java @@ -4,16 +4,17 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class CheckedFile { +public class ReferencedFile { private final String fileName; private final String filePath; private final String fileContent; - public CheckedFile(File file) { + public ReferencedFile(File file) { this.fileName = file.getName(); this.filePath = file.getPath(); try { @@ -23,7 +24,7 @@ public class CheckedFile { } } - public CheckedFile(String fileName, String filePath, String fileContent) { + public ReferencedFile(String fileName, String filePath, String fileContent) { this.fileName = fileName; this.filePath = filePath; this.fileContent = fileContent; @@ -50,4 +51,22 @@ public class CheckedFile { } return ""; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ReferencedFile that = (ReferencedFile) o; + return Objects.equals(filePath, that.filePath); + } + + @Override + public int hashCode() { + return Objects.hash(filePath); + } } diff --git a/codegpt-core/src/main/resources/prompts/default-completion-system-prompt.txt b/codegpt-core/src/main/resources/prompts/default-completion-system-prompt.txt new file mode 100644 index 00000000..646a27fc --- /dev/null +++ b/codegpt-core/src/main/resources/prompts/default-completion-system-prompt.txt @@ -0,0 +1,15 @@ +You are an AI programming assistant. +Follow the user's requirements carefully & to the letter. +Your responses should be informative and logical. +You should always adhere to technical information. +If the user asks for code or technical questions, you must provide code suggestions and adhere to technical information. +If the question is related to a developer, you must respond with content related to a developer. +First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail. +Then output the code in a single code block. +Minimize any other prose. +Keep your answers short and impersonal. +Use Markdown formatting in your answers. +Make sure to include the programming language name at the start of the Markdown code blocks. +Avoid wrapping the whole response in triple backticks. +The user works in an IDE built by JetBrains which has a concept for editors with open files, integrated unit test support, and output pane that shows the output of running the code as well as an integrated terminal. +You can only give one reply for each conversation turn. \ No newline at end of file diff --git a/codegpt-core/src/main/resources/prompts/fix-compile-errors.txt b/codegpt-core/src/main/resources/prompts/fix-compile-errors.txt new file mode 100644 index 00000000..cd525143 --- /dev/null +++ b/codegpt-core/src/main/resources/prompts/fix-compile-errors.txt @@ -0,0 +1,3 @@ +I will provide you with a snippet of code that is causing a compilation error. +Your task is to identify the potential causes of the compilation error(s) and propose code solutions to fix them. +Please approach this step by step, explaining your reasoning as you go. \ No newline at end of file diff --git a/codegpt-core/src/main/resources/prompts/generate-commit-message-system-prompt.txt b/codegpt-core/src/main/resources/prompts/generate-commit-message-system-prompt.txt new file mode 100644 index 00000000..78d7cb09 --- /dev/null +++ b/codegpt-core/src/main/resources/prompts/generate-commit-message-system-prompt.txt @@ -0,0 +1,3 @@ +Write a short and descriptive git commit message for the following git diff. +Use imperative mood, present tense, active voice and verbs. +Your entire response will be passed directly into git commit. \ No newline at end of file diff --git a/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java b/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java index 1d93840c..e5a99fe3 100644 --- a/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java +++ b/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java @@ -1,10 +1,10 @@ package ee.carlrobert.codegpt; import com.intellij.openapi.util.Key; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.util.List; public class CodeGPTKeys { - public static final Key> SELECTED_FILES = Key.create("selectedFiles"); + public static final Key> SELECTED_FILES = Key.create("selectedFiles"); } diff --git a/src/main/java/ee/carlrobert/codegpt/ProjectCompilationStatusListener.java b/src/main/java/ee/carlrobert/codegpt/ProjectCompilationStatusListener.java new file mode 100644 index 00000000..19331118 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/ProjectCompilationStatusListener.java @@ -0,0 +1,109 @@ +package ee.carlrobert.codegpt; + +import static ee.carlrobert.codegpt.completions.ConversationType.FIX_COMPILE_ERRORS; +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import com.intellij.compiler.CompilerMessageImpl; +import com.intellij.notification.NotificationAction; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.compiler.CompilationStatusListener; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompilerMessage; +import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.project.Project; +import ee.carlrobert.codegpt.completions.CompletionRequestProvider; +import ee.carlrobert.codegpt.conversations.message.Message; +import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; +import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; +import ee.carlrobert.codegpt.ui.OverlayUtil; +import ee.carlrobert.embedding.ReferencedFile; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public class ProjectCompilationStatusListener implements CompilationStatusListener { + + private final Project project; + + public ProjectCompilationStatusListener(Project project) { + this.project = project; + } + + @Override + public void compilationFinished( + boolean aborted, + int errors, + int warnings, + @NotNull CompileContext compileContext) { + var configuration = ConfigurationState.getInstance(); + var success = !configuration.isCaptureCompileErrors() + || (!aborted && errors == 0 && warnings == 0); + if (success) { + return; + } + if (errors > 0) { + OverlayUtil.getDefaultNotification( + CodeGPTBundle.get("notification.compilationError.description"), + NotificationType.INFORMATION) + .addAction(NotificationAction.createSimpleExpiring( + CodeGPTBundle.get("notification.compilationError.okLabel"), + () -> project.getService(StandardChatToolWindowContentManager.class) + .sendMessage(getMultiFileMessage(compileContext), FIX_COMPILE_ERRORS))) + .addAction(NotificationAction.createSimpleExpiring( + CodeGPTBundle.get("checkForUpdatesTask.notification.hideButton"), + () -> ConfigurationState.getInstance().setCaptureCompileErrors(false))) + .notify(project); + } + } + + private Message getMultiFileMessage(CompileContext compileContext) { + var errorMapping = getErrorMapping(compileContext); + var prompt = errorMapping.values().stream() + .flatMap(Collection::stream) + .collect(joining("\n\n")); + + var message = new Message("Fix the following compile errors:\n\n" + prompt); + message.setReferencedFilePaths(errorMapping.keySet().stream() + .map(ReferencedFile::getFilePath) + .collect(toList())); + message.setUserMessage(message.getPrompt()); + message.setPrompt(CompletionRequestProvider.getPromptWithContext( + new ArrayList<>(errorMapping.keySet()), + prompt)); + return message; + } + + private HashMap> getErrorMapping(CompileContext compileContext) { + var errorMapping = new HashMap>(); + for (var compilerMessage : compileContext.getMessages(CompilerMessageCategory.ERROR)) { + var key = new ReferencedFile(new File(compilerMessage.getVirtualFile().getPath())); + var prevValue = errorMapping.get(key); + if (prevValue == null) { + prevValue = new ArrayList<>(); + } + prevValue.add(getCompilerErrorDetails(compilerMessage)); + errorMapping.put(key, prevValue); + } + return errorMapping; + } + + private String getCompilerErrorDetails(CompilerMessage compilerMessage) { + if (compilerMessage instanceof CompilerMessageImpl) { + return format( + "%s:%d:%d - `%s`", + compilerMessage.getVirtualFile().getName(), + ((CompilerMessageImpl) compilerMessage).getLine(), + ((CompilerMessageImpl) compilerMessage).getColumn(), + compilerMessage.getMessage()); + } + return format( + "%s - `%s`", + compilerMessage.getVirtualFile().getName(), + compilerMessage.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/ee/carlrobert/codegpt/actions/GenerateGitCommitMessageAction.java b/src/main/java/ee/carlrobert/codegpt/actions/GenerateGitCommitMessageAction.java index 91f90564..b2ada8ec 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/GenerateGitCommitMessageAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/GenerateGitCommitMessageAction.java @@ -23,17 +23,13 @@ import com.intellij.openapi.vcs.ui.CommitMessage; import ee.carlrobert.codegpt.CodeGPTBundle; import ee.carlrobert.codegpt.EncodingManager; import ee.carlrobert.codegpt.Icons; -import ee.carlrobert.codegpt.completions.CompletionClientProvider; +import ee.carlrobert.codegpt.completions.CompletionRequestService; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; -import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.settings.service.ServiceType; -import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.ui.OverlayUtil; import ee.carlrobert.llm.client.openai.completion.ErrorDetails; -import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage; -import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest; import ee.carlrobert.llm.completion.CompletionEventListener; import java.io.BufferedReader; import java.io.File; @@ -59,7 +55,7 @@ public class GenerateGitCommitMessageAction extends AnAction { public void update(@NotNull AnActionEvent event) { var selectedService = SettingsState.getInstance().getSelectedService(); if (selectedService == ServiceType.OPENAI || selectedService == ServiceType.AZURE) { - var filesSelected = !getCheckedFilePaths(event).isEmpty(); + var filesSelected = !getReferencedFilePaths(event).isEmpty(); var callAllowed = (selectedService == ServiceType.OPENAI && OpenAICredentialsManager.getInstance().isApiKeySet()) || (selectedService == ServiceType.AZURE @@ -82,7 +78,7 @@ public class GenerateGitCommitMessageAction extends AnAction { return; } - var gitDiff = getGitDiff(project, getCheckedFilePaths(event)); + var gitDiff = getGitDiff(project, getReferencedFilePaths(event)); var tokenCount = encodingManager.countTokens(gitDiff); if (tokenCount > 4096 && OverlayUtil.showTokenSoftLimitWarningDialog(tokenCount) != OK) { return; @@ -91,25 +87,8 @@ public class GenerateGitCommitMessageAction extends AnAction { var editor = getCommitMessageEditor(event); if (editor != null) { ((EditorEx) editor).setCaretVisible(false); - generateMessage(project, editor, gitDiff); - } - } - - private void generateMessage(Project project, Editor editor, String gitDiff) { - var request = new OpenAIChatCompletionRequest.Builder(List.of( - new OpenAIChatCompletionMessage("system", - ConfigurationState.getInstance().getCommitMessagePrompt()), - new OpenAIChatCompletionMessage("user", gitDiff))) - .setModel(OpenAISettingsState.getInstance().getModel()) - .build(); - var selectedService = SettingsState.getInstance().getSelectedService(); - if (selectedService == ServiceType.OPENAI) { - CompletionClientProvider.getOpenAIClient() - .getChatCompletion(request, getEventListener(project, editor.getDocument())); - } - if (selectedService == ServiceType.AZURE) { - CompletionClientProvider.getAzureClient() - .getChatCompletion(request, getEventListener(project, editor.getDocument())); + CompletionRequestService.getInstance() + .generateCommitMessageAsync(gitDiff, getEventListener(project, editor.getDocument())); } } @@ -166,7 +145,7 @@ public class GenerateGitCommitMessageAction extends AnAction { } } - private @NotNull List getCheckedFilePaths(AnActionEvent event) { + private @NotNull List getReferencedFilePaths(AnActionEvent event) { var changesBrowserBase = event.getData(ChangesBrowserBase.DATA_KEY); if (changesBrowserBase == null) { return List.of(); diff --git a/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextAction.java b/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextAction.java index 86c71149..9d724cce 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextAction.java @@ -30,7 +30,7 @@ import ee.carlrobert.codegpt.ui.checkbox.FileCheckboxTree; import ee.carlrobert.codegpt.ui.checkbox.PsiElementCheckboxTree; import ee.carlrobert.codegpt.ui.checkbox.VirtualFileCheckboxTree; import ee.carlrobert.codegpt.util.file.FileUtil; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.awt.Dimension; import java.io.IOException; import java.nio.file.Files; @@ -62,7 +62,7 @@ public class IncludeFilesInContextAction extends AnAction { throw new RuntimeException("Could not obtain file tree"); } - var totalTokensLabel = new TotalTokensLabel(checkboxTree.getCheckedFiles()); + var totalTokensLabel = new TotalTokensLabel(checkboxTree.getReferencedFiles()); checkboxTree.addCheckboxTreeListener(new CheckboxTreeListener() { @Override public void nodeStateChanged(@NotNull CheckedTreeNode node) { @@ -81,10 +81,10 @@ public class IncludeFilesInContextAction extends AnAction { totalTokensLabel, checkboxTree); if (show == OK_EXIT_CODE) { - project.putUserData(CodeGPTKeys.SELECTED_FILES, checkboxTree.getCheckedFiles()); + project.putUserData(CodeGPTKeys.SELECTED_FILES, checkboxTree.getReferencedFiles()); project.getMessageBus() .syncPublisher(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC) - .filesIncluded(checkboxTree.getCheckedFiles()); + .filesIncluded(checkboxTree.getReferencedFiles()); includedFilesSettings.setPromptTemplate(promptTemplateTextArea.getText()); includedFilesSettings.setRepeatableContext(repeatableContextTextArea.getText()); } @@ -111,9 +111,9 @@ public class IncludeFilesInContextAction extends AnAction { private int fileCount; private int totalTokens; - TotalTokensLabel(List checkedFiles) { - fileCount = checkedFiles.size(); - totalTokens = calculateTotalTokens(checkedFiles); + TotalTokensLabel(List referencedFiles) { + fileCount = referencedFiles.size(); + totalTokens = calculateTotalTokens(referencedFiles); updateText(); } @@ -167,8 +167,8 @@ public class IncludeFilesInContextAction extends AnAction { FileUtil.convertLongValue(totalTokens))); } - private int calculateTotalTokens(List checkedFiles) { - return checkedFiles.stream() + private int calculateTotalTokens(List referencedFiles) { + return referencedFiles.stream() .mapToInt(file -> encodingManager.countTokens(file.getFileContent())) .sum(); } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextNotifier.java b/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextNotifier.java index 1ca00ed7..0bd22934 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextNotifier.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/IncludeFilesInContextNotifier.java @@ -1,7 +1,7 @@ package ee.carlrobert.codegpt.actions; import com.intellij.util.messages.Topic; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.util.List; public interface IncludeFilesInContextNotifier { @@ -9,5 +9,5 @@ public interface IncludeFilesInContextNotifier { Topic FILES_INCLUDED_IN_CONTEXT_TOPIC = Topic.create("filesIncludedInContext", IncludeFilesInContextNotifier.class); - void filesIncluded(List includedFiles); + void filesIncluded(List includedFiles); } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java b/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java index feee78c1..53f0eead 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java @@ -15,7 +15,7 @@ import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; import ee.carlrobert.codegpt.util.file.FileUtil; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -67,15 +67,12 @@ public class EditorActionsUtil { message.setUserMessage(prompt.replace("{{selectedCode}}", "")); var toolWindowContentManager = project.getService(StandardChatToolWindowContentManager.class); - var toolWindow = toolWindowContentManager.getToolWindow(); - if (toolWindow != null) { - toolWindow.show(); - } + toolWindowContentManager.getToolWindow().show(); message.setReferencedFilePaths( Stream.ofNullable(project.getUserData(CodeGPTKeys.SELECTED_FILES)) .flatMap(Collection::stream) - .map(CheckedFile::getFilePath) + .map(ReferencedFile::getFilePath) .collect(toList())); toolWindowContentManager.sendMessage(message); } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/OpenInEditorAction.java b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/OpenInEditorAction.java index 3dfb2e39..c3ba49d7 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/OpenInEditorAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/OpenInEditorAction.java @@ -1,5 +1,6 @@ package ee.carlrobert.codegpt.actions.toolwindow; +import static java.lang.String.format; import static java.util.Objects.requireNonNull; import com.intellij.icons.AllIcons; @@ -40,11 +41,11 @@ public class OpenInEditorAction extends AnAction { if (project != null && currentConversation != null) { var dateTimeStamp = currentConversation.getUpdatedOn() .format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); - var fileName = String.format("%s_%s.md", currentConversation.getModel(), dateTimeStamp); + var fileName = format("%s_%s.md", currentConversation.getModel(), dateTimeStamp); var fileContent = currentConversation .getMessages() .stream() - .map(it -> String.format("### User:\n%s\n### CodeGPT:\n%s\n", it.getPrompt(), + .map(it -> format("### User:\n%s\n### CodeGPT:\n%s\n", it.getPrompt(), it.getResponse())) .collect(Collectors.joining()); VirtualFile file = new LightVirtualFile(fileName, fileContent); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java b/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java new file mode 100644 index 00000000..e4aa3137 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/completions/CallParameters.java @@ -0,0 +1,39 @@ +package ee.carlrobert.codegpt.completions; + +import ee.carlrobert.codegpt.conversations.Conversation; +import ee.carlrobert.codegpt.conversations.message.Message; + +public class CallParameters { + + private final Conversation conversation; + private final ConversationType conversationType; + private final Message message; + private final boolean retry; + + public CallParameters( + Conversation conversation, + ConversationType conversationType, + Message message, + boolean retry) { + this.conversation = conversation; + this.conversationType = conversationType; + this.message = message; + this.retry = retry; + } + + public Conversation getConversation() { + return conversation; + } + + public ConversationType getConversationType() { + return conversationType; + } + + public Message getMessage() { + return message; + } + + public boolean isRetry() { + return retry; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index a8ad98b8..c2e3b719 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -1,5 +1,7 @@ package ee.carlrobert.codegpt.completions; +import static java.lang.String.format; + import ee.carlrobert.codegpt.CodeGPTPlugin; import ee.carlrobert.codegpt.completions.you.YouUserManager; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; @@ -43,7 +45,7 @@ public class CompletionClientProvider { .setActiveDirectoryAuthentication(settings.isUseAzureActiveDirectoryAuthentication()); var baseHost = settings.getBaseHost(); if (baseHost != null) { - builder.setUrl(String.format(baseHost, params.getResourceName())); + builder.setUrl(format(baseHost, params.getResourceName())); } return builder.build(); } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java index 8e387185..d338cdf4 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java @@ -1,8 +1,6 @@ package ee.carlrobert.codegpt.completions; import com.intellij.openapi.diagnostic.Logger; -import ee.carlrobert.codegpt.conversations.Conversation; -import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.llm.client.openai.completion.ErrorDetails; @@ -12,7 +10,6 @@ import ee.carlrobert.llm.completion.CompletionEventListener; import java.util.List; import javax.swing.SwingWorker; import okhttp3.sse.EventSource; -import org.jetbrains.annotations.NotNull; public class CompletionRequestHandler { @@ -31,8 +28,8 @@ public class CompletionRequestHandler { this.completionResponseEventListener = completionResponseEventListener; } - public void call(Conversation conversation, Message message, boolean retry) { - swingWorker = new CompletionRequestWorker(conversation, message, retry); + public void call(CallParameters callParameters) { + swingWorker = new CompletionRequestWorker(callParameters); swingWorker.execute(); } @@ -44,13 +41,11 @@ public class CompletionRequestHandler { } private EventSource startCall( - @NotNull Conversation conversation, - @NotNull Message message, - boolean retry, + CallParameters callParameters, CompletionEventListener eventListener) { try { return CompletionRequestService.getInstance() - .getChatCompletionAsync(conversation, message, retry, useContextualSearch, eventListener); + .getChatCompletionAsync(callParameters, useContextualSearch, eventListener); } catch (Throwable ex) { handleCallException(ex); throw ex; @@ -69,26 +64,20 @@ public class CompletionRequestHandler { private class CompletionRequestWorker extends SwingWorker { - private final Conversation conversation; - private final Message message; - private final boolean retry; + private final CallParameters callParameters; - public CompletionRequestWorker(Conversation conversation, Message message, boolean retry) { - this.conversation = conversation; - this.message = message; - this.retry = retry; + public CompletionRequestWorker(CallParameters callParameters) { + this.callParameters = callParameters; } protected Void doInBackground() { var settings = SettingsState.getInstance(); try { - eventSource = startCall( - conversation, - message, - retry, - new YouRequestCompletionEventListener()); + eventSource = startCall(callParameters, new YouRequestCompletionEventListener()); } catch (TotalUsageExceededException e) { - completionResponseEventListener.handleTokensExceeded(conversation, message); + completionResponseEventListener.handleTokensExceeded( + callParameters.getConversation(), + callParameters.getMessage()); } finally { sendInfo(settings); } @@ -96,7 +85,7 @@ public class CompletionRequestHandler { } protected void process(List chunks) { - message.setResponse(messageBuilder.toString()); + callParameters.getMessage().setResponse(messageBuilder.toString()); for (String text : chunks) { messageBuilder.append(text); completionResponseEventListener.handleMessage(text); @@ -107,7 +96,7 @@ public class CompletionRequestHandler { @Override public void onSerpResults(List results) { - completionResponseEventListener.handleSerpResults(results, message); + completionResponseEventListener.handleSerpResults(results, callParameters.getMessage()); } @Override @@ -117,11 +106,7 @@ public class CompletionRequestHandler { @Override public void onComplete(StringBuilder messageBuilder) { - completionResponseEventListener.handleCompleted( - messageBuilder.toString(), - message, - conversation, - retry); + completionResponseEventListener.handleCompleted(messageBuilder.toString(), callParameters); } @Override @@ -136,8 +121,8 @@ public class CompletionRequestHandler { private void sendInfo(SettingsState settings) { TelemetryAction.COMPLETION.createActionMessage() - .property("conversationId", conversation.getId().toString()) - .property("model", conversation.getModel()) + .property("conversationId", callParameters.getConversation().getId().toString()) + .property("model", callParameters.getConversation().getModel()) .property("service", settings.getSelectedService().getCode().toLowerCase()) .send(); } @@ -150,8 +135,8 @@ public class CompletionRequestHandler { .property("code", "INSUFFICIENT_QUOTA"); } else { telemetryMessage - .property("conversationId", conversation.getId().toString()) - .property("model", conversation.getModel()) + .property("conversationId", callParameters.getConversation().getId().toString()) + .property("model", callParameters.getConversation().getModel()) .error(new RuntimeException(error.toString(), ex)); } telemetryMessage.send(); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index b62bfa15..8e41322f 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -1,6 +1,8 @@ package ee.carlrobert.codegpt.completions; import static ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent; +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import com.intellij.openapi.application.ApplicationManager; @@ -14,6 +16,7 @@ import ee.carlrobert.codegpt.conversations.ConversationsState; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.settings.service.ServiceType; +import ee.carlrobert.codegpt.settings.state.IncludedFilesSettingsState; import ee.carlrobert.codegpt.settings.state.LlamaSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; @@ -21,6 +24,7 @@ import ee.carlrobert.codegpt.settings.state.YouSettingsState; import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration; import ee.carlrobert.codegpt.telemetry.core.service.UserId; import ee.carlrobert.embedding.EmbeddingsService; +import ee.carlrobert.embedding.ReferencedFile; import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest; import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel; import ee.carlrobert.llm.client.openai.completion.OpenAICompletionRequest; @@ -39,32 +43,14 @@ public class CompletionRequestProvider { private static final Logger LOG = Logger.getInstance(CompletionRequestProvider.class); - public static final String COMPLETION_SYSTEM_PROMPT = "You are an AI programming assistant.\n" - + "Follow the user's requirements carefully & to the letter.\n" - + "Your responses should be informative and logical.\n" - + "You should always adhere to technical information.\n" - + "If the user asks for code or technical questions, you must provide code suggestions and " - + "adhere to technical information.\n" - + "If the question is related to a developer, you must respond with " - + "content related to a developer.\n" - + "First think step-by-step - describe your plan for what to build in pseudocode, " - + "written out in great detail.\n" - + "Then output the code in a single code block.\n" - + "Minimize any other prose.\n" - + "Keep your answers short and impersonal.\n" - + "Use Markdown formatting in your answers.\n" - + "Make sure to include the programming language name at the start of the " - + "Markdown code blocks.\n" - + "Avoid wrapping the whole response in triple backticks.\n" - + "The user works in an IDE built by JetBrains which has a concept for editors " - + "with open files, integrated unit test support, and output pane that shows " - + "the output of running the code as well as an integrated terminal.\n" - + "You can only give one reply for each conversation turn."; + public static final String COMPLETION_SYSTEM_PROMPT = getResourceContent( + "/prompts/default-completion-system-prompt.txt"); - public static final String COMPLETION_COMMIT_MESSAGE_PROMPT = - "Write a short and descriptive git commit message for the following git diff.\n" - + "Use imperative mood, present tense, active voice and verbs.\n" - + "Your entire response will be passed directly into git commit."; + public static final String GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT = getResourceContent( + "/prompts/generate-commit-message-system-prompt.txt"); + + public static final String FIX_COMPILE_ERRORS_SYSTEM_PROMPT = getResourceContent( + "/prompts/fix-compile-errors.txt"); private final EncodingManager encodingManager = EncodingManager.getInstance(); private final EmbeddingsService embeddingsService; @@ -77,6 +63,23 @@ public class CompletionRequestProvider { this.conversation = conversation; } + public static String getPromptWithContext(List referencedFiles, + String userPrompt) { + var includedFilesSettings = IncludedFilesSettingsState.getInstance(); + var repeatableContext = referencedFiles.stream() + .map(item -> includedFilesSettings.getRepeatableContext() + .replace("{FILE_PATH}", item.getFilePath()) + .replace("{FILE_CONTENT}", format( + "```%s\n%s\n```", + item.getFileExtension(), + item.getFileContent().trim()))) + .collect(joining("\n\n")); + + return includedFilesSettings.getPromptTemplate() + .replace("{REPEATABLE_CONTEXT}", repeatableContext) + .replace("{QUESTION}", userPrompt); + } + public static OpenAICompletionRequest buildOpenAILookupCompletionRequest( String context) { return new OpenAIChatCompletionRequest.Builder( @@ -96,13 +99,21 @@ public class CompletionRequestProvider { .build(); } - public LlamaCompletionRequest buildLlamaCompletionRequest(Message message) { + public LlamaCompletionRequest buildLlamaCompletionRequest( + Message message, + ConversationType conversationType) { var settings = LlamaSettingsState.getInstance(); var promptTemplate = settings.isUseCustomModel() ? settings.getPromptTemplate() : LlamaModel.findByHuggingFaceModel(settings.getHuggingFaceModel()).getPromptTemplate(); + + var systemPrompt = COMPLETION_SYSTEM_PROMPT; + if (conversationType == ConversationType.FIX_COMPILE_ERRORS) { + systemPrompt = FIX_COMPILE_ERRORS_SYSTEM_PROMPT; + } + var prompt = promptTemplate.buildPrompt( - COMPLETION_SYSTEM_PROMPT, + systemPrompt, message.getPrompt(), conversation.getMessages()); var configuration = ConfigurationState.getInstance(); @@ -131,21 +142,13 @@ public class CompletionRequestProvider { return requestBuilder.build(); } - public OpenAIChatCompletionRequest buildOpenAIChatCompletionRequest( - String model, - Message message, - boolean retry) { - return buildOpenAIChatCompletionRequest(model, message, retry, false, null); - } - public OpenAIChatCompletionRequest buildOpenAIChatCompletionRequest( @Nullable String model, - Message message, - boolean retry, + CallParameters callParameters, boolean useContextualSearch, @Nullable String overriddenPath) { var builder = new OpenAIChatCompletionRequest.Builder( - buildMessages(model, message, retry, useContextualSearch)) + buildMessages(model, callParameters, useContextualSearch)) .setModel(model) .setMaxTokens(ConfigurationState.getInstance().getMaxTokens()) .setTemperature(ConfigurationState.getInstance().getTemperature()); @@ -158,21 +161,27 @@ public class CompletionRequestProvider { } public List buildMessages( - Message message, - boolean retry, + CallParameters callParameters, boolean useContextualSearch) { + var message = callParameters.getMessage(); var messages = new ArrayList(); if (useContextualSearch) { - var prompt = embeddingsService.buildPromptWithContext(message.getPrompt()); + var prompt = embeddingsService.buildPromptWithContext( + message.getPrompt()); LOG.info("Retrieved context:\n" + prompt); messages.add(new OpenAIChatCompletionMessage("user", prompt)); } else { - var systemPrompt = ConfigurationState.getInstance().getSystemPrompt(); - messages.add(new OpenAIChatCompletionMessage("system", - systemPrompt.isEmpty() ? COMPLETION_SYSTEM_PROMPT : systemPrompt)); + if (callParameters.getConversationType() == ConversationType.DEFAULT) { + messages.add(new OpenAIChatCompletionMessage( + "system", + ConfigurationState.getInstance().getSystemPrompt())); + } + if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) { + messages.add(new OpenAIChatCompletionMessage("system", FIX_COMPILE_ERRORS_SYSTEM_PROMPT)); + } for (var prevMessage : conversation.getMessages()) { - if (retry && prevMessage.getId().equals(message.getId())) { + if (callParameters.isRetry() && prevMessage.getId().equals(message.getId())) { break; } messages.add(new OpenAIChatCompletionMessage("user", prevMessage.getPrompt())); @@ -185,10 +194,9 @@ public class CompletionRequestProvider { private List buildMessages( @Nullable String model, - Message message, - boolean retry, + CallParameters callParameters, boolean useContextualSearch) { - var messages = buildMessages(message, retry, useContextualSearch); + var messages = buildMessages(callParameters, useContextualSearch); if (model == null || SettingsState.getInstance().getSelectedService() == ServiceType.YOU) { return messages; diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index ab925de3..d11334e2 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -1,18 +1,24 @@ package ee.carlrobert.codegpt.completions; +import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP; +import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI; +import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU; + import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; -import ee.carlrobert.codegpt.conversations.Conversation; -import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; +import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage; +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest; import ee.carlrobert.llm.completion.CompletionEventListener; +import java.util.List; +import java.util.Optional; import okhttp3.sse.EventSource; -import org.jetbrains.annotations.NotNull; @Service public final class CompletionRequestService { @@ -25,46 +31,79 @@ public final class CompletionRequestService { } public EventSource getChatCompletionAsync( - @NotNull Conversation conversation, - @NotNull Message message, - boolean retry, + CallParameters callParameters, boolean useContextualSearch, CompletionEventListener eventListener) { - var requestProvider = new CompletionRequestProvider(conversation); + var requestProvider = new CompletionRequestProvider(callParameters.getConversation()); switch (SettingsState.getInstance().getSelectedService()) { case OPENAI: var openAISettings = OpenAISettingsState.getInstance(); - return CompletionClientProvider.getOpenAIClient().getChatCompletion( + return CompletionClientProvider.getOpenAIClient().getChatCompletionAsync( requestProvider.buildOpenAIChatCompletionRequest( openAISettings.getModel(), - message, - retry, + callParameters, useContextualSearch, openAISettings.isUsingCustomPath() ? openAISettings.getPath() : null), eventListener); case AZURE: var azureSettings = AzureSettingsState.getInstance(); - return CompletionClientProvider.getAzureClient().getChatCompletion( + return CompletionClientProvider.getAzureClient().getChatCompletionAsync( requestProvider.buildOpenAIChatCompletionRequest( null, - message, - retry, + callParameters, useContextualSearch, azureSettings.isUsingCustomPath() ? azureSettings.getPath() : null), eventListener); case YOU: - return CompletionClientProvider.getYouClient().getChatCompletion( - requestProvider.buildYouCompletionRequest(message), + return CompletionClientProvider.getYouClient().getChatCompletionAsync( + requestProvider.buildYouCompletionRequest(callParameters.getMessage()), eventListener); case LLAMA_CPP: - return CompletionClientProvider.getLlamaClient().getChatCompletion( - requestProvider.buildLlamaCompletionRequest(message), + return CompletionClientProvider.getLlamaClient().getChatCompletionAsync( + requestProvider.buildLlamaCompletionRequest( + callParameters.getMessage(), + callParameters.getConversationType()), eventListener); default: throw new IllegalArgumentException(); } } + public void generateCommitMessageAsync( + String prompt, + CompletionEventListener eventListener) { + var request = new OpenAIChatCompletionRequest.Builder(List.of( + new OpenAIChatCompletionMessage("system", + ConfigurationState.getInstance().getCommitMessagePrompt()), + new OpenAIChatCompletionMessage("user", prompt))) + .setModel(OpenAISettingsState.getInstance().getModel()) + .build(); + var selectedService = SettingsState.getInstance().getSelectedService(); + if (selectedService == ServiceType.OPENAI) { + CompletionClientProvider.getOpenAIClient().getChatCompletionAsync(request, eventListener); + } + if (selectedService == ServiceType.AZURE) { + CompletionClientProvider.getAzureClient().getChatCompletionAsync(request, eventListener); + } + } + + public Optional getLookupCompletion(String prompt) { + var selectedService = SettingsState.getInstance().getSelectedService(); + if (selectedService == YOU || selectedService == LLAMA_CPP) { + return Optional.empty(); + } + + var request = CompletionRequestProvider.buildOpenAILookupCompletionRequest(prompt); + var response = selectedService == OPENAI + ? CompletionClientProvider.getOpenAIClient().getChatCompletion(request) + : CompletionClientProvider.getAzureClient().getChatCompletion(request); + return response + .getChoices() + .stream() + .findFirst() + .map(item -> item.getMessage().getContent()); + } + public boolean isRequestAllowed() { var selectedService = SettingsState.getInstance().getSelectedService(); if (selectedService == ServiceType.AZURE) { diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java index a86a1ab2..87c94c06 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java @@ -8,17 +8,18 @@ import java.util.List; public interface CompletionResponseEventListener { - default void handleMessage(String message) {} + default void handleMessage(String message) { + } - default void handleError(ErrorDetails error, Throwable ex) {} + default void handleError(ErrorDetails error, Throwable ex) { + } - default void handleTokensExceeded(Conversation conversation, Message message) {} + default void handleTokensExceeded(Conversation conversation, Message message) { + } - default void handleCompleted( - String fullMessage, - Message message, - Conversation conversation, - boolean retry) {} + default void handleCompleted(String fullMessage, CallParameters callParameters) { + } - default void handleSerpResults(List results, Message message) {} + default void handleSerpResults(List results, Message message) { + } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/ConversationType.java b/src/main/java/ee/carlrobert/codegpt/completions/ConversationType.java new file mode 100644 index 00000000..59e4ee76 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/completions/ConversationType.java @@ -0,0 +1,9 @@ +package ee.carlrobert.codegpt.completions; + +public enum ConversationType { + CUSTOM_PROMPT, + DEFAULT, + EDITOR_ACTION, + FIX_COMPILE_ERRORS, + MULTI_FILE, +} diff --git a/src/main/java/ee/carlrobert/codegpt/completions/MethodNameLookupListener.java b/src/main/java/ee/carlrobert/codegpt/completions/MethodNameLookupListener.java index 1a0aa2fb..88c817ef 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/MethodNameLookupListener.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/MethodNameLookupListener.java @@ -1,8 +1,5 @@ package ee.carlrobert.codegpt.completions; -import static ee.carlrobert.codegpt.settings.service.ServiceType.AZURE; -import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI; - import com.intellij.codeInsight.completion.PrefixMatcher; import com.intellij.codeInsight.lookup.Lookup; import com.intellij.codeInsight.lookup.LookupElementBuilder; @@ -15,7 +12,6 @@ import com.intellij.psi.util.PsiUtilCore; import ee.carlrobert.codegpt.Icons; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; -import ee.carlrobert.codegpt.settings.state.SettingsState; import java.util.Optional; import org.jetbrains.annotations.Nullable; @@ -51,35 +47,16 @@ public class MethodNameLookupListener implements LookupManagerListener { LookupImpl lookup, Application application, String prompt) { - getCompletionResponse(prompt).ifPresent(response -> { - for (var value : response.split(",")) { - application.runReadAction(() -> { - lookup.addItem( - LookupElementBuilder.create(value.trim()).withIcon(Icons.Sparkle), - PrefixMatcher.ALWAYS_TRUE); - application.invokeLater(() -> lookup.refreshUi(true, true)); + CompletionRequestService.getInstance().getLookupCompletion(prompt) + .ifPresent(response -> { + for (var value : response.split(",")) { + application.runReadAction(() -> { + lookup.addItem( + LookupElementBuilder.create(value.trim()).withIcon(Icons.Sparkle), + PrefixMatcher.ALWAYS_TRUE); + application.invokeLater(() -> lookup.refreshUi(true, true)); + }); + } }); - } - }); - } - - // TODO: Refactor - private Optional getCompletionResponse(String prompt) { - var selectedService = SettingsState.getInstance().getSelectedService(); - if (selectedService == OPENAI) { - return Optional.ofNullable(CompletionClientProvider.getOpenAIClient() - .getChatCompletion( - CompletionRequestProvider.buildOpenAILookupCompletionRequest(prompt)) - .getChoices()) - .map(choices -> choices.get(0).getMessage().getContent()); - } - if (selectedService == AZURE) { - return Optional.ofNullable(CompletionClientProvider.getAzureClient() - .getChatCompletion( - CompletionRequestProvider.buildOpenAILookupCompletionRequest(prompt)) - .getChoices()) - .map(choices -> choices.get(0).getMessage().getContent()); - } - return Optional.empty(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java b/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java index 3a2d444e..0aba2cbc 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java @@ -83,8 +83,4 @@ public class Conversation { .filter(message -> !message.getId().equals(messageId)) .collect(toList())); } - - public void removeMessages() { - messages.clear(); - } } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java index 2d88c336..bc73c737 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java @@ -4,6 +4,7 @@ import static java.util.stream.Collectors.toList; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; +import ee.carlrobert.codegpt.completions.CallParameters; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; @@ -14,6 +15,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.ListIterator; import java.util.Optional; import java.util.UUID; import org.jetbrains.annotations.NotNull; @@ -60,13 +62,11 @@ public final class ConversationService { conversationsMapping.put(conversation.getClientCode(), conversations); } - public void saveMessage( - String response, - Message message, - Conversation conversation, - boolean retry) { + public void saveMessage(String response, CallParameters callParameters) { + var conversation = callParameters.getConversation(); + var message = callParameters.getMessage(); var conversationMessages = conversation.getMessages(); - if (retry && !conversationMessages.isEmpty()) { + if (callParameters.isRetry() && !conversationMessages.isEmpty()) { var messageToBeSaved = conversationMessages.stream() .filter(item -> item.getId().equals(message.getId())) .findFirst().orElseThrow(); @@ -82,9 +82,7 @@ public final class ConversationService { public void saveMessage(@NotNull Conversation conversation, @NotNull Message message) { conversation.setUpdatedOn(LocalDateTime.now()); - var iterator = conversationState.getConversationsMapping() - .get(conversation.getClientCode()) - .listIterator(); + var iterator = getIterator(conversation.getClientCode()); while (iterator.hasNext()) { var next = iterator.next(); next.setMessages( @@ -102,9 +100,7 @@ public final class ConversationService { public void saveConversation(Conversation conversation) { conversation.setUpdatedOn(LocalDateTime.now()); - var iterator = conversationState.getConversationsMapping() - .get(conversation.getClientCode()) - .listIterator(); + var iterator = getIterator(conversation.getClientCode()); while (iterator.hasNext()) { var next = iterator.next(); if (next.getId().equals(conversation.getId())) { @@ -128,9 +124,7 @@ public final class ConversationService { } public void deleteConversation(Conversation conversation) { - var iterator = conversationState.getConversationsMapping() - .get(conversation.getClientCode()) - .listIterator(); + var iterator = getIterator(conversation.getClientCode()); while (iterator.hasNext()) { var next = iterator.next(); if (next.getId().equals(conversation.getId())) { @@ -168,6 +162,12 @@ public final class ConversationService { return tryGetNextOrPreviousConversation(false); } + private ListIterator getIterator(String clientCode) { + return conversationState.getConversationsMapping() + .get(clientCode) + .listIterator(); + } + private Optional tryGetNextOrPreviousConversation(boolean isPrevious) { var currentConversation = ConversationsState.getCurrentConversation(); if (currentConversation != null) { diff --git a/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingAction.java b/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingAction.java index 055de0fe..ceeb8333 100644 --- a/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingAction.java +++ b/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingAction.java @@ -21,7 +21,7 @@ public class CodebaseIndexingAction extends AnAction { var folderStructureTreePanel = new FolderStructureTreePanel(project); var show = OverlayUtil.showFileStructureDialog(project, folderStructureTreePanel); if (show == OK_EXIT_CODE) { - new CodebaseIndexingTask(project, folderStructureTreePanel.getCheckedFiles()).run(); + new CodebaseIndexingTask(project, folderStructureTreePanel.getReferencedFiles()).run(); } } } diff --git a/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java b/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java index ad550f2c..a1bfb748 100644 --- a/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java +++ b/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java @@ -15,8 +15,8 @@ import ee.carlrobert.codegpt.CodeGPTPlugin; import ee.carlrobert.codegpt.completions.CompletionClientProvider; import ee.carlrobert.codegpt.ui.OverlayUtil; import ee.carlrobert.codegpt.util.file.FileUtil; -import ee.carlrobert.embedding.CheckedFile; import ee.carlrobert.embedding.EmbeddingsService; +import ee.carlrobert.embedding.ReferencedFile; import ee.carlrobert.vector.VectorStore; import java.util.List; import java.util.Map; @@ -26,13 +26,13 @@ public class CodebaseIndexingTask extends Task.Backgroundable { private static final Logger LOG = Logger.getInstance(CodebaseIndexingTask.class); private final Project project; - private final List checkedFiles; + private final List referencedFiles; private final EmbeddingsService embeddingsService; - public CodebaseIndexingTask(Project project, List checkedFiles) { + public CodebaseIndexingTask(Project project, List referencedFiles) { super(project, CodeGPTBundle.get("codebaseIndexing.task.title"), true); this.project = project; - this.checkedFiles = checkedFiles; + this.referencedFiles = referencedFiles; this.embeddingsService = new EmbeddingsService( CompletionClientProvider.getOpenAIClient(), CodeGPTPlugin.getPluginBasePath()); @@ -49,7 +49,7 @@ public class CodebaseIndexingTask extends Task.Backgroundable { String fileContent; try { - fileContent = new ObjectMapper().writeValueAsString(Map.of("content", checkedFiles)); + fileContent = new ObjectMapper().writeValueAsString(Map.of("content", referencedFiles)); } catch (JsonProcessingException e) { throw new RuntimeException("Unable to serialize json file"); } @@ -63,7 +63,7 @@ public class CodebaseIndexingTask extends Task.Backgroundable { try { indicator.setFraction(0); List> embeddings = - embeddingsService.createEmbeddings(checkedFiles, indicator); + embeddingsService.createEmbeddings(referencedFiles, indicator); VectorStore.getInstance(CodeGPTPlugin.getPluginBasePath()).save(embeddings); OverlayUtil.showNotification("Indexing completed", NotificationType.INFORMATION); diff --git a/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java b/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java index 86f143e3..d6e13dba 100644 --- a/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java @@ -20,7 +20,7 @@ import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.AsyncProcessIcon; import com.intellij.util.ui.JBUI; import ee.carlrobert.codegpt.util.file.FileUtil; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.MouseAdapter; @@ -132,9 +132,9 @@ public class FolderStructureTreePanel { return panel; } - public List getCheckedFiles() { + public List getReferencedFiles() { return getCheckedVirtualFiles().stream() - .map(item -> new CheckedFile(new File(item.getPath()))) + .map(item -> new ReferencedFile(new File(item.getPath()))) .collect(toList()); } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java index 00a2d289..3904433f 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java @@ -1,7 +1,7 @@ package ee.carlrobert.codegpt.settings.configuration; -import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_COMMIT_MESSAGE_PROMPT; import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT; +import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; @@ -19,13 +19,14 @@ import org.jetbrains.annotations.Nullable; public class ConfigurationState implements PersistentStateComponent { private String systemPrompt = COMPLETION_SYSTEM_PROMPT; - private String commitMessagePrompt = COMPLETION_COMMIT_MESSAGE_PROMPT; + private String commitMessagePrompt = GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT; private int maxTokens = 1000; private double temperature = 0.1; private boolean checkForPluginUpdates = true; private boolean createNewChatOnEachAction; private boolean ignoreGitCommitTokenLimit; private boolean methodNameGenerationEnabled = true; + private boolean captureCompileErrors = true; private boolean autoFormattingEnabled = true; private Map tableData = EditorActionsUtil.DEFAULT_ACTIONS; @@ -116,6 +117,14 @@ public class ConfigurationState implements PersistentStateComponent" + return format("" + "

File Size: %.2f GB

" + "

Max RAM Required: %.2f GB

" + "", details.fileSize, details.maxRAMRequired); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java index 42368a54..fa36d6fc 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java @@ -1,5 +1,7 @@ package ee.carlrobert.codegpt.settings.state; +import static java.lang.String.format; + import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; @@ -82,7 +84,7 @@ public class SettingsState implements PersistentStateComponent { } var huggingFaceModel = llamaSettings.getHuggingFaceModel(); var llamaModel = LlamaModel.findByHuggingFaceModel(huggingFaceModel); - return String.format( + return format( "%s %dB (Q%d)", llamaModel.getLabel(), huggingFaceModel.getParameterSize(), diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java deleted file mode 100644 index 04230a95..00000000 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java +++ /dev/null @@ -1,276 +0,0 @@ -package ee.carlrobert.codegpt.toolwindow.chat; - -import static ee.carlrobert.codegpt.ui.UIUtil.createScrollPaneWithSmartScroller; -import static java.lang.String.format; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.impl.EditorImpl; -import com.intellij.openapi.project.Project; -import com.intellij.ui.JBColor; -import com.intellij.util.ui.JBUI; -import ee.carlrobert.codegpt.CodeGPTKeys; -import ee.carlrobert.codegpt.EncodingManager; -import ee.carlrobert.codegpt.actions.ActionType; -import ee.carlrobert.codegpt.completions.CompletionRequestHandler; -import ee.carlrobert.codegpt.completions.CompletionRequestService; -import ee.carlrobert.codegpt.conversations.Conversation; -import ee.carlrobert.codegpt.conversations.ConversationService; -import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.settings.service.ServiceType; -import ee.carlrobert.codegpt.settings.state.IncludedFilesSettingsState; -import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.codegpt.telemetry.TelemetryAction; -import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; -import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowPanel; -import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody; -import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatToolWindowScrollablePanel; -import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel; -import ee.carlrobert.codegpt.toolwindow.chat.ui.UserMessagePanel; -import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensDetails; -import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel; -import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.UserPromptTextArea; -import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.UserPromptTextAreaHeader; -import ee.carlrobert.codegpt.util.EditorUtil; -import ee.carlrobert.codegpt.util.file.FileUtil; -import ee.carlrobert.embedding.CheckedFile; -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.util.List; -import java.util.UUID; -import javax.swing.JComponent; -import javax.swing.JPanel; -import org.jetbrains.annotations.NotNull; - -public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPanel { - - private static final Logger LOG = Logger.getInstance(BaseChatToolWindowTabPanel.class); - - private final boolean useContextualSearch; - private final JPanel rootPanel; - private final Conversation conversation; - private final UserPromptTextArea userPromptTextArea; - private final ConversationService conversationService; - - protected final Project project; - protected final TotalTokensPanel totalTokensPanel; - protected final ChatToolWindowScrollablePanel toolWindowScrollablePanel; - - protected abstract JComponent getLandingView(); - - public BaseChatToolWindowTabPanel( - @NotNull Project project, - @NotNull Conversation conversation, - boolean useContextualSearch) { - this.project = project; - this.conversation = conversation; - this.useContextualSearch = useContextualSearch; - conversationService = ConversationService.getInstance(); - toolWindowScrollablePanel = new ChatToolWindowScrollablePanel(); - totalTokensPanel = new TotalTokensPanel( - project, - conversation, - EditorUtil.getSelectedEditorSelectedText(project), - this); - userPromptTextArea = new UserPromptTextArea(this::handleSubmit, totalTokensPanel); - rootPanel = createRootPanel(); - userPromptTextArea.requestFocusInWindow(); - userPromptTextArea.requestFocus(); - } - - @Override - public void dispose() { - } - - @Override - public JComponent getContent() { - return rootPanel; - } - - @Override - public Conversation getConversation() { - return conversation; - } - - @Override - public void sendMessage(Message message) { - var referencedFiles = project.getUserData(CodeGPTKeys.SELECTED_FILES); - if (referencedFiles != null && !referencedFiles.isEmpty()) { - var referencedFilePaths = referencedFiles.stream() - .map(CheckedFile::getFilePath) - .collect(toList()); - message.setReferencedFilePaths(referencedFilePaths); - message.setUserMessage(message.getPrompt()); - message.setPrompt(getPromptWithContext(referencedFiles, message.getPrompt())); - totalTokensPanel.updateReferencedFilesTokens(referencedFiles); - } - - var userMessagePanel = new UserMessagePanel(project, message, this); - var messagePanel = toolWindowScrollablePanel.addMessage(message.getId()); - messagePanel.add(userMessagePanel); - var responsePanel = new ResponsePanel() - .withReloadAction(() -> reloadMessage(message, conversation)) - .withDeleteAction(() -> removeMessage(message.getId(), conversation)) - .addContent(new ChatMessageResponseBody(project, true, this)); - messagePanel.add(responsePanel); - - var userPromptTokens = EncodingManager.getInstance().countTokens(message.getPrompt()); - var conversationTokens = EncodingManager.getInstance().countConversationTokens(conversation); - totalTokensPanel.updateConversationTokens(conversationTokens + userPromptTokens); - - call(message, responsePanel, false); - project.getService(StandardChatToolWindowContentManager.class) - .tryFindChatToolWindowPanel() - .ifPresent(StandardChatToolWindowPanel::clearSelectedFilesNotification); - } - - private String getPromptWithContext(List referencedFiles, String userPrompt) { - var includedFilesSettings = IncludedFilesSettingsState.getInstance(); - var repeatableContext = referencedFiles.stream() - .map(item -> includedFilesSettings.getRepeatableContext() - .replace("{FILE_PATH}", item.getFilePath()) - .replace("{FILE_CONTENT}", format( - "```%s\n%s\n```", - item.getFileExtension(), - item.getFileContent().trim()))) - .collect(joining("\n\n")); - - return includedFilesSettings.getPromptTemplate() - .replace("{REPEATABLE_CONTEXT}", repeatableContext) - .replace("{QUESTION}", userPrompt); - } - - @Override - public TotalTokensDetails getTokenDetails() { - return totalTokensPanel.getTokenDetails(); - } - - @Override - public void requestFocusForTextArea() { - userPromptTextArea.focus(); - } - - @Override - public void displayLandingView() { - toolWindowScrollablePanel.displayLandingView(getLandingView()); - totalTokensPanel.updateConversationTokens(conversation); - } - - protected void reloadMessage(Message message, Conversation conversation) { - ResponsePanel responsePanel = null; - try { - responsePanel = toolWindowScrollablePanel.getMessageResponsePanel(message.getId()); - ((ChatMessageResponseBody) responsePanel.getContent()).clear(); - toolWindowScrollablePanel.update(); - } catch (Exception e) { - throw new RuntimeException("Could not delete the existing message component", e); - } finally { - LOG.debug("Reloading message: " + message.getId()); - - if (responsePanel != null) { - message.setResponse(""); - conversationService.saveMessage(conversation, message); - call(message, responsePanel, true); - } - - totalTokensPanel.updateConversationTokens(conversation); - - TelemetryAction.IDE_ACTION.createActionMessage() - .property("action", ActionType.RELOAD_MESSAGE.name()) - .send(); - } - } - - protected void removeMessage(UUID messageId, Conversation conversation) { - toolWindowScrollablePanel.removeMessage(messageId); - conversation.removeMessage(messageId); - conversationService.saveConversation(conversation); - totalTokensPanel.updateConversationTokens(conversation); - - if (conversation.getMessages().isEmpty()) { - displayLandingView(); - } - } - - protected void clearWindow() { - toolWindowScrollablePanel.clearAll(); - totalTokensPanel.updateConversationTokens(conversation); - } - - private void call(Message message, ResponsePanel responsePanel, boolean retry) { - var responseContainer = (ChatMessageResponseBody) responsePanel.getContent(); - - if (!CompletionRequestService.getInstance().isRequestAllowed()) { - responseContainer.displayMissingCredential(); - return; - } - - var requestHandler = new CompletionRequestHandler( - useContextualSearch, - new ToolWindowCompletionResponseEventListener( - conversationService, - responsePanel, - totalTokensPanel, - userPromptTextArea) { - @Override - public void handleTokensExceededPolicyAccepted() { - call(message, responsePanel, true); - } - }); - userPromptTextArea.setRequestHandler(requestHandler); - userPromptTextArea.setSubmitEnabled(false); - requestHandler.call(conversation, message, retry); - } - - private void handleSubmit(String text) { - var message = new Message(text); - var editor = EditorUtil.getSelectedEditor(project); - if (editor != null) { - var selectionModel = editor.getSelectionModel(); - var selectedText = selectionModel.getSelectedText(); - if (selectedText != null && !selectedText.isEmpty()) { - var fileExtension = FileUtil.getFileExtension( - ((EditorImpl) editor).getVirtualFile().getName()); - message = new Message(text + format("\n```%s\n%s\n```", fileExtension, selectedText)); - selectionModel.removeSelection(); - } - } - message.setUserMessage(text); - sendMessage(message); - } - - private JPanel createUserPromptPanel(ServiceType selectedService) { - var panel = new JPanel(new BorderLayout()); - panel.setBorder(JBUI.Borders.compound( - JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0), - JBUI.Borders.empty(8))); - var contentManager = project.getService(StandardChatToolWindowContentManager.class); - panel.add(JBUI.Panels.simplePanel(new UserPromptTextAreaHeader( - selectedService, - totalTokensPanel, - contentManager::createNewTabPanel)), BorderLayout.NORTH); - panel.add(JBUI.Panels.simplePanel(userPromptTextArea), BorderLayout.CENTER); - return panel; - } - - private JPanel createRootPanel() { - var gbc = new GridBagConstraints(); - gbc.fill = GridBagConstraints.BOTH; - gbc.weighty = 1; - gbc.weightx = 1; - gbc.gridx = 0; - gbc.gridy = 0; - - var rootPanel = new JPanel(new GridBagLayout()); - rootPanel.add(createScrollPaneWithSmartScroller(toolWindowScrollablePanel), gbc); - - gbc.weighty = 0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.gridy = 1; - rootPanel.add( - createUserPromptPanel(SettingsState.getInstance().getSelectedService()), gbc); - return rootPanel; - } -} \ No newline at end of file diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index 10b9271a..f5ee5ba2 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -1,23 +1,286 @@ package ee.carlrobert.codegpt.toolwindow.chat; +import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.getPromptWithContext; +import static ee.carlrobert.codegpt.ui.UIUtil.createScrollPaneWithSmartScroller; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; + import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.project.Project; +import com.intellij.ui.JBColor; +import com.intellij.util.ui.JBUI; +import ee.carlrobert.codegpt.CodeGPTKeys; +import ee.carlrobert.codegpt.EncodingManager; +import ee.carlrobert.codegpt.actions.ActionType; +import ee.carlrobert.codegpt.completions.CallParameters; +import ee.carlrobert.codegpt.completions.CompletionRequestHandler; +import ee.carlrobert.codegpt.completions.CompletionRequestService; +import ee.carlrobert.codegpt.completions.ConversationType; import ee.carlrobert.codegpt.conversations.Conversation; +import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.message.Message; +import ee.carlrobert.codegpt.settings.service.ServiceType; +import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.telemetry.TelemetryAction; +import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; +import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowPanel; +import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody; +import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatToolWindowScrollablePanel; +import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel; +import ee.carlrobert.codegpt.toolwindow.chat.ui.UserMessagePanel; import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensDetails; +import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel; +import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.UserPromptTextArea; +import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.UserPromptTextAreaHeader; +import ee.carlrobert.codegpt.util.EditorUtil; +import ee.carlrobert.codegpt.util.file.FileUtil; +import ee.carlrobert.embedding.ReferencedFile; +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.UUID; import javax.swing.JComponent; import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import org.jetbrains.annotations.NotNull; -public interface ChatToolWindowTabPanel extends Disposable { +public abstract class ChatToolWindowTabPanel implements Disposable { - JComponent getContent(); + private static final Logger LOG = Logger.getInstance(ChatToolWindowTabPanel.class); - Conversation getConversation(); + private final boolean useContextualSearch; + private final JPanel rootPanel; + private final Conversation conversation; + private final UserPromptTextArea userPromptTextArea; + private final ConversationService conversationService; - TotalTokensDetails getTokenDetails(); + protected final Project project; + protected final TotalTokensPanel totalTokensPanel; + protected final ChatToolWindowScrollablePanel toolWindowScrollablePanel; - void displayLandingView(); + protected abstract JComponent getLandingView(); - void sendMessage(Message message); + public ChatToolWindowTabPanel( + @NotNull Project project, + @NotNull Conversation conversation, + boolean useContextualSearch) { + this.project = project; + this.conversation = conversation; + this.useContextualSearch = useContextualSearch; + conversationService = ConversationService.getInstance(); + toolWindowScrollablePanel = new ChatToolWindowScrollablePanel(); + totalTokensPanel = new TotalTokensPanel( + project, + conversation, + EditorUtil.getSelectedEditorSelectedText(project), + this); + userPromptTextArea = new UserPromptTextArea(this::handleSubmit, totalTokensPanel); + rootPanel = createRootPanel(); + userPromptTextArea.requestFocusInWindow(); + userPromptTextArea.requestFocus(); + } - void requestFocusForTextArea(); -} + public void dispose() { + LOG.info("Disposing BaseChatToolWindowTabPanel component"); + } + + public JComponent getContent() { + return rootPanel; + } + + public Conversation getConversation() { + return conversation; + } + + public void sendMessage(Message message) { + sendMessage(message, ConversationType.DEFAULT); + } + + public void sendMessage(Message message, ConversationType conversationType) { + Runnable runnable = () -> { + var referencedFiles = project.getUserData(CodeGPTKeys.SELECTED_FILES); + if (referencedFiles != null && !referencedFiles.isEmpty()) { + var referencedFilePaths = referencedFiles.stream() + .map(ReferencedFile::getFilePath) + .collect(toList()); + message.setReferencedFilePaths(referencedFilePaths); + message.setUserMessage(message.getPrompt()); + message.setPrompt(getPromptWithContext(referencedFiles, message.getPrompt())); + + totalTokensPanel.updateReferencedFilesTokens(referencedFiles); + + project.getService(StandardChatToolWindowContentManager.class) + .tryFindChatToolWindowPanel() + .ifPresent(StandardChatToolWindowPanel::clearSelectedFilesNotification); + } + + var messagePanel = toolWindowScrollablePanel.addMessage(message.getId()); + messagePanel.add(new UserMessagePanel(project, message, this)); + var responsePanel = createResponsePanel(message, conversationType); + messagePanel.add(responsePanel); + + updateTotalTokens(message); + + call(message, conversationType, responsePanel, false); + }; + // TODO + if (ApplicationManager.getApplication().isUnitTestMode()) { + runnable.run(); + } else { + SwingUtilities.invokeLater(runnable); + } + } + + private void updateTotalTokens(Message message) { + int userPromptTokens = EncodingManager.getInstance().countTokens(message.getPrompt()); + int conversationTokens = EncodingManager.getInstance().countConversationTokens(conversation); + totalTokensPanel.updateConversationTokens(conversationTokens + userPromptTokens); + } + + private ResponsePanel createResponsePanel(Message message, ConversationType conversationType) { + return new ResponsePanel() + .withReloadAction(() -> reloadMessage(message, conversation, conversationType)) + .withDeleteAction(() -> removeMessage(message.getId(), conversation)) + .addContent(new ChatMessageResponseBody(project, true, this)); + } + + public TotalTokensDetails getTokenDetails() { + return totalTokensPanel.getTokenDetails(); + } + + public void requestFocusForTextArea() { + userPromptTextArea.focus(); + } + + public void displayLandingView() { + toolWindowScrollablePanel.displayLandingView(getLandingView()); + totalTokensPanel.updateConversationTokens(conversation); + } + + protected void reloadMessage( + Message message, + Conversation conversation, + ConversationType conversationType) { + ResponsePanel responsePanel = null; + try { + responsePanel = toolWindowScrollablePanel.getMessageResponsePanel(message.getId()); + ((ChatMessageResponseBody) responsePanel.getContent()).clear(); + toolWindowScrollablePanel.update(); + } catch (Exception e) { + throw new RuntimeException("Could not delete the existing message component", e); + } finally { + LOG.debug("Reloading message: " + message.getId()); + + if (responsePanel != null) { + message.setResponse(""); + conversationService.saveMessage(conversation, message); + call(message, conversationType, responsePanel, true); + } + + totalTokensPanel.updateConversationTokens(conversation); + + TelemetryAction.IDE_ACTION.createActionMessage() + .property("action", ActionType.RELOAD_MESSAGE.name()) + .send(); + } + } + + protected void removeMessage(UUID messageId, Conversation conversation) { + toolWindowScrollablePanel.removeMessage(messageId); + conversation.removeMessage(messageId); + conversationService.saveConversation(conversation); + totalTokensPanel.updateConversationTokens(conversation); + + if (conversation.getMessages().isEmpty()) { + displayLandingView(); + } + } + + protected void clearWindow() { + toolWindowScrollablePanel.clearAll(); + totalTokensPanel.updateConversationTokens(conversation); + } + + private void call( + Message message, + ConversationType conversationType, + ResponsePanel responsePanel, + boolean retry) { + var responseContainer = (ChatMessageResponseBody) responsePanel.getContent(); + + if (!CompletionRequestService.getInstance().isRequestAllowed()) { + responseContainer.displayMissingCredential(); + return; + } + + var requestHandler = new CompletionRequestHandler( + useContextualSearch, + new ToolWindowCompletionResponseEventListener( + conversationService, + responsePanel, + totalTokensPanel, + userPromptTextArea) { + @Override + public void handleTokensExceededPolicyAccepted() { + call(message, conversationType, responsePanel, true); + } + }); + userPromptTextArea.setRequestHandler(requestHandler); + userPromptTextArea.setSubmitEnabled(false); + + requestHandler.call(new CallParameters(conversation, conversationType, message, retry)); + } + + private void handleSubmit(String text) { + var message = new Message(text); + var editor = EditorUtil.getSelectedEditor(project); + if (editor != null) { + var selectionModel = editor.getSelectionModel(); + var selectedText = selectionModel.getSelectedText(); + if (selectedText != null && !selectedText.isEmpty()) { + var fileExtension = FileUtil.getFileExtension( + ((EditorImpl) editor).getVirtualFile().getName()); + message = new Message(text + format("\n```%s\n%s\n```", fileExtension, selectedText)); + selectionModel.removeSelection(); + } + } + message.setUserMessage(text); + sendMessage(message, ConversationType.DEFAULT); + } + + private JPanel createUserPromptPanel(ServiceType selectedService) { + var panel = new JPanel(new BorderLayout()); + panel.setBorder(JBUI.Borders.compound( + JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0), + JBUI.Borders.empty(8))); + var contentManager = project.getService(StandardChatToolWindowContentManager.class); + panel.add(JBUI.Panels.simplePanel(new UserPromptTextAreaHeader( + selectedService, + totalTokensPanel, + contentManager::createNewTabPanel)), BorderLayout.NORTH); + panel.add(JBUI.Panels.simplePanel(userPromptTextArea), BorderLayout.CENTER); + return panel; + } + + private JPanel createRootPanel() { + var gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1; + gbc.weightx = 1; + gbc.gridx = 0; + gbc.gridy = 0; + + var rootPanel = new JPanel(new GridBagLayout()); + rootPanel.add(createScrollPaneWithSmartScroller(toolWindowScrollablePanel), gbc); + + gbc.weighty = 0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridy = 1; + rootPanel.add( + createUserPromptPanel(SettingsState.getInstance().getSelectedService()), gbc); + return rootPanel; + } +} \ No newline at end of file diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java index 5d8dca24..794f23d5 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java @@ -5,6 +5,7 @@ import static com.intellij.openapi.ui.Messages.OK; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import ee.carlrobert.codegpt.EncodingManager; +import ee.carlrobert.codegpt.completions.CallParameters; import ee.carlrobert.codegpt.completions.CompletionResponseEventListener; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; @@ -118,12 +119,9 @@ abstract class ToolWindowCompletionResponseEventListener implements } @Override - public void handleCompleted( - String fullMessage, - Message message, - Conversation conversation, - boolean retry) { - conversationService.saveMessage(fullMessage, message, conversation, retry); + public void handleCompleted(String fullMessage, CallParameters callParameters) { + var message = callParameters.getMessage(); + conversationService.saveMessage(fullMessage, callParameters); var serpResults = serpResultsMapping.get(message.getId()); var containsResults = serpResults != null && !serpResults.isEmpty(); @@ -139,7 +137,7 @@ abstract class ToolWindowCompletionResponseEventListener implements responseContainer.displaySerpResults(serpResults); } totalTokensPanel.updateUserPromptTokens(userPromptTextArea.getText()); - totalTokensPanel.updateConversationTokens(conversation); + totalTokensPanel.updateConversationTokens(callParameters.getConversation()); } finally { stopStreaming(responseContainer); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java index 81490fcc..5cf2cd39 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java @@ -97,7 +97,8 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel { var folderStructureTreePanel = new FolderStructureTreePanel(project); var show = OverlayUtil.showFileStructureDialog(project, folderStructureTreePanel); if (show == OK_EXIT_CODE) { - new CodebaseIndexingTask(project, folderStructureTreePanel.getCheckedFiles()).run(); + new CodebaseIndexingTask(project, folderStructureTreePanel.getReferencedFiles()) + .run(); } break; default: diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java index 1b3448e6..7baced65 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java @@ -1,13 +1,14 @@ package ee.carlrobert.codegpt.toolwindow.chat.contextual; import com.intellij.openapi.project.Project; +import ee.carlrobert.codegpt.completions.ConversationType; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.toolwindow.chat.BaseChatToolWindowTabPanel; +import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowTabPanel; import javax.swing.JComponent; import org.jetbrains.annotations.NotNull; -public class ContextualChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { +public class ContextualChatToolWindowTabPanel extends ChatToolWindowTabPanel { public ContextualChatToolWindowTabPanel( @NotNull Project project, @@ -20,6 +21,6 @@ public class ContextualChatToolWindowTabPanel extends BaseChatToolWindowTabPanel protected JComponent getLandingView() { return new ContextualChatToolWindowLandingPanel( project, - (prompt) -> sendMessage(new Message(prompt))); + (prompt) -> sendMessage(new Message(prompt), ConversationType.DEFAULT)); } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java index 54ea29fc..c21ccb6d 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java @@ -11,6 +11,7 @@ import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.ui.content.Content; import ee.carlrobert.codegpt.Icons; +import ee.carlrobert.codegpt.completions.ConversationType; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.ConversationsState; @@ -30,18 +31,22 @@ public final class StandardChatToolWindowContentManager { } public void sendMessage(Message message) { + sendMessage(message, ConversationType.DEFAULT); + } + + public void sendMessage(Message message, ConversationType conversationType) { getToolWindow().show(); if (ConfigurationState.getInstance().isCreateNewChatOnEachAction() || ConversationsState.getCurrentConversation() == null) { - createNewTabPanel().sendMessage(message); + createNewTabPanel().sendMessage(message, conversationType); return; } tryFindChatTabbedPane() .map(tabbedPane -> tabbedPane.tryFindActiveTabPanel().orElseGet(this::createNewTabPanel)) .orElseGet(this::createNewTabPanel) - .sendMessage(message); + .sendMessage(message, conversationType); } public void displayConversation(@NotNull Conversation conversation) { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java index 0c1e1f1a..537c06fa 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java @@ -15,7 +15,7 @@ import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.ConversationsState; import ee.carlrobert.codegpt.toolwindow.chat.ui.SelectedFilesNotification; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.awt.BorderLayout; import java.util.List; import javax.swing.JPanel; @@ -39,8 +39,8 @@ public class StandardChatToolWindowPanel extends SimpleToolWindowPanel { (IncludeFilesInContextNotifier) this::displaySelectedFilesNotification); } - public void displaySelectedFilesNotification(List checkedFiles) { - selectedFilesNotification.displaySelectedFilesNotification(checkedFiles); + public void displaySelectedFilesNotification(List referencedFiles) { + selectedFilesNotification.displaySelectedFilesNotification(referencedFiles); } public void clearSelectedFilesNotification() { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java index aa81e2f7..ba55b47c 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java @@ -4,10 +4,11 @@ import static java.lang.String.format; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; +import ee.carlrobert.codegpt.completions.ConversationType; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.state.YouSettingsState; -import ee.carlrobert.codegpt.toolwindow.chat.BaseChatToolWindowTabPanel; +import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowTabPanel; import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody; import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel; import ee.carlrobert.codegpt.toolwindow.chat.ui.UserMessagePanel; @@ -17,7 +18,7 @@ import ee.carlrobert.codegpt.util.file.FileUtil; import javax.swing.JComponent; import org.jetbrains.annotations.NotNull; -public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { +public class StandardChatToolWindowTabPanel extends ChatToolWindowTabPanel { public StandardChatToolWindowTabPanel( @NotNull Project project, @@ -49,7 +50,7 @@ public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { format("\n```%s\n%s\n```", fileExtension, editor.getSelectionModel().getSelectedText()))); message.setUserMessage(action.getUserMessage()); - sendMessage(message); + sendMessage(message, ConversationType.DEFAULT); }); } @@ -69,7 +70,7 @@ public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { var messagePanel = toolWindowScrollablePanel.addMessage(message.getId()); messagePanel.add(new UserMessagePanel(project, message, this)); messagePanel.add(new ResponsePanel() - .withReloadAction(() -> reloadMessage(message, conversation)) + .withReloadAction(() -> reloadMessage(message, conversation, ConversationType.DEFAULT)) .withDeleteAction(() -> removeMessage(message.getId(), conversation)) .addContent(messageResponseBody)); }); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java index 9873333b..775d1612 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java @@ -11,12 +11,10 @@ import com.intellij.util.ui.JBUI; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.ConversationsState; import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.embedding.CheckedFile; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesAccordion.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesAccordion.java index 31566c4d..11b38f8c 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesAccordion.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesAccordion.java @@ -39,7 +39,7 @@ public class SelectedFilesAccordion extends JPanel { panel.setOpaque(false); panel.setVisible(false); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - panel.setBorder(JBUI.Borders.emptyBottom(4)); + panel.setBorder(JBUI.Borders.empty(4, 0)); referencedFilePaths.stream() .map(filePath -> LocalFileSystem.getInstance().findFileByPath(filePath)) .filter(Objects::nonNull) diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesNotification.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesNotification.java index 77826acc..6ff2c1ed 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesNotification.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesNotification.java @@ -12,7 +12,7 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.JBUI.CurrentTheme.NotificationInfo; import ee.carlrobert.codegpt.CodeGPTKeys; import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.awt.BorderLayout; import java.nio.file.Paths; import java.util.List; @@ -47,14 +47,14 @@ public class SelectedFilesNotification extends JPanel { }), BorderLayout.LINE_END); } - public void displaySelectedFilesNotification(@NotNull List checkedFiles) { - if (checkedFiles.isEmpty()) { + public void displaySelectedFilesNotification(@NotNull List referencedFiles) { + if (referencedFiles.isEmpty()) { return; } - label.setText(checkedFiles.size() + " files selected"); - var referencedFilePaths = checkedFiles.stream() - .map(CheckedFile::getFilePath) + label.setText(referencedFiles.size() + " files selected"); + var referencedFilePaths = referencedFiles.stream() + .map(ReferencedFile::getFilePath) .collect(Collectors.toList()); label.setToolTipText(getHtml(referencedFilePaths)); setVisible(true); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/TotalTokensPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/TotalTokensPanel.java index ca4d2cb0..fab7e156 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/TotalTokensPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/TotalTokensPanel.java @@ -16,8 +16,7 @@ import ee.carlrobert.codegpt.CodeGPTKeys; import ee.carlrobert.codegpt.EncodingManager; import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier; import ee.carlrobert.codegpt.conversations.Conversation; -import ee.carlrobert.codegpt.indexes.CodebaseIndexingCompletedNotifier; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.awt.FlowLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -114,7 +113,7 @@ public class TotalTokensPanel extends JPanel { update(); } - public void updateReferencedFilesTokens(List includedFiles) { + public void updateReferencedFilesTokens(List includedFiles) { totalTokensDetails.setReferencedFilesTokens(includedFiles.stream() .mapToInt(file -> encodingManager.countTokens(file.getFileContent())) .sum()); @@ -123,7 +122,7 @@ public class TotalTokensPanel extends JPanel { private TotalTokensDetails createTokenDetails( Conversation conversation, - List includedFiles, + List includedFiles, @Nullable String highlightedText) { var tokenDetails = new TotalTokensDetails(encodingManager); tokenDetails.setConversationTokens(encodingManager.countConversationTokens(conversation)); diff --git a/src/main/java/ee/carlrobert/codegpt/ui/checkbox/FileCheckboxTree.java b/src/main/java/ee/carlrobert/codegpt/ui/checkbox/FileCheckboxTree.java index 324df9f3..546f91a9 100644 --- a/src/main/java/ee/carlrobert/codegpt/ui/checkbox/FileCheckboxTree.java +++ b/src/main/java/ee/carlrobert/codegpt/ui/checkbox/FileCheckboxTree.java @@ -6,7 +6,7 @@ import com.intellij.ui.CheckboxTree; import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.ColoredTreeCellRenderer; import ee.carlrobert.codegpt.util.file.FileUtil; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,7 @@ public abstract class FileCheckboxTree extends CheckboxTree { super(cellRenderer, node); } - public abstract List getCheckedFiles(); + public abstract List getReferencedFiles(); protected static void updateFilePresentation( ColoredTreeCellRenderer textRenderer, diff --git a/src/main/java/ee/carlrobert/codegpt/ui/checkbox/PsiElementCheckboxTree.java b/src/main/java/ee/carlrobert/codegpt/ui/checkbox/PsiElementCheckboxTree.java index e0245516..6b5197ab 100644 --- a/src/main/java/ee/carlrobert/codegpt/ui/checkbox/PsiElementCheckboxTree.java +++ b/src/main/java/ee/carlrobert/codegpt/ui/checkbox/PsiElementCheckboxTree.java @@ -9,7 +9,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.file.PsiDirectoryImpl; import com.intellij.ui.CheckedTreeNode; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.io.File; import java.util.Arrays; import java.util.List; @@ -23,7 +23,7 @@ public class PsiElementCheckboxTree extends FileCheckboxTree { setRootVisible(true); } - public List getCheckedFiles() { + public List getReferencedFiles() { var checkedNodes = getCheckedNodes( PsiElement.class, node -> Optional.ofNullable(node.getContainingFile()) @@ -34,7 +34,8 @@ public class PsiElementCheckboxTree extends FileCheckboxTree { } return Arrays.stream(checkedNodes) - .map(item -> new CheckedFile(new File(item.getContainingFile().getVirtualFile().getPath()))) + .map(item -> new ReferencedFile( + new File(item.getContainingFile().getVirtualFile().getPath()))) .collect(toList()); } diff --git a/src/main/java/ee/carlrobert/codegpt/ui/checkbox/VirtualFileCheckboxTree.java b/src/main/java/ee/carlrobert/codegpt/ui/checkbox/VirtualFileCheckboxTree.java index 712fc7ca..da16b6fa 100644 --- a/src/main/java/ee/carlrobert/codegpt/ui/checkbox/VirtualFileCheckboxTree.java +++ b/src/main/java/ee/carlrobert/codegpt/ui/checkbox/VirtualFileCheckboxTree.java @@ -6,7 +6,7 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.CheckedTreeNode; import com.intellij.util.PlatformIcons; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import java.io.File; import java.util.Arrays; import java.util.List; @@ -19,14 +19,14 @@ public class VirtualFileCheckboxTree extends FileCheckboxTree { super(createFileTypesRenderer(), createRootNode(rootFiles)); } - public List getCheckedFiles() { + public List getReferencedFiles() { var checkedNodes = getCheckedNodes(VirtualFile.class, Objects::nonNull); if (checkedNodes.length > 1000) { throw new RuntimeException("Too many files selected"); } return Arrays.stream(checkedNodes) - .map(item -> new CheckedFile(new File(item.getPath()))) + .map(item -> new ReferencedFile(new File(item.getPath()))) .collect(toList()); } diff --git a/src/main/resources/META-INF/plugin-java.xml b/src/main/resources/META-INF/plugin-java.xml new file mode 100644 index 00000000..409509c0 --- /dev/null +++ b/src/main/resources/META-INF/plugin-java.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8ea5f428..66707add 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -3,6 +3,8 @@ CodeGPT Carl-Robert Linnupuu com.intellij.modules.platform + com.intellij.modules.lang + com.intellij.modules.java new CompletionRequestProvider(conversation) - .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), - createDummyMessage(100), false)); + .buildOpenAIChatCompletionRequest( + OpenAIChatCompletionModel.GPT_3_5.getCode(), + new CallParameters( + conversation, + ConversationType.DEFAULT, + createDummyMessage(100), + false), + false, + null)); } public void testContextualSearch() { @@ -173,8 +205,15 @@ public class CompletionRequestProviderTest extends IntegrationTest { }); var request = new CompletionRequestProvider(conversation) - .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), - new Message("TEST_CHAT_COMPLETION_PROMPT"), false, true, null); + .buildOpenAIChatCompletionRequest( + OpenAIChatCompletionModel.GPT_3_5.getCode(), + new CallParameters( + conversation, + ConversationType.DEFAULT, + new Message("TEST_CHAT_COMPLETION_PROMPT"), + false), + true, + null); assertThat(request.getModel()).isEqualTo("gpt-3.5-turbo"); assertThat(request.getMessages().size()).isEqualTo(1); diff --git a/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java b/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java index 0a24808f..c4dc5537 100644 --- a/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java +++ b/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java @@ -50,7 +50,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!"))))); }); - requestHandler.call(conversation, message, false); + requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false)); await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); } @@ -68,7 +68,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { "/openai/deployments/TEST_DEPLOYMENT_ID/chat/completions"); assertThat(request.getUri().getQuery()).isEqualTo("api-version=TEST_API_VERSION"); assertThat(request.getHeaders().get("Api-key").get(0)).isEqualTo("TEST_API_KEY"); - assertThat(request.getHeaders().get("X-application-name").get(0)).isEqualTo("CODEGPT"); + assertThat(request.getHeaders().get("X-llm-application-tag").get(0)).isEqualTo("codegpt"); assertThat(request.getBody()) .extracting("messages") .isEqualTo( @@ -86,7 +86,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { var message = new Message("TEST_PROMPT"); var requestHandler = new CompletionRequestHandler(false, getRequestEventListener(message)); - requestHandler.call(conversation, message, false); + requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false)); await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); } @@ -105,7 +105,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { .isEqualTo("/codegpt/deployments/TEST_DEPLOYMENT_ID/completions"); assertThat(request.getUri().getQuery()).isEqualTo("api-version=TEST_API_VERSION"); assertThat(request.getHeaders().get("Api-key").get(0)).isEqualTo("TEST_API_KEY"); - assertThat(request.getHeaders().get("X-application-name").get(0)).isEqualTo("CODEGPT"); + assertThat(request.getHeaders().get("X-llm-application-tag").get(0)).isEqualTo("codegpt"); assertThat(request.getBody()) .extracting("messages") .isEqualTo( @@ -123,7 +123,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { var message = new Message("TEST_PROMPT"); var requestHandler = new CompletionRequestHandler(false, getRequestEventListener(message)); - requestHandler.call(conversation, message, false); + requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false)); await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); } @@ -173,7 +173,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { jsonMapResponse("youChatToken", "!")); }); - requestHandler.call(conversation, message, false); + requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false)); await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); } @@ -207,7 +207,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { e("stop", true))); }); - requestHandler.call(conversation, message, false); + requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false)); await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); } @@ -215,11 +215,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest { private CompletionResponseEventListener getRequestEventListener(Message message) { return new CompletionResponseEventListener() { @Override - public void handleCompleted( - String fullMessage, - Message conversationMessage, - Conversation conversation, - boolean retry) { + public void handleCompleted(String fullMessage, CallParameters callParameters) { message.setResponse(fullMessage); } }; diff --git a/src/test/java/ee/carlrobert/codegpt/toolwindow/chat/StandardChatToolWindowTabPanelTest.java b/src/test/java/ee/carlrobert/codegpt/toolwindow/chat/StandardChatToolWindowTabPanelTest.java index 15e8f368..2d52fdba 100644 --- a/src/test/java/ee/carlrobert/codegpt/toolwindow/chat/StandardChatToolWindowTabPanelTest.java +++ b/src/test/java/ee/carlrobert/codegpt/toolwindow/chat/StandardChatToolWindowTabPanelTest.java @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt.toolwindow.chat; import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT; +import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.FIX_COMPILE_ERRORS_SYSTEM_PROMPT; import static ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA; import static ee.carlrobert.llm.client.util.JSONUtil.e; import static ee.carlrobert.llm.client.util.JSONUtil.jsonArray; @@ -13,13 +14,14 @@ import static org.awaitility.Awaitility.await; import ee.carlrobert.codegpt.CodeGPTKeys; import ee.carlrobert.codegpt.EncodingManager; +import ee.carlrobert.codegpt.completions.ConversationType; import ee.carlrobert.codegpt.completions.HuggingFaceModel; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.settings.state.LlamaSettingsState; import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabPanel; -import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.ReferencedFile; import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange; import java.util.List; import java.util.Map; @@ -88,12 +90,15 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest { public void testSendingOpenAIMessageWithReferencedContext() { getProject().putUserData(CodeGPTKeys.SELECTED_FILES, List.of( - new CheckedFile("TEST_FILE_NAME_1", "TEST_FILE_PATH_1", "TEST_FILE_CONTENT_1"), - new CheckedFile("TEST_FILE_NAME_2", "TEST_FILE_PATH_2", "TEST_FILE_CONTENT_2"), - new CheckedFile("TEST_FILE_NAME_3", "TEST_FILE_PATH_3", "TEST_FILE_CONTENT_3"))); + new ReferencedFile("TEST_FILE_NAME_1", "TEST_FILE_PATH_1", "TEST_FILE_CONTENT_1"), + new ReferencedFile("TEST_FILE_NAME_2", "TEST_FILE_PATH_2", "TEST_FILE_CONTENT_2"), + new ReferencedFile("TEST_FILE_NAME_3", "TEST_FILE_PATH_3", "TEST_FILE_CONTENT_3"))); useOpenAIService(); ConfigurationState.getInstance().setSystemPrompt(COMPLETION_SYSTEM_PROMPT); - var message = new Message("Hello!"); + var message = new Message("TEST_MESSAGE"); + message.setUserMessage("TEST_MESSAGE"); + message.setReferencedFilePaths( + List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")); var conversation = ConversationService.getInstance().startConversation(); var panel = new StandardChatToolWindowTabPanel(getProject(), conversation); expectOpenAI((StreamHttpExchange) request -> { @@ -125,7 +130,7 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest { + "```TEST_FILE_NAME_3\n" + "TEST_FILE_CONTENT_3\n" + "```\n\n" - + "Question: Hello!"))); + + "Question: TEST_MESSAGE"))); return List.of( jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))), jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))), @@ -170,6 +175,93 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest { List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")); } + public void testFixCompileErrorsWithOpenAIService() { + getProject().putUserData(CodeGPTKeys.SELECTED_FILES, List.of( + new ReferencedFile("TEST_FILE_NAME_1", "TEST_FILE_PATH_1", "TEST_FILE_CONTENT_1"), + new ReferencedFile("TEST_FILE_NAME_2", "TEST_FILE_PATH_2", "TEST_FILE_CONTENT_2"), + new ReferencedFile("TEST_FILE_NAME_3", "TEST_FILE_PATH_3", "TEST_FILE_CONTENT_3"))); + useOpenAIService(); + ConfigurationState.getInstance().setSystemPrompt(COMPLETION_SYSTEM_PROMPT); + var message = new Message("TEST_MESSAGE"); + message.setUserMessage("TEST_MESSAGE"); + message.setReferencedFilePaths( + List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")); + var conversation = ConversationService.getInstance().startConversation(); + var panel = new StandardChatToolWindowTabPanel(getProject(), conversation); + expectOpenAI((StreamHttpExchange) request -> { + assertThat(request.getUri().getPath()).isEqualTo("/v1/chat/completions"); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getHeaders().get(AUTHORIZATION).get(0)).isEqualTo("Bearer TEST_API_KEY"); + assertThat(request.getBody()) + .extracting( + "model", + "messages") + .containsExactly( + "gpt-4", + List.of( + Map.of("role", "system", "content", FIX_COMPILE_ERRORS_SYSTEM_PROMPT), + Map.of("role", "user", "content", + "Use the following context to answer question at the end:\n\n" + + "File Path: TEST_FILE_PATH_1\n" + + "File Content:\n" + + "```TEST_FILE_NAME_1\n" + + "TEST_FILE_CONTENT_1\n" + + "```\n\n" + + "File Path: TEST_FILE_PATH_2\n" + + "File Content:\n" + + "```TEST_FILE_NAME_2\n" + + "TEST_FILE_CONTENT_2\n" + + "```\n\n" + + "File Path: TEST_FILE_PATH_3\n" + + "File Content:\n" + + "```TEST_FILE_NAME_3\n" + + "TEST_FILE_CONTENT_3\n" + + "```\n\n" + + "Question: TEST_MESSAGE"))); + return List.of( + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "lo")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!"))))); + }); + + panel.sendMessage(message, ConversationType.FIX_COMPILE_ERRORS); + + await().atMost(5, SECONDS) + .until(() -> { + var messages = conversation.getMessages(); + return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse()); + }); + var encodingManager = EncodingManager.getInstance(); + assertThat(panel.getTokenDetails()).extracting( + "systemPromptTokens", + "conversationTokens", + "userPromptTokens", + "highlightedTokens") + .containsExactly( + encodingManager.countTokens(COMPLETION_SYSTEM_PROMPT), + encodingManager.countTokens(message.getPrompt()), + 0, + 0); + assertThat(panel.getConversation()) + .isNotNull() + .extracting("id", "model", "clientCode", "discardTokenLimit") + .containsExactly( + conversation.getId(), + conversation.getModel(), + conversation.getClientCode(), + false); + var messages = panel.getConversation().getMessages(); + assertThat(messages.size()).isOne(); + assertThat(messages.get(0)) + .extracting("id", "prompt", "response", "referencedFilePaths") + .containsExactly( + message.getId(), + message.getPrompt(), + message.getResponse(), + List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")); + } + public void testSendingLlamaMessage() { useLlamaService(); var configurationState = ConfigurationState.getInstance(); @@ -218,7 +310,7 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest { e("stop", true))); }); - panel.sendMessage(message); + panel.sendMessage(message, ConversationType.DEFAULT); await().atMost(5, SECONDS) .until(() -> {