From 7fbae569b188bdbab9f7953da78022f10c34e380 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Mon, 17 Mar 2025 15:32:08 +0000 Subject: [PATCH] feat: improved multi-line edits (#923) * fix: llama.cpp copy task * feat: next edit predictions * feat: minor clean up --- build.gradle.kts | 23 +++ .../configuration/TelemetryConfiguration.java | 9 + .../ui/preferences/TelemetryComponent.java | 33 +++- .../ui/preferences/TelemetryConfigurable.java | 9 +- config/checkstyle/suppressions.xml | 4 + gradle/libs.versions.toml | 9 + .../codegpt/CodeGPTCopyPastePreProcessor.kt | 76 -------- .../codegpt/CodeGPTLookupListener.kt | 14 +- .../CodeAssistantFeatureToggleActions.kt.kt | 8 +- .../CodeCompletionFeatureToggleActions.kt | 5 +- .../CodeCompletionInsertAction.kt | 12 +- .../DebouncedCodeCompletionProvider.kt | 32 +++- .../codecompletions/edit/GrpcClientService.kt | 179 ++++++++++++++++++ .../predictions/CodeSuggestionDiffViewer.kt | 21 +- .../codegpt/predictions/PredictionService.kt | 159 ++-------------- .../TriggerCustomPredictionAction.kt | 23 +-- .../settings/prompts/PromptsSettings.kt | 7 - .../settings/prompts/form/PromptsForm.kt | 3 - .../form/details/CoreActionsDetailsPanel.kt | 22 --- .../service/codegpt/CodeGPTServiceForm.kt | 16 +- .../service/codegpt/CodeGPTServiceSettings.kt | 2 +- src/main/proto/next-edit.proto | 29 +++ src/main/resources/META-INF/plugin.xml | 12 +- .../resources/messages/codegpt.properties | 15 +- .../resources/prompts/core/code-assistant.txt | 18 -- .../CodeCompletionServiceTest.kt | 6 +- 26 files changed, 382 insertions(+), 364 deletions(-) delete mode 100644 src/main/kotlin/ee/carlrobert/codegpt/CodeGPTCopyPastePreProcessor.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt create mode 100644 src/main/proto/next-edit.proto delete mode 100644 src/main/resources/prompts/core/code-assistant.txt diff --git a/build.gradle.kts b/build.gradle.kts index b5bcaa3d..6c570e3c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ fun environment(key: String) = providers.environmentVariable(key) plugins { id("codegpt.java-conventions") alias(libs.plugins.changelog) + alias(libs.plugins.protobuf) } group = properties("pluginGroup").get() @@ -67,6 +68,9 @@ dependencies { implementation(libs.jsoup) implementation(libs.commons.text) implementation(libs.jtokkit) + implementation(libs.grpc.protobuf) + implementation(libs.grpc.stub) + implementation(libs.grpc.netty.shaded) testImplementation(kotlin("test")) } @@ -162,3 +166,22 @@ tasks { } } } + +protobuf { + protoc { + artifact = libs.protobuf.protoc.get().toString() + } + plugins { + create("grpc") { + artifact = libs.protobuf.java.get().toString() + } + } + generateProtoTasks { + all() + .forEach { + it.plugins { + create("grpc") + } + } + } +} \ No newline at end of file diff --git a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/core/configuration/TelemetryConfiguration.java b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/core/configuration/TelemetryConfiguration.java index a5c0cbc0..a24271c9 100644 --- a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/core/configuration/TelemetryConfiguration.java +++ b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/core/configuration/TelemetryConfiguration.java @@ -20,6 +20,7 @@ import java.util.List; public class TelemetryConfiguration extends CompositeConfiguration { public static final String KEY_MODE = "ee.carlrobert.telemetry.mode"; + public static final String KEY_COMPLETION_TELEMETRY_ENABLED = "ee.carlrobert.telemetry.completionStatisticsEnabled"; private static final SaveableFileConfiguration FILE = new SaveableFileConfiguration( Directories.PATH.resolve("ee.carlrobert.intellij.telemetry")); @@ -50,6 +51,14 @@ public class TelemetryConfiguration extends CompositeConfiguration { setMode(Mode.valueOf(enabled)); } + public boolean isCompletionTelemetryEnabled() { + return Boolean.parseBoolean(get(KEY_COMPLETION_TELEMETRY_ENABLED)); + } + + public void setCompletionTelemetryEnabled(boolean enabled) { + put(KEY_COMPLETION_TELEMETRY_ENABLED, String.valueOf(enabled)); + } + public boolean isDebug() { return getMode() == Mode.DEBUG; } diff --git a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryComponent.java b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryComponent.java index 75b75bbc..5989590d 100644 --- a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryComponent.java +++ b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryComponent.java @@ -21,19 +21,27 @@ import javax.swing.JPanel; */ public class TelemetryComponent { - private static final String DESCRIPTION = + private static final String USAGE_DESCRIPTION = "Help ProxyAI improve its products by sending anonymous data about features and plugins used, " + "hardware and software configuration.
" + "
" - + "Please note that this will not include personal data or any sensitive Information.
" + + "Please note that this will not include personal data or any sensitive Information."; + + private static final String COMPLETION_DESCRIPTION = + "Help ProxyAI improve its products by sharing your code inputs and completions.
" + + "
" + "The data sent complies with the Privacy Policy."; private final JPanel panel; - private final JBCheckBox enabled = new JBCheckBox("Send usage statistics"); + private final JBCheckBox usageStatisticsEnabled = new JBCheckBox("Send usage statistics"); + private final JBCheckBox completionStatisticsEnabled = new JBCheckBox("Send completion statistics"); public TelemetryComponent() { this.panel = FormBuilder.createFormBuilder() - .addComponent(createCommentedPanel(enabled, DESCRIPTION), 1) + .addComponent( + createCommentedPanel(usageStatisticsEnabled, USAGE_DESCRIPTION), 1) + .addComponent( + createCommentedPanel(completionStatisticsEnabled, COMPLETION_DESCRIPTION), 1) .addComponentFillVertically(new JPanel(), 0) .getPanel(); } @@ -50,15 +58,22 @@ public class TelemetryComponent { } public JComponent getPreferredFocusedComponent() { - return enabled; + return usageStatisticsEnabled; } - public boolean isEnabled() { - return enabled.isSelected(); + public boolean getUsageStatisticsEnabled() { + return usageStatisticsEnabled.isSelected(); } - public void setEnabled(boolean enabled) { - this.enabled.setSelected(enabled); + public void setUsageStatisticsEnabled(boolean usageStatisticsEnabled) { + this.usageStatisticsEnabled.setSelected(usageStatisticsEnabled); } + public boolean getCompletionStatisticsEnabled() { + return completionStatisticsEnabled.isSelected(); + } + + public void setCompletionStatisticsEnabled(boolean completionStatisticsEnabled) { + this.completionStatisticsEnabled.setSelected(completionStatisticsEnabled); + } } diff --git a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryConfigurable.java b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryConfigurable.java index f98f7566..e64e15f4 100644 --- a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryConfigurable.java +++ b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/ui/preferences/TelemetryConfigurable.java @@ -53,13 +53,15 @@ public class TelemetryConfigurable implements SearchableConfigurable { @Override public boolean isModified() { boolean modified = false; - modified |= (component.isEnabled() != configuration.isEnabled()); + modified |= (component.getUsageStatisticsEnabled() != configuration.isEnabled()); + modified |= (component.getCompletionStatisticsEnabled() != configuration.isCompletionTelemetryEnabled()); return modified; } @Override public void apply() { - configuration.setEnabled(component.isEnabled()); + configuration.setEnabled(component.getUsageStatisticsEnabled()); + configuration.setCompletionTelemetryEnabled(component.getCompletionStatisticsEnabled()); try { configuration.save(); } catch (IOException e) { @@ -69,7 +71,8 @@ public class TelemetryConfigurable implements SearchableConfigurable { @Override public void reset() { - component.setEnabled(configuration.isEnabled()); + component.setUsageStatisticsEnabled(configuration.isEnabled()); + component.setCompletionStatisticsEnabled(configuration.isCompletionTelemetryEnabled()); } @Override diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index 811e51c3..1aeb5281 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -9,4 +9,8 @@ files="[\\/]src[\\/]main[\\/]java[\\/]ee[\\/]carlrobert[\\/]codegpt[\\/]telemetry" checks="."/> + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index faedaee5..4666eb4d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,9 @@ kotlin = "2.0.0" llm-client = "0.8.37" okio = "3.9.0" tree-sitter = "0.24.4" +grpc = "1.71.0" +protobuf = "3.25.1" +protobuf-plugin = "0.9.4" [libraries] analytics = { module = "com.rudderstack.sdk.java.analytics:analytics", version.ref = "analytics" } @@ -31,6 +34,12 @@ kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", v llm-client = { module = "ee.carlrobert:llm-client", version.ref = "llm-client" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } tree-sitter = { module = "io.github.bonede:tree-sitter", version.ref = "tree-sitter" } +grpc-protobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" } +grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" } +grpc-netty-shaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpc" } +protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } +protobuf-java = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" } [plugins] changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } +protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTCopyPastePreProcessor.kt b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTCopyPastePreProcessor.kt deleted file mode 100644 index 7cd7881f..00000000 --- a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTCopyPastePreProcessor.kt +++ /dev/null @@ -1,76 +0,0 @@ -package ee.carlrobert.codegpt - -import com.intellij.codeInsight.editorActions.CopyPastePreProcessor -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.RawText -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiFile -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey -import ee.carlrobert.codegpt.credentials.CredentialsStore.isCredentialSet -import ee.carlrobert.codegpt.predictions.PredictionService -import ee.carlrobert.codegpt.settings.GeneralSettings -import ee.carlrobert.codegpt.settings.service.ServiceType -import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -class CodeGPTCopyPastePreProcessor : CopyPastePreProcessor { - - - private companion object { - const val MAX_TOKEN_LIMIT = 4096 - const val FREE_TIER_TOKEN_LIMIT = 2048 - - private val logger = thisLogger() - } - - override fun preprocessOnCopy( - file: PsiFile, - startOffsets: IntArray, - endOffsets: IntArray, - text: String - ): String { - return text - } - - override fun preprocessOnPaste( - project: Project, - file: PsiFile, - editor: Editor, - text: String, - rawText: RawText? - ): String { - try { - displayPrediction(editor.document.text) { - service().displayPastePrediction(editor, text) - } - } catch (e: Exception) { - logger.error("Error displaying paste prediction") - } - - return text - } - - private fun displayPrediction(documentText: String, handleDisplay: () -> Unit = {}) { - if (!isPredictionEnabled()) return - - val currentTokens = getDocumentTokenCount(documentText) - if (currentTokens > MAX_TOKEN_LIMIT) return - - if (!isCredentialSet(CodeGptApiKey) && currentTokens > FREE_TIER_TOKEN_LIMIT) return - - CoroutineScope(Dispatchers.IO).launch { - handleDisplay() - } - } - - private fun isPredictionEnabled(): Boolean = - GeneralSettings.getSelectedService() == ServiceType.CODEGPT && - service().state.codeAssistantEnabled - - private fun getDocumentTokenCount(text: String): Int = - EncodingManager.getInstance().countTokens(text) -} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTLookupListener.kt b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTLookupListener.kt index 04731d47..71800bc8 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTLookupListener.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTLookupListener.kt @@ -8,8 +8,6 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.service -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey -import ee.carlrobert.codegpt.credentials.CredentialsStore.isCredentialSet import ee.carlrobert.codegpt.predictions.PredictionService import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.service.ServiceType @@ -34,22 +32,14 @@ class CodeGPTLookupListener : LookupManagerListener { override fun itemSelected(event: LookupEvent) { val editor = newLookup.editor - val encodingManager = EncodingManager.getInstance() if (GeneralSettings.getSelectedService() != ServiceType.CODEGPT - || !service().state.codeAssistantEnabled - || encodingManager.countTokens(editor.document.text) > 4096 - || !isCredentialSet(CodeGptApiKey) && encodingManager.countTokens(editor.document.text) > 2048 + || !service().state.nextEditsEnabled ) { return } ApplicationManager.getApplication().executeOnPooledThread { - service().displayLookupPrediction( - editor, - event, - beforeApply, - cursorOffset - ) + service().displayInlineDiff(editor) } } }) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeAssistantFeatureToggleActions.kt.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeAssistantFeatureToggleActions.kt.kt index 2a254d37..dcf39c32 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeAssistantFeatureToggleActions.kt.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeAssistantFeatureToggleActions.kt.kt @@ -14,11 +14,11 @@ abstract class CodeAssistantFeatureToggleAction( override fun actionPerformed(e: AnActionEvent) { val settings = service().state - settings.codeAssistantEnabled = enableFeatureAction + settings.nextEditsEnabled = enableFeatureAction } override fun update(e: AnActionEvent) { - val codeAssistantEnabled = service().state.codeAssistantEnabled + val codeAssistantEnabled = service().state.nextEditsEnabled e.presentation.isVisible = GeneralSettings.getSelectedService() == CODEGPT && codeAssistantEnabled != enableFeatureAction @@ -30,6 +30,6 @@ abstract class CodeAssistantFeatureToggleAction( } } -class EnableCodeAssistantAction : CodeAssistantFeatureToggleAction(true) +class EnableNextEditsAction : CodeAssistantFeatureToggleAction(true) -class DisableCodeAssistantAction : CodeAssistantFeatureToggleAction(false) \ No newline at end of file +class DisableNextEditsAction : CodeAssistantFeatureToggleAction(false) \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt index 26181380..dddb98de 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt @@ -37,7 +37,8 @@ abstract class CodeCompletionFeatureToggleActions( override fun update(e: AnActionEvent) { val selectedService = GeneralSettings.getSelectedService() val codeCompletionEnabled = - e.project?.service()?.isCodeCompletionsEnabled(selectedService) ?: false + e.project?.service()?.isCodeCompletionsEnabled(selectedService) + ?: false e.presentation.isVisible = codeCompletionEnabled != enableFeatureAction e.presentation.isEnabled = when (selectedService) { CODEGPT, @@ -60,4 +61,4 @@ abstract class CodeCompletionFeatureToggleActions( class EnableCompletionsAction : CodeCompletionFeatureToggleActions(true) -class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false) +class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false) \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertAction.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertAction.kt index c81900ba..79caee60 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertAction.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionInsertAction.kt @@ -17,7 +17,6 @@ import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler import com.intellij.psi.PsiDocumentManager import com.intellij.util.concurrency.ThreadingAssertions import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_EDITOR_COMPLETION -import ee.carlrobert.codegpt.EncodingManager import ee.carlrobert.codegpt.predictions.PredictionService import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.service.ServiceType @@ -47,18 +46,13 @@ class CodeCompletionInsertAction : ) } - val beforeApply = editor.document.text InlineCompletion.getHandlerOrNull(editor)?.insert() if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT - && service().state.codeAssistantEnabled - && service().countTokens(editor.document.text) <= 4096) { + && service().state.nextEditsEnabled + ) { ApplicationManager.getApplication().executeOnPooledThread { - service().displayAutocompletePrediction( - editor, - textToInsert, - beforeApply - ) + service().displayInlineDiff(editor) } return } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt index 92e06eb7..832f62dd 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_EDITOR_COMPLETION +import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings import ee.carlrobert.codegpt.settings.service.ServiceType @@ -47,6 +48,13 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { get() = CodeCompletionProviderPresentation() override suspend fun getSuggestionDebounced(request: InlineCompletionRequest): InlineCompletionSuggestion { + if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT + && service().state.nextEditsEnabled + ) { + predictNextEdit(request) + return InlineCompletionSingleSuggestion.build(elements = emptyFlow()) + } + return if (service().state.codeCompletionSettings.multiLineEnabled) { getMultiLineSuggestionDebounced(request) } else { @@ -54,6 +62,18 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { } } + private fun predictNextEdit(request: InlineCompletionRequest) { + val project = request.editor.project ?: return + try { + CompletionProgressNotifier.update(project, true) + project.service().getNextEdit(request.editor) + } catch (ex: Exception) { + logger.error("Error communicating with server: ${ex.message}") + } finally { + CompletionProgressNotifier.update(project, false) + } + } + private fun getSingleLineSuggestionDebounced(request: InlineCompletionRequest): InlineCompletionSuggestion { val editor = request.editor val remainingCompletion = REMAINING_EDITOR_COMPLETION.get(editor) ?: "" @@ -85,7 +105,11 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { .getCodeCompletionAsync( infillRequest, CodeCompletionMultiLineEventListener(request) { - trySend(InlineCompletionGrayTextElement(it)) + if (it.isEmpty() && service().state.nextEditsEnabled) { + predictNextEdit(request) + } else { + trySend(InlineCompletionGrayTextElement(it)) + } } ) } @@ -106,10 +130,6 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { return InlineCompletionSuggestion.Default(emptyFlow()) } - if (LookupManager.getActiveLookup(request.editor) != null) { - return InlineCompletionSuggestion.Default(emptyFlow()) - } - request.editor.project?.let { CompletionProgressNotifier.update(it, true) } @@ -140,7 +160,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { } if (!codeCompletionsEnabled) { - return false + return selectedService == ServiceType.CODEGPT && service().state.nextEditsEnabled } if (LookupManager.getActiveLookup(event.toRequest()?.editor) != null) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt new file mode 100644 index 00000000..14b7d3e6 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/edit/GrpcClientService.kt @@ -0,0 +1,179 @@ +package ee.carlrobert.codegpt.codecompletions.edit + +import com.intellij.notification.NotificationAction.createSimpleExpiring +import com.intellij.notification.NotificationType +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.jetbrains.rd.util.UUID +import ee.carlrobert.codegpt.credentials.CredentialsStore +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey +import ee.carlrobert.codegpt.predictions.CodeSuggestionDiffViewer +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings +import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration +import ee.carlrobert.codegpt.ui.OverlayUtil +import ee.carlrobert.codegpt.util.GitUtil +import ee.carlrobert.service.AcceptEditRequest +import ee.carlrobert.service.NextEditRequest +import ee.carlrobert.service.NextEditResponse +import ee.carlrobert.service.NextEditServiceImplGrpc +import io.grpc.* +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder +import io.grpc.stub.StreamObserver +import java.util.concurrent.Executor +import java.util.concurrent.TimeUnit +import kotlin.coroutines.cancellation.CancellationException + +@Service(Service.Level.PROJECT) +class GrpcClientService(private val project: Project) : Disposable { + + private var channel: ManagedChannel? = null + private var stub: NextEditServiceImplGrpc.NextEditServiceImplStub? = null + private var prevObserver: NextEditStreamObserver? = null + + companion object { + private const val HOST = "grpc.tryproxy.io" + private const val PORT = 9090 + private const val SHUTDOWN_TIMEOUT_SECONDS = 5L + + private val logger = thisLogger() + } + + @Synchronized + private fun ensureConnection() { + if (channel == null || channel?.isShutdown == true) { + try { + channel = NettyChannelBuilder.forAddress(HOST, PORT).build() + stub = NextEditServiceImplGrpc.newStub(channel) + .withCallCredentials( + ApiKeyCredentials(CredentialsStore.getCredential(CodeGptApiKey) ?: "") + ) + + logger.info("gRPC connection established") + } catch (e: Exception) { + logger.error("Failed to establish gRPC connection", e) + throw e + } + } + } + + fun getNextEdit(editor: Editor, isManuallyOpened: Boolean = false) { + ensureConnection() + prevObserver?.onCompleted() + + val request = NextEditRequest.newBuilder() + .setFileName(editor.virtualFile.name) + .setFileContent(editor.document.text) + .setGitDiff(GitUtil.getCurrentChanges(project) ?: "") + .setCursorPosition(runReadAction { editor.caretModel.offset }) + .setEnableTelemetry(TelemetryConfiguration.getInstance().isCompletionTelemetryEnabled) + .build() + prevObserver = NextEditStreamObserver(editor, isManuallyOpened) { + dispose() + } + + stub?.nextEdit(request, prevObserver) + } + + class NextEditStreamObserver( + private val editor: Editor, + private val isManuallyOpened: Boolean, + private val onDispose: () -> Unit + ) : StreamObserver { + override fun onNext(response: NextEditResponse) { + runInEdt { + CodeSuggestionDiffViewer.displayInlineDiff(editor, response, isManuallyOpened) + } + } + + override fun onError(ex: Throwable) { + if (ex is CancellationException) { + onCompleted() + return + } + + try { + if (ex is StatusRuntimeException) { + if (ex.status.code == Status.Code.CANCELLED) { + onCompleted() + return + } + + OverlayUtil.showNotification( + ex.status.description ?: ex.localizedMessage, + NotificationType.ERROR, + createSimpleExpiring("Disable multi-line edits") { + service().state.nextEditsEnabled = + false + }) + } else { + logger.error("Something went wrong", ex) + } + } finally { + onDispose() + } + } + + override fun onCompleted() { + } + } + + fun acceptEdit(responseId: UUID, acceptedEdit: String) { + NextEditServiceImplGrpc + .newBlockingStub(channel) + .acceptEdit( + AcceptEditRequest.newBuilder() + .setResponseId(responseId.toString()) + .setAcceptedEdit(acceptedEdit) + .build() + ) + } + + override fun dispose() { + channel?.let { ch -> + if (!ch.isShutdown) { + try { + ch.shutdown().awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS) + logger.info("gRPC connection closed") + } catch (e: InterruptedException) { + logger.warn("Interrupted while shutting down gRPC channel", e) + Thread.currentThread().interrupt() + } finally { + if (!ch.isTerminated) { + ch.shutdownNow() + } + channel = null + } + } + } + } +} + +internal class ApiKeyCredentials(private val apiKey: String) : CallCredentials() { + + companion object { + private val API_KEY_HEADER: Metadata.Key = + Metadata.Key.of("x-api-key", Metadata.ASCII_STRING_MARSHALLER) + } + + override fun applyRequestMetadata( + requestInfo: RequestInfo?, + executor: Executor, + metadataApplier: MetadataApplier + ) { + executor.execute { + try { + val headers = Metadata() + headers.put(API_KEY_HEADER, apiKey) + metadataApplier.apply(headers) + } catch (e: Throwable) { + metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e)) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt b/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt index 50eaf0df..127f7846 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/predictions/CodeSuggestionDiffViewer.kt @@ -27,15 +27,19 @@ import com.intellij.openapi.util.* import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane +import com.intellij.util.application import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.ui.JBUI import com.intellij.util.ui.components.BorderLayoutPanel import ee.carlrobert.codegpt.CodeGPTBundle import ee.carlrobert.codegpt.CodeGPTKeys +import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService +import ee.carlrobert.service.NextEditResponse import java.awt.BorderLayout import java.awt.Dimension import java.awt.FlowLayout import java.awt.Point +import java.util.* import javax.swing.Box import javax.swing.JComponent import javax.swing.JPanel @@ -45,6 +49,7 @@ import kotlin.math.max class CodeSuggestionDiffViewer( request: DiffRequest, + private val responseId: UUID, private val mainEditor: Editor, private val isManuallyOpened: Boolean ) : UnifiedDiffViewer(MyDiffContext(mainEditor.project), request), Disposable { @@ -117,6 +122,10 @@ class CodeSuggestionDiffViewer( if (changes.size == 1) { popup.dispose() } + + application.executeOnPooledThread { + project?.service()?.acceptEdit(responseId, change.toString()) + } } fun isVisible(): Boolean { @@ -288,10 +297,11 @@ class CodeSuggestionDiffViewer( @RequiresEdt fun displayInlineDiff( editor: Editor, - nextRevision: String, + nextEditResponse: NextEditResponse, isManuallyOpened: Boolean = false ) { - if (editor.virtualFile == null || editor.isViewer) { + val nextRevision = nextEditResponse.nextRevision + if (editor.virtualFile == null || editor.isViewer || nextRevision.isEmpty()) { return } @@ -304,7 +314,12 @@ class CodeSuggestionDiffViewer( } val diffRequest = createSimpleDiffRequest(editor, nextRevision) - val diffViewer = CodeSuggestionDiffViewer(diffRequest, editor, isManuallyOpened) + val diffViewer = CodeSuggestionDiffViewer( + diffRequest, + UUID.fromString(nextEditResponse.id), + editor, + isManuallyOpened + ) editor.putUserData(CodeGPTKeys.EDITOR_PREDICTION_DIFF_VIEWER, diffViewer) diffViewer.rediff(true) } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt b/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt index 8d75b693..5668964d 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/predictions/PredictionService.kt @@ -1,76 +1,23 @@ package ee.carlrobert.codegpt.predictions -import com.intellij.codeInsight.lookup.LookupEvent import com.intellij.diff.DiffManager -import com.intellij.notification.NotificationListener -import com.intellij.notification.NotificationType import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.testFramework.LightVirtualFile import ee.carlrobert.codegpt.CodeGPTKeys -import ee.carlrobert.codegpt.CodeGPTKeys.PENDING_PREDICTION_CALL import ee.carlrobert.codegpt.codecompletions.CompletionProgressNotifier -import ee.carlrobert.codegpt.completions.CompletionClientProvider -import ee.carlrobert.codegpt.conversations.ConversationsState -import ee.carlrobert.codegpt.settings.prompts.PromptsSettings -import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings -import ee.carlrobert.codegpt.ui.OverlayUtil -import ee.carlrobert.codegpt.ui.OverlayUtil.getDefaultNotification +import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService import ee.carlrobert.codegpt.util.EditorDiffUtil.createDiffRequest -import ee.carlrobert.codegpt.util.EditorUtil -import ee.carlrobert.codegpt.util.GitUtil -import ee.carlrobert.llm.client.codegpt.request.prediction.AutocompletionPredictionRequest -import ee.carlrobert.llm.client.codegpt.request.prediction.DirectPredictionRequest -import ee.carlrobert.llm.client.codegpt.request.prediction.PastePredictionRequest -import ee.carlrobert.llm.client.codegpt.request.prediction.PredictionRequest -import ee.carlrobert.llm.client.codegpt.response.CodeGPTException -import ee.carlrobert.llm.client.codegpt.response.PredictionResponse -import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage -import okhttp3.Request @Service class PredictionService { - fun displayDirectPrediction(editor: Editor) { - val request = CompletionClientProvider.getCodeGPTClient() - .buildDirectPredictionRequest(createDirectPredictionRequest(editor)) - displayInlineDiff(editor, request, true) - } - - fun displayAutocompletePrediction(editor: Editor, textToInsert: String, beforeApply: String) { - val request = CompletionClientProvider.getCodeGPTClient() - .buildAutocompletionPredictionRequest( - createAutocompletePredictionRequest(editor, textToInsert, beforeApply) - ) - displayInlineDiff(editor, request) - } - - fun displayLookupPrediction( - editor: Editor, - event: LookupEvent, - beforeApply: String, - cursorOffset: Int - ) { - val request = CompletionClientProvider.getCodeGPTClient() - .buildLookupPredictionRequest( - createAutocompletePredictionRequest( - editor, - event.item?.lookupString ?: "", - beforeApply, - cursorOffset - ) - ) - displayInlineDiff(editor, request) - } - - fun displayPastePrediction(editor: Editor, pastedText: String) { - val request = CompletionClientProvider.getCodeGPTClient() - .buildPastePredictionRequest(createPastePredictionRequest(editor, pastedText)) - displayInlineDiff(editor, request) + companion object { + private val logger = thisLogger() } fun acceptPrediction(editor: Editor) { @@ -81,102 +28,18 @@ class PredictionService { } } - private fun displayInlineDiff( + fun displayInlineDiff( editor: Editor, - request: Request, isManuallyOpened: Boolean = false ) { - val prediction = getPrediction(editor, request) - if (prediction != null && !prediction.nextRevision.isNullOrEmpty()) { - runInEdt { - CodeSuggestionDiffViewer.displayInlineDiff( - editor, - prediction.nextRevision, - isManuallyOpened - ) - } - } - } - - private fun getPrediction(editor: Editor, request: Request): PredictionResponse? { - editor.project?.let { - CompletionProgressNotifier.update(it, true) - } - - val pendingCall = PENDING_PREDICTION_CALL.get(editor) - if (pendingCall != null) { - pendingCall.cancel() - return null - } - + val project = editor.project ?: return try { - val client = CompletionClientProvider.getCodeGPTClient() - val call = client.createNewCall(request) - PENDING_PREDICTION_CALL.set(editor, call) - return client.getPrediction(call) - } catch (e: CodeGPTException) { - OverlayUtil.notify( - getDefaultNotification(e.detail, NotificationType.ERROR).apply { - setListener(NotificationListener.UrlOpeningListener(true)) - }) - - service().state.codeAssistantEnabled = false - return null - } catch (e: Exception) { - if (e.cause?.message != "Canceled") { - throw e - } - return null + CompletionProgressNotifier.update(project, true) + project.service().getNextEdit(editor, isManuallyOpened) + } catch (ex: Exception) { + logger.error("Error communicating with server: ${ex.message}") } finally { - PENDING_PREDICTION_CALL.set(editor, null) - editor.project?.let { - CompletionProgressNotifier.update(it, false) - } - } - } - - private fun createAutocompletePredictionRequest( - editor: Editor, - textToInsert: String, - beforeApply: String, - cursorOffset: Int? = null, - ): AutocompletionPredictionRequest { - val predictionRequest = AutocompletionPredictionRequest() - predictionRequest.appliedCompletion = textToInsert - predictionRequest.previousRevision = beforeApply - setDefaultParams(editor, predictionRequest, cursorOffset) - return predictionRequest - } - - private fun createPastePredictionRequest( - editor: Editor, - pastedCode: String - ): PastePredictionRequest { - val predictionRequest = PastePredictionRequest(pastedCode) - setDefaultParams(editor, predictionRequest) - return predictionRequest - } - - private fun createDirectPredictionRequest(editor: Editor): DirectPredictionRequest { - val predictionRequest = DirectPredictionRequest() - setDefaultParams(editor, predictionRequest) - return predictionRequest - } - - private fun setDefaultParams(editor: Editor, request: PredictionRequest, offset: Int? = null) { - val messages: MutableList = mutableListOf() - ConversationsState.getInstance().currentConversation?.messages?.forEach { - messages.add(OpenAIChatCompletionStandardMessage("user", it.prompt)) - messages.add(OpenAIChatCompletionStandardMessage("assistant", it.response)) - } - request.apply { - currentRevision = runReadAction { editor.document.text } - customPrompt = - service().state.coreActions.codeAssistant.instructions - cursorOffset = offset ?: runReadAction { editor.caretModel.offset } - gitChanges = GitUtil.getCurrentChanges(editor.project!!) - openFiles = EditorUtil.getOpenFiles(editor.project!!) - conversationMessages = messages.toList() + CompletionProgressNotifier.update(project, false) } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt b/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt index 18499b62..39034a7a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/predictions/TriggerCustomPredictionAction.kt @@ -12,9 +12,6 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.EditorAction import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler import ee.carlrobert.codegpt.CodeGPTKeys -import ee.carlrobert.codegpt.EncodingManager -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey -import ee.carlrobert.codegpt.credentials.CredentialsStore.isCredentialSet import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings @@ -33,14 +30,14 @@ class TriggerCustomPredictionAction : EditorAction(Handler()), HintManagerImpl.A return } - if (!service().state.codeAssistantEnabled) { + if (!service().state.nextEditsEnabled) { val notification = OverlayUtil.getDefaultNotification( - "Please enable Code Assistant before using this feature.", + "Please enable multi-line edits before using this feature.", NotificationType.WARNING, ) - notification.addAction(object : AnAction("Enable Code Assistant") { + notification.addAction(object : AnAction("Enable Multi-Line Edits") { override fun actionPerformed(e: AnActionEvent) { - service().state.codeAssistantEnabled = true + service().state.nextEditsEnabled = true notification.hideBalloon() } }) @@ -49,18 +46,8 @@ class TriggerCustomPredictionAction : EditorAction(Handler()), HintManagerImpl.A return } - val encodingManager = service() - if (!isCredentialSet(CodeGptApiKey) && encodingManager.countTokens(editor.document.text) > 2048) { - OverlayUtil.showNotification("The file exceeds the token limit of 2,048. Please upgrade your plan to access higher limits.") - return - } - if (encodingManager.countTokens(editor.document.text) > 4096) { - OverlayUtil.showNotification("The file exceeds the token limit of 4,096.") - return - } - ApplicationManager.getApplication().executeOnPooledThread { - service().displayDirectPrediction(editor) + service().displayInlineDiff(editor, true) } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/PromptsSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/PromptsSettings.kt index fad4c194..0b9e1fbf 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/PromptsSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/PromptsSettings.kt @@ -30,8 +30,6 @@ class PromptsSettingsState : BaseState() { class CoreActionsState : BaseState() { companion object { - val DEFAULT_CODE_ASSISTANT_PROMPT = - getResourceContent("/prompts/core/code-assistant.txt") val DEFAULT_EDIT_CODE_PROMPT = getResourceContent("/prompts/core/edit-code.txt") val DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT = getResourceContent("/prompts/core/generate-commit-message.txt") @@ -43,11 +41,6 @@ class CoreActionsState : BaseState() { getResourceContent("/prompts/core/review-changes.txt") } - var codeAssistant by property(CoreActionPromptDetailsState().apply { - name = "Code Assistant" - code = "CODE_ASSISTANT" - instructions = DEFAULT_CODE_ASSISTANT_PROMPT - }) var editCode by property(CoreActionPromptDetailsState().apply { name = "Edit Code" code = "EDIT_CODE" diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/PromptsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/PromptsForm.kt index feaa37bc..76691c7c 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/PromptsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/PromptsForm.kt @@ -104,7 +104,6 @@ class PromptsForm { val coreActionsFormState = getFormState(coreActionsNode) settings.coreActions.apply { - codeAssistant = coreActionsFormState[0].toState() editCode = coreActionsFormState[1].toState() fixCompileErrors = coreActionsFormState[2].toState() generateCommitMessage = coreActionsFormState[3].toState() @@ -157,7 +156,6 @@ class PromptsForm { val formState = getFormState(coreActionsNode) val stateActions = listOf( - settingsState.codeAssistant, settingsState.editCode, settingsState.fixCompileErrors, settingsState.generateCommitMessage, @@ -213,7 +211,6 @@ class PromptsForm { val settings = service().state listOf( - settings.coreActions.codeAssistant, settings.coreActions.editCode, settings.coreActions.fixCompileErrors, settings.coreActions.generateCommitMessage, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/details/CoreActionsDetailsPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/details/CoreActionsDetailsPanel.kt index 108a88c4..c2b02f10 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/details/CoreActionsDetailsPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/prompts/form/details/CoreActionsDetailsPanel.kt @@ -11,7 +11,6 @@ import com.intellij.util.ui.components.BorderLayoutPanel import ee.carlrobert.codegpt.settings.Placeholder import ee.carlrobert.codegpt.settings.Placeholder.GIT_DIFF import ee.carlrobert.codegpt.settings.prompts.CommitMessageTemplate -import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_CODE_ASSISTANT_PROMPT import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_EDIT_CODE_PROMPT import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_FIX_COMPILE_ERRORS_PROMPT import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT @@ -30,26 +29,6 @@ class CoreActionsDetailsPanel : PromptDetailsPanel { override fun create(details: CoreActionPromptDetails): JComponent { val editorPanel = when (details.code) { - "CODE_ASSISTANT" -> CoreActionEditorPanel( - details, - DEFAULT_CODE_ASSISTANT_PROMPT, - buildString { - append("

Template for generating code assistant messages. Use the following placeholders to insert dynamic values:

\n") - append( - "
    ${ - listOf( - GIT_DIFF, - Placeholder.OPEN_FILES, - Placeholder.ACTIVE_CONVERSATION, - ).joinToString("\n") { - "
  • ${it.name}: ${it.description}
  • " - } - }
\n" - ) - }, - listOf("{GIT_DIFF}", "{OPEN_FILES}", "{ACTIVE_CONVERSATION}") - ) - "EDIT_CODE" -> CoreActionEditorPanel( details, DEFAULT_EDIT_CODE_PROMPT, @@ -91,7 +70,6 @@ class CoreActionsDetailsPanel : PromptDetailsPanel { init { val settings = service().state.coreActions listOf( - settings.codeAssistant, settings.editCode, settings.fixCompileErrors, settings.generateCommitMessage, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt index 8092c9a4..08ab74aa 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt @@ -32,9 +32,9 @@ class CodeGPTServiceForm { renderer = CustomComboBoxRenderer() } - private val codeAssistantEnabledCheckBox = JBCheckBox( - CodeGPTBundle.get("shared.enableCodeAssistant"), - service().state.codeAssistantEnabled + private val enableNextEditsEnabledCheckBox = JBCheckBox( + "Enable multi-line edits", + service().state.nextEditsEnabled ) private val codeCompletionsEnabledCheckBox = JBCheckBox( @@ -73,9 +73,9 @@ class CodeGPTServiceForm { UIUtil.createComment("settingsConfigurable.service.codegpt.codeCompletionModel.comment") ) .addVerticalGap(4) - .addComponent(codeAssistantEnabledCheckBox) + .addComponent(enableNextEditsEnabledCheckBox) .addComponent( - UIUtil.createComment("settingsConfigurable.service.codegpt.enableCodeAssistant.comment", 90) + UIUtil.createComment("settingsConfigurable.service.codegpt.enableNextEdits.comment", 90) ) .addVerticalGap(4) .addComponent(codeCompletionsEnabledCheckBox) @@ -90,14 +90,14 @@ class CodeGPTServiceForm { fun isModified() = service().state.run { (chatCompletionModelComboBox.selectedItem as CodeGPTModel).code != chatCompletionSettings.model || (codeCompletionModelComboBox.selectedItem as CodeGPTModel).code != codeCompletionSettings.model - || codeAssistantEnabledCheckBox.isSelected != codeAssistantEnabled + || enableNextEditsEnabledCheckBox.isSelected != nextEditsEnabled || codeCompletionsEnabledCheckBox.isSelected != codeCompletionSettings.codeCompletionsEnabled || getApiKey() != getCredential(CodeGptApiKey) } fun applyChanges() { service().state.run { - codeAssistantEnabled = codeAssistantEnabledCheckBox.isSelected + nextEditsEnabled = enableNextEditsEnabledCheckBox.isSelected chatCompletionSettings.model = (chatCompletionModelComboBox.selectedItem as CodeGPTModel).code codeCompletionSettings.codeCompletionsEnabled = @@ -110,7 +110,7 @@ class CodeGPTServiceForm { fun resetForm() { service().state.run { - codeAssistantEnabledCheckBox.isSelected = codeAssistantEnabled + enableNextEditsEnabledCheckBox.isSelected = nextEditsEnabled chatCompletionModelComboBox.selectedItem = chatCompletionSettings.model codeCompletionModelComboBox.selectedItem = codeCompletionSettings.model codeCompletionsEnabledCheckBox.isSelected = diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt index 9c1de08c..31259221 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt @@ -13,7 +13,7 @@ class CodeGPTServiceSettings : class CodeGPTServiceSettingsState : BaseState() { var chatCompletionSettings by property(CodeGPTServiceChatCompletionSettingsState()) var codeCompletionSettings by property(CodeGPTServiceCodeCompletionSettingsState()) - var codeAssistantEnabled by property(false) + var nextEditsEnabled by property(true) } class CodeGPTServiceChatCompletionSettingsState : BaseState() { diff --git a/src/main/proto/next-edit.proto b/src/main/proto/next-edit.proto new file mode 100644 index 00000000..1f6288f9 --- /dev/null +++ b/src/main/proto/next-edit.proto @@ -0,0 +1,29 @@ +// src/main/proto/edit.proto +syntax = "proto3"; +option java_multiple_files = true; +option java_package = "ee.carlrobert.service"; + +import "google/protobuf/empty.proto"; + +service NextEditServiceImpl { + rpc NextEdit (NextEditRequest) returns (stream NextEditResponse); + rpc AcceptEdit (AcceptEditRequest) returns (google.protobuf.Empty); +} + +message NextEditRequest { + string file_content = 1; + string file_name = 2; + int32 cursor_position = 3; + string git_diff = 4; + bool enable_telemetry = 5; +} + +message NextEditResponse { + string id = 1; + string next_revision = 2; +} + +message AcceptEditRequest { + string response_id = 1; + string accepted_edit = 2; +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7274eb41..6c3df8c8 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -238,16 +238,16 @@ + id="statusbar.enableNextEdits" + class="ee.carlrobert.codegpt.actions.EnableNextEditsAction"> + id="statusbar.disableNextEdits" + class="ee.carlrobert.codegpt.actions.DisableNextEditsAction"> @@ -275,8 +275,8 @@ - - + + diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 8e5d0ebb..568795e5 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -25,12 +25,12 @@ action.statusbar.enableCompletions.MainMenu.text=Enable Completions action.statusbar.disableCompletions.text=Disable Completions action.statusbar.disableCompletions.description=Disable Code Completions action.statusbar.disableCompletions.MainMenu.text=Disable Completions -action.statusbar.enableCodeAssistant.text=Enable Code Assistant -action.statusbar.enableCodeAssistant.description=Enable Code Assistant -action.statusbar.enableCodeAssistant.MainMenu.text=Enable Code Assistant -action.statusbar.disableCodeAssistant.text=Disable Code Assistant -action.statusbar.disableCodeAssistant.description=Disable Code Assistant -action.statusbar.disableCodeAssistant.MainMenu.text=Disable Code Assistant +action.statusbar.enableNextEdits.text=Enable Multi-Line Edits +action.statusbar.enableNextEdits.description=Enable Multi-Line Edits +action.statusbar.enableNextEdits.MainMenu.text=Enable Multi-Line Edits +action.statusbar.disableNextEdits.text=Disable Multi-Line Edits +action.statusbar.disableNextEdits.description=Disable Multi-Line Edits +action.statusbar.disableNextEdits.MainMenu.text=Disable Multi-Line Edits action.compareWithOriginal.title=Compare with Original action.applyDirectly.title=Auto Apply action.explainGitCommit.title=Explain Commit with ProxyAI @@ -42,7 +42,7 @@ settingsConfigurable.service.label=Selected provider: settingsConfigurable.service.codegpt.apiKey.comment=You can find the API key in your User settings. settingsConfigurable.service.codegpt.chatCompletionModel.comment=Choose a model optimized for conversational interactions, including assistance with general queries and explanations. settingsConfigurable.service.codegpt.codeCompletionModel.comment=Choose a model tailored for code completion-related tasks. -settingsConfigurable.service.codegpt.enableCodeAssistant.comment=If checked, Code Assistant will suggest related code updates as you make changes. +settingsConfigurable.service.codegpt.enableNextEdits.comment=If checked, ProxyAI will suggest multi-line changes as you type. settingsConfigurable.service.codegpt.enableCodeCompletion.comment=If checked, ProxyAI will suggest changes as you type. settingsConfigurable.service.custom.openai.apiKey.comment=A secret value stored in the system's Keychain or KeePass, depending on your OS. This approach is recommended over storing the secret in the header as plain text. settingsConfigurable.service.custom.openai.apiKey.provider.name=Custom provider name: @@ -280,7 +280,6 @@ imageAccordion.title=Attached image shared.image=Image shared.chatCompletions=Chat Completions shared.codeCompletions=Code Completions -shared.enableCodeAssistant=Enable Code Assistant NEW codeCompletionsForm.enableFeatureText=Enable code completions codeCompletionsForm.parseResponseAsChatCompletions=Parse response as Chat Completions codeCompletionsForm.overrideFimTemplate.label=Use built-in FIM template diff --git a/src/main/resources/prompts/core/code-assistant.txt b/src/main/resources/prompts/core/code-assistant.txt deleted file mode 100644 index 6b3294b7..00000000 --- a/src/main/resources/prompts/core/code-assistant.txt +++ /dev/null @@ -1,18 +0,0 @@ -Recent changes made to the project: - -{GIT_DIFF} - - -Currently open files: - -{OPEN_FILES} - - -Previous chat history between the AI and user: - -{ACTIVE_CONVERSATION} - - -Important Guidelines: - -- Carefully follow the instructions marked with the @AI keyword and act accordingly. Then, delete the instruction. \ No newline at end of file diff --git a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt index fbb6deb9..1ac56163 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt @@ -18,6 +18,7 @@ class CodeCompletionServiceTest : IntegrationTest() { fun `test code completion with ProxyAI provider`() { useCodeGPTService() + service().state.nextEditsEnabled = false service().state.codeCompletionSettings.multiLineEnabled = false myFixture.configureByText( "CompletionTest.java", @@ -58,6 +59,7 @@ class CodeCompletionServiceTest : IntegrationTest() { fun `test code completion with OpenAI provider`() { useOpenAIService() + service().state.nextEditsEnabled = false service().state.codeCompletionSettings.multiLineEnabled = false myFixture.configureByText( "CompletionTest.java", @@ -98,6 +100,7 @@ class CodeCompletionServiceTest : IntegrationTest() { fun `_test apply inline suggestions without initial following text`() { useCodeGPTService() + service().state.nextEditsEnabled = false service().state.codeCompletionSettings.multiLineEnabled = false myFixture.configureByText( "CompletionTest.java", @@ -215,7 +218,7 @@ class CodeCompletionServiceTest : IntegrationTest() { fun `_test apply inline suggestions with initial following text`() { useCodeGPTService() - service().state.codeAssistantEnabled = false + service().state.nextEditsEnabled = false service().state.codeCompletionSettings.multiLineEnabled = false myFixture.configureByText( "CompletionTest.java", @@ -287,6 +290,7 @@ class CodeCompletionServiceTest : IntegrationTest() { fun `test adjust completion line whitespaces`() { useCodeGPTService() + service().state.nextEditsEnabled = false service().state.codeCompletionSettings.multiLineEnabled = false myFixture.configureByText( "CompletionTest.java",