mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-19 07:54:46 +00:00
feat: improved multi-line edits (#923)
* fix: llama.cpp copy task * feat: next edit predictions * feat: minor clean up
This commit is contained in:
parent
9382160fe4
commit
7fbae569b1
26 changed files with 382 additions and 364 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.<br/>"
|
||||
+ "<br/>"
|
||||
+ "Please note that this will not include personal data or any sensitive Information.<br/>"
|
||||
+ "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.<br/>"
|
||||
+ "<br/>"
|
||||
+ "The data sent complies with the <a href=\"https://tryproxy.io/privacy\">Privacy Policy</a>.";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,4 +9,8 @@
|
|||
files="[\\/]src[\\/]main[\\/]java[\\/]ee[\\/]carlrobert[\\/]codegpt[\\/]telemetry"
|
||||
checks="."/>
|
||||
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]grammar" checks="."/>
|
||||
<suppress
|
||||
files="[\\/]build[\\/]generated[\\/]source[\\/]proto[\\/]main"
|
||||
checks="."/>
|
||||
|
||||
</suppressions>
|
||||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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<PredictionService>().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<CodeGPTServiceSettings>().state.codeAssistantEnabled
|
||||
|
||||
private fun getDocumentTokenCount(text: String): Int =
|
||||
EncodingManager.getInstance().countTokens(text)
|
||||
}
|
||||
|
|
@ -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<CodeGPTServiceSettings>().state.codeAssistantEnabled
|
||||
|| encodingManager.countTokens(editor.document.text) > 4096
|
||||
|| !isCredentialSet(CodeGptApiKey) && encodingManager.countTokens(editor.document.text) > 2048
|
||||
|| !service<CodeGPTServiceSettings>().state.nextEditsEnabled
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
service<PredictionService>().displayLookupPrediction(
|
||||
editor,
|
||||
event,
|
||||
beforeApply,
|
||||
cursorOffset
|
||||
)
|
||||
service<PredictionService>().displayInlineDiff(editor)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ abstract class CodeAssistantFeatureToggleAction(
|
|||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val settings = service<CodeGPTServiceSettings>().state
|
||||
settings.codeAssistantEnabled = enableFeatureAction
|
||||
settings.nextEditsEnabled = enableFeatureAction
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val codeAssistantEnabled = service<CodeGPTServiceSettings>().state.codeAssistantEnabled
|
||||
val codeAssistantEnabled = service<CodeGPTServiceSettings>().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)
|
||||
class DisableNextEditsAction : CodeAssistantFeatureToggleAction(false)
|
||||
|
|
@ -37,7 +37,8 @@ abstract class CodeCompletionFeatureToggleActions(
|
|||
override fun update(e: AnActionEvent) {
|
||||
val selectedService = GeneralSettings.getSelectedService()
|
||||
val codeCompletionEnabled =
|
||||
e.project?.service<CodeCompletionService>()?.isCodeCompletionsEnabled(selectedService) ?: false
|
||||
e.project?.service<CodeCompletionService>()?.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)
|
||||
|
|
@ -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<CodeGPTServiceSettings>().state.codeAssistantEnabled
|
||||
&& service<EncodingManager>().countTokens(editor.document.text) <= 4096) {
|
||||
&& service<CodeGPTServiceSettings>().state.nextEditsEnabled
|
||||
) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
service<PredictionService>().displayAutocompletePrediction(
|
||||
editor,
|
||||
textToInsert,
|
||||
beforeApply
|
||||
)
|
||||
service<PredictionService>().displayInlineDiff(editor)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CodeGPTServiceSettings>().state.nextEditsEnabled
|
||||
) {
|
||||
predictNextEdit(request)
|
||||
return InlineCompletionSingleSuggestion.build(elements = emptyFlow())
|
||||
}
|
||||
|
||||
return if (service<ConfigurationSettings>().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<GrpcClientService>().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<CodeGPTServiceSettings>().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<CodeGPTServiceSettings>().state.nextEditsEnabled
|
||||
}
|
||||
|
||||
if (LookupManager.getActiveLookup(event.toRequest()?.editor) != null) {
|
||||
|
|
|
|||
|
|
@ -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<NextEditResponse> {
|
||||
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<CodeGPTServiceSettings>().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<String> =
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<GrpcClientService>()?.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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CodeGPTServiceSettings>().state.codeAssistantEnabled = false
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
if (e.cause?.message != "Canceled") {
|
||||
throw e
|
||||
}
|
||||
return null
|
||||
CompletionProgressNotifier.update(project, true)
|
||||
project.service<GrpcClientService>().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<OpenAIChatCompletionStandardMessage> = 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<PromptsSettings>().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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CodeGPTServiceSettings>().state.codeAssistantEnabled) {
|
||||
if (!service<CodeGPTServiceSettings>().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<CodeGPTServiceSettings>().state.codeAssistantEnabled = true
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = true
|
||||
notification.hideBalloon()
|
||||
}
|
||||
})
|
||||
|
|
@ -49,18 +46,8 @@ class TriggerCustomPredictionAction : EditorAction(Handler()), HintManagerImpl.A
|
|||
return
|
||||
}
|
||||
|
||||
val encodingManager = service<EncodingManager>()
|
||||
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<PredictionService>().displayDirectPrediction(editor)
|
||||
service<PredictionService>().displayInlineDiff(editor, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ class PromptsForm {
|
|||
|
||||
val coreActionsFormState = getFormState<CoreActionPromptDetails>(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<CoreActionPromptDetails>(coreActionsNode)
|
||||
|
||||
val stateActions = listOf(
|
||||
settingsState.codeAssistant,
|
||||
settingsState.editCode,
|
||||
settingsState.fixCompileErrors,
|
||||
settingsState.generateCommitMessage,
|
||||
|
|
@ -213,7 +211,6 @@ class PromptsForm {
|
|||
val settings = service<PromptsSettings>().state
|
||||
|
||||
listOf(
|
||||
settings.coreActions.codeAssistant,
|
||||
settings.coreActions.editCode,
|
||||
settings.coreActions.fixCompileErrors,
|
||||
settings.coreActions.generateCommitMessage,
|
||||
|
|
|
|||
|
|
@ -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("<p>Template for generating code assistant messages. Use the following placeholders to insert dynamic values:</p>\n")
|
||||
append(
|
||||
"<ul>${
|
||||
listOf(
|
||||
GIT_DIFF,
|
||||
Placeholder.OPEN_FILES,
|
||||
Placeholder.ACTIVE_CONVERSATION,
|
||||
).joinToString("\n") {
|
||||
"<li><strong>${it.name}</strong>: ${it.description}</li>"
|
||||
}
|
||||
}</ul>\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<PromptsSettings>().state.coreActions
|
||||
listOf(
|
||||
settings.codeAssistant,
|
||||
settings.editCode,
|
||||
settings.fixCompileErrors,
|
||||
settings.generateCommitMessage,
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ class CodeGPTServiceForm {
|
|||
renderer = CustomComboBoxRenderer()
|
||||
}
|
||||
|
||||
private val codeAssistantEnabledCheckBox = JBCheckBox(
|
||||
CodeGPTBundle.get("shared.enableCodeAssistant"),
|
||||
service<CodeGPTServiceSettings>().state.codeAssistantEnabled
|
||||
private val enableNextEditsEnabledCheckBox = JBCheckBox(
|
||||
"Enable multi-line edits",
|
||||
service<CodeGPTServiceSettings>().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<CodeGPTServiceSettings>().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<CodeGPTServiceSettings>().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<CodeGPTServiceSettings>().state.run {
|
||||
codeAssistantEnabledCheckBox.isSelected = codeAssistantEnabled
|
||||
enableNextEditsEnabledCheckBox.isSelected = nextEditsEnabled
|
||||
chatCompletionModelComboBox.selectedItem = chatCompletionSettings.model
|
||||
codeCompletionModelComboBox.selectedItem = codeCompletionSettings.model
|
||||
codeCompletionsEnabledCheckBox.isSelected =
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
29
src/main/proto/next-edit.proto
Normal file
29
src/main/proto/next-edit.proto
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -238,16 +238,16 @@
|
|||
<override-text place="popup" use-text-of-place="MainMenu"/>
|
||||
</action>
|
||||
<action
|
||||
id="statusbar.enableCodeAssistant"
|
||||
class="ee.carlrobert.codegpt.actions.EnableCodeAssistantAction">
|
||||
id="statusbar.enableNextEdits"
|
||||
class="ee.carlrobert.codegpt.actions.EnableNextEditsAction">
|
||||
<keyboard-shortcut first-keystroke="ctrl shift alt a" keymap="$default"/>
|
||||
|
||||
<override-text place="MainMenu"/>
|
||||
<override-text place="popup" use-text-of-place="MainMenu"/>
|
||||
</action>
|
||||
<action
|
||||
id="statusbar.disableCodeAssistant"
|
||||
class="ee.carlrobert.codegpt.actions.DisableCodeAssistantAction">
|
||||
id="statusbar.disableNextEdits"
|
||||
class="ee.carlrobert.codegpt.actions.DisableNextEditsAction">
|
||||
<keyboard-shortcut first-keystroke="ctrl shift alt a" keymap="$default"/>
|
||||
|
||||
<override-text place="MainMenu"/>
|
||||
|
|
@ -275,8 +275,8 @@
|
|||
<separator/>
|
||||
<reference id="statusbar.stopServer" />
|
||||
<reference id="statusbar.startServer" />
|
||||
<reference id="statusbar.disableCodeAssistant" />
|
||||
<reference id="statusbar.enableCodeAssistant" />
|
||||
<reference id="statusbar.disableNextEdits" />
|
||||
<reference id="statusbar.enableNextEdits" />
|
||||
<reference id="statusbar.disableCompletions" />
|
||||
<reference id="statusbar.enableCompletions" />
|
||||
</group>
|
||||
|
|
|
|||
|
|
@ -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 <a href="https://tryproxy.io/account">User settings</a>.
|
||||
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=<html>Enable Code Assistant <small><strong><sup style="color: green;">NEW</sup></strong></small></html>
|
||||
codeCompletionsForm.enableFeatureText=Enable code completions
|
||||
codeCompletionsForm.parseResponseAsChatCompletions=Parse response as Chat Completions
|
||||
codeCompletionsForm.overrideFimTemplate.label=Use built-in FIM template
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
Recent changes made to the project:
|
||||
<git_diff>
|
||||
{GIT_DIFF}
|
||||
</git_diff>
|
||||
|
||||
Currently open files:
|
||||
<open_files>
|
||||
{OPEN_FILES}
|
||||
</open_files>
|
||||
|
||||
Previous chat history between the AI and user:
|
||||
<active_conversation_history>
|
||||
{ACTIVE_CONVERSATION}
|
||||
</active_conversation_history>
|
||||
|
||||
Important Guidelines:
|
||||
|
||||
- Carefully follow the instructions marked with the @AI keyword and act accordingly. Then, delete the instruction.
|
||||
|
|
@ -18,6 +18,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
|
||||
fun `test code completion with ProxyAI provider`() {
|
||||
useCodeGPTService()
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
|
|
@ -58,6 +59,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
|
||||
fun `test code completion with OpenAI provider`() {
|
||||
useOpenAIService()
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
service<ConfigurationSettings>().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<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
service<ConfigurationSettings>().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<CodeGPTServiceSettings>().state.codeAssistantEnabled = false
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
|
|
@ -287,6 +290,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
|
||||
fun `test adjust completion line whitespaces`() {
|
||||
useCodeGPTService()
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue