From 9c61b06e0f55354db8cb0c0c0ce2f1ec9101040f Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Mon, 22 Apr 2024 12:04:58 +0300 Subject: [PATCH 01/30] fix: kotlin build interoperability --- src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt index cec7fbfb..54e83b8d 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt @@ -7,7 +7,7 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.intellij.util.xmlb.Converter -abstract class BaseConverter protected constructor(private val typeReference: TypeReference) : Converter() { +abstract class BaseConverter protected constructor(private val typeReference: TypeReference) : Converter() { private val objectMapper: ObjectMapper = ObjectMapper() .registerModule(Jdk8Module()) .registerModule(JavaTimeModule()) @@ -20,7 +20,7 @@ abstract class BaseConverter protected constructor(private val typeReference: } } - override fun toString(value: T & Any): String? { + override fun toString(value: T): String? { try { return objectMapper.writeValueAsString(value) } catch (e: JsonProcessingException) { From 48aa2f45a23bcb6d4453f32a4d3e3ad40af8bcc4 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Mon, 22 Apr 2024 12:30:29 +0300 Subject: [PATCH 02/30] 2.6.3 --- CHANGELOG.md | 5 ++++- gradle.properties | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fca33de..a5bdb762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.6.3-233] - 2024-04-22 + ### Added - Support for Llama 3 model via llama.cpp port (#479) @@ -444,7 +446,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `OPENAI_API_KEY` persistence, key is saved in the OS password safe from now on -[Unreleased]: https://github.com/carlrobertoh/CodeGPT/compare/v2.6.2-233...HEAD +[Unreleased]: https://github.com/carlrobertoh/CodeGPT/compare/v2.6.3-233...HEAD +[2.6.3-233]: https://github.com/carlrobertoh/CodeGPT/compare/v2.6.2-233...v2.6.3-233 [2.6.2-233]: https://github.com/carlrobertoh/CodeGPT/compare/v2.6.1-233...v2.6.2-233 [2.6.1-233]: https://github.com/carlrobertoh/CodeGPT/compare/v2.6.0-233...v2.6.1-233 [2.6.0-233]: https://github.com/carlrobertoh/CodeGPT/compare/v2.5.1...v2.6.0-233 diff --git a/gradle.properties b/gradle.properties index cc4e57a4..97b9861f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = ee.carlrobert pluginName = CodeGPT pluginRepositoryUrl = https://github.com/carlrobertoh/CodeGPT # SemVer format -> https://semver.org -pluginVersion = 2.6.2 +pluginVersion = 2.6.3 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 233 From 0b2387c2f6ad191cd714cb8b9029ddb88e86838e Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Tue, 23 Apr 2024 17:21:29 +0300 Subject: [PATCH 03/30] chore(deps): bump deps --- codegpt-treesitter/build.gradle.kts | 12 ++++++------ gradle/libs.versions.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/codegpt-treesitter/build.gradle.kts b/codegpt-treesitter/build.gradle.kts index 532f50f5..cab4a71c 100644 --- a/codegpt-treesitter/build.gradle.kts +++ b/codegpt-treesitter/build.gradle.kts @@ -9,13 +9,13 @@ dependencies { implementation("io.github.bonede:tree-sitter-dockerfile:0.1.2") implementation("io.github.bonede:tree-sitter-dart:master") implementation("io.github.bonede:tree-sitter-css:0.20.0") - implementation("io.github.bonede:tree-sitter-cpp:0.20.3") + implementation("io.github.bonede:tree-sitter-cpp:0.22.0") implementation("io.github.bonede:tree-sitter-c-sharp:0.20.0") implementation("io.github.bonede:tree-sitter-fortran:master") implementation("io.github.bonede:tree-sitter-gitattributes:0.1.3") - implementation("io.github.bonede:tree-sitter-go:0.20.0") + implementation("io.github.bonede:tree-sitter-go:0.21.0") implementation("io.github.bonede:tree-sitter-graphql:master") - implementation("io.github.bonede:tree-sitter-html:0.19.0") + implementation("io.github.bonede:tree-sitter-html:0.20.2") implementation("io.github.bonede:tree-sitter-javascript:0.20.1") implementation("io.github.bonede:tree-sitter-json:0.20.1") implementation("io.github.bonede:tree-sitter-kotlin:0.3.1") @@ -25,9 +25,9 @@ dependencies { implementation("io.github.bonede:tree-sitter-markdown:0.7.1") implementation("io.github.bonede:tree-sitter-objc:main") implementation("io.github.bonede:tree-sitter-perl:0.4.0") - implementation("io.github.bonede:tree-sitter-ruby:0.19.0") - implementation("io.github.bonede:tree-sitter-rust:0.20.4") - implementation("io.github.bonede:tree-sitter-scala:0.20.2") + implementation("io.github.bonede:tree-sitter-ruby:0.20.1") + implementation("io.github.bonede:tree-sitter-rust:0.21.0") + implementation("io.github.bonede:tree-sitter-scala:0.21.0") implementation("io.github.bonede:tree-sitter-scss:1.0.0") implementation("io.github.bonede:tree-sitter-svelte:0.11.0") implementation("io.github.bonede:tree-sitter-swift:0.3.6") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 49c0c4c7..29c2068a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ analytics = "3.0.0" assertj = "3.25.3" changelog = "2.2.0" checkstyle = "10.15.0" -commons-text = "1.11.0" +commons-text = "1.12.0" flexmark = "0.64.8" gradle-intellij-plugin-version = "1.17.3" gson = "2.10.1" @@ -14,7 +14,7 @@ junit = "5.10.2" kotlin = "1.9.23" llm-client = "0.7.2" okio = "3.9.0" -tree-sitter = "0.22.2" +tree-sitter = "0.22.5" [libraries] analytics = { module = "com.rudderstack.sdk.java.analytics:analytics", version.ref = "analytics" } From 9823010526c9a00add29998a677e66d05bdf0e9e Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:30:40 +0200 Subject: [PATCH 04/30] feat: Add Llama 3 download sizes (#498) --- .../codegpt/completions/HuggingFaceModel.java | 43 ++++++++++++------- .../llama/form/LlamaModelPreferencesForm.java | 11 ++--- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index fc9f06b8..857a50a4 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -21,15 +21,15 @@ public enum HuggingFaceModel { CODE_BOOGA_34B_Q4(34, 4, "CodeBooga-34B-v0.1-GGUF"), CODE_BOOGA_34B_Q5(34, 5, "CodeBooga-34B-v0.1-GGUF"), - DEEPSEEK_CODER_1_3B_Q3(1, 3, "deepseek-coder-1.3b-instruct-GGUF"), - DEEPSEEK_CODER_1_3B_Q4(1, 4, "deepseek-coder-1.3b-instruct-GGUF"), - DEEPSEEK_CODER_1_3B_Q5(1, 5, "deepseek-coder-1.3b-instruct-GGUF"), + DEEPSEEK_CODER_1_3B_Q3(1, 3, "deepseek-coder-1.3b-instruct-GGUF", 0.705), + DEEPSEEK_CODER_1_3B_Q4(1, 4, "deepseek-coder-1.3b-instruct-GGUF", 0.874), + DEEPSEEK_CODER_1_3B_Q5(1, 5, "deepseek-coder-1.3b-instruct-GGUF", 1.0), DEEPSEEK_CODER_6_7B_Q3(7, 3, "deepseek-coder-6.7b-instruct-GGUF"), DEEPSEEK_CODER_6_7B_Q4(7, 4, "deepseek-coder-6.7b-instruct-GGUF"), DEEPSEEK_CODER_6_7B_Q5(7, 5, "deepseek-coder-6.7b-instruct-GGUF"), - DEEPSEEK_CODER_33B_Q3(33, 3, "deepseek-coder-33b-instruct-GGUF"), - DEEPSEEK_CODER_33B_Q4(33, 4, "deepseek-coder-33b-instruct-GGUF"), - DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct-GGUF"), + DEEPSEEK_CODER_33B_Q3(33, 3, "deepseek-coder-33b-instruct-GGUF", 16.1), + DEEPSEEK_CODER_33B_Q4(33, 4, "deepseek-coder-33b-instruct-GGUF", 19.9), + DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct-GGUF", 23.5), PHIND_CODE_LLAMA_34B_Q3(34, 3, "Phind-CodeLlama-34B-v2-GGUF"), PHIND_CODE_LLAMA_34B_Q4(34, 4, "Phind-CodeLlama-34B-v2-GGUF"), @@ -45,29 +45,36 @@ public enum HuggingFaceModel { WIZARD_CODER_PYTHON_34B_Q4(34, 4, "WizardCoder-Python-34B-V1.0-GGUF"), WIZARD_CODER_PYTHON_34B_Q5(34, 5, "WizardCoder-Python-34B-V1.0-GGUF"), - LLAMA_3_8B_IQ3_M(8, 3, "Meta-Llama-3-8B-Instruct-IQ3_M.gguf", "lmstudio-community"), - LLAMA_3_8B_Q4_K_M(8, 4, "Meta-Llama-3-8B-Instruct-Q4_K_M.gguf", "lmstudio-community"), - LLAMA_3_8B_Q5_K_M(8, 5, "Meta-Llama-3-8B-Instruct-Q5_K_M.gguf", "lmstudio-community"), - LLAMA_3_8B_Q6_K(8, 6, "Meta-Llama-3-8B-Instruct-Q6_K.gguf", "lmstudio-community"), - LLAMA_3_8B_Q8_0(8, 8, "Meta-Llama-3-8B-Instruct-Q8_0.gguf", "lmstudio-community"), - LLAMA_3_70B_IQ1(70, 1, "Meta-Llama-3-70B-Instruct-IQ1_M.gguf", "lmstudio-community"), - LLAMA_3_70B_IQ2_XS(70, 2, "Meta-Llama-3-70B-Instruct-IQ2_XS.gguf", "lmstudio-community"), - LLAMA_3_70B_Q4_K_M(70, 4, "Meta-Llama-3-70B-Instruct-Q4_K_M.gguf", "lmstudio-community"); + LLAMA_3_8B_IQ3_M(8, 3, "Meta-Llama-3-8B-Instruct-IQ3_M.gguf", "lmstudio-community", 3.78), + LLAMA_3_8B_Q4_K_M(8, 4, "Meta-Llama-3-8B-Instruct-Q4_K_M.gguf", "lmstudio-community", 4.92), + LLAMA_3_8B_Q5_K_M(8, 5, "Meta-Llama-3-8B-Instruct-Q5_K_M.gguf", "lmstudio-community", 5.73), + LLAMA_3_8B_Q6_K(8, 6, "Meta-Llama-3-8B-Instruct-Q6_K.gguf", "lmstudio-community", 6.6), + LLAMA_3_8B_Q8_0(8, 8, "Meta-Llama-3-8B-Instruct-Q8_0.gguf", "lmstudio-community", 8.54), + LLAMA_3_70B_IQ1(70, 1, "Meta-Llama-3-70B-Instruct-IQ1_M.gguf", "lmstudio-community", 16.8), + LLAMA_3_70B_IQ2_XS(70, 2, "Meta-Llama-3-70B-Instruct-IQ2_XS.gguf", "lmstudio-community", 21.1), + LLAMA_3_70B_Q4_K_M(70, 4, "Meta-Llama-3-70B-Instruct-Q4_K_M.gguf", "lmstudio-community", 42.5); private final int parameterSize; private final int quantization; private final String modelName; private final String user; + private final Double downloadSize; // in GB HuggingFaceModel(int parameterSize, int quantization, String modelName) { - this(parameterSize, quantization, modelName, "TheBloke"); + this(parameterSize, quantization, modelName, "TheBloke", null); } - HuggingFaceModel(int parameterSize, int quantization, String modelName, String user) { + HuggingFaceModel(int parameterSize, int quantization, String modelName, Double downloadSize) { + this(parameterSize, quantization, modelName, "TheBloke", downloadSize); + } + + HuggingFaceModel(int parameterSize, int quantization, String modelName, String user, + Double downloadSize) { this.parameterSize = parameterSize; this.quantization = quantization; this.modelName = modelName; this.user = user; + this.downloadSize = downloadSize; } public int getParameterSize() { @@ -82,6 +89,10 @@ public enum HuggingFaceModel { return name(); } + public Double getDownloadSize() { + return downloadSize; + } + public String getFileName() { if ("TheBloke".equals(user)) { return modelName.toLowerCase().replace("-gguf", format(".Q%d_K_M.gguf", quantization)); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaModelPreferencesForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaModelPreferencesForm.java index 42428673..ad88366d 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaModelPreferencesForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaModelPreferencesForm.java @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt.settings.service.llama.form; import static java.lang.String.format; +import static java.util.Collections.emptyMap; import com.intellij.icons.AllIcons.Actions; import com.intellij.icons.AllIcons.General; @@ -291,15 +292,15 @@ public class LlamaModelPreferencesForm { int parameterSize = model.getParameterSize(); int quantization = model.getQuantization(); - if (!modelDetailsMap.containsKey(parameterSize)) { + var details = modelDetailsMap.getOrDefault(parameterSize, emptyMap()).get(quantization); + if (details == null && model.getDownloadSize() == null) { return ""; } - - ModelDetails details = modelDetailsMap.get(parameterSize).get(quantization); if (details == null) { - return ""; + return format("" + + "

File Size: %.2f GB

" + + "", model.getDownloadSize()); } - return format("" + "

File Size: %.2f GB

" + "

Max RAM Required: %.2f GB

" From a9e147ffc777642c56127fa4caf0bc6218cca3f3 Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Wed, 24 Apr 2024 09:24:44 +0200 Subject: [PATCH 05/30] fix: NPE when using unsupported model for code completions (#499) --- .../codegpt/settings/service/llama/LlamaSettings.java | 10 ++++++++++ .../actions/CodeCompletionFeatureToggleActions.kt | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java index ef952d2f..c27edac9 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java @@ -6,6 +6,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import ee.carlrobert.codegpt.completions.llama.LlamaModel; import ee.carlrobert.codegpt.credentials.CredentialsStore; import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm; import org.apache.commons.lang3.StringUtils; @@ -31,6 +32,15 @@ public class LlamaSettings implements PersistentStateComponent OpenAISettings.getCurrentState().isCodeCompletionsEnabled CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled - LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled + LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible() else -> false } } @@ -57,4 +57,4 @@ abstract class CodeCompletionFeatureToggleActions( class EnableCompletionsAction : CodeCompletionFeatureToggleActions(true) -class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false) \ No newline at end of file +class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false) From 7d05d17797a7c27b6b5e48cd4eed9ef0ecc2e8da Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Thu, 25 Apr 2024 15:20:48 +0300 Subject: [PATCH 06/30] fix: commit message generation for custom openai services (closes #496) --- .../codegpt/completions/CompletionRequestProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index 58f28c3a..fc5c41a7 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -116,7 +116,8 @@ public class CompletionRequestProvider { public static Request buildCustomOpenAICompletionRequest(String system, String context) { return buildCustomOpenAIChatCompletionRequest( - ApplicationManager.getApplication().getService(CustomServiceState.class) + ApplicationManager.getApplication().getService(CustomServiceSettings.class) + .getState() .getChatCompletionSettings(), List.of( new OpenAIChatCompletionStandardMessage("system", system), From 8de72b330178fa97b0482e1dbb2829964f8b737a Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 25 Apr 2024 15:47:56 +0200 Subject: [PATCH 07/30] fix: use /infill for llama.cpp code-completions (#513) --- .../codegpt/completions/CompletionRequestService.java | 2 +- .../codecompletions/CodeCompletionRequestFactory.kt | 9 ++++----- .../codegpt/codecompletions/CodeCompletionServiceTest.kt | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index edacf655..c2c63831 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -122,7 +122,7 @@ public final class CompletionRequestService { CodeCompletionRequestFactory.buildCustomRequest(requestDetails), new OpenAITextCompletionEventSourceListener(eventListener)); case LLAMA_CPP -> CompletionClientProvider.getLlamaClient() - .getChatCompletionAsync( + .getInfillAsync( CodeCompletionRequestFactory.buildLlamaRequest(requestDetails), eventListener); default -> diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt index d7723f0d..203a3add 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt @@ -12,6 +12,7 @@ import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest +import ee.carlrobert.llm.client.llama.completion.LlamaInfillRequest import ee.carlrobert.llm.client.openai.completion.request.OpenAITextCompletionRequest import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request @@ -59,16 +60,14 @@ object CodeCompletionRequestFactory { } @JvmStatic - fun buildLlamaRequest(details: InfillRequestDetails): LlamaCompletionRequest { + fun buildLlamaRequest(details: InfillRequestDetails): LlamaInfillRequest { val settings = LlamaSettings.getCurrentState() val promptTemplate = getLlamaInfillPromptTemplate(settings) - val prompt = promptTemplate.buildPrompt(details.prefix, details.suffix) - return LlamaCompletionRequest.Builder(prompt) + return LlamaInfillRequest(LlamaCompletionRequest.Builder(null) .setN_predict(settings.codeCompletionMaxTokens) .setStream(true) .setTemperature(0.4) - .setStop(promptTemplate.stopTokens) - .build() + .setStop(promptTemplate.stopTokens), details.prefix, details.suffix) } private fun getLlamaInfillPromptTemplate(settings: LlamaSettingsState): InfillPromptTemplate { diff --git a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt index d92c00b9..5c2aeeed 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt @@ -35,11 +35,11 @@ class CodeCompletionServiceTest : IntegrationTest() { ${"z".repeat(247)} """.trimIndent() // 128 tokens expectLlama(StreamHttpExchange { request: RequestEntity -> - assertThat(request.uri.path).isEqualTo("/completion") + assertThat(request.uri.path).isEqualTo("/infill") assertThat(request.method).isEqualTo("POST") assertThat(request.body) - .extracting("prompt") - .isEqualTo(InfillPromptTemplate.LLAMA.buildPrompt(prefix, suffix)) + .extracting("input_prefix", "input_suffix") + .containsExactly(prefix, suffix) listOf(jsonMapResponse(e("content", expectedCompletion), e("stop", true))) }) From 1415f387ff368736f8c220bc9f1beeb497ed77df Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 27 Apr 2024 22:49:36 +0200 Subject: [PATCH 08/30] fix: focus on new editor action and refresh editor actions on apply (#518) --- .../settings/configuration/ConfigurationComponent.java | 8 ++++++-- .../settings/configuration/ConfigurationConfigurable.java | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java index 0020137a..26c6be61 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java @@ -28,7 +28,6 @@ import ee.carlrobert.codegpt.ui.UIUtil; import java.awt.Dimension; import java.util.Arrays; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JComponent; @@ -191,7 +190,12 @@ public class ConfigurationComponent { private JPanel createTablePanel() { return ToolbarDecorator.createDecorator(table) .setPreferredSize(new Dimension(table.getPreferredSize().width, 140)) - .setAddAction(anActionButton -> getModel().addRow(new Object[]{"", ""})) + .setAddAction(anActionButton -> { + getModel().addRow(new Object[]{"", ""}); + int lastRowIndex = getModel().getRowCount() - 1; + table.changeSelection(lastRowIndex, 0, false, false); + table.editCellAt(lastRowIndex, 0); + }) .setRemoveAction(anActionButton -> getModel().removeRow(table.getSelectedRow())) .disableUpAction() .disableDownAction() diff --git a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationConfigurable.java index 5b2a4a62..9dc85840 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationConfigurable.java @@ -4,6 +4,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.util.Disposer; import ee.carlrobert.codegpt.CodeGPTBundle; +import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil; import javax.swing.JComponent; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; @@ -39,6 +40,7 @@ public class ConfigurationConfigurable implements Configurable { @Override public void apply() { ConfigurationSettings.getInstance().loadState(component.getCurrentFormState()); + EditorActionsUtil.refreshActions(); } @Override From 6d6e0a3ccb739ff9a08a35ce06a7d342cf2510a4 Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Sat, 27 Apr 2024 22:50:03 +0200 Subject: [PATCH 09/30] feat: Support Phi-3 Mini model (#516) --- src/main/cpp/llama.cpp | 2 +- .../codegpt/completions/HuggingFaceModel.java | 9 ++- .../codegpt/completions/llama/LlamaModel.java | 16 ++++- .../completions/llama/PromptTemplate.java | 19 ++++++ .../codegpt/completions/PromptTemplateTest.kt | 63 +++++++++++++++++++ 5 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/main/cpp/llama.cpp b/src/main/cpp/llama.cpp index 7dbdba56..46e12c46 160000 --- a/src/main/cpp/llama.cpp +++ b/src/main/cpp/llama.cpp @@ -1 +1 @@ -Subproject commit 7dbdba5690ca61b3ee8c92cfac8e7e251042e787 +Subproject commit 46e12c4692a37bdd31a0432fc5153d7d22bc7f72 diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index 857a50a4..51c51dba 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -52,7 +52,14 @@ public enum HuggingFaceModel { LLAMA_3_8B_Q8_0(8, 8, "Meta-Llama-3-8B-Instruct-Q8_0.gguf", "lmstudio-community", 8.54), LLAMA_3_70B_IQ1(70, 1, "Meta-Llama-3-70B-Instruct-IQ1_M.gguf", "lmstudio-community", 16.8), LLAMA_3_70B_IQ2_XS(70, 2, "Meta-Llama-3-70B-Instruct-IQ2_XS.gguf", "lmstudio-community", 21.1), - LLAMA_3_70B_Q4_K_M(70, 4, "Meta-Llama-3-70B-Instruct-Q4_K_M.gguf", "lmstudio-community", 42.5); + LLAMA_3_70B_Q4_K_M(70, 4, "Meta-Llama-3-70B-Instruct-Q4_K_M.gguf", "lmstudio-community", 42.5), + + PHI_3_3_8B_4K_IQ4_NL(4, 4, "Phi-3-mini-4k-instruct-IQ4_NL.gguf", "lmstudio-community", 2.18), + PHI_3_3_8B_4K_Q5_K_M(4, 5, "Phi-3-mini-4k-instruct-Q5_K_M.gguf", "lmstudio-community", 2.64), + PHI_3_3_8B_4K_Q5_K_S(4, 5, "Phi-3-mini-4k-instruct-Q5_K_S.gguf", "lmstudio-community", 2.82), + PHI_3_3_8B_4K_Q6_K(4, 6, "Phi-3-mini-4k-instruct-Q6_K.gguf", "lmstudio-community", 3.14), + PHI_3_3_8B_4K_Q8_0(4, 8, "Phi-3-mini-4k-instruct-Q8_0.gguf", "lmstudio-community", 4.06), + PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-fp16.gguf", "lmstudio-community", 7.64); private final int parameterSize; private final int quantization; diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java index dc1288ba..770bd3b9 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java @@ -99,7 +99,21 @@ public enum LlamaModel { HuggingFaceModel.LLAMA_3_8B_Q8_0, HuggingFaceModel.LLAMA_3_70B_IQ1, HuggingFaceModel.LLAMA_3_70B_IQ2_XS, - HuggingFaceModel.LLAMA_3_70B_Q4_K_M)); + HuggingFaceModel.LLAMA_3_70B_Q4_K_M)), + PHI_3( + "Phi-3 Mini", + "Phi-3 Mini is a 3.8B parameters, lightweight, state-of-the-art open model. " + + "When assessed against benchmarks testing common sense, language understanding, math, " + + "code, long context and logical reasoning, Phi-3 Mini-4K-Instruct showcased a robust " + + "and state-of-the-art performance among models with less than 13 billion parameters.", + PromptTemplate.PHI_3, + List.of( + HuggingFaceModel.PHI_3_3_8B_4K_IQ4_NL, + HuggingFaceModel.PHI_3_3_8B_4K_Q5_K_M, + HuggingFaceModel.PHI_3_3_8B_4K_Q5_K_S, + HuggingFaceModel.PHI_3_3_8B_4K_Q6_K, + HuggingFaceModel.PHI_3_3_8B_4K_Q8_0, + HuggingFaceModel.PHI_3_3_8B_4K_FP16)); private final String label; private final String description; diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java index 7101587a..27164bd4 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java @@ -125,6 +125,25 @@ public enum PromptTemplate { .toString(); } }, + PHI_3("Phi-3 Mini", List.of("<|end|>")) { + @Override + public String buildPrompt(String systemPrompt, String userPrompt, List history) { + StringBuilder prompt = new StringBuilder(); + + for (Message message : history) { + prompt.append("<|user|>\n") + .append(message.getPrompt()) + .append("<|end|>\n<|assistant|>\n") + .append(message.getResponse()) + .append("<|end|>\n"); + } + + return prompt.append("<|user|>\n") + .append(userPrompt) + .append("<|end|>\n<|assistant|>") + .toString(); + } + }, ALPACA("Alpaca/Vicuna") { @Override public String buildPrompt(String systemPrompt, String userPrompt, List history) { diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt index 84dad146..7847ec54 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt @@ -4,6 +4,7 @@ import ee.carlrobert.codegpt.completions.llama.PromptTemplate.ALPACA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.CHAT_ML import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA_3 +import ee.carlrobert.codegpt.completions.llama.PromptTemplate.PHI_3 import ee.carlrobert.codegpt.completions.llama.PromptTemplate.TORA import ee.carlrobert.codegpt.conversations.message.Message import org.assertj.core.api.Assertions.assertThat @@ -104,6 +105,68 @@ class PromptTemplateTest { TEST_USER_PROMPT<|eot_id|><|start_header_id|>assistant<|end_header_id|>""".trimIndent()) } + @Test + fun shouldBuildPhi3PromptWithoutHistory() { + val prompt = PHI_3.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, listOf()) + + assertThat(prompt).isEqualTo(""" + <|user|> + TEST_USER_PROMPT<|end|> + <|assistant|>""".trimIndent() + ) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", "\t", "\n"]) + fun shouldBuildPhi3PromptWithoutHistorySkippingBlankSystemPrompt(systemPrompt: String?) { + val prompt = PHI_3.buildPrompt(systemPrompt, USER_PROMPT, listOf()) + + assertThat(prompt).isEqualTo(""" + <|user|> + TEST_USER_PROMPT<|end|> + <|assistant|>""".trimIndent() + ) + } + + @Test + fun shouldBuildPhi3PromptWithHistory() { + val prompt = PHI_3.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, HISTORY) + + assertThat(prompt).isEqualTo(""" + <|user|> + TEST_PREV_PROMPT_1<|end|> + <|assistant|> + TEST_PREV_RESPONSE_1<|end|> + <|user|> + TEST_PREV_PROMPT_2<|end|> + <|assistant|> + TEST_PREV_RESPONSE_2<|end|> + <|user|> + TEST_USER_PROMPT<|end|> + <|assistant|>""".trimIndent()) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", "\t", "\n"]) + fun shouldBuildPhi3PromptWithHistorySkippingBlankSystemPrompt(systemPrompt: String?) { + val prompt = PHI_3.buildPrompt(systemPrompt, USER_PROMPT, HISTORY) + + assertThat(prompt).isEqualTo(""" + <|user|> + TEST_PREV_PROMPT_1<|end|> + <|assistant|> + TEST_PREV_RESPONSE_1<|end|> + <|user|> + TEST_PREV_PROMPT_2<|end|> + <|assistant|> + TEST_PREV_RESPONSE_2<|end|> + <|user|> + TEST_USER_PROMPT<|end|> + <|assistant|>""".trimIndent()) + } + @Test fun shouldBuildAlpacaPromptWithHistory() { val prompt = ALPACA.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, HISTORY) From e0f54a6b93f6f56c48da82f6d1835538071fd0a6 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 7 May 2024 09:41:10 +0200 Subject: [PATCH 10/30] fix: add optional Git4Idea dependency to plugin.xml (#526) --- src/main/resources/META-INF/plugin.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5c355356..7c64684a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -5,6 +5,7 @@ com.intellij.modules.platform com.intellij.modules.lang com.intellij.modules.java + Git4Idea Date: Tue, 7 May 2024 19:41:45 +1200 Subject: [PATCH 11/30] chore: Bump llm-client to 0.7.5 (#520) * Bump llm-client to 0.7.3 * llm-client 0.7.5 Co-authored-by: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> --------- Co-authored-by: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 29c2068a..9bb5cf56 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2" kotlin = "1.9.23" -llm-client = "0.7.2" +llm-client = "0.7.5" okio = "3.9.0" tree-sitter = "0.22.5" From f44fab551b8cec8ae7366645e2426241a9fb5abf Mon Sep 17 00:00:00 2001 From: Jack Boswell Date: Tue, 7 May 2024 19:42:45 +1200 Subject: [PATCH 12/30] refactor: Expand and explicitly handle cases where a ServiceType is checked (#521) This streamlines changes to ServiceType, where any additions or removals will be flagged at compile time to be handled, instead of silently falling back to a default value. --- .../completions/CompletionRequestService.java | 24 +++++------- .../CodeCompletionFeatureToggleActions.kt | 39 +++++++++++-------- .../CodeGPTInlineCompletionProvider.kt | 5 ++- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index c2c63831..9d98e100 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -1,7 +1,5 @@ package ee.carlrobert.codegpt.completions; -import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC; -import static ee.carlrobert.codegpt.settings.service.ServiceType.AZURE; import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI; import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP; import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI; @@ -226,19 +224,15 @@ public final class CompletionRequestService { } public static boolean isRequestAllowed(ServiceType serviceType) { - if (serviceType == OPENAI - && CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.OPENAI_API_KEY)) { - return true; - } - - var azureCredentialKey = AzureSettings.getCurrentState().isUseAzureApiKeyAuthentication() - ? CredentialKey.AZURE_OPENAI_API_KEY - : CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN; - if (serviceType == AZURE && CredentialsStore.INSTANCE.isCredentialSet(azureCredentialKey)) { - return true; - } - - return List.of(LLAMA_CPP, ANTHROPIC, CUSTOM_OPENAI).contains(serviceType); + return switch (serviceType) { + case OPENAI -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.OPENAI_API_KEY); + case AZURE -> CredentialsStore.INSTANCE.isCredentialSet( + AzureSettings.getCurrentState().isUseAzureApiKeyAuthentication() + ? CredentialKey.AZURE_OPENAI_API_KEY + : CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN); + case CUSTOM_OPENAI, ANTHROPIC, LLAMA_CPP -> true; + case YOU -> false; + }; } /** diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt index 420ec641..be2d0b66 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt @@ -17,28 +17,31 @@ abstract class CodeCompletionFeatureToggleActions( override fun actionPerformed(e: AnActionEvent) { - GeneralSettings.getCurrentState().selectedService - .takeIf { it in listOf(OPENAI, CUSTOM_OPENAI, LLAMA_CPP) } - ?.also { selectedService -> - if (OPENAI == selectedService) { - OpenAISettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction - } else if (CUSTOM_OPENAI == selectedService) { - service().state.codeCompletionSettings.codeCompletionsEnabled = - enableFeatureAction - } else { - LlamaSettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction - } - } + when (GeneralSettings.getCurrentState().selectedService) { + OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction + LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction + CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled = + enableFeatureAction + ANTHROPIC, + AZURE, + YOU, + null -> { /* no-op for these services */ } + } } override fun update(e: AnActionEvent) { val selectedService = GeneralSettings.getCurrentState().selectedService val codeCompletionEnabled = isCodeCompletionsEnabled(selectedService) e.presentation.isEnabled = codeCompletionEnabled != enableFeatureAction - e.presentation.isVisible = - e.presentation.isEnabled && listOf(OPENAI, CUSTOM_OPENAI, LLAMA_CPP).contains( - selectedService - ) + e.presentation.isVisible = when (selectedService) { + OPENAI, + CUSTOM_OPENAI, + LLAMA_CPP -> true + ANTHROPIC, + AZURE, + YOU, + null -> false + } } override fun getActionUpdateThread(): ActionUpdateThread { @@ -50,7 +53,9 @@ abstract class CodeCompletionFeatureToggleActions( OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible() - else -> false + ANTHROPIC, + AZURE, + YOU -> false } } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt index 9812902f..32811531 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt @@ -70,7 +70,10 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider { ServiceType.OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled ServiceType.CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled ServiceType.LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled - else -> false + ServiceType.ANTHROPIC, + ServiceType.AZURE, + ServiceType.YOU, + null -> false } return event is InlineCompletionEvent.DocumentChange && codeCompletionsEnabled } From a2a8747acaf0619cc21d577803d3f7717cb9543d Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Tue, 7 May 2024 09:43:14 +0200 Subject: [PATCH 13/30] feat: Support CodeGemma 7b Instruct model (#524) (#525) --- .../codegpt/completions/HuggingFaceModel.java | 26 ++++++- .../codegpt/completions/llama/LlamaModel.java | 33 ++++++++- .../completions/llama/PromptTemplate.java | 18 +++++ .../codegpt/completions/PromptTemplateTest.kt | 69 +++++++++++++++++++ 4 files changed, 144 insertions(+), 2 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index 51c51dba..7bb7b9ba 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -59,7 +59,31 @@ public enum HuggingFaceModel { PHI_3_3_8B_4K_Q5_K_S(4, 5, "Phi-3-mini-4k-instruct-Q5_K_S.gguf", "lmstudio-community", 2.82), PHI_3_3_8B_4K_Q6_K(4, 6, "Phi-3-mini-4k-instruct-Q6_K.gguf", "lmstudio-community", 3.14), PHI_3_3_8B_4K_Q8_0(4, 8, "Phi-3-mini-4k-instruct-Q8_0.gguf", "lmstudio-community", 4.06), - PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-fp16.gguf", "lmstudio-community", 7.64); + PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-fp16.gguf", "lmstudio-community", 7.64), + + CODE_GEMMA_7B_IQ1_S(7, 1, "codegemma-7b-it-IQ1_S.gguf", "lmstudio-community", 2.16), + CODE_GEMMA_7B_IQ1_M(7, 1, "codegemma-7b-it-IQ1_M.gguf", "lmstudio-community", 2.32), + CODE_GEMMA_7B_IQ2_XXS(7, 2, "codegemma-7b-it-IQ2_XXS.gguf", "lmstudio-community", 2.59), + CODE_GEMMA_7B_IQ2_XS(7, 2, "codegemma-7b-it-IQ2_XS.gguf", "lmstudio-community", 2.81), + CODE_GEMMA_7B_IQ2_S(7, 2, "codegemma-7b-it-IQ2_S.gguf", "lmstudio-community", 2.92), + CODE_GEMMA_7B_IQ2_M(7, 2, "codegemma-7b-it-IQ2_M.gguf", "lmstudio-community", 3.13), + CODE_GEMMA_7B_Q2_K(7, 2, "codegemma-7b-it-Q2_K.gguf", "lmstudio-community", 3.48), + CODE_GEMMA_7B_IQ3_XXS(7, 3, "codegemma-7b-it-IQ3_XXS.gguf", "lmstudio-community", 3.49), + CODE_GEMMA_7B_IQ3_XS(7, 3, "codegemma-7b-it-IQ3_XS.gguf", "lmstudio-community", 3.80), + CODE_GEMMA_7B_IQ3_S(7, 3, "codegemma-7b-it-IQ3_S.gguf", "lmstudio-community", 3.98), + CODE_GEMMA_7B_Q3_K_S(7, 3, "codegemma-7b-it-Q3_K_S.gguf", "lmstudio-community", 3.98), + CODE_GEMMA_7B_IQ3_M(7, 3, "codegemma-7b-it-IQ3_M.gguf", "lmstudio-community", 4.11), + CODE_GEMMA_7B_Q3_K_M(7, 3, "codegemma-7b-it-Q3_K_M.gguf", "lmstudio-community", 4.37), + CODE_GEMMA_7B_Q3_K_L(7, 3, "codegemma-7b-it-Q3_K_L.gguf", "lmstudio-community", 4.71), + CODE_GEMMA_7B_IQ4_XS(7, 4, "codegemma-7b-it-IQ4_XS.gguf", "lmstudio-community", 4.77), + CODE_GEMMA_7B_IQ4_NL(7, 4, "codegemma-7b-it-IQ4_NL.gguf", "lmstudio-community", 5.01), + CODE_GEMMA_7B_Q4_K_S(7, 4, "codegemma-7b-it-Q4_K_S.gguf", "lmstudio-community", 5.05), + CODE_GEMMA_7B_Q4_K_M(7, 4, "codegemma-7b-it-Q4_K_M.gguf", "lmstudio-community", 5.33), + CODE_GEMMA_7B_Q5_K_S(7, 5, "codegemma-7b-it-Q5_K_S.gguf", "lmstudio-community", 5.98), + CODE_GEMMA_7B_Q5_K_M(7, 5, "codegemma-7b-it-Q5_K_M.gguf", "lmstudio-community", 6.14), + CODE_GEMMA_7B_Q6_K(7, 6, "codegemma-7b-it-Q6_K.gguf", "lmstudio-community", 7.01), + CODE_GEMMA_7B_Q8_0(7, 8, "codegemma-7b-it-Q8_0.gguf", "lmstudio-community", 9.08), + ; private final int parameterSize; private final int quantization; diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java index 770bd3b9..c2aaeb80 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java @@ -113,7 +113,38 @@ public enum LlamaModel { HuggingFaceModel.PHI_3_3_8B_4K_Q5_K_S, HuggingFaceModel.PHI_3_3_8B_4K_Q6_K, HuggingFaceModel.PHI_3_3_8B_4K_Q8_0, - HuggingFaceModel.PHI_3_3_8B_4K_FP16)); + HuggingFaceModel.PHI_3_3_8B_4K_FP16)), + CODE_GEMMA( + "CodeGemma 7b Instruct", + "CodeGemma 7b Instruct is the first in a series of coding models released by Google. " + + "As an instruct model, it specializes in being asked coding related questions, but can " + + "also function as an autocomplete/fill-in-middle model for tools like co-pilot.\n" + + "This model is perfect for general coding questions or code generation.", + PromptTemplate.CODE_GEMMA, + List.of( + HuggingFaceModel.CODE_GEMMA_7B_IQ1_S, + HuggingFaceModel.CODE_GEMMA_7B_IQ1_M, + HuggingFaceModel.CODE_GEMMA_7B_IQ2_XXS, + HuggingFaceModel.CODE_GEMMA_7B_IQ2_XS, + HuggingFaceModel.CODE_GEMMA_7B_IQ2_S, + HuggingFaceModel.CODE_GEMMA_7B_IQ2_M, + HuggingFaceModel.CODE_GEMMA_7B_Q2_K, + HuggingFaceModel.CODE_GEMMA_7B_IQ3_XXS, + HuggingFaceModel.CODE_GEMMA_7B_IQ3_XS, + HuggingFaceModel.CODE_GEMMA_7B_IQ3_S, + HuggingFaceModel.CODE_GEMMA_7B_Q3_K_S, + HuggingFaceModel.CODE_GEMMA_7B_IQ3_M, + HuggingFaceModel.CODE_GEMMA_7B_Q3_K_M, + HuggingFaceModel.CODE_GEMMA_7B_Q3_K_L, + HuggingFaceModel.CODE_GEMMA_7B_IQ4_XS, + HuggingFaceModel.CODE_GEMMA_7B_IQ4_NL, + HuggingFaceModel.CODE_GEMMA_7B_Q4_K_S, + HuggingFaceModel.CODE_GEMMA_7B_Q4_K_M, + HuggingFaceModel.CODE_GEMMA_7B_Q5_K_S, + HuggingFaceModel.CODE_GEMMA_7B_Q5_K_M, + HuggingFaceModel.CODE_GEMMA_7B_Q6_K, + HuggingFaceModel.CODE_GEMMA_7B_Q8_0)), + ; private final String label; private final String description; diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java index 27164bd4..7a4fc091 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java @@ -144,6 +144,24 @@ public enum PromptTemplate { .toString(); } }, + CODE_GEMMA("CodeGemma 7b Instruct") { + @Override + public String buildPrompt(String systemPrompt, String userPrompt, List history) { + StringBuilder prompt = new StringBuilder(); + + for (Message message : history) { + prompt.append("user\n") + .append(message.getPrompt()) + .append("\nmodel\n") + .append(message.getResponse()).append("\n"); + } + + return prompt.append("user\n") + .append(userPrompt) + .append("\nmodel\n") + .toString(); + } + }, ALPACA("Alpaca/Vicuna") { @Override public String buildPrompt(String systemPrompt, String userPrompt, List history) { diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt index 7847ec54..a3f98bc5 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt @@ -2,6 +2,7 @@ package ee.carlrobert.codegpt.completions import ee.carlrobert.codegpt.completions.llama.PromptTemplate.ALPACA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.CHAT_ML +import ee.carlrobert.codegpt.completions.llama.PromptTemplate.CODE_GEMMA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA_3 import ee.carlrobert.codegpt.completions.llama.PromptTemplate.PHI_3 @@ -167,6 +168,74 @@ class PromptTemplateTest { <|assistant|>""".trimIndent()) } + @Test + fun shouldBuildCodeGemmaPromptWithoutHistory() { + val prompt = CODE_GEMMA.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, listOf()) + + assertThat(prompt).isEqualTo(""" + user + TEST_USER_PROMPT + model + + """.trimIndent()) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", "\t", "\n"]) + fun shouldBuildCodeGemmaPromptWithoutHistorySkippingBlankSystemPrompt(systemPrompt: String?) { + val prompt = CODE_GEMMA.buildPrompt(systemPrompt, USER_PROMPT, listOf()) + + assertThat(prompt).isEqualTo(""" + user + TEST_USER_PROMPT + model + + """.trimIndent()) + } + + @Test + fun shouldBuildCodeGemmaPromptWithHistory() { + val prompt = CODE_GEMMA.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, HISTORY) + + assertThat(prompt).isEqualTo(""" + user + TEST_PREV_PROMPT_1 + model + TEST_PREV_RESPONSE_1 + user + TEST_PREV_PROMPT_2 + model + TEST_PREV_RESPONSE_2 + user + TEST_USER_PROMPT + model + + """.trimIndent()) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", "\t", "\n"]) + fun shouldBuildCodeGemmaPromptWithHistorySkippingBlankSystemPrompt(systemPrompt: String?) { + val prompt = CODE_GEMMA.buildPrompt(systemPrompt, USER_PROMPT, HISTORY) + + assertThat(prompt).isEqualTo(""" + user + TEST_PREV_PROMPT_1 + model + TEST_PREV_RESPONSE_1 + user + TEST_PREV_PROMPT_2 + model + TEST_PREV_RESPONSE_2 + user + TEST_USER_PROMPT + model + + """.trimIndent()) + } + @Test fun shouldBuildAlpacaPromptWithHistory() { val prompt = ALPACA.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, HISTORY) From 2c0a28a9122f41d8787939cf24e818facf76befc Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 7 May 2024 16:51:04 +0200 Subject: [PATCH 14/30] feat: add CodeGemma InfillPromptTemplate (#530) --- .../codegpt/completions/llama/LlamaModel.java | 7 ++++--- .../codegpt/completions/llama/PromptTemplate.java | 2 +- .../settings/service/llama/LlamaSettingsState.java | 4 ++-- .../codegpt/codecompletions/InfillPromptTemplate.kt | 10 +++++++++- .../service/custom/CustomServiceCodeCompletionForm.kt | 2 +- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java index c2aaeb80..0c3500e1 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java @@ -17,7 +17,7 @@ public enum LlamaModel { + "support for large input contexts, and zero-shot instruction following ability for " + "programming tasks.", PromptTemplate.LLAMA, - InfillPromptTemplate.LLAMA, + InfillPromptTemplate.CODE_LLAMA, List.of( HuggingFaceModel.CODE_LLAMA_7B_Q3, HuggingFaceModel.CODE_LLAMA_7B_Q4, @@ -115,12 +115,13 @@ public enum LlamaModel { HuggingFaceModel.PHI_3_3_8B_4K_Q8_0, HuggingFaceModel.PHI_3_3_8B_4K_FP16)), CODE_GEMMA( - "CodeGemma 7b Instruct", - "CodeGemma 7b Instruct is the first in a series of coding models released by Google. " + "CodeGemma Instruct", + "CodeGemma Instruct is the first in a series of coding models released by Google. " + "As an instruct model, it specializes in being asked coding related questions, but can " + "also function as an autocomplete/fill-in-middle model for tools like co-pilot.\n" + "This model is perfect for general coding questions or code generation.", PromptTemplate.CODE_GEMMA, + InfillPromptTemplate.CODE_GEMMA, List.of( HuggingFaceModel.CODE_GEMMA_7B_IQ1_S, HuggingFaceModel.CODE_GEMMA_7B_IQ1_M, diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java index 7a4fc091..708b2c8e 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java @@ -144,7 +144,7 @@ public enum PromptTemplate { .toString(); } }, - CODE_GEMMA("CodeGemma 7b Instruct") { + CODE_GEMMA("CodeGemma Instruct") { @Override public String buildPrompt(String systemPrompt, String userPrompt, List history) { StringBuilder prompt = new StringBuilder(); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettingsState.java index fc3ca47b..bff9b890 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettingsState.java @@ -16,8 +16,8 @@ public class LlamaSettingsState { private HuggingFaceModel huggingFaceModel = HuggingFaceModel.CODE_LLAMA_7B_Q4; private PromptTemplate localModelPromptTemplate = PromptTemplate.LLAMA; private PromptTemplate remoteModelPromptTemplate = PromptTemplate.LLAMA; - private InfillPromptTemplate localModelInfillPromptTemplate = InfillPromptTemplate.LLAMA; - private InfillPromptTemplate remoteModelInfillPromptTemplate = InfillPromptTemplate.LLAMA; + private InfillPromptTemplate localModelInfillPromptTemplate = InfillPromptTemplate.CODE_LLAMA; + private InfillPromptTemplate remoteModelInfillPromptTemplate = InfillPromptTemplate.CODE_LLAMA; private String baseHost = "http://localhost:8080"; private Integer serverPort = getRandomAvailablePortOrDefault(); private int contextSize = 2048; diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt index ea68aa40..e4f9d37a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt @@ -7,11 +7,19 @@ enum class InfillPromptTemplate(val label: String, val stopTokens: List? return "<|fim_prefix|> $prefix <|fim_suffix|>$suffix <|fim_middle|>" } }, - LLAMA("Llama", listOf("")) { + CODE_LLAMA("Code Llama", listOf("")) { override fun buildPrompt(prefix: String, suffix: String): String { return "
 $prefix $suffix "
         }
     },
+    CODE_GEMMA(
+        "CodeGemma Instruct",
+        listOf("<|file_separator|>", "<|fim_prefix|>", "<|fim_suffix|>", "<|fim_middle|>", "")
+    ) {
+        override fun buildPrompt(prefix: String, suffix: String): String {
+            return "<|fim_prefix|>$prefix<|fim_suffix|>$suffix<|fim_middle|>"
+        }
+    },
     STABILITY("Stability AI", listOf("<|endoftext|>")) {
         override fun buildPrompt(prefix: String, suffix: String): String {
             return "$prefix$suffix"
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt
index fcac639b..c53558e9 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt
@@ -37,7 +37,7 @@ class CustomServiceCodeCompletionForm(state: CustomServiceCodeCompletionSettings
     private val promptTemplateComboBox =
         ComboBox(EnumComboBoxModel(InfillPromptTemplate::class.java)).apply {
             selectedItem = state.infillTemplate
-            setSelectedItem(InfillPromptTemplate.LLAMA)
+            setSelectedItem(InfillPromptTemplate.CODE_LLAMA)
             addItemListener {
                 updatePromptTemplateHelpTooltip(it.item as InfillPromptTemplate)
             }

From 33aa0e1065db6902187d026190b0168faa3f7fb6 Mon Sep 17 00:00:00 2001
From: Phil 
Date: Tue, 7 May 2024 17:01:07 +0200
Subject: [PATCH 15/30] feat: add Mistral AI service template (#532)

---
 .../custom/CustomServiceChatCompletionTemplate.kt      | 10 ++++++++++
 .../settings/service/custom/CustomServiceTemplate.kt   |  5 +++++
 2 files changed, 15 insertions(+)

diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt
index f6d39271..c90369c5 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt
@@ -89,6 +89,16 @@ enum class CustomServiceChatCompletionTemplate(
         "http://localhost:8080/v1/chat/completions",
         getDefaultHeaders(),
         getDefaultBodyParams(emptyMap())
+    ),
+    MISTRAL_AI(
+        "https://api.mistral.ai/v1/chat/completions",
+        getDefaultHeaders("Authorization", "Bearer \$CUSTOM_SERVICE_API_KEY"),
+        getDefaultBodyParams(
+            mapOf(
+                "model" to "open-mistral-7b",
+                "max_tokens" to 1024
+            )
+        )
     );
 }
 
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt
index 2d837d4b..f2961943 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt
@@ -61,6 +61,11 @@ enum class CustomServiceTemplate(
         "LLaMA C/C++",
         "https://github.com/ggerganov/llama.cpp/blob/master/examples/server/README.md",
         CustomServiceChatCompletionTemplate.LLAMA_CPP
+    ),
+    MISTRAL_AI(
+        "Mistral AI",
+        "https://docs.mistral.ai/getting-started/quickstart",
+        CustomServiceChatCompletionTemplate.MISTRAL_AI
     );
 
     override fun toString(): String {

From 2dfb1b080084200c0ff2416ad4e98e7a0a4849ba Mon Sep 17 00:00:00 2001
From: Phil 
Date: Tue, 7 May 2024 17:14:19 +0200
Subject: [PATCH 16/30] fix: Storing HuggingFaceModel by modelName instead of
 quantization only (#529)

---
 .../codegpt/completions/HuggingFaceModel.java | 97 ++++++++-----------
 .../codegpt/completions/llama/LlamaModel.java | 18 ----
 .../settings/service/llama/LlamaSettings.java | 13 +++
 .../llama/form/LlamaModelPreferencesForm.java | 18 +++-
 4 files changed, 69 insertions(+), 77 deletions(-)

diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java
index 7bb7b9ba..3e8841e3 100644
--- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java
+++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java
@@ -4,46 +4,47 @@ import static java.lang.String.format;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.regex.Pattern;
 
 public enum HuggingFaceModel {
 
-  CODE_LLAMA_7B_Q3(7, 3, "CodeLlama-7B-Instruct-GGUF"),
-  CODE_LLAMA_7B_Q4(7, 4, "CodeLlama-7B-Instruct-GGUF"),
-  CODE_LLAMA_7B_Q5(7, 5, "CodeLlama-7B-Instruct-GGUF"),
-  CODE_LLAMA_13B_Q3(13, 3, "CodeLlama-13B-Instruct-GGUF"),
-  CODE_LLAMA_13B_Q4(13, 4, "CodeLlama-13B-Instruct-GGUF"),
-  CODE_LLAMA_13B_Q5(13, 5, "CodeLlama-13B-Instruct-GGUF"),
-  CODE_LLAMA_34B_Q3(34, 3, "CodeLlama-34B-Instruct-GGUF"),
-  CODE_LLAMA_34B_Q4(34, 4, "CodeLlama-34B-Instruct-GGUF"),
-  CODE_LLAMA_34B_Q5(34, 5, "CodeLlama-34B-Instruct-GGUF"),
+  CODE_LLAMA_7B_Q3(7, 3, "codellama-7b-instruct.Q3_K_M.gguf"),
+  CODE_LLAMA_7B_Q4(7, 4, "codellama-7b-instruct.Q4_K_M.gguf"),
+  CODE_LLAMA_7B_Q5(7, 5, "codellama-7b-instruct.Q5_K_M.gguf"),
+  CODE_LLAMA_13B_Q3(13, 3, "codellama-13b-instruct.Q3_K_M.gguf"),
+  CODE_LLAMA_13B_Q4(13, 4, "codellama-13b-instruct.Q4_K_M.gguf"),
+  CODE_LLAMA_13B_Q5(13, 5, "codellama-13b-instruct.Q5_K_M.gguf"),
+  CODE_LLAMA_34B_Q3(34, 3, "codellama-34b-instruct.Q3_K_M.gguf"),
+  CODE_LLAMA_34B_Q4(34, 4, "codellama-34b-instruct.Q4_K_M.gguf"),
+  CODE_LLAMA_34B_Q5(34, 5, "codellama-34b-instruct.Q5_K_M.gguf"),
 
-  CODE_BOOGA_34B_Q3(34, 3, "CodeBooga-34B-v0.1-GGUF"),
-  CODE_BOOGA_34B_Q4(34, 4, "CodeBooga-34B-v0.1-GGUF"),
-  CODE_BOOGA_34B_Q5(34, 5, "CodeBooga-34B-v0.1-GGUF"),
+  CODE_BOOGA_34B_Q3(34, 3, "codebooga-34b-v0.1.Q3_K_M.gguf"),
+  CODE_BOOGA_34B_Q4(34, 4, "codebooga-34b-v0.1.Q4_K_M.gguf"),
+  CODE_BOOGA_34B_Q5(34, 5, "codebooga-34b-v0.1.Q5_K_M.gguf"),
 
-  DEEPSEEK_CODER_1_3B_Q3(1, 3, "deepseek-coder-1.3b-instruct-GGUF", 0.705),
-  DEEPSEEK_CODER_1_3B_Q4(1, 4, "deepseek-coder-1.3b-instruct-GGUF", 0.874),
-  DEEPSEEK_CODER_1_3B_Q5(1, 5, "deepseek-coder-1.3b-instruct-GGUF", 1.0),
-  DEEPSEEK_CODER_6_7B_Q3(7, 3, "deepseek-coder-6.7b-instruct-GGUF"),
-  DEEPSEEK_CODER_6_7B_Q4(7, 4, "deepseek-coder-6.7b-instruct-GGUF"),
-  DEEPSEEK_CODER_6_7B_Q5(7, 5, "deepseek-coder-6.7b-instruct-GGUF"),
-  DEEPSEEK_CODER_33B_Q3(33, 3, "deepseek-coder-33b-instruct-GGUF", 16.1),
-  DEEPSEEK_CODER_33B_Q4(33, 4, "deepseek-coder-33b-instruct-GGUF", 19.9),
-  DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct-GGUF", 23.5),
+  DEEPSEEK_CODER_1_3B_Q3(1, 3, "deepseek-coder-1.3b-instruct.Q3_K_M.gguf", 0.705),
+  DEEPSEEK_CODER_1_3B_Q4(1, 4, "deepseek-coder-1.3b-instruct.Q4_K_M.gguf", 0.874),
+  DEEPSEEK_CODER_1_3B_Q5(1, 5, "deepseek-coder-1.3b-instruct.Q5_K_M.gguf", 1.0),
+  DEEPSEEK_CODER_6_7B_Q3(7, 3, "deepseek-coder-6.7b-instruct.Q3_K_M.gguf"),
+  DEEPSEEK_CODER_6_7B_Q4(7, 4, "deepseek-coder-6.7b-instruct.Q4_K_M.gguf"),
+  DEEPSEEK_CODER_6_7B_Q5(7, 5, "deepseek-coder-6.7b-instruct.Q5_K_M.gguf"),
+  DEEPSEEK_CODER_33B_Q3(33, 3, "deepseek-coder-33b-instruct.Q3_K_M.gguf", 16.1),
+  DEEPSEEK_CODER_33B_Q4(33, 4, "deepseek-coder-33b-instruct.Q4_K_M.gguf", 19.9),
+  DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct.Q5_K_M.gguf", 23.5),
 
-  PHIND_CODE_LLAMA_34B_Q3(34, 3, "Phind-CodeLlama-34B-v2-GGUF"),
-  PHIND_CODE_LLAMA_34B_Q4(34, 4, "Phind-CodeLlama-34B-v2-GGUF"),
-  PHIND_CODE_LLAMA_34B_Q5(34, 5, "Phind-CodeLlama-34B-v2-GGUF"),
+  PHIND_CODE_LLAMA_34B_Q3(34, 3, "phind-codellama-34b-v2.Q3_K_M.gguf"),
+  PHIND_CODE_LLAMA_34B_Q4(34, 4, "phind-codellama-34b-v2.Q4_K_M.gguf"),
+  PHIND_CODE_LLAMA_34B_Q5(34, 5, "phind-codellama-34b-v2.Q5_K_M.gguf"),
 
-  WIZARD_CODER_PYTHON_7B_Q3(7, 3, "WizardCoder-Python-7B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_7B_Q4(7, 4, "WizardCoder-Python-7B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_7B_Q5(7, 5, "WizardCoder-Python-7B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_13B_Q3(13, 3, "WizardCoder-Python-13B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_13B_Q4(13, 4, "WizardCoder-Python-13B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_13B_Q5(13, 5, "WizardCoder-Python-13B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_34B_Q3(34, 3, "WizardCoder-Python-34B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_34B_Q4(34, 4, "WizardCoder-Python-34B-V1.0-GGUF"),
-  WIZARD_CODER_PYTHON_34B_Q5(34, 5, "WizardCoder-Python-34B-V1.0-GGUF"),
+  WIZARD_CODER_PYTHON_7B_Q3(7, 3, "wizardcoder-python-7b-v1.0.Q3_K_M.gguf"),
+  WIZARD_CODER_PYTHON_7B_Q4(7, 4, "wizardcoder-python-7b-v1.0.Q4_K_M.gguf"),
+  WIZARD_CODER_PYTHON_7B_Q5(7, 5, "wizardcoder-python-7b-v1.0.Q5_K_M.gguf"),
+  WIZARD_CODER_PYTHON_13B_Q3(13, 3, "wizardcoder-python-13b-v1.0.Q3_K_M.gguf"),
+  WIZARD_CODER_PYTHON_13B_Q4(13, 4, "wizardcoder-python-13b-v1.0.Q4_K_M.gguf"),
+  WIZARD_CODER_PYTHON_13B_Q5(13, 5, "wizardcoder-python-13b-v1.0.Q5_K_M.gguf"),
+  WIZARD_CODER_PYTHON_34B_Q3(34, 3, "wizardcoder-python-34b-v1.0.Q3_K_M.gguf"),
+  WIZARD_CODER_PYTHON_34B_Q4(34, 4, "wizardcoder-python-34b-v1.0.Q4_K_M.gguf"),
+  WIZARD_CODER_PYTHON_34B_Q5(34, 5, "wizardcoder-python-34b-v1.0.Q5_K_M.gguf"),
 
   LLAMA_3_8B_IQ3_M(8, 3, "Meta-Llama-3-8B-Instruct-IQ3_M.gguf", "lmstudio-community", 3.78),
   LLAMA_3_8B_Q4_K_M(8, 4, "Meta-Llama-3-8B-Instruct-Q4_K_M.gguf", "lmstudio-community", 4.92),
@@ -56,30 +57,12 @@ public enum HuggingFaceModel {
 
   PHI_3_3_8B_4K_IQ4_NL(4, 4, "Phi-3-mini-4k-instruct-IQ4_NL.gguf", "lmstudio-community", 2.18),
   PHI_3_3_8B_4K_Q5_K_M(4, 5, "Phi-3-mini-4k-instruct-Q5_K_M.gguf", "lmstudio-community", 2.64),
-  PHI_3_3_8B_4K_Q5_K_S(4, 5, "Phi-3-mini-4k-instruct-Q5_K_S.gguf", "lmstudio-community", 2.82),
   PHI_3_3_8B_4K_Q6_K(4, 6, "Phi-3-mini-4k-instruct-Q6_K.gguf", "lmstudio-community", 3.14),
   PHI_3_3_8B_4K_Q8_0(4, 8, "Phi-3-mini-4k-instruct-Q8_0.gguf", "lmstudio-community", 4.06),
   PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-fp16.gguf", "lmstudio-community", 7.64),
 
-  CODE_GEMMA_7B_IQ1_S(7, 1, "codegemma-7b-it-IQ1_S.gguf", "lmstudio-community", 2.16),
-  CODE_GEMMA_7B_IQ1_M(7, 1, "codegemma-7b-it-IQ1_M.gguf", "lmstudio-community", 2.32),
-  CODE_GEMMA_7B_IQ2_XXS(7, 2, "codegemma-7b-it-IQ2_XXS.gguf", "lmstudio-community", 2.59),
-  CODE_GEMMA_7B_IQ2_XS(7, 2, "codegemma-7b-it-IQ2_XS.gguf", "lmstudio-community", 2.81),
-  CODE_GEMMA_7B_IQ2_S(7, 2, "codegemma-7b-it-IQ2_S.gguf", "lmstudio-community", 2.92),
-  CODE_GEMMA_7B_IQ2_M(7, 2, "codegemma-7b-it-IQ2_M.gguf", "lmstudio-community", 3.13),
-  CODE_GEMMA_7B_Q2_K(7, 2, "codegemma-7b-it-Q2_K.gguf", "lmstudio-community", 3.48),
-  CODE_GEMMA_7B_IQ3_XXS(7, 3, "codegemma-7b-it-IQ3_XXS.gguf", "lmstudio-community", 3.49),
-  CODE_GEMMA_7B_IQ3_XS(7, 3, "codegemma-7b-it-IQ3_XS.gguf", "lmstudio-community", 3.80),
-  CODE_GEMMA_7B_IQ3_S(7, 3, "codegemma-7b-it-IQ3_S.gguf", "lmstudio-community", 3.98),
-  CODE_GEMMA_7B_Q3_K_S(7, 3, "codegemma-7b-it-Q3_K_S.gguf", "lmstudio-community", 3.98),
-  CODE_GEMMA_7B_IQ3_M(7, 3, "codegemma-7b-it-IQ3_M.gguf", "lmstudio-community", 4.11),
   CODE_GEMMA_7B_Q3_K_M(7, 3, "codegemma-7b-it-Q3_K_M.gguf", "lmstudio-community", 4.37),
-  CODE_GEMMA_7B_Q3_K_L(7, 3, "codegemma-7b-it-Q3_K_L.gguf", "lmstudio-community", 4.71),
-  CODE_GEMMA_7B_IQ4_XS(7, 4, "codegemma-7b-it-IQ4_XS.gguf", "lmstudio-community", 4.77),
-  CODE_GEMMA_7B_IQ4_NL(7, 4, "codegemma-7b-it-IQ4_NL.gguf", "lmstudio-community", 5.01),
-  CODE_GEMMA_7B_Q4_K_S(7, 4, "codegemma-7b-it-Q4_K_S.gguf", "lmstudio-community", 5.05),
   CODE_GEMMA_7B_Q4_K_M(7, 4, "codegemma-7b-it-Q4_K_M.gguf", "lmstudio-community", 5.33),
-  CODE_GEMMA_7B_Q5_K_S(7, 5, "codegemma-7b-it-Q5_K_S.gguf", "lmstudio-community", 5.98),
   CODE_GEMMA_7B_Q5_K_M(7, 5, "codegemma-7b-it-Q5_K_M.gguf", "lmstudio-community", 6.14),
   CODE_GEMMA_7B_Q6_K(7, 6, "codegemma-7b-it-Q6_K.gguf", "lmstudio-community", 7.01),
   CODE_GEMMA_7B_Q8_0(7, 8, "codegemma-7b-it-Q8_0.gguf", "lmstudio-community", 9.08),
@@ -149,15 +132,15 @@ public enum HuggingFaceModel {
   }
 
   private String getDirectory() {
-    if ("lmstudio-community".equals(user)) {
-      // Meta-Llama-3-8B-Instruct-Q4_K_M.gguf -> Meta-Llama-3-8B-Instruct-GGUF
-      return modelName.replaceFirst("-[^.-]+\\.gguf$", "-GGUF");
-    }
-    return modelName;
+    return modelName.replaceFirst("-[^.-]+\\.gguf$", "-GGUF");
+  }
+
+  public String getQuantizationLabel() {
+    return format("%d-bit precision", quantization);
   }
 
   @Override
   public String toString() {
-    return format("%d-bit precision", quantization);
+    return modelName;
   }
 }
diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java
index 0c3500e1..5c0af737 100644
--- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java
+++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java
@@ -110,7 +110,6 @@ public enum LlamaModel {
       List.of(
           HuggingFaceModel.PHI_3_3_8B_4K_IQ4_NL,
           HuggingFaceModel.PHI_3_3_8B_4K_Q5_K_M,
-          HuggingFaceModel.PHI_3_3_8B_4K_Q5_K_S,
           HuggingFaceModel.PHI_3_3_8B_4K_Q6_K,
           HuggingFaceModel.PHI_3_3_8B_4K_Q8_0,
           HuggingFaceModel.PHI_3_3_8B_4K_FP16)),
@@ -123,25 +122,8 @@ public enum LlamaModel {
       PromptTemplate.CODE_GEMMA,
       InfillPromptTemplate.CODE_GEMMA,
       List.of(
-          HuggingFaceModel.CODE_GEMMA_7B_IQ1_S,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ1_M,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ2_XXS,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ2_XS,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ2_S,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ2_M,
-          HuggingFaceModel.CODE_GEMMA_7B_Q2_K,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ3_XXS,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ3_XS,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ3_S,
-          HuggingFaceModel.CODE_GEMMA_7B_Q3_K_S,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ3_M,
           HuggingFaceModel.CODE_GEMMA_7B_Q3_K_M,
-          HuggingFaceModel.CODE_GEMMA_7B_Q3_K_L,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ4_XS,
-          HuggingFaceModel.CODE_GEMMA_7B_IQ4_NL,
-          HuggingFaceModel.CODE_GEMMA_7B_Q4_K_S,
           HuggingFaceModel.CODE_GEMMA_7B_Q4_K_M,
-          HuggingFaceModel.CODE_GEMMA_7B_Q5_K_S,
           HuggingFaceModel.CODE_GEMMA_7B_Q5_K_M,
           HuggingFaceModel.CODE_GEMMA_7B_Q6_K,
           HuggingFaceModel.CODE_GEMMA_7B_Q8_0)),
diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java
index c27edac9..fa3fef84 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java
@@ -6,6 +6,8 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.State;
 import com.intellij.openapi.components.Storage;
+import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate;
+import ee.carlrobert.codegpt.completions.HuggingFaceModel;
 import ee.carlrobert.codegpt.completions.llama.LlamaModel;
 import ee.carlrobert.codegpt.credentials.CredentialsStore;
 import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm;
@@ -26,6 +28,17 @@ public class LlamaSettings implements PersistentStateComponent createHuggingFaceComboBox(
+  private ComboBox createModelQuantizationComboBox(
       DefaultComboBoxModel huggingFaceComboBoxModel,
       JBLabel modelExistsIcon,
       JBLabel modelDetailsLabel,
@@ -380,6 +383,17 @@ public class LlamaModelPreferencesForm {
       modelExistsIcon.setVisible(modelExists);
       downloadModelActionLinkWrapper.setVisible(!modelExists);
     });
+    comboBox.setRenderer(new DefaultListCellRenderer() {
+      @Override
+      public Component getListCellRendererComponent(JList list, Object value, int index,
+          boolean isSelected, boolean cellHasFocus) {
+        Object item = value;
+        if (item instanceof HuggingFaceModel) {
+          item = ((HuggingFaceModel) item).getQuantizationLabel();
+        }
+        return super.getListCellRendererComponent(list, item, index, isSelected, cellHasFocus);
+      }
+    });
     return comboBox;
   }
 

From 13c59cc97bdc4800ddee6c06a1c16e1ca2024208 Mon Sep 17 00:00:00 2001
From: Carl-Robert Linnupuu 
Date: Tue, 7 May 2024 18:20:06 +0300
Subject: [PATCH 17/30] fix: build

---
 .../codegpt/settings/service/llama/LlamaSettings.java         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java
index fa3fef84..926fd021 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java
@@ -34,10 +34,10 @@ public class LlamaSettings implements PersistentStateComponent
Date: Tue, 7 May 2024 17:34:35 +0200
Subject: [PATCH 18/30] fix: CustomService Test connection with correct
 settings (#531)

---
 .../CompletionRequestProvider.java            | 26 +++++++++++++-----
 .../CodeCompletionRequestFactory.kt           | 27 ++++++++++++++++---
 .../custom/CustomServiceChatCompletionForm.kt | 13 +++++++--
 .../custom/CustomServiceCodeCompletionForm.kt | 14 ++++++++--
 .../service/custom/CustomServiceForm.kt       |  4 +--
 5 files changed, 67 insertions(+), 17 deletions(-)

diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java
index fc5c41a7..a5dc6280 100644
--- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java
+++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java
@@ -125,13 +125,17 @@ public class CompletionRequestProvider {
         true);
   }
 
-  public static Request buildCustomOpenAICompletionRequest(String input) {
+  public static Request buildCustomOpenAICompletionRequest(String context, String url,
+      Map headers, Map body, String credential) {
+    var usedSettings = new CustomServiceChatCompletionSettingsState();
+    usedSettings.setBody(body);
+    usedSettings.setHeaders(headers);
+    usedSettings.setUrl(url);
     return buildCustomOpenAIChatCompletionRequest(
-        ApplicationManager.getApplication().getService(CustomServiceSettings.class)
-            .getState()
-            .getChatCompletionSettings(),
-        List.of(new OpenAIChatCompletionStandardMessage("user", input)),
-        true);
+        usedSettings,
+        List.of(new OpenAIChatCompletionStandardMessage("user", context)),
+        true,
+        credential);
   }
 
   public static Request buildCustomOpenAILookupCompletionRequest(String context) {
@@ -226,8 +230,16 @@ public class CompletionRequestProvider {
       CustomServiceChatCompletionSettingsState settings,
       List messages,
       boolean streamRequest) {
+    return buildCustomOpenAIChatCompletionRequest(settings, messages, streamRequest,
+        CredentialsStore.INSTANCE.getCredential(CUSTOM_SERVICE_API_KEY));
+  }
+
+  private static Request buildCustomOpenAIChatCompletionRequest(
+      CustomServiceChatCompletionSettingsState settings,
+      List messages,
+      boolean streamRequest,
+      String credential) {
     var requestBuilder = new Request.Builder().url(requireNonNull(settings.getUrl()).trim());
-    var credential = CredentialsStore.INSTANCE.getCredential(CUSTOM_SERVICE_API_KEY);
     for (var entry : settings.getHeaders().entrySet()) {
       String value = entry.getValue();
       if (credential != null && value.contains("$CUSTOM_SERVICE_API_KEY")) {
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt
index 203a3add..d335144a 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt
@@ -34,17 +34,36 @@ object CodeCompletionRequestFactory {
     @JvmStatic
     fun buildCustomRequest(details: InfillRequestDetails): Request {
         val settings = service().state.codeCompletionSettings
-        val requestBuilder = Request.Builder().url(settings.url!!)
         val credential = getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY)
-        for (entry in settings.headers.entries) {
+        return buildCustomRequest(
+            details,
+            settings.url!!,
+            settings.headers,
+            settings.body,
+            settings.infillTemplate,
+            credential
+        )
+    }
+
+    @JvmStatic
+    fun buildCustomRequest(
+        details: InfillRequestDetails,
+        url: String,
+        headers: Map,
+        body: Map,
+        infillTemplate: InfillPromptTemplate,
+        credential: String?
+    ): Request {
+        val requestBuilder = Request.Builder().url(url)
+        for (entry in headers.entries) {
             var value = entry.value
             if (credential != null && value.contains("\$CUSTOM_SERVICE_API_KEY")) {
                 value = value.replace("\$CUSTOM_SERVICE_API_KEY", credential)
             }
             requestBuilder.addHeader(entry.key, value)
         }
-        val transformedBody = settings.body.entries.associate { (key, value) ->
-            key to transformValue(value, settings.infillTemplate, details)
+        val transformedBody = body.entries.associate { (key, value) ->
+            key to transformValue(value, infillTemplate, details)
         }
 
         try {
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt
index 7bf46f95..b57917aa 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt
@@ -15,7 +15,10 @@ import javax.swing.JButton
 import javax.swing.JPanel
 import javax.swing.SwingUtilities
 
-class CustomServiceChatCompletionForm(state: CustomServiceChatCompletionSettingsState) {
+class CustomServiceChatCompletionForm(
+    state: CustomServiceChatCompletionSettingsState,
+    val getApiKey: () -> String?
+) {
 
     private val urlField = JBTextField(state.url, 30)
     private val tabbedPane = CustomServiceFormTabbedPane(state.headers, state.body)
@@ -67,7 +70,13 @@ class CustomServiceChatCompletionForm(state: CustomServiceChatCompletionSettings
 
     private fun testConnection() {
         CompletionRequestService.getInstance().getCustomOpenAIChatCompletionAsync(
-            CompletionRequestProvider.buildCustomOpenAICompletionRequest("Hello!"),
+            CompletionRequestProvider.buildCustomOpenAICompletionRequest(
+                "Test",
+                urlField.text,
+                tabbedPane.headers,
+                tabbedPane.body,
+                getApiKey.invoke()
+            ),
             TestConnectionEventListener()
         )
     }
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt
index c53558e9..b2938efd 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt
@@ -28,7 +28,10 @@ import javax.swing.JButton
 import javax.swing.JPanel
 import javax.swing.SwingUtilities
 
-class CustomServiceCodeCompletionForm(state: CustomServiceCodeCompletionSettingsState) {
+class CustomServiceCodeCompletionForm(
+    state: CustomServiceCodeCompletionSettingsState,
+    val getApiKey: () -> String?
+) {
 
     private val featureEnabledCheckBox = JBCheckBox(
         CodeGPTBundle.get("codeCompletionsForm.enableFeatureText"),
@@ -138,7 +141,14 @@ class CustomServiceCodeCompletionForm(state: CustomServiceCodeCompletionSettings
 
     private fun testConnection() {
         CompletionRequestService.getInstance().getCustomOpenAICompletionAsync(
-            CodeCompletionRequestFactory.buildCustomRequest(InfillRequestDetails("Hello", "!")),
+            CodeCompletionRequestFactory.buildCustomRequest(
+                InfillRequestDetails("Hello", "!"),
+                urlField.text,
+                tabbedPane.headers,
+                tabbedPane.body,
+                promptTemplateComboBox.selectedItem as InfillPromptTemplate,
+                getApiKey.invoke()
+            ),
             TestConnectionEventListener()
         )
     }
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt
index b5c8344d..be995355 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt
@@ -34,8 +34,8 @@ class CustomServiceForm {
 
     init {
         val state = service().state
-        chatCompletionsForm = CustomServiceChatCompletionForm(state.chatCompletionSettings)
-        codeCompletionsForm = CustomServiceCodeCompletionForm(state.codeCompletionSettings)
+        chatCompletionsForm = CustomServiceChatCompletionForm(state.chatCompletionSettings, this::getApiKey)
+        codeCompletionsForm = CustomServiceCodeCompletionForm(state.codeCompletionSettings, this::getApiKey)
         tabbedPane = JTabbedPane().apply {
             add(CodeGPTBundle.get("shared.chatCompletions"), chatCompletionsForm.form)
             add(CodeGPTBundle.get("shared.codeCompletions"), codeCompletionsForm.form)

From e40630d7967daaaa7e64960da80eb881ec3e7aa8 Mon Sep 17 00:00:00 2001
From: Jack Boswell 
Date: Wed, 8 May 2024 10:11:13 +1200
Subject: [PATCH 19/30] feat: Implement Ollama as a high-level service (#510)

* Initial implementation of Ollama as a service

* Fix model selector in tool window

* Enable image attachment

* Rewrite OllamaSettingsForm in Kt

* Create OllamaInlineCompletionModel and use it for building completion template

* Add support for blocking code completion on models that we don't know support it

* Allow disabling code completion settings

* Disable code completion settings when an unsupported model is entered

* Track FIM template in settings as a derived state

* Update llm-client

* Initial implementation of model combo box

* Add Ollama icon and display models as list

* Make OllamaSettingsState immutable & convert OllamaSettings to Kotlin

* Add refresh models button

* Distinguish between empty/needs refresh/loading

* Avoid storing any model if the combo box is empty

* Fix icon size

* Back to mutable settings
There were some bugs with immutable settings

* Store available models in settings state

* Expose available models in model dropdown

* Add dark icon

* Cleanups for CompletionRequestProvider

* Fix checkstyle issues

* refactor: migrate to SimplePersistentStateComponent

* fix: add code completion stop tokens

* fix: display only one item in the model popup action group

* fix: add back multi model selection

---------

Co-authored-by: Carl-Robert Linnupuu 
---
 .../java/ee/carlrobert/codegpt/Icons.java     |   1 +
 .../completions/CompletionClientProvider.java |  13 ++
 .../CompletionRequestProvider.java            |  83 ++++++++-
 .../completions/CompletionRequestService.java |  29 +++-
 .../conversations/ConversationService.java    |   9 +-
 .../codegpt/settings/GeneralSettings.java     |   9 +
 .../settings/GeneralSettingsComponent.java    |  11 ++
 .../settings/GeneralSettingsConfigurable.java |  10 +-
 .../codegpt/settings/service/ServiceType.java |   3 +-
 .../service/llama/form/LlamaSettingsForm.java |   3 +-
 .../service/openai/OpenAISettingsForm.java    |   3 +-
 .../chat/ui/textarea/ModelComboBoxAction.java |  43 +++++
 .../chat/ui/textarea/UserPromptTextArea.java  |   2 +
 .../CodeCompletionFeatureToggleActions.kt     |  29 +++-
 .../CodeCompletionRequestFactory.kt           |  33 +++-
 .../CodeGPTInlineCompletionProvider.kt        |   2 +
 .../CodeCompletionConfigurationForm.kt        |  61 ++++++-
 .../settings/service/ollama/OllamaSettings.kt |  20 +++
 .../service/ollama/OllamaSettingsForm.kt      | 163 ++++++++++++++++++
 src/main/resources/META-INF/plugin.xml        |   1 +
 src/main/resources/icons/ollama.svg           |   7 +
 src/main/resources/icons/ollama_dark.svg      |   7 +
 .../resources/messages/codegpt.properties     |   2 +
 23 files changed, 505 insertions(+), 39 deletions(-)
 create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettings.kt
 create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt
 create mode 100644 src/main/resources/icons/ollama.svg
 create mode 100644 src/main/resources/icons/ollama_dark.svg

diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java
index 6dbeaf12..0e210996 100644
--- a/src/main/java/ee/carlrobert/codegpt/Icons.java
+++ b/src/main/java/ee/carlrobert/codegpt/Icons.java
@@ -16,6 +16,7 @@ public final class Icons {
   public static final Icon Sparkle = IconLoader.getIcon("/icons/sparkle.svg", Icons.class);
   public static final Icon You = IconLoader.getIcon("/icons/you.svg", Icons.class);
   public static final Icon YouSmall = IconLoader.getIcon("/icons/you_small.png", Icons.class);
+  public static final Icon Ollama = IconLoader.getIcon("/icons/ollama.svg", Icons.class);
   public static final Icon User = IconLoader.getIcon("/icons/user.svg", Icons.class);
   public static final Icon Upload = IconLoader.getIcon("/icons/upload.svg", Icons.class);
 }
diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java
index ee1506d4..0e74e9bd 100644
--- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java
+++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java
@@ -1,5 +1,6 @@
 package ee.carlrobert.codegpt.completions;
 
+import com.intellij.openapi.application.ApplicationManager;
 import ee.carlrobert.codegpt.CodeGPTPlugin;
 import ee.carlrobert.codegpt.completions.you.YouUserManager;
 import ee.carlrobert.codegpt.credentials.CredentialsStore;
@@ -8,11 +9,13 @@ import ee.carlrobert.codegpt.settings.advanced.AdvancedSettings;
 import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
 import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import ee.carlrobert.llm.client.anthropic.ClaudeClient;
 import ee.carlrobert.llm.client.azure.AzureClient;
 import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams;
 import ee.carlrobert.llm.client.llama.LlamaClient;
+import ee.carlrobert.llm.client.ollama.OllamaClient;
 import ee.carlrobert.llm.client.openai.OpenAIClient;
 import ee.carlrobert.llm.client.you.UTMParameters;
 import ee.carlrobert.llm.client.you.YouClient;
@@ -92,6 +95,16 @@ public class CompletionClientProvider {
     return builder.build(getDefaultClientBuilder());
   }
 
+  public static OllamaClient getOllamaClient() {
+    var host = ApplicationManager.getApplication()
+        .getService(OllamaSettings.class)
+        .getState()
+        .getHost();
+    return new OllamaClient.Builder()
+        .setHost(host)
+        .build(getDefaultClientBuilder());
+  }
+
   public static OkHttpClient.Builder getDefaultClientBuilder() {
     OkHttpClient.Builder builder = new OkHttpClient.Builder();
     var advancedSettings = AdvancedSettings.getCurrentState();
diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java
index a5dc6280..04f0d39c 100644
--- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java
+++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java
@@ -26,8 +26,8 @@ import ee.carlrobert.codegpt.settings.service.ServiceType;
 import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState;
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings;
-import ee.carlrobert.codegpt.settings.service.custom.CustomServiceState;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import ee.carlrobert.codegpt.settings.service.you.YouSettings;
 import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
@@ -41,6 +41,8 @@ import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMes
 import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageImageContent;
 import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageTextContent;
 import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
+import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionMessage;
+import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionRequest;
 import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel;
 import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionDetailedMessage;
 import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage;
@@ -56,6 +58,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -140,7 +143,8 @@ public class CompletionRequestProvider {
 
   public static Request buildCustomOpenAILookupCompletionRequest(String context) {
     return buildCustomOpenAIChatCompletionRequest(
-        ApplicationManager.getApplication().getService(CustomServiceState.class)
+        ApplicationManager.getApplication().getService(CustomServiceSettings.class)
+            .getState()
             .getChatCompletionSettings(),
         List.of(
             new OpenAIChatCompletionStandardMessage(
@@ -210,7 +214,7 @@ public class CompletionRequestProvider {
       @Nullable String model,
       CallParameters callParameters) {
     var configuration = ConfigurationSettings.getCurrentState();
-    return new OpenAIChatCompletionRequest.Builder(buildMessages(model, callParameters))
+    return new OpenAIChatCompletionRequest.Builder(buildOpenAIMessages(model, callParameters))
         .setModel(model)
         .setMaxTokens(configuration.getMaxTokens())
         .setStream(true)
@@ -222,7 +226,7 @@ public class CompletionRequestProvider {
       CallParameters callParameters) {
     return buildCustomOpenAIChatCompletionRequest(
         settings,
-        buildMessages(callParameters),
+        buildOpenAIMessages(callParameters),
         true);
   }
 
@@ -307,7 +311,68 @@ public class CompletionRequestProvider {
     return request;
   }
 
-  private List buildMessages(CallParameters callParameters) {
+  public OllamaChatCompletionRequest buildOllamaChatCompletionRequest(
+      CallParameters callParameters
+  ) {
+    var settings = ApplicationManager.getApplication().getService(OllamaSettings.class).getState();
+    return new OllamaChatCompletionRequest
+        .Builder(settings.getModel(), buildOllamaMessages(callParameters))
+        .build();
+  }
+
+  private List buildOllamaMessages(CallParameters callParameters) {
+    var message = callParameters.getMessage();
+    var messages = new ArrayList();
+    if (callParameters.getConversationType() == ConversationType.DEFAULT) {
+      String systemPrompt = ConfigurationSettings.getCurrentState().getSystemPrompt();
+      messages.add(new OllamaChatCompletionMessage("system", systemPrompt, null));
+    }
+    if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) {
+      messages.add(
+          new OllamaChatCompletionMessage("system", FIX_COMPILE_ERRORS_SYSTEM_PROMPT, null)
+      );
+    }
+
+    for (var prevMessage : conversation.getMessages()) {
+      if (callParameters.isRetry() && prevMessage.getId().equals(message.getId())) {
+        break;
+      }
+      var prevMessageImageFilePath = prevMessage.getImageFilePath();
+      if (prevMessageImageFilePath != null && !prevMessageImageFilePath.isEmpty()) {
+        try {
+          var imageFilePath = Path.of(prevMessageImageFilePath);
+          var imageBytes = Files.readAllBytes(imageFilePath);
+          var imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
+          messages.add(
+              new OllamaChatCompletionMessage(
+                  "user", prevMessage.getPrompt(), List.of(imageBase64)
+              )
+          );
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      } else {
+        messages.add(
+            new OllamaChatCompletionMessage("user", prevMessage.getPrompt(), null)
+        );
+      }
+      messages.add(
+          new OllamaChatCompletionMessage("assistant", prevMessage.getResponse(), null)
+      );
+    }
+
+    if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) {
+      var imageBase64 = Base64.getEncoder().encodeToString(callParameters.getImageData());
+      messages.add(
+          new OllamaChatCompletionMessage("user", message.getPrompt(), List.of(imageBase64))
+      );
+    } else {
+      messages.add(new OllamaChatCompletionMessage("user", message.getPrompt(), null));
+    }
+    return messages;
+  }
+
+  private List buildOpenAIMessages(CallParameters callParameters) {
     var message = callParameters.getMessage();
     var messages = new ArrayList();
     if (callParameters.getConversationType() == ConversationType.DEFAULT) {
@@ -339,7 +404,9 @@ public class CompletionRequestProvider {
       } else {
         messages.add(new OpenAIChatCompletionStandardMessage("user", prevMessage.getPrompt()));
       }
-      messages.add(new OpenAIChatCompletionStandardMessage("assistant", prevMessage.getResponse()));
+      messages.add(
+          new OpenAIChatCompletionStandardMessage("assistant", prevMessage.getResponse())
+      );
     }
 
     if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) {
@@ -355,10 +422,10 @@ public class CompletionRequestProvider {
     return messages;
   }
 
-  private List buildMessages(
+  private List buildOpenAIMessages(
       @Nullable String model,
       CallParameters callParameters) {
-    var messages = buildMessages(callParameters);
+    var messages = buildOpenAIMessages(callParameters);
 
     if (model == null
         || GeneralSettings.getCurrentState().getSelectedService() == ServiceType.YOU) {
diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java
index 9d98e100..6edfe92d 100644
--- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java
+++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java
@@ -21,11 +21,14 @@ import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
 import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import ee.carlrobert.llm.client.DeserializationUtil;
 import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest;
 import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMessage;
 import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
+import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionMessage;
+import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionRequest;
 import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionEventSourceListener;
 import ee.carlrobert.llm.client.openai.completion.OpenAITextCompletionEventSourceListener;
 import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest;
@@ -104,6 +107,9 @@ public final class CompletionRequestService {
               callParameters.getMessage(),
               callParameters.getConversationType()),
           eventListener);
+      case OLLAMA -> CompletionClientProvider.getOllamaClient().getChatCompletionAsync(
+          requestProvider.buildOllamaChatCompletionRequest(callParameters),
+          eventListener);
     };
   }
 
@@ -123,6 +129,9 @@ public final class CompletionRequestService {
           .getInfillAsync(
               CodeCompletionRequestFactory.buildLlamaRequest(requestDetails),
               eventListener);
+      case OLLAMA -> CompletionClientProvider.getOllamaClient().getCompletionAsync(
+          CodeCompletionRequestFactory.INSTANCE.buildOllamaRequest(requestDetails),
+          eventListener);
       default ->
           throw new IllegalArgumentException("Code completion not supported for selected service");
     };
@@ -189,6 +198,20 @@ public final class CompletionRequestService {
                 .setRepeat_penalty(settings.getRepeatPenalty())
                 .build(), eventListener);
         break;
+      case OLLAMA:
+        var model = ApplicationManager.getApplication()
+            .getService(OllamaSettings.class)
+            .getState()
+            .getModel();
+        var request = new OllamaChatCompletionRequest.Builder(
+            model,
+            List.of(
+                new OllamaChatCompletionMessage("system", systemPrompt, null),
+                new OllamaChatCompletionMessage("user", gitDiff, null)
+            )
+        ).build();
+        CompletionClientProvider.getOllamaClient().getChatCompletionAsync(request, eventListener);
+        break;
       default:
         LOG.debug("Unknown service: {}", selectedService);
         break;
@@ -228,9 +251,9 @@ public final class CompletionRequestService {
       case OPENAI -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.OPENAI_API_KEY);
       case AZURE -> CredentialsStore.INSTANCE.isCredentialSet(
           AzureSettings.getCurrentState().isUseAzureApiKeyAuthentication()
-            ? CredentialKey.AZURE_OPENAI_API_KEY
-            : CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN);
-      case CUSTOM_OPENAI, ANTHROPIC, LLAMA_CPP -> true;
+              ? CredentialKey.AZURE_OPENAI_API_KEY
+              : CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN);
+      case CUSTOM_OPENAI, ANTHROPIC, LLAMA_CPP, OLLAMA -> true;
       case YOU -> false;
     };
   }
diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java
index 8e5757fd..fcded8e6 100644
--- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java
+++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java
@@ -9,6 +9,7 @@ import ee.carlrobert.codegpt.settings.service.ServiceType;
 import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
 import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
@@ -195,9 +196,13 @@ public final class ConversationService {
       case LLAMA_CPP -> {
         var llamaSettings = LlamaSettings.getCurrentState();
         yield llamaSettings.isUseCustomModel()
-                ? llamaSettings.getCustomLlamaModelPath()
-                : llamaSettings.getHuggingFaceModel().getCode();
+            ? llamaSettings.getCustomLlamaModelPath()
+            : llamaSettings.getHuggingFaceModel().getCode();
       }
+      case OLLAMA -> ApplicationManager.getApplication()
+          .getService(OllamaSettings.class)
+          .getState()
+          .getModel();
     };
   }
 }
diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java
index 56d9ba3c..423fcc06 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java
@@ -11,6 +11,7 @@ import ee.carlrobert.codegpt.settings.service.ServiceType;
 import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
 import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import org.jetbrains.annotations.NotNull;
 
@@ -69,6 +70,9 @@ public class GeneralSettings implements PersistentStateComponent();
     serviceComboBoxModel.addAll(Arrays.stream(ServiceType.values()).toList());
     serviceComboBox = new ComboBox<>(serviceComboBoxModel);
@@ -106,6 +112,10 @@ public class GeneralSettingsComponent {
     return youSettingsForm;
   }
 
+  public OllamaSettingsForm getOllamaSettingsForm() {
+    return ollamaSettingsForm;
+  }
+
   public ServiceType getSelectedService() {
     return serviceComboBox.getItem();
   }
@@ -137,6 +147,7 @@ public class GeneralSettingsComponent {
     azureSettingsForm.resetForm();
     youSettingsForm.resetForm();
     llamaSettingsForm.resetForm();
+    ollamaSettingsForm.resetForm();
   }
 
   static class DynamicCardLayout extends CardLayout {
diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java
index 3d12089a..17a5fe8e 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java
@@ -20,6 +20,8 @@ import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm;
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
 import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsForm;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm;
 import ee.carlrobert.codegpt.settings.service.you.YouSettings;
@@ -68,7 +70,8 @@ public class GeneralSettingsConfigurable implements Configurable {
         || AnthropicSettings.getInstance().isModified(component.getAnthropicSettingsForm())
         || AzureSettings.getInstance().isModified(component.getAzureSettingsForm())
         || YouSettings.getInstance().isModified(component.getYouSettingsForm())
-        || LlamaSettings.getInstance().isModified(component.getLlamaSettingsForm());
+        || LlamaSettings.getInstance().isModified(component.getLlamaSettingsForm())
+        || component.getOllamaSettingsForm().isModified();
   }
 
   @Override
@@ -84,6 +87,7 @@ public class GeneralSettingsConfigurable implements Configurable {
     applyAzureSettings(component.getAzureSettingsForm());
     applyYouSettings(component.getYouSettingsForm());
     applyLlamaSettings(component.getLlamaSettingsForm());
+    component.getOllamaSettingsForm().applyChanges();
 
     var serviceChanged = component.getSelectedService() != settings.getSelectedService();
     var modelChanged = !OpenAISettings.getCurrentState().getModel()
@@ -133,6 +137,10 @@ public class GeneralSettingsConfigurable implements Configurable {
         form.getActiveDirectoryToken());
   }
 
+  private void applyOllamaSettings(OllamaSettingsForm form) {
+    form.applyChanges();
+  }
+
   @Override
   public void reset() {
     var settings = GeneralSettings.getCurrentState();
diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java
index 8ee130cd..d8c6425b 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java
@@ -8,7 +8,8 @@ public enum ServiceType {
   ANTHROPIC("ANTHROPIC", "service.anthropic.title", "anthropic.chat.completion"),
   AZURE("AZURE", "service.azure.title", "azure.chat.completion"),
   YOU("YOU", "service.you.title", "you.chat.completion"),
-  LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion");
+  LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion"),
+  OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion");
 
   private final String code;
   private final String label;
diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaSettingsForm.java
index d5260b24..da415871 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaSettingsForm.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaSettingsForm.java
@@ -22,7 +22,8 @@ public class LlamaSettingsForm extends JPanel {
     llamaRequestPreferencesForm = new LlamaRequestPreferencesForm(settings);
     codeCompletionConfigurationForm = new CodeCompletionConfigurationForm(
         settings.isCodeCompletionsEnabled(),
-        settings.getCodeCompletionMaxTokens());
+        settings.getCodeCompletionMaxTokens(),
+        null);
     init();
   }
 
diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java
index f0bdc78b..d0f4b4dc 100644
--- a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java
+++ b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java
@@ -36,7 +36,8 @@ public class OpenAISettingsForm {
         OpenAIChatCompletionModel.findByCode(settings.getModel()));
     codeCompletionConfigurationForm = new CodeCompletionConfigurationForm(
         settings.isCodeCompletionsEnabled(),
-        settings.getCodeCompletionMaxTokens());
+        settings.getCodeCompletionMaxTokens(),
+        null);
   }
 
   public JPanel getForm() {
diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java
index 71aef244..38457966 100644
--- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java
+++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java
@@ -1,6 +1,7 @@
 package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
 
 import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI;
+import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA;
 import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
 import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU;
 import static java.lang.String.format;
@@ -23,6 +24,8 @@ import ee.carlrobert.codegpt.settings.GeneralSettingsState;
 import ee.carlrobert.codegpt.settings.service.ServiceType;
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings;
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsState;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsState;
 import ee.carlrobert.codegpt.settings.service.you.YouSettings;
@@ -41,12 +44,16 @@ public class ModelComboBoxAction extends ComboBoxAction {
   private final GeneralSettingsState settings;
   private final OpenAISettingsState openAISettings;
   private final YouSettingsState youSettings;
+  private final OllamaSettingsState ollamaSettings;
 
   public ModelComboBoxAction(Runnable onModelChange, ServiceType selectedService) {
     this.onModelChange = onModelChange;
     settings = GeneralSettings.getCurrentState();
     openAISettings = OpenAISettings.getCurrentState();
     youSettings = YouSettings.getCurrentState();
+    ollamaSettings = ApplicationManager.getApplication()
+        .getService(OllamaSettings.class)
+        .getState();
     updateTemplatePresentation(selectedService);
 
     subscribeToYouSignedOutTopic(ApplicationManager.getApplication().getMessageBus().connect());
@@ -103,6 +110,9 @@ public class ModelComboBoxAction extends ComboBoxAction {
         getLlamaCppPresentationText(),
         Icons.Llama,
         presentation));
+    actionGroup.addSeparator("Ollama");
+    ollamaSettings.getAvailableModels().forEach(model ->
+        actionGroup.add(createOllamaModelAction(model, presentation)));
 
     if (YouUserManager.getInstance().isSubscribed()) {
       actionGroup.addSeparator("You.com");
@@ -179,7 +189,12 @@ public class ModelComboBoxAction extends ComboBoxAction {
         templatePresentation.setText(getLlamaCppPresentationText());
         templatePresentation.setIcon(Icons.Llama);
         break;
+      case OLLAMA:
+        templatePresentation.setIcon(Icons.Ollama);
+        templatePresentation.setText(ollamaSettings.getModel());
+        break;
       default:
+        break;
     }
   }
 
@@ -235,6 +250,34 @@ public class ModelComboBoxAction extends ComboBoxAction {
     onModelChange.run();
   }
 
+  private AnAction createOllamaModelAction(
+      String model,
+      Presentation comboBoxPresentation
+  ) {
+    return new DumbAwareAction(model, "", Icons.Ollama) {
+      @Override
+      public void update(@NotNull AnActionEvent event) {
+        var presentation = event.getPresentation();
+        presentation.setEnabled(!presentation.getText().equals(comboBoxPresentation.getText()));
+      }
+
+      @Override
+      public void actionPerformed(@NotNull AnActionEvent e) {
+        ollamaSettings.setModel(model);
+        handleModelChange(
+            OLLAMA,
+            model,
+            Icons.Ollama,
+            comboBoxPresentation);
+      }
+
+      @Override
+      public @NotNull ActionUpdateThread getActionUpdateThread() {
+        return ActionUpdateThread.BGT;
+      }
+    };
+  }
+
   private AnAction createOpenAIModelAction(
       OpenAIChatCompletionModel model,
       Presentation comboBoxPresentation) {
diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/UserPromptTextArea.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/UserPromptTextArea.java
index e8cedd82..e7bc6862 100644
--- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/UserPromptTextArea.java
+++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/UserPromptTextArea.java
@@ -1,6 +1,7 @@
 package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
 
 import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC;
+import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA;
 import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
 import static ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel.GPT_4_VISION_PREVIEW;
 
@@ -192,6 +193,7 @@ public class UserPromptTextArea extends JPanel {
         }));
     var selectedService = GeneralSettings.getCurrentState().getSelectedService();
     if (selectedService == ANTHROPIC
+        || selectedService == OLLAMA
         || (selectedService == OPENAI
         && GPT_4_VISION_PREVIEW.getCode().equals(OpenAISettings.getCurrentState().getModel()))) {
       iconsPanel.add(new IconActionButton(new AttachImageAction()));
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt
index be2d0b66..9faf9666 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt
@@ -9,34 +9,44 @@ import ee.carlrobert.codegpt.settings.service.ServiceType
 import ee.carlrobert.codegpt.settings.service.ServiceType.*
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
 
 abstract class CodeCompletionFeatureToggleActions(
     private val enableFeatureAction: Boolean
 ) : DumbAwareAction() {
 
-
     override fun actionPerformed(e: AnActionEvent) {
         when (GeneralSettings.getCurrentState().selectedService) {
-            OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction
-            LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction
-            CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled =
-                enableFeatureAction
+            OPENAI ->
+                OpenAISettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction
+
+            LLAMA_CPP ->
+                LlamaSettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction
+
+            OLLAMA -> service().state.codeCompletionsEnabled = enableFeatureAction
+            CUSTOM_OPENAI -> service().state
+                .codeCompletionSettings
+                .codeCompletionsEnabled = enableFeatureAction
+
             ANTHROPIC,
             AZURE,
             YOU,
-            null -> { /* no-op for these services */ }
+            null -> { /* no-op for these services */
+            }
         }
     }
 
     override fun update(e: AnActionEvent) {
         val selectedService = GeneralSettings.getCurrentState().selectedService
         val codeCompletionEnabled = isCodeCompletionsEnabled(selectedService)
-        e.presentation.isEnabled = codeCompletionEnabled != enableFeatureAction
-        e.presentation.isVisible = when (selectedService) {
+        e.presentation.isVisible = codeCompletionEnabled != enableFeatureAction
+        e.presentation.isEnabled = when (selectedService) {
             OPENAI,
             CUSTOM_OPENAI,
-            LLAMA_CPP -> true
+            LLAMA_CPP,
+            OLLAMA -> true
+
             ANTHROPIC,
             AZURE,
             YOU,
@@ -53,6 +63,7 @@ abstract class CodeCompletionFeatureToggleActions(
             OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled
             CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled
             LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible()
+            OLLAMA -> service().state.codeCompletionsEnabled
             ANTHROPIC,
             AZURE,
             YOU -> false
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt
index d335144a..aadc7d36 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt
@@ -10,9 +10,12 @@ import ee.carlrobert.codegpt.settings.configuration.Placeholder
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
 import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest
 import ee.carlrobert.llm.client.llama.completion.LlamaInfillRequest
+import ee.carlrobert.llm.client.ollama.completion.request.OllamaCompletionRequest
+import ee.carlrobert.llm.client.ollama.completion.request.OllamaParameters
 import ee.carlrobert.llm.client.openai.completion.request.OpenAITextCompletionRequest
 import okhttp3.MediaType.Companion.toMediaType
 import okhttp3.Request
@@ -82,11 +85,29 @@ object CodeCompletionRequestFactory {
     fun buildLlamaRequest(details: InfillRequestDetails): LlamaInfillRequest {
         val settings = LlamaSettings.getCurrentState()
         val promptTemplate = getLlamaInfillPromptTemplate(settings)
-        return LlamaInfillRequest(LlamaCompletionRequest.Builder(null)
-            .setN_predict(settings.codeCompletionMaxTokens)
-            .setStream(true)
-            .setTemperature(0.4)
-            .setStop(promptTemplate.stopTokens), details.prefix, details.suffix)
+        return LlamaInfillRequest(
+            LlamaCompletionRequest.Builder(null)
+                .setN_predict(settings.codeCompletionMaxTokens)
+                .setStream(true)
+                .setTemperature(0.4)
+                .setStop(promptTemplate.stopTokens), details.prefix, details.suffix
+        )
+    }
+
+    fun buildOllamaRequest(details: InfillRequestDetails): OllamaCompletionRequest {
+        val settings = service().state
+        return OllamaCompletionRequest.Builder(
+            settings.model,
+            settings.fimTemplate.buildPrompt(details.prefix, details.suffix)
+        )
+            .setOptions(
+                OllamaParameters.Builder()
+                    .stop(settings.fimTemplate.stopTokens)
+                    .numPredict(settings.codeCompletionMaxTokens)
+                    .build()
+            )
+            .setRaw(true)
+            .build()
     }
 
     private fun getLlamaInfillPromptTemplate(settings: LlamaSettingsState): InfillPromptTemplate {
@@ -112,4 +133,4 @@ object CodeCompletionRequestFactory {
             else -> value
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt
index 32811531..27f3d43e 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt
@@ -12,6 +12,7 @@ import ee.carlrobert.codegpt.settings.GeneralSettings
 import ee.carlrobert.codegpt.settings.service.ServiceType
 import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings
 import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
+import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
 import ee.carlrobert.codegpt.ui.OverlayUtil.showNotification
 import ee.carlrobert.llm.client.openai.completion.ErrorDetails
@@ -70,6 +71,7 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider {
             ServiceType.OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled
             ServiceType.CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled
             ServiceType.LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled
+            ServiceType.OLLAMA -> service().state.codeCompletionsEnabled
             ServiceType.ANTHROPIC,
             ServiceType.AZURE,
             ServiceType.YOU,
diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/CodeCompletionConfigurationForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/CodeCompletionConfigurationForm.kt
index 1fddb3fa..ddf60b53 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/CodeCompletionConfigurationForm.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/CodeCompletionConfigurationForm.kt
@@ -1,13 +1,26 @@
 package ee.carlrobert.codegpt.settings.service
 
+import com.intellij.icons.AllIcons.General
+import com.intellij.ide.HelpTooltip
+import com.intellij.openapi.ui.ComboBox
 import com.intellij.openapi.ui.panel.ComponentPanelBuilder
+import com.intellij.ui.EnumComboBoxModel
 import com.intellij.ui.components.JBCheckBox
+import com.intellij.ui.components.JBLabel
 import com.intellij.ui.components.fields.IntegerField
 import com.intellij.util.ui.FormBuilder
 import ee.carlrobert.codegpt.CodeGPTBundle
+import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate
+import org.apache.commons.text.StringEscapeUtils
+import java.awt.FlowLayout
+import javax.swing.Box
 import javax.swing.JPanel
 
-class CodeCompletionConfigurationForm(codeCompletionsEnabled: Boolean, maxTokens: Int) {
+class CodeCompletionConfigurationForm(
+    codeCompletionsEnabled: Boolean,
+    maxTokens: Int,
+    fimTemplate: InfillPromptTemplate?
+) {
 
     private val codeCompletionsEnabledCheckBox = JBCheckBox(
         CodeGPTBundle.get("codeCompletionsForm.enableFeatureText"),
@@ -18,15 +31,33 @@ class CodeCompletionConfigurationForm(codeCompletionsEnabled: Boolean, maxTokens
             columns = 12
             value = maxTokens
         }
+    private val promptTemplateComboBox =
+        ComboBox(EnumComboBoxModel(InfillPromptTemplate::class.java)).apply {
+            item = fimTemplate
+            addItemListener {
+                updatePromptTemplateHelpTooltip(it.item as InfillPromptTemplate)
+            }
+        }
+    private val promptTemplateHelpText = JBLabel(General.ContextHelp)
 
     fun getForm(): JPanel {
-        return FormBuilder.createFormBuilder()
+        val formBuilder = FormBuilder.createFormBuilder()
             .addComponent(codeCompletionsEnabledCheckBox)
-            .addVerticalGap(4)
-            .addLabeledComponent(
-                CodeGPTBundle.get("codeCompletionsForm.maxTokensLabel"),
-                codeCompletionMaxTokensField
-            )
+            .addVerticalGap(4);
+        if (fimTemplate != null) {
+            formBuilder.addVerticalGap(4)
+                .addLabeledComponent(
+                    "FIM template:",
+                    JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)).apply {
+                        add(promptTemplateComboBox)
+                        add(Box.createHorizontalStrut(4))
+                        add(promptTemplateHelpText)
+                    })
+        }
+        return formBuilder.addLabeledComponent(
+            CodeGPTBundle.get("codeCompletionsForm.maxTokensLabel"),
+            codeCompletionMaxTokensField
+        )
             .addComponentToRightColumn(
                 ComponentPanelBuilder.createCommentComponent(
                     CodeGPTBundle.get("codeCompletionsForm.maxTokensComment"), true, 48, true
@@ -46,4 +77,20 @@ class CodeCompletionConfigurationForm(codeCompletionsEnabled: Boolean, maxTokens
         set(maxTokens) {
             codeCompletionMaxTokensField.value = maxTokens
         }
+
+    var fimTemplate: InfillPromptTemplate?
+        get() = promptTemplateComboBox.item
+        set(template) {
+            promptTemplateComboBox.item = template
+        }
+
+    private fun updatePromptTemplateHelpTooltip(template: InfillPromptTemplate) {
+        promptTemplateHelpText.setToolTipText(null)
+
+        val description = StringEscapeUtils.escapeHtml4(template.buildPrompt("PREFIX", "SUFFIX"))
+        HelpTooltip()
+            .setTitle(template.toString())
+            .setDescription("

$description

") + .installOn(promptTemplateHelpText) + } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettings.kt new file mode 100644 index 00000000..9743c808 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettings.kt @@ -0,0 +1,20 @@ +package ee.carlrobert.codegpt.settings.service.ollama + +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.SimplePersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate + +@State(name = "CodeGPT_OllamaSettings_210", storages = [Storage("CodeGPT_OllamaSettings_210.xml")]) +class OllamaSettings : + SimplePersistentStateComponent(OllamaSettingsState()) + +class OllamaSettingsState : BaseState() { + var host by string("http://localhost:11434") + var model by string() + var codeCompletionsEnabled by property(true) + var codeCompletionMaxTokens by property(128) + var fimTemplate by enum(InfillPromptTemplate.CODE_LLAMA) + var availableModels by list() +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt new file mode 100644 index 00000000..e9ac10c7 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt @@ -0,0 +1,163 @@ +package ee.carlrobert.codegpt.settings.service.ollama + +import com.intellij.notification.NotificationType +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.observable.util.whenTextChangedFromUi +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.TitledSeparator +import com.intellij.ui.components.JBTextField +import com.intellij.util.ui.FormBuilder +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.settings.service.CodeCompletionConfigurationForm +import ee.carlrobert.codegpt.ui.OverlayUtil +import ee.carlrobert.codegpt.ui.UIUtil +import ee.carlrobert.llm.client.ollama.OllamaClient +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import java.awt.BorderLayout +import java.net.ConnectException +import javax.swing.ComboBoxModel +import javax.swing.DefaultComboBoxModel +import javax.swing.JButton +import javax.swing.JPanel + +class OllamaSettingsForm { + + private val refreshModelsButton = + JButton(CodeGPTBundle.get("settingsConfigurable.service.ollama.models.refresh")) + private val hostField: JBTextField + private val modelComboBox: ComboBox + private val codeCompletionConfigurationForm: CodeCompletionConfigurationForm + + companion object { + private val logger = thisLogger() + } + + init { + val settings = service().state + codeCompletionConfigurationForm = CodeCompletionConfigurationForm( + settings.codeCompletionsEnabled, + settings.codeCompletionMaxTokens, + settings.fimTemplate + ) + val emptyModelsComboBoxModel = + DefaultComboBoxModel(arrayOf("Hit refresh to see models for this host")) + modelComboBox = ComboBox(emptyModelsComboBoxModel).apply { + isEnabled = false + } + hostField = JBTextField().apply { + text = settings.host + whenTextChangedFromUi { + modelComboBox.model = emptyModelsComboBoxModel + modelComboBox.isEnabled = false + } + } + refreshModelsButton.addActionListener { refreshModels() } + refreshModels() + } + + fun getForm(): JPanel = FormBuilder.createFormBuilder() + .addComponent(TitledSeparator(CodeGPTBundle.get("shared.configuration"))) + .addComponent( + FormBuilder.createFormBuilder() + .setFormLeftIndent(16) + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.shared.baseHost.label"), + hostField + ) + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.shared.model.label"), + JPanel(BorderLayout(8, 0)).apply { + add(modelComboBox, BorderLayout.CENTER) + add(refreshModelsButton, BorderLayout.EAST) + } + ) + .panel + ) + .addComponent(TitledSeparator(CodeGPTBundle.get("shared.codeCompletions"))) + .addComponent(UIUtil.withEmptyLeftBorder(codeCompletionConfigurationForm.getForm())) + .panel + + fun getModel(): String { + return if (modelComboBox.isEnabled) { + modelComboBox.item + } else { + "" + } + } + + fun resetForm() { + service().state.run { + hostField.text = host + modelComboBox.item = model + codeCompletionConfigurationForm.isCodeCompletionsEnabled = codeCompletionsEnabled + codeCompletionConfigurationForm.maxTokens = codeCompletionMaxTokens + codeCompletionConfigurationForm.fimTemplate = fimTemplate + } + } + + fun applyChanges() { + service().state.run { + host = hostField.text + model = modelComboBox.item + codeCompletionsEnabled = codeCompletionConfigurationForm.isCodeCompletionsEnabled + codeCompletionMaxTokens = codeCompletionConfigurationForm.maxTokens + fimTemplate = codeCompletionConfigurationForm.fimTemplate!! + } + } + + fun isModified() = service().state.run { + hostField.text != host + || modelComboBox.item != model + || codeCompletionConfigurationForm.isCodeCompletionsEnabled != codeCompletionsEnabled + || codeCompletionConfigurationForm.maxTokens != codeCompletionMaxTokens + || codeCompletionConfigurationForm.fimTemplate != fimTemplate + } + + private fun disableModelComboBoxWithPlaceholder(placeholderModel: ComboBoxModel) { + invokeLater { + modelComboBox.apply { + model = placeholderModel + isEnabled = false + } + } + } + + private fun refreshModels() { + disableModelComboBoxWithPlaceholder(DefaultComboBoxModel(arrayOf("Loading"))) + try { + val models = runBlocking(Dispatchers.IO) { + OllamaClient.Builder() + .setHost(hostField.text) + .build() + .modelTags + .models + .map { it.name } + } + service().state.availableModels = models.toMutableList() + invokeLater { + modelComboBox.apply { + if (models.isNotEmpty()) { + model = DefaultComboBoxModel(models.toTypedArray()) + isEnabled = true + } else { + model = DefaultComboBoxModel(arrayOf("No models")) + } + } + } + } catch (ex: RuntimeException) { + logger.error(ex) + if (ex.cause is ConnectException) { + OverlayUtil.showNotification( + "Unable to connect to Ollama server", + NotificationType.ERROR + ) + } else { + OverlayUtil.showNotification(ex.message, NotificationType.ERROR) + } + disableModelComboBoxWithPlaceholder(DefaultComboBoxModel(arrayOf("Unable to load models"))) + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7c64684a..2c883574 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -36,6 +36,7 @@ + diff --git a/src/main/resources/icons/ollama.svg b/src/main/resources/icons/ollama.svg new file mode 100644 index 00000000..87079a82 --- /dev/null +++ b/src/main/resources/icons/ollama.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/ollama_dark.svg b/src/main/resources/icons/ollama_dark.svg new file mode 100644 index 00000000..b090a840 --- /dev/null +++ b/src/main/resources/icons/ollama_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 40764742..b4ae54fd 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -116,6 +116,7 @@ settingsConfigurable.service.custom.openai.url.label=URL: settingsConfigurable.service.custom.openai.linkToDocs=Link to API docs settingsConfigurable.service.custom.openai.connectionSuccess=Connection successful. settingsConfigurable.service.custom.openai.connectionFailed=Connection failed. +settingsConfigurable.service.ollama.models.refresh=Refresh Models configurationConfigurable.section.commitMessage.title=Commit Message Template configurationConfigurable.section.commitMessage.systemPromptField.label=Prompt template: configurationConfigurable.section.inlineCompletion.title=Inline Completion @@ -173,6 +174,7 @@ service.anthropic.title=Anthropic Service service.azure.title=Azure Service service.you.title=You.com Service (Free, Cloud) service.llama.title=LLaMA C/C++ Port (Free, Local) +service.ollama.title=Ollama (Free, Local) validation.error.fieldRequired=This field is required. validation.error.invalidEmail=The email you entered is invalid. validation.error.mustBeNumber=Value must be number. From ee16bfee10344d29acfd9f587a27359dac5555aa Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Wed, 8 May 2024 15:05:51 +0200 Subject: [PATCH 20/30] feat: Support CodeQwen1.5-Chat model (#527) * feat: Support CodeQwen1.5-Chat model * Declare model directories explicitly --- .../codegpt/completions/HuggingFaceModel.java | 190 +++++++++++------- .../codegpt/completions/llama/LlamaModel.java | 17 ++ .../completions/llama/PromptTemplate.java | 24 +++ .../codecompletions/InfillPromptTemplate.kt | 5 + .../codegpt/completions/PromptTemplateTest.kt | 73 +++++++ 5 files changed, 233 insertions(+), 76 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index 3e8841e3..f11c8419 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -4,89 +4,139 @@ import static java.lang.String.format; import java.net.MalformedURLException; import java.net.URL; -import java.util.regex.Pattern; public enum HuggingFaceModel { - CODE_LLAMA_7B_Q3(7, 3, "codellama-7b-instruct.Q3_K_M.gguf"), - CODE_LLAMA_7B_Q4(7, 4, "codellama-7b-instruct.Q4_K_M.gguf"), - CODE_LLAMA_7B_Q5(7, 5, "codellama-7b-instruct.Q5_K_M.gguf"), - CODE_LLAMA_13B_Q3(13, 3, "codellama-13b-instruct.Q3_K_M.gguf"), - CODE_LLAMA_13B_Q4(13, 4, "codellama-13b-instruct.Q4_K_M.gguf"), - CODE_LLAMA_13B_Q5(13, 5, "codellama-13b-instruct.Q5_K_M.gguf"), - CODE_LLAMA_34B_Q3(34, 3, "codellama-34b-instruct.Q3_K_M.gguf"), - CODE_LLAMA_34B_Q4(34, 4, "codellama-34b-instruct.Q4_K_M.gguf"), - CODE_LLAMA_34B_Q5(34, 5, "codellama-34b-instruct.Q5_K_M.gguf"), + CODE_LLAMA_7B_Q3(7, 3, "CodeLlama-7B-Instruct-GGUF", "codellama-7b-instruct.Q3_K_M.gguf"), + CODE_LLAMA_7B_Q4(7, 4, "CodeLlama-7B-Instruct-GGUF", "codellama-7b-instruct.Q4_K_M.gguf"), + CODE_LLAMA_7B_Q5(7, 5, "CodeLlama-7B-Instruct-GGUF", "codellama-7b-instruct.Q5_K_M.gguf"), + CODE_LLAMA_13B_Q3(13, 3, "CodeLlama-13B-Instruct-GGUF", "codellama-13b-instruct.Q3_K_M.gguf"), + CODE_LLAMA_13B_Q4(13, 4, "CodeLlama-13B-Instruct-GGUF", "codellama-13b-instruct.Q4_K_M.gguf"), + CODE_LLAMA_13B_Q5(13, 5, "CodeLlama-13B-Instruct-GGUF", "codellama-13b-instruct.Q5_K_M.gguf"), + CODE_LLAMA_34B_Q3(34, 3, "CodeLlama-34B-Instruct-GGUF", "codellama-34b-instruct.Q3_K_M.gguf"), + CODE_LLAMA_34B_Q4(34, 4, "CodeLlama-34B-Instruct-GGUF", "codellama-34b-instruct.Q4_K_M.gguf"), + CODE_LLAMA_34B_Q5(34, 5, "CodeLlama-34B-Instruct-GGUF", "codellama-34b-instruct.Q5_K_M.gguf"), - CODE_BOOGA_34B_Q3(34, 3, "codebooga-34b-v0.1.Q3_K_M.gguf"), - CODE_BOOGA_34B_Q4(34, 4, "codebooga-34b-v0.1.Q4_K_M.gguf"), - CODE_BOOGA_34B_Q5(34, 5, "codebooga-34b-v0.1.Q5_K_M.gguf"), + CODE_BOOGA_34B_Q3(34, 3, "CodeBooga-34B-v0.1-GGUF", "codebooga-34b-v0.1.Q3_K_M.gguf"), + CODE_BOOGA_34B_Q4(34, 4, "CodeBooga-34B-v0.1-GGUF", "codebooga-34b-v0.1.Q4_K_M.gguf"), + CODE_BOOGA_34B_Q5(34, 5, "CodeBooga-34B-v0.1-GGUF", "codebooga-34b-v0.1.Q5_K_M.gguf"), - DEEPSEEK_CODER_1_3B_Q3(1, 3, "deepseek-coder-1.3b-instruct.Q3_K_M.gguf", 0.705), - DEEPSEEK_CODER_1_3B_Q4(1, 4, "deepseek-coder-1.3b-instruct.Q4_K_M.gguf", 0.874), - DEEPSEEK_CODER_1_3B_Q5(1, 5, "deepseek-coder-1.3b-instruct.Q5_K_M.gguf", 1.0), - DEEPSEEK_CODER_6_7B_Q3(7, 3, "deepseek-coder-6.7b-instruct.Q3_K_M.gguf"), - DEEPSEEK_CODER_6_7B_Q4(7, 4, "deepseek-coder-6.7b-instruct.Q4_K_M.gguf"), - DEEPSEEK_CODER_6_7B_Q5(7, 5, "deepseek-coder-6.7b-instruct.Q5_K_M.gguf"), - DEEPSEEK_CODER_33B_Q3(33, 3, "deepseek-coder-33b-instruct.Q3_K_M.gguf", 16.1), - DEEPSEEK_CODER_33B_Q4(33, 4, "deepseek-coder-33b-instruct.Q4_K_M.gguf", 19.9), - DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct.Q5_K_M.gguf", 23.5), + DEEPSEEK_CODER_1_3B_Q3(1, 3, "deepseek-coder-1.3b-instruct-GGUF", + "deepseek-coder-1.3b-instruct.Q3_K_M.gguf", 0.705), + DEEPSEEK_CODER_1_3B_Q4(1, 4, "deepseek-coder-1.3b-instruct-GGUF", + "deepseek-coder-1.3b-instruct.Q4_K_M.gguf", 0.874), + DEEPSEEK_CODER_1_3B_Q5(1, 5, "deepseek-coder-1.3b-instruct-GGUF", + "deepseek-coder-1.3b-instruct.Q5_K_M.gguf", 1.0), + DEEPSEEK_CODER_6_7B_Q3(7, 3, "deepseek-coder-6.7b-instruct-GGUF", + "deepseek-coder-6.7b-instruct.Q3_K_M.gguf"), + DEEPSEEK_CODER_6_7B_Q4(7, 4, "deepseek-coder-6.7b-instruct-GGUF", + "deepseek-coder-6.7b-instruct.Q4_K_M.gguf"), + DEEPSEEK_CODER_6_7B_Q5(7, 5, "deepseek-coder-6.7b-instruct-GGUF", + "deepseek-coder-6.7b-instruct.Q5_K_M.gguf"), + DEEPSEEK_CODER_33B_Q3(33, 3, "deepseek-coder-33b-instruct-GGUF", + "deepseek-coder-33b-instruct.Q3_K_M.gguf", 16.1), + DEEPSEEK_CODER_33B_Q4(33, 4, "deepseek-coder-33b-instruct-GGUF", + "deepseek-coder-33b-instruct.Q4_K_M.gguf", 19.9), + DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct-GGUF", + "deepseek-coder-33b-instruct.Q5_K_M.gguf", 23.5), - PHIND_CODE_LLAMA_34B_Q3(34, 3, "phind-codellama-34b-v2.Q3_K_M.gguf"), - PHIND_CODE_LLAMA_34B_Q4(34, 4, "phind-codellama-34b-v2.Q4_K_M.gguf"), - PHIND_CODE_LLAMA_34B_Q5(34, 5, "phind-codellama-34b-v2.Q5_K_M.gguf"), + PHIND_CODE_LLAMA_34B_Q3(34, 3, "Phind-CodeLlama-34B-v2-GGUF", + "phind-codellama-34b-v2.Q3_K_M.gguf"), + PHIND_CODE_LLAMA_34B_Q4(34, 4, "Phind-CodeLlama-34B-v2-GGUF", + "phind-codellama-34b-v2.Q4_K_M.gguf"), + PHIND_CODE_LLAMA_34B_Q5(34, 5, "Phind-CodeLlama-34B-v2-GGUF", + "phind-codellama-34b-v2.Q5_K_M.gguf"), - WIZARD_CODER_PYTHON_7B_Q3(7, 3, "wizardcoder-python-7b-v1.0.Q3_K_M.gguf"), - WIZARD_CODER_PYTHON_7B_Q4(7, 4, "wizardcoder-python-7b-v1.0.Q4_K_M.gguf"), - WIZARD_CODER_PYTHON_7B_Q5(7, 5, "wizardcoder-python-7b-v1.0.Q5_K_M.gguf"), - WIZARD_CODER_PYTHON_13B_Q3(13, 3, "wizardcoder-python-13b-v1.0.Q3_K_M.gguf"), - WIZARD_CODER_PYTHON_13B_Q4(13, 4, "wizardcoder-python-13b-v1.0.Q4_K_M.gguf"), - WIZARD_CODER_PYTHON_13B_Q5(13, 5, "wizardcoder-python-13b-v1.0.Q5_K_M.gguf"), - WIZARD_CODER_PYTHON_34B_Q3(34, 3, "wizardcoder-python-34b-v1.0.Q3_K_M.gguf"), - WIZARD_CODER_PYTHON_34B_Q4(34, 4, "wizardcoder-python-34b-v1.0.Q4_K_M.gguf"), - WIZARD_CODER_PYTHON_34B_Q5(34, 5, "wizardcoder-python-34b-v1.0.Q5_K_M.gguf"), + WIZARD_CODER_PYTHON_7B_Q3(7, 3, "WizardCoder-Python-7B-V1.0-GGUF", + "wizardcoder-python-7b-v1.0.Q3_K_M.gguf"), + WIZARD_CODER_PYTHON_7B_Q4(7, 4, "WizardCoder-Python-7B-V1.0-GGUF", + "wizardcoder-python-7b-v1.0.Q4_K_M.gguf"), + WIZARD_CODER_PYTHON_7B_Q5(7, 5, "WizardCoder-Python-7B-V1.0-GGUF", + "wizardcoder-python-7b-v1.0.Q5_K_M.gguf"), + WIZARD_CODER_PYTHON_13B_Q3(13, 3, "WizardCoder-Python-13B-V1.0-GGUF", + "wizardcoder-python-13b-v1.0.Q3_K_M.gguf"), + WIZARD_CODER_PYTHON_13B_Q4(13, 4, "WizardCoder-Python-13B-V1.0-GGUF", + "wizardcoder-python-13b-v1.0.Q4_K_M.gguf"), + WIZARD_CODER_PYTHON_13B_Q5(13, 5, "WizardCoder-Python-13B-V1.0-GGUF", + "wizardcoder-python-13b-v1.0.Q5_K_M.gguf"), + WIZARD_CODER_PYTHON_34B_Q3(34, 3, "WizardCoder-Python-34B-V1.0-GGUF", + "wizardcoder-python-34b-v1.0.Q3_K_M.gguf"), + WIZARD_CODER_PYTHON_34B_Q4(34, 4, "WizardCoder-Python-34B-V1.0-GGUF", + "wizardcoder-python-34b-v1.0.Q4_K_M.gguf"), + WIZARD_CODER_PYTHON_34B_Q5(34, 5, "WizardCoder-Python-34B-V1.0-GGUF", + "wizardcoder-python-34b-v1.0.Q5_K_M.gguf"), - LLAMA_3_8B_IQ3_M(8, 3, "Meta-Llama-3-8B-Instruct-IQ3_M.gguf", "lmstudio-community", 3.78), - LLAMA_3_8B_Q4_K_M(8, 4, "Meta-Llama-3-8B-Instruct-Q4_K_M.gguf", "lmstudio-community", 4.92), - LLAMA_3_8B_Q5_K_M(8, 5, "Meta-Llama-3-8B-Instruct-Q5_K_M.gguf", "lmstudio-community", 5.73), - LLAMA_3_8B_Q6_K(8, 6, "Meta-Llama-3-8B-Instruct-Q6_K.gguf", "lmstudio-community", 6.6), - LLAMA_3_8B_Q8_0(8, 8, "Meta-Llama-3-8B-Instruct-Q8_0.gguf", "lmstudio-community", 8.54), - LLAMA_3_70B_IQ1(70, 1, "Meta-Llama-3-70B-Instruct-IQ1_M.gguf", "lmstudio-community", 16.8), - LLAMA_3_70B_IQ2_XS(70, 2, "Meta-Llama-3-70B-Instruct-IQ2_XS.gguf", "lmstudio-community", 21.1), - LLAMA_3_70B_Q4_K_M(70, 4, "Meta-Llama-3-70B-Instruct-Q4_K_M.gguf", "lmstudio-community", 42.5), + LLAMA_3_8B_IQ3_M(8, 3, "Meta-Llama-3-8B-Instruct-GGUF", "Meta-Llama-3-8B-Instruct-IQ3_M.gguf", + "lmstudio-community", 3.78), + LLAMA_3_8B_Q4_K_M(8, 4, "Meta-Llama-3-8B-Instruct-GGUF", "Meta-Llama-3-8B-Instruct-Q4_K_M.gguf", + "lmstudio-community", 4.92), + LLAMA_3_8B_Q5_K_M(8, 5, "Meta-Llama-3-8B-Instruct-GGUF", "Meta-Llama-3-8B-Instruct-Q5_K_M.gguf", + "lmstudio-community", 5.73), + LLAMA_3_8B_Q6_K(8, 6, "Meta-Llama-3-8B-Instruct-GGUF", "Meta-Llama-3-8B-Instruct-Q6_K.gguf", + "lmstudio-community", 6.6), + LLAMA_3_8B_Q8_0(8, 8, "Meta-Llama-3-8B-Instruct-GGUF", "Meta-Llama-3-8B-Instruct-Q8_0.gguf", + "lmstudio-community", 8.54), + LLAMA_3_70B_IQ1(70, 1, "Meta-Llama-3-70B-Instruct-GGUF", "Meta-Llama-3-70B-Instruct-IQ1_M.gguf", + "lmstudio-community", 16.8), + LLAMA_3_70B_IQ2_XS(70, 2, "Meta-Llama-3-70B-Instruct-GGUF", + "Meta-Llama-3-70B-Instruct-IQ2_XS.gguf", "lmstudio-community", 21.1), + LLAMA_3_70B_Q4_K_M(70, 4, "Meta-Llama-3-70B-Instruct-GGUF", + "Meta-Llama-3-70B-Instruct-Q4_K_M.gguf", "lmstudio-community", 42.5), - PHI_3_3_8B_4K_IQ4_NL(4, 4, "Phi-3-mini-4k-instruct-IQ4_NL.gguf", "lmstudio-community", 2.18), - PHI_3_3_8B_4K_Q5_K_M(4, 5, "Phi-3-mini-4k-instruct-Q5_K_M.gguf", "lmstudio-community", 2.64), - PHI_3_3_8B_4K_Q6_K(4, 6, "Phi-3-mini-4k-instruct-Q6_K.gguf", "lmstudio-community", 3.14), - PHI_3_3_8B_4K_Q8_0(4, 8, "Phi-3-mini-4k-instruct-Q8_0.gguf", "lmstudio-community", 4.06), - PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-fp16.gguf", "lmstudio-community", 7.64), + PHI_3_3_8B_4K_IQ4_NL(4, 4, "Phi-3-mini-4k-instruct-GGUF", "Phi-3-mini-4k-instruct-IQ4_NL.gguf", + "lmstudio-community", 2.18), + PHI_3_3_8B_4K_Q5_K_M(4, 5, "Phi-3-mini-4k-instruct-GGUF", "Phi-3-mini-4k-instruct-Q5_K_M.gguf", + "lmstudio-community", 2.64), + PHI_3_3_8B_4K_Q6_K(4, 6, "Phi-3-mini-4k-instruct-GGUF", "Phi-3-mini-4k-instruct-Q6_K.gguf", + "lmstudio-community", 3.14), + PHI_3_3_8B_4K_Q8_0(4, 8, "Phi-3-mini-4k-instruct-GGUF", "Phi-3-mini-4k-instruct-Q8_0.gguf", + "lmstudio-community", 4.06), + PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-GGUF", "Phi-3-mini-4k-instruct-fp16.gguf", + "lmstudio-community", 7.64), - CODE_GEMMA_7B_Q3_K_M(7, 3, "codegemma-7b-it-Q3_K_M.gguf", "lmstudio-community", 4.37), - CODE_GEMMA_7B_Q4_K_M(7, 4, "codegemma-7b-it-Q4_K_M.gguf", "lmstudio-community", 5.33), - CODE_GEMMA_7B_Q5_K_M(7, 5, "codegemma-7b-it-Q5_K_M.gguf", "lmstudio-community", 6.14), - CODE_GEMMA_7B_Q6_K(7, 6, "codegemma-7b-it-Q6_K.gguf", "lmstudio-community", 7.01), - CODE_GEMMA_7B_Q8_0(7, 8, "codegemma-7b-it-Q8_0.gguf", "lmstudio-community", 9.08), + CODE_GEMMA_7B_Q3_K_M(7, 3, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q3_K_M.gguf", + "lmstudio-community", 4.37), + CODE_GEMMA_7B_Q4_K_M(7, 4, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q4_K_M.gguf", + "lmstudio-community", 5.33), + CODE_GEMMA_7B_Q5_K_M(7, 5, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q5_K_M.gguf", + "lmstudio-community", 6.14), + CODE_GEMMA_7B_Q6_K(7, 6, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q6_K.gguf", + "lmstudio-community", 7.01), + CODE_GEMMA_7B_Q8_0(7, 8, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q8_0.gguf", + "lmstudio-community", 9.08), + + CODE_QWEN_1_5_7B_Q3_K_M(7, 3, "Qwen_-_CodeQwen1.5-7B-Chat-gguf", + "CodeQwen1.5-7B-Chat.Q3_K_M.gguf", "RichardErkhov", 3.81), + CODE_QWEN_1_5_7B_Q4_K_M(7, 4, "Qwen_-_CodeQwen1.5-7B-Chat-gguf", + "CodeQwen1.5-7B-Chat.Q4_K_M.gguf", "RichardErkhov", 4.74), + CODE_QWEN_1_5_7B_Q5_K_M(7, 5, "Qwen_-_CodeQwen1.5-7B-Chat-gguf", + "CodeQwen1.5-7B-Chat.Q5_K_M.gguf", "RichardErkhov", 5.43), + CODE_QWEN_1_5_7B_Q6_K(7, 6, "Qwen_-_CodeQwen1.5-7B-Chat-gguf", + "CodeQwen1.5-7B-Chat.Q6_K.gguf", "RichardErkhov", 6.38), ; private final int parameterSize; private final int quantization; - private final String modelName; + private final String directory; + private final String fileName; private final String user; private final Double downloadSize; // in GB - HuggingFaceModel(int parameterSize, int quantization, String modelName) { - this(parameterSize, quantization, modelName, "TheBloke", null); + HuggingFaceModel(int parameterSize, int quantization, String directory, String fileName) { + this(parameterSize, quantization, directory, fileName, "TheBloke", null); } - HuggingFaceModel(int parameterSize, int quantization, String modelName, Double downloadSize) { - this(parameterSize, quantization, modelName, "TheBloke", downloadSize); - } - - HuggingFaceModel(int parameterSize, int quantization, String modelName, String user, + HuggingFaceModel(int parameterSize, int quantization, String directory, String fileName, Double downloadSize) { + this(parameterSize, quantization, directory, fileName, "TheBloke", downloadSize); + } + + HuggingFaceModel(int parameterSize, int quantization, String directory, String fileName, + String user, Double downloadSize) { this.parameterSize = parameterSize; this.quantization = quantization; - this.modelName = modelName; + this.directory = directory; + this.fileName = fileName; this.user = user; this.downloadSize = downloadSize; } @@ -108,16 +158,13 @@ public enum HuggingFaceModel { } public String getFileName() { - if ("TheBloke".equals(user)) { - return modelName.toLowerCase().replace("-gguf", format(".Q%d_K_M.gguf", quantization)); - } - return modelName; + return fileName; } public URL getFileURL() { try { return new URL( - "https://huggingface.co/%s/%s/resolve/main/%s".formatted(user, getDirectory(), getFileName())); + "https://huggingface.co/%s/%s/resolve/main/%s".formatted(user, directory, fileName)); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } @@ -125,22 +172,13 @@ public enum HuggingFaceModel { public URL getHuggingFaceURL() { try { - return new URL("https://huggingface.co/%s/%s".formatted(user, getDirectory())); + return new URL("https://huggingface.co/%s/%s".formatted(user, directory)); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } } - private String getDirectory() { - return modelName.replaceFirst("-[^.-]+\\.gguf$", "-GGUF"); - } - public String getQuantizationLabel() { return format("%d-bit precision", quantization); } - - @Override - public String toString() { - return modelName; - } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java index 5c0af737..69840481 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java @@ -127,6 +127,23 @@ public enum LlamaModel { HuggingFaceModel.CODE_GEMMA_7B_Q5_K_M, HuggingFaceModel.CODE_GEMMA_7B_Q6_K, HuggingFaceModel.CODE_GEMMA_7B_Q8_0)), + CODE_QWEN( + "CodeQwen1.5", """ + A specialized codeLLM built upon the Qwen1.5 language model. \ + CodeQwen1.5-7B has been pretrained with around 3 trillion tokens of code-related data. \ + It supports an extensive repertoire of 92 programming languages, and it exhibits \ + exceptional capacity in long-context understanding and generation with the ability to \ + process information of 64K tokens. In terms of performance, CodeQwen1.5 demonstrates \ + impressive capabilities in basic code generation, long-context modelling, code editing \ + and SQL. We believe this model can significantly enhance developer productivity and \ + streamline software development workflows within diverse technological environments.""", + PromptTemplate.CODE_QWEN, + InfillPromptTemplate.CODE_QWEN, + List.of( + HuggingFaceModel.CODE_QWEN_1_5_7B_Q3_K_M, + HuggingFaceModel.CODE_QWEN_1_5_7B_Q4_K_M, + HuggingFaceModel.CODE_QWEN_1_5_7B_Q5_K_M, + HuggingFaceModel.CODE_QWEN_1_5_7B_Q6_K)), ; private final String label; diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java index 708b2c8e..9e7c161b 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java @@ -162,6 +162,30 @@ public enum PromptTemplate { .toString(); } }, + CODE_QWEN("CodeQwen1.5", List.of("<|endoftext|>")) { + @Override + public String buildPrompt(String systemPrompt, String userPrompt, List history) { + StringBuilder prompt = new StringBuilder(); + + if (systemPrompt != null && !systemPrompt.isBlank()) { + prompt.append("<|im_start|>system\n") + .append(systemPrompt) + .append("<|im_end|>\n"); + } + + for (Message message : history) { + prompt.append("<|im_start|>user\n") + .append(message.getPrompt()) + .append("<|im_end|>\n<|im_start|>assistant\n") + .append(message.getResponse()).append("<|im_end|>\n"); + } + + return prompt.append("<|im_start|>user\n") + .append(userPrompt) + .append("<|im_end|>\n<|im_start|>assistant\n") + .toString(); + } + }, ALPACA("Alpaca/Vicuna") { @Override public String buildPrompt(String systemPrompt, String userPrompt, List history) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt index e4f9d37a..e434418d 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt @@ -20,6 +20,11 @@ enum class InfillPromptTemplate(val label: String, val stopTokens: List? return "<|fim_prefix|>$prefix<|fim_suffix|>$suffix<|fim_middle|>" } }, + CODE_QWEN("CodeQwen1.5", listOf("<|endoftext|>")) { + override fun buildPrompt(prefix: String, suffix: String): String { + return "$prefix$suffix" + } + }, STABILITY("Stability AI", listOf("<|endoftext|>")) { override fun buildPrompt(prefix: String, suffix: String): String { return "$prefix$suffix" diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt index a3f98bc5..c78286b0 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt @@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.completions import ee.carlrobert.codegpt.completions.llama.PromptTemplate.ALPACA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.CHAT_ML import ee.carlrobert.codegpt.completions.llama.PromptTemplate.CODE_GEMMA +import ee.carlrobert.codegpt.completions.llama.PromptTemplate.CODE_QWEN import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA_3 import ee.carlrobert.codegpt.completions.llama.PromptTemplate.PHI_3 @@ -236,6 +237,78 @@ class PromptTemplateTest { """.trimIndent()) } + @Test + fun shouldBuildCodeQwenPromptWithoutHistory() { + val prompt = CODE_QWEN.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, listOf()) + + assertThat(prompt).isEqualTo(""" + <|im_start|>system + TEST_SYSTEM_PROMPT<|im_end|> + <|im_start|>user + TEST_USER_PROMPT<|im_end|> + <|im_start|>assistant + + """.trimIndent()) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", "\t", "\n"]) + fun shouldBuildCodeQwenPromptWithoutHistorySkippingBlankSystemPrompt(systemPrompt: String?) { + val prompt = CODE_QWEN.buildPrompt(systemPrompt, USER_PROMPT, listOf()) + + assertThat(prompt).isEqualTo(""" + <|im_start|>user + TEST_USER_PROMPT<|im_end|> + <|im_start|>assistant + + """.trimIndent()) + } + + @Test + fun shouldBuildCodeQwenPromptWithHistory() { + val prompt = CODE_QWEN.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, HISTORY) + + assertThat(prompt).isEqualTo(""" + <|im_start|>system + TEST_SYSTEM_PROMPT<|im_end|> + <|im_start|>user + TEST_PREV_PROMPT_1<|im_end|> + <|im_start|>assistant + TEST_PREV_RESPONSE_1<|im_end|> + <|im_start|>user + TEST_PREV_PROMPT_2<|im_end|> + <|im_start|>assistant + TEST_PREV_RESPONSE_2<|im_end|> + <|im_start|>user + TEST_USER_PROMPT<|im_end|> + <|im_start|>assistant + + """.trimIndent()) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", "\t", "\n"]) + fun shouldBuildCodeQwenPromptWithHistorySkippingBlankSystemPrompt(systemPrompt: String?) { + val prompt = CODE_QWEN.buildPrompt(systemPrompt, USER_PROMPT, HISTORY) + + assertThat(prompt).isEqualTo(""" + <|im_start|>user + TEST_PREV_PROMPT_1<|im_end|> + <|im_start|>assistant + TEST_PREV_RESPONSE_1<|im_end|> + <|im_start|>user + TEST_PREV_PROMPT_2<|im_end|> + <|im_start|>assistant + TEST_PREV_RESPONSE_2<|im_end|> + <|im_start|>user + TEST_USER_PROMPT<|im_end|> + <|im_start|>assistant + + """.trimIndent()) + } + @Test fun shouldBuildAlpacaPromptWithHistory() { val prompt = ALPACA.buildPrompt(SYSTEM_PROMPT, USER_PROMPT, HISTORY) From dcd0a3fc512a9dc2b7d005c9743d4bd6654562a3 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 8 May 2024 15:06:14 +0200 Subject: [PATCH 21/30] Revert "fix: use /infill for llama.cpp code-completions (#513)" (#533) This reverts commit 8de72b330178fa97b0482e1dbb2829964f8b737a. --- .../completions/CompletionRequestService.java | 2 +- .../CodeCompletionRequestFactory.kt | 17 ++++++++--------- .../CodeCompletionServiceTest.kt | 6 +++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index 6edfe92d..1f78000e 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -126,7 +126,7 @@ public final class CompletionRequestService { CodeCompletionRequestFactory.buildCustomRequest(requestDetails), new OpenAITextCompletionEventSourceListener(eventListener)); case LLAMA_CPP -> CompletionClientProvider.getLlamaClient() - .getInfillAsync( + .getChatCompletionAsync( CodeCompletionRequestFactory.buildLlamaRequest(requestDetails), eventListener); case OLLAMA -> CompletionClientProvider.getOllamaClient().getCompletionAsync( diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt index aadc7d36..a743a76c 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt @@ -13,7 +13,6 @@ import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest -import ee.carlrobert.llm.client.llama.completion.LlamaInfillRequest import ee.carlrobert.llm.client.ollama.completion.request.OllamaCompletionRequest import ee.carlrobert.llm.client.ollama.completion.request.OllamaParameters import ee.carlrobert.llm.client.openai.completion.request.OpenAITextCompletionRequest @@ -82,16 +81,16 @@ object CodeCompletionRequestFactory { } @JvmStatic - fun buildLlamaRequest(details: InfillRequestDetails): LlamaInfillRequest { + fun buildLlamaRequest(details: InfillRequestDetails): LlamaCompletionRequest { val settings = LlamaSettings.getCurrentState() val promptTemplate = getLlamaInfillPromptTemplate(settings) - return LlamaInfillRequest( - LlamaCompletionRequest.Builder(null) - .setN_predict(settings.codeCompletionMaxTokens) - .setStream(true) - .setTemperature(0.4) - .setStop(promptTemplate.stopTokens), details.prefix, details.suffix - ) + val prompt = promptTemplate.buildPrompt(details.prefix, details.suffix) + return LlamaCompletionRequest.Builder(prompt) + .setN_predict(settings.codeCompletionMaxTokens) + .setStream(true) + .setTemperature(0.4) + .setStop(promptTemplate.stopTokens) + .build() } fun buildOllamaRequest(details: InfillRequestDetails): OllamaCompletionRequest { diff --git a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt index 5c2aeeed..71f02c20 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt @@ -35,11 +35,11 @@ class CodeCompletionServiceTest : IntegrationTest() { ${"z".repeat(247)} """.trimIndent() // 128 tokens expectLlama(StreamHttpExchange { request: RequestEntity -> - assertThat(request.uri.path).isEqualTo("/infill") + assertThat(request.uri.path).isEqualTo("/completion") assertThat(request.method).isEqualTo("POST") assertThat(request.body) - .extracting("input_prefix", "input_suffix") - .containsExactly(prefix, suffix) + .extracting("prompt") + .isEqualTo(InfillPromptTemplate.CODE_LLAMA.buildPrompt(prefix, suffix)) listOf(jsonMapResponse(e("content", expectedCompletion), e("stop", true))) }) From 5d2bc13f8cb57351e8f0550a66b9a6142dc595c8 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 8 May 2024 15:07:00 +0200 Subject: [PATCH 22/30] fix: refresh Ollama models only when service is changed to Ollama (#536) --- .../settings/GeneralSettingsComponent.java | 9 ++++++-- .../service/ollama/OllamaSettingsForm.kt | 21 +++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java index 9583040b..7f20f736 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java @@ -74,8 +74,13 @@ public class GeneralSettingsComponent { serviceComboBox = new ComboBox<>(serviceComboBoxModel); serviceComboBox.setSelectedItem(OPENAI); serviceComboBox.setPreferredSize(displayNameField.getPreferredSize()); - serviceComboBox.addItemListener(e -> - cardLayout.show(cards, ((ServiceType) e.getItem()).getCode())); + serviceComboBox.addItemListener(e -> { + ServiceType selectedService = (ServiceType) e.getItem(); + cardLayout.show(cards, selectedService.getCode()); + if (selectedService == OLLAMA) { + ollamaSettingsForm.refreshModels(); + } + }); mainPanel = FormBuilder.createFormBuilder() .addLabeledComponent( CodeGPTBundle.get("settingsConfigurable.displayName.label"), diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt index e9ac10c7..8ede4ecf 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt @@ -55,7 +55,6 @@ class OllamaSettingsForm { } } refreshModelsButton.addActionListener { refreshModels() } - refreshModels() } fun getForm(): JPanel = FormBuilder.createFormBuilder() @@ -116,16 +115,7 @@ class OllamaSettingsForm { || codeCompletionConfigurationForm.fimTemplate != fimTemplate } - private fun disableModelComboBoxWithPlaceholder(placeholderModel: ComboBoxModel) { - invokeLater { - modelComboBox.apply { - model = placeholderModel - isEnabled = false - } - } - } - - private fun refreshModels() { + fun refreshModels() { disableModelComboBoxWithPlaceholder(DefaultComboBoxModel(arrayOf("Loading"))) try { val models = runBlocking(Dispatchers.IO) { @@ -160,4 +150,13 @@ class OllamaSettingsForm { disableModelComboBoxWithPlaceholder(DefaultComboBoxModel(arrayOf("Unable to load models"))) } } + + private fun disableModelComboBoxWithPlaceholder(placeholderModel: ComboBoxModel) { + invokeLater { + modelComboBox.apply { + model = placeholderModel + isEnabled = false + } + } + } } From f5a63eb889381caf747557b6d90b4982a2914c64 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Wed, 8 May 2024 16:38:00 +0300 Subject: [PATCH 23/30] chore(deps): bump llm-client and tree-sitter --- codegpt-treesitter/build.gradle.kts | 10 +++++----- gradle/libs.versions.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codegpt-treesitter/build.gradle.kts b/codegpt-treesitter/build.gradle.kts index cab4a71c..7df5b0fd 100644 --- a/codegpt-treesitter/build.gradle.kts +++ b/codegpt-treesitter/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation("io.github.bonede:tree-sitter-graphql:master") implementation("io.github.bonede:tree-sitter-html:0.20.2") implementation("io.github.bonede:tree-sitter-javascript:0.20.1") - implementation("io.github.bonede:tree-sitter-json:0.20.1") + implementation("io.github.bonede:tree-sitter-json:0.21.0") implementation("io.github.bonede:tree-sitter-kotlin:0.3.1") implementation("io.github.bonede:tree-sitter-latex:0.3.0") implementation("io.github.bonede:tree-sitter-lua:2.1.3") @@ -32,9 +32,9 @@ dependencies { implementation("io.github.bonede:tree-sitter-svelte:0.11.0") implementation("io.github.bonede:tree-sitter-swift:0.3.6") implementation("io.github.bonede:tree-sitter-yaml:0.5.0") - implementation("io.github.bonede:tree-sitter-java:0.20.2") - implementation("io.github.bonede:tree-sitter-python:0.20.4") - implementation("io.github.bonede:tree-sitter-php:0.20.0") - implementation("io.github.bonede:tree-sitter-typescript:0.20.3") + implementation("io.github.bonede:tree-sitter-java:0.21.0") + implementation("io.github.bonede:tree-sitter-python:0.21.0") + implementation("io.github.bonede:tree-sitter-php:0.22.2") + implementation("io.github.bonede:tree-sitter-typescript:0.20.6") implementation("io.github.bonede:tree-sitter-query:0.1.0") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9bb5cf56..1dc9d9a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2" kotlin = "1.9.23" -llm-client = "0.7.5" +llm-client = "0.8.0" okio = "3.9.0" tree-sitter = "0.22.5" From 74fc2e6219c36493e3a0d99fc3956d7efaf84f0b Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 8 May 2024 15:51:32 +0200 Subject: [PATCH 24/30] feat: add Google Gemini API support (#535) --- .../java/ee/carlrobert/codegpt/Icons.java | 1 + .../completions/CompletionClientProvider.java | 7 ++ .../CompletionRequestProvider.java | 118 ++++++++++++++++++ .../completions/CompletionRequestService.java | 35 +++++- .../conversations/ConversationService.java | 6 + .../codegpt/settings/GeneralSettings.java | 6 + .../settings/GeneralSettingsComponent.java | 11 +- .../settings/GeneralSettingsConfigurable.java | 11 +- .../codegpt/settings/service/ServiceType.java | 3 +- .../chat/ui/textarea/ModelComboBoxAction.java | 10 ++ .../carlrobert/codegpt/ui/ModelIconLabel.java | 3 + .../CodeCompletionFeatureToggleActions.kt | 3 + .../CodeGPTInlineCompletionProvider.kt | 1 + .../codegpt/credentials/CredentialsStore.kt | 3 +- .../settings/service/google/GoogleSettings.kt | 11 ++ .../service/google/GoogleSettingsForm.kt | 92 ++++++++++++++ src/main/resources/META-INF/plugin.xml | 1 + src/main/resources/icons/google.svg | 14 +++ .../resources/messages/codegpt.properties | 3 + .../DefaultCompletionRequestHandlerTest.kt | 42 ++++++- .../testsupport/mixin/ShortcutsTestMixin.kt | 13 +- 21 files changed, 379 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt create mode 100644 src/main/resources/icons/google.svg diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index 0e210996..89467c2c 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -10,6 +10,7 @@ public final class Icons { IconLoader.getIcon("/icons/codegpt-small.svg", Icons.class); public static final Icon Anthropic = IconLoader.getIcon("/icons/anthropic.svg", Icons.class); public static final Icon Azure = IconLoader.getIcon("/icons/azure.svg", Icons.class); + public static final Icon Google = IconLoader.getIcon("/icons/google.svg", Icons.class); public static final Icon Llama = IconLoader.getIcon("/icons/llama.svg", Icons.class); public static final Icon OpenAI = IconLoader.getIcon("/icons/openai.svg", Icons.class); public static final Icon Send = IconLoader.getIcon("/icons/send.svg", Icons.class); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index 0e74e9bd..40435aa0 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -14,6 +14,7 @@ import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import ee.carlrobert.llm.client.anthropic.ClaudeClient; import ee.carlrobert.llm.client.azure.AzureClient; import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams; +import ee.carlrobert.llm.client.google.GoogleClient; import ee.carlrobert.llm.client.llama.LlamaClient; import ee.carlrobert.llm.client.ollama.OllamaClient; import ee.carlrobert.llm.client.openai.OpenAIClient; @@ -105,6 +106,12 @@ public class CompletionClientProvider { .build(getDefaultClientBuilder()); } + + public static GoogleClient getGoogleClient() { + return new GoogleClient.Builder(getCredential(CredentialKey.GOOGLE_API_KEY)) + .build(getDefaultClientBuilder()); + } + public static OkHttpClient.Builder getDefaultClientBuilder() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); var advancedSettings = AdvancedSettings.getCurrentState(); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index 04f0d39c..9742b245 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -40,6 +40,12 @@ import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest; import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMessage; import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageImageContent; import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageTextContent; +import ee.carlrobert.llm.client.google.completion.GoogleCompletionContent; +import ee.carlrobert.llm.client.google.completion.GoogleCompletionRequest; +import ee.carlrobert.llm.client.google.completion.GoogleContentPart; +import ee.carlrobert.llm.client.google.completion.GoogleContentPart.Blob; +import ee.carlrobert.llm.client.google.completion.GoogleGenerationConfig; +import ee.carlrobert.llm.client.google.models.GoogleModel; import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest; import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionMessage; import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionRequest; @@ -221,6 +227,16 @@ public class CompletionRequestProvider { .setTemperature(configuration.getTemperature()).build(); } + public GoogleCompletionRequest buildGoogleChatCompletionRequest( + @Nullable String model, + CallParameters callParameters) { + var configuration = ConfigurationSettings.getCurrentState(); + return new GoogleCompletionRequest.Builder(buildGoogleMessages(model, callParameters)) + .generationConfig(new GoogleGenerationConfig.Builder() + .maxOutputTokens(configuration.getMaxTokens()) + .temperature(configuration.getTemperature()).build()).build(); + } + public Request buildCustomOpenAIChatCompletionRequest( CustomServiceChatCompletionSettingsState settings, CallParameters callParameters) { @@ -448,6 +464,83 @@ public class CompletionRequestProvider { return tryReducingMessagesOrThrow(messages, totalUsage, modelMaxTokens); } + private List buildGoogleMessages(CallParameters callParameters) { + var message = callParameters.getMessage(); + var messages = new ArrayList(); + // Gemini API does not support direct 'system' prompts: + // see https://www.reddit.com/r/Bard/comments/1b90i8o/does_gemini_have_a_system_prompt_option_while/ + if (callParameters.getConversationType() == ConversationType.DEFAULT) { + String systemPrompt = ConfigurationSettings.getCurrentState().getSystemPrompt(); + messages.add(new GoogleCompletionContent("user", List.of(systemPrompt))); + messages.add(new GoogleCompletionContent("model", List.of("Understood."))); + } + if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) { + messages.add( + new GoogleCompletionContent("user", List.of(FIX_COMPILE_ERRORS_SYSTEM_PROMPT))); + messages.add(new GoogleCompletionContent("model", List.of("Understood."))); + } + + for (var prevMessage : conversation.getMessages()) { + if (callParameters.isRetry() && prevMessage.getId().equals(message.getId())) { + break; + } + var prevMessageImageFilePath = prevMessage.getImageFilePath(); + if (prevMessageImageFilePath != null && !prevMessageImageFilePath.isEmpty()) { + try { + var imageFilePath = Path.of(prevMessageImageFilePath); + var imageData = Files.readAllBytes(imageFilePath); + var imageMediaType = FileUtil.getImageMediaType(imageFilePath.getFileName().toString()); + messages.add(new GoogleCompletionContent( + List.of( + new GoogleContentPart(null, new Blob(imageMediaType, imageData)), + new GoogleContentPart(prevMessage.getPrompt())), "user")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + messages.add(new GoogleCompletionContent("user", List.of(prevMessage.getPrompt()))); + } + messages.add(new GoogleCompletionContent("model", List.of(prevMessage.getResponse()))); + } + + if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) { + messages.add(new GoogleCompletionContent( + List.of( + new GoogleContentPart(null, + new Blob(callParameters.getImageMediaType(), callParameters.getImageData())), + new GoogleContentPart(message.getPrompt())), "user")); + } else { + messages.add(new GoogleCompletionContent("user", List.of(message.getPrompt()))); + } + return messages; + } + + private List buildGoogleMessages( + @Nullable String model, + CallParameters callParameters) { + var messages = buildGoogleMessages(callParameters); + + if (model == null) { + return messages; + } + + int totalUsage = messages.parallelStream() + .mapToInt(message -> encodingManager.countMessageTokens(message.getRole(), + String.join(",", message.getParts().stream().map(GoogleContentPart::getText).toList()))) + .sum() + ConfigurationSettings.getCurrentState().getMaxTokens(); + int modelMaxTokens; + try { + modelMaxTokens = GoogleModel.findByCode(model).getMaxTokens(); + + if (totalUsage <= modelMaxTokens) { + return messages; + } + } catch (NoSuchElementException ex) { + return messages; + } + return tryReducingGoogleMessagesOrThrow(messages, totalUsage, modelMaxTokens); + } + private List tryReducingMessagesOrThrow( List messages, int totalUsage, @@ -473,4 +566,29 @@ public class CompletionRequestProvider { return messages.stream().filter(Objects::nonNull).toList(); } + + private List tryReducingGoogleMessagesOrThrow( + List messages, + int totalUsage, + int modelMaxTokens) { + if (!ConversationsState.getInstance().discardAllTokenLimits) { + if (!conversation.isDiscardTokenLimit()) { + throw new TotalUsageExceededException(); + } + } + + // skip the system prompt + for (int i = 1; i < messages.size(); i++) { + if (totalUsage <= modelMaxTokens) { + break; + } + + var message = messages.get(i); + totalUsage -= encodingManager.countMessageTokens(message.getRole(), + String.join(",", message.getParts().stream().map(GoogleContentPart::getText).toList())); + messages.set(i, null); + } + + return messages.stream().filter(Objects::nonNull).toList(); + } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index 1f78000e..47e117a9 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -20,18 +20,23 @@ import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings; +import ee.carlrobert.codegpt.settings.service.google.GoogleSettings; +import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsState; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import ee.carlrobert.llm.client.DeserializationUtil; import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest; import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMessage; +import ee.carlrobert.llm.client.google.completion.GoogleCompletionContent; +import ee.carlrobert.llm.client.google.completion.GoogleCompletionRequest; +import ee.carlrobert.llm.client.google.completion.GoogleGenerationConfig; import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest; import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionMessage; import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionRequest; import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionEventSourceListener; import ee.carlrobert.llm.client.openai.completion.OpenAITextCompletionEventSourceListener; -import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest; +import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest.Builder; import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage; import ee.carlrobert.llm.client.openai.completion.response.OpenAIChatCompletionResponse; import ee.carlrobert.llm.client.openai.completion.response.OpenAIChatCompletionResponseChoice; @@ -110,6 +115,16 @@ public final class CompletionRequestService { case OLLAMA -> CompletionClientProvider.getOllamaClient().getChatCompletionAsync( requestProvider.buildOllamaChatCompletionRequest(callParameters), eventListener); + case GOOGLE -> { + var settings = ApplicationManager.getApplication() + .getService(GoogleSettings.class).getState(); + yield CompletionClientProvider.getGoogleClient().getChatCompletionAsync( + requestProvider.buildGoogleChatCompletionRequest( + settings.getModel(), + callParameters), + settings.getModel(), + eventListener); + } }; } @@ -142,7 +157,7 @@ public final class CompletionRequestService { String gitDiff, CompletionEventListener eventListener) { var configuration = ConfigurationSettings.getCurrentState(); - var openaiRequest = new OpenAIChatCompletionRequest.Builder(List.of( + var openaiRequest = new Builder(List.of( new OpenAIChatCompletionStandardMessage("system", systemPrompt), new OpenAIChatCompletionStandardMessage("user", gitDiff))) .setModel(OpenAISettings.getCurrentState().getModel()) @@ -212,6 +227,21 @@ public final class CompletionRequestService { ).build(); CompletionClientProvider.getOllamaClient().getChatCompletionAsync(request, eventListener); break; + case GOOGLE: + GoogleSettingsState state = ApplicationManager.getApplication() + .getService(GoogleSettings.class).getState(); + CompletionClientProvider.getGoogleClient() + .getChatCompletionAsync(new GoogleCompletionRequest.Builder( + List.of( + new GoogleCompletionContent("user", List.of(systemPrompt)), + new GoogleCompletionContent("model", List.of("Understood.")), + new GoogleCompletionContent("user", List.of(gitDiff)) + )) + .generationConfig(new GoogleGenerationConfig.Builder() + .maxOutputTokens(configuration.getMaxTokens()) + .temperature(configuration.getTemperature()).build()) + .build(), state.getModel(), eventListener); + break; default: LOG.debug("Unknown service: {}", selectedService); break; @@ -255,6 +285,7 @@ public final class CompletionRequestService { : CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN); case CUSTOM_OPENAI, ANTHROPIC, LLAMA_CPP, OLLAMA -> true; case YOU -> false; + case GOOGLE -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.GOOGLE_API_KEY); }; } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java index fcded8e6..d11a29e3 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java @@ -8,6 +8,7 @@ import ee.carlrobert.codegpt.settings.GeneralSettings; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; +import ee.carlrobert.codegpt.settings.service.google.GoogleSettings; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; @@ -203,6 +204,11 @@ public final class ConversationService { .getService(OllamaSettings.class) .getState() .getModel(); + case GOOGLE -> + ApplicationManager.getApplication() + .getService(GoogleSettings.class) + .getState() + .getModel(); }; } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java index 423fcc06..b72ee03d 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java @@ -10,6 +10,7 @@ import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; +import ee.carlrobert.codegpt.settings.service.google.GoogleSettings; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; @@ -107,6 +108,11 @@ public class GeneralSettings implements PersistentStateComponent(); serviceComboBoxModel.addAll(Arrays.stream(ServiceType.values()).toList()); serviceComboBox = new ComboBox<>(serviceComboBoxModel); @@ -121,6 +125,10 @@ public class GeneralSettingsComponent { return ollamaSettingsForm; } + public GoogleSettingsForm getGoogleSettingsForm() { + return googleSettingsForm; + } + public ServiceType getSelectedService() { return serviceComboBox.getItem(); } @@ -153,6 +161,7 @@ public class GeneralSettingsComponent { youSettingsForm.resetForm(); llamaSettingsForm.resetForm(); ollamaSettingsForm.resetForm(); + googleSettingsForm.resetForm(); } static class DynamicCardLayout extends CardLayout { diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java index 17a5fe8e..cef436f8 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java @@ -4,6 +4,7 @@ import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.A import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_OPENAI_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CUSTOM_SERVICE_API_KEY; +import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.GOOGLE_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LLAMA_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY; @@ -18,10 +19,9 @@ import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettingsForm; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm; import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm; +import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsForm; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm; -import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; -import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsForm; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm; import ee.carlrobert.codegpt.settings.service.you.YouSettings; @@ -71,7 +71,8 @@ public class GeneralSettingsConfigurable implements Configurable { || AzureSettings.getInstance().isModified(component.getAzureSettingsForm()) || YouSettings.getInstance().isModified(component.getYouSettingsForm()) || LlamaSettings.getInstance().isModified(component.getLlamaSettingsForm()) - || component.getOllamaSettingsForm().isModified(); + || component.getOllamaSettingsForm().isModified() + || component.getGoogleSettingsForm().isModified(); } @Override @@ -88,6 +89,7 @@ public class GeneralSettingsConfigurable implements Configurable { applyYouSettings(component.getYouSettingsForm()); applyLlamaSettings(component.getLlamaSettingsForm()); component.getOllamaSettingsForm().applyChanges(); + applyGoogleSettings(component.getGoogleSettingsForm()); var serviceChanged = component.getSelectedService() != settings.getSelectedService(); var modelChanged = !OpenAISettings.getCurrentState().getModel() @@ -137,8 +139,9 @@ public class GeneralSettingsConfigurable implements Configurable { form.getActiveDirectoryToken()); } - private void applyOllamaSettings(OllamaSettingsForm form) { + private void applyGoogleSettings(GoogleSettingsForm form) { form.applyChanges(); + CredentialsStore.INSTANCE.setCredential(GOOGLE_API_KEY, form.getApiKey()); } @Override diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java index d8c6425b..dc749997 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java @@ -9,7 +9,8 @@ public enum ServiceType { AZURE("AZURE", "service.azure.title", "azure.chat.completion"), YOU("YOU", "service.you.title", "you.chat.completion"), LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion"), - OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion"); + OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion"), + GOOGLE("GOOGLE", "service.google.title", "google.chat.completion"); private final String code; private final String label; diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java index 38457966..5a22d28d 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java @@ -113,6 +113,12 @@ public class ModelComboBoxAction extends ComboBoxAction { actionGroup.addSeparator("Ollama"); ollamaSettings.getAvailableModels().forEach(model -> actionGroup.add(createOllamaModelAction(model, presentation))); + actionGroup.addSeparator(); + actionGroup.add(createModelAction( + ServiceType.GOOGLE, + "Google (Gemini)", + Icons.Google, + presentation)); if (YouUserManager.getInstance().isSubscribed()) { actionGroup.addSeparator("You.com"); @@ -193,6 +199,10 @@ public class ModelComboBoxAction extends ComboBoxAction { templatePresentation.setIcon(Icons.Ollama); templatePresentation.setText(ollamaSettings.getModel()); break; + case GOOGLE: + templatePresentation.setText("Google (Gemini)"); + templatePresentation.setIcon(Icons.Google); + break; default: break; } diff --git a/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java b/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java index 02fdd5a7..f125a5b1 100644 --- a/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java +++ b/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java @@ -27,6 +27,9 @@ public class ModelIconLabel extends JBLabel { if ("llama.chat.completion".equals(clientCode)) { setIcon(Icons.Llama); } + if ("google.chat.completion".equals(clientCode)) { + setIcon(Icons.Google); + } setText(formatModelName(modelCode)); setFont(JBFont.small()); setHorizontalAlignment(SwingConstants.LEADING); diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt index 9faf9666..121c4a54 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt @@ -32,6 +32,7 @@ abstract class CodeCompletionFeatureToggleActions( ANTHROPIC, AZURE, YOU, + GOOGLE, null -> { /* no-op for these services */ } } @@ -50,6 +51,7 @@ abstract class CodeCompletionFeatureToggleActions( ANTHROPIC, AZURE, YOU, + GOOGLE, null -> false } } @@ -66,6 +68,7 @@ abstract class CodeCompletionFeatureToggleActions( OLLAMA -> service().state.codeCompletionsEnabled ANTHROPIC, AZURE, + GOOGLE, YOU -> false } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt index 27f3d43e..9e632510 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt @@ -75,6 +75,7 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider { ServiceType.ANTHROPIC, ServiceType.AZURE, ServiceType.YOU, + ServiceType.GOOGLE, null -> false } return event is InlineCompletionEvent.DocumentChange && codeCompletionsEnabled diff --git a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt index 284c6084..de6f5055 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt @@ -40,6 +40,7 @@ object CredentialsStore { AZURE_OPENAI_API_KEY, AZURE_ACTIVE_DIRECTORY_TOKEN, YOU_ACCOUNT_PASSWORD, - LLAMA_API_KEY + LLAMA_API_KEY, + GOOGLE_API_KEY } } \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt new file mode 100644 index 00000000..5ca647a9 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt @@ -0,0 +1,11 @@ +package ee.carlrobert.codegpt.settings.service.google + +import com.intellij.openapi.components.* +import ee.carlrobert.llm.client.google.models.GoogleModel + +@State(name = "CodeGPT_GoogleSettings_210", storages = [Storage("CodeGPT_GoogleSettings_210.xml")]) +class GoogleSettings : SimplePersistentStateComponent(GoogleSettingsState()) + +class GoogleSettingsState : BaseState() { + var model by string(GoogleModel.GEMINI_PRO.code) +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt new file mode 100644 index 00000000..3491a4df --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt @@ -0,0 +1,92 @@ +package ee.carlrobert.codegpt.settings.service.google + +import com.intellij.openapi.components.service +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.EnumComboBoxModel +import com.intellij.ui.TitledSeparator +import com.intellij.ui.components.JBPasswordField +import com.intellij.util.ui.FormBuilder +import com.intellij.util.ui.UI +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.ui.UIUtil +import ee.carlrobert.llm.client.google.models.GoogleModel +import javax.swing.JPanel +import javax.swing.event.HyperlinkEvent + +class GoogleSettingsForm { + private val apiKeyField = JBPasswordField() + private val completionModelComboBox: ComboBox + + init { + val state = service().state + apiKeyField.columns = 30 + apiKeyField.text = + getCredential(CredentialKey.GOOGLE_API_KEY) + completionModelComboBox = ComboBox( + EnumComboBoxModel(GoogleModel::class.java) + ) + completionModelComboBox.selectedItem = GoogleModel.findByCode(state.model) + } + + fun getForm(): JPanel = FormBuilder.createFormBuilder() + .addComponent(TitledSeparator(CodeGPTBundle.get("shared.configuration"))) + .addComponent( + UIUtil.withEmptyLeftBorder( + UI.PanelFactory.grid() + .add( + UI.PanelFactory.panel(apiKeyField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.service.google.apiKey.comment")) + .withCommentHyperlinkListener { event: HyperlinkEvent? -> + UIUtil.handleHyperlinkClicked( + event + ) + }) + + .add( + UI.PanelFactory.panel(completionModelComboBox) + .withLabel(CodeGPTBundle.get("settingsConfigurable.shared.model.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.service.google.model.comment")) + .withCommentHyperlinkListener { event: HyperlinkEvent? -> + UIUtil.handleHyperlinkClicked( + event + ) + } + ) + .createPanel() + ) + ) + .addComponentFillVertically(JPanel(), 0) + .panel + + + fun getApiKey(): String? = String(apiKeyField.password).ifEmpty { null } + + fun getModel(): String = (completionModelComboBox.model + .selectedItem as GoogleModel) + .code + + fun getCurrentState() = GoogleSettingsState().apply { model = getModel() } + + fun resetForm() { + val state = service().state + apiKeyField.text = + getCredential(CredentialKey.GOOGLE_API_KEY) + completionModelComboBox.selectedItem = GoogleModel.findByCode(state.model) + } + + fun isModified(): Boolean = service().state.run { + model != getModel() || getApiKey() != getCredential(CredentialKey.GOOGLE_API_KEY) + } + + fun applyChanges() { + service().state.run { + model = getModel() + } + } + +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 2c883574..bf0503bb 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -37,6 +37,7 @@ + diff --git a/src/main/resources/icons/google.svg b/src/main/resources/icons/google.svg new file mode 100644 index 00000000..bc301fcd --- /dev/null +++ b/src/main/resources/icons/google.svg @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index b4ae54fd..04b1d7e5 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -23,6 +23,8 @@ settingsConfigurable.service.openai.apiKey.comment=You can find the API key in y settingsConfigurable.service.openai.customModel.label=Custom model: settingsConfigurable.service.openai.organization.label=Organization: settingsConfigurable.section.openai.organization.comment=Useful when you are part of multiple organizations optional +settingsConfigurable.service.google.apiKey.comment=You can find the API key in your User settings. +settingsConfigurable.service.google.model.comment=Note: Gemini Vision models do not yet support chats. settingsConfigurable.service.anthropic.apiKey.comment=You can find the API key in your User settings. settingsConfigurable.service.anthropic.apiVersion.comment=We always recommend using the latest API version whenever possible. settingsConfigurable.service.anthropic.model.comment=For details on model comparison metrics, see model comparison. @@ -175,6 +177,7 @@ service.azure.title=Azure Service service.you.title=You.com Service (Free, Cloud) service.llama.title=LLaMA C/C++ Port (Free, Local) service.ollama.title=Ollama (Free, Local) +service.google.title=Google Service validation.error.fieldRequired=This field is required. validation.error.invalidEmail=The email you entered is invalid. validation.error.mustBeNumber=Value must be number. diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt index 86161a1e..9cbbdea7 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt @@ -7,10 +7,7 @@ import ee.carlrobert.codegpt.conversations.message.Message import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings import ee.carlrobert.llm.client.http.RequestEntity import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange -import ee.carlrobert.llm.client.util.JSONUtil.e -import ee.carlrobert.llm.client.util.JSONUtil.jsonArray -import ee.carlrobert.llm.client.util.JSONUtil.jsonMap -import ee.carlrobert.llm.client.util.JSONUtil.jsonMapResponse +import ee.carlrobert.llm.client.util.JSONUtil.* import org.apache.http.HttpHeaders import org.assertj.core.api.Assertions.assertThat import testsupport.IntegrationTest @@ -171,6 +168,43 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() { waitExpecting { "Hello!" == message.response } } + + fun testGoogleChatCompletionCall() { + useGoogleService() + ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT" + val message = Message("TEST_PROMPT") + val conversation = ConversationService.getInstance().startConversation() + val requestHandler = CompletionRequestHandler(getRequestEventListener(message)) + expectGoogle(StreamHttpExchange { request: RequestEntity -> + assertThat(request.uri.path).isEqualTo("/v1/models/gemini-pro:streamGenerateContent") + assertThat(request.method).isEqualTo("POST") + assertThat(request.uri.query).isEqualTo("key=TEST_API_KEY&alt=sse") + assertThat(request.body) + .extracting("contents") + .isEqualTo( + listOf( + mapOf("parts" to listOf(mapOf("text" to "TEST_SYSTEM_PROMPT")), "role" to "user"), + mapOf("parts" to listOf(mapOf("text" to "Understood.")), "role" to "model"), + mapOf("parts" to listOf(mapOf("text" to "TEST_PROMPT")), "role" to "user"), + ) + ) + listOf( + jsonMapResponse( + "candidates", + jsonArray(jsonMap("content", jsonMap("parts", jsonArray(jsonMap("text", "Hello"))))) + ), + jsonMapResponse( + "candidates", + jsonArray(jsonMap("content", jsonMap("parts", jsonArray(jsonMap("text", "!"))))) + ) + ) + }) + + requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false)) + + waitExpecting { "Hello!" == message.response } + } + private fun getRequestEventListener(message: Message): CompletionResponseEventListener { return object : CompletionResponseEventListener { override fun handleCompleted(fullMessage: String, callParameters: CallParameters) { diff --git a/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt b/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt index 284a7ba9..2ce62938 100644 --- a/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt +++ b/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt @@ -1,14 +1,16 @@ package testsupport.mixin +import com.intellij.openapi.components.service import com.intellij.testFramework.PlatformTestUtil -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_OPENAI_API_KEY -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.* import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.settings.service.azure.AzureSettings +import ee.carlrobert.codegpt.settings.service.google.GoogleSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings +import ee.carlrobert.llm.client.google.models.GoogleModel import java.util.function.BooleanSupplier interface ShortcutsTestMixin { @@ -41,6 +43,13 @@ interface ShortcutsTestMixin { LlamaSettings.getCurrentState().serverPort = null } + fun useGoogleService() { + GeneralSettings.getCurrentState().selectedService = ServiceType.GOOGLE + setCredential(GOOGLE_API_KEY, "TEST_API_KEY") + service().state.model = GoogleModel.GEMINI_PRO.code + } + + fun waitExpecting(condition: BooleanSupplier?) { PlatformTestUtil.waitWithEventsDispatching( "Waiting for message response timed out or did not meet expected conditions", From 0852c2717057a49fd2acf0866624d43f01b6397f Mon Sep 17 00:00:00 2001 From: Carl-Robert Date: Wed, 8 May 2024 23:59:51 +0300 Subject: [PATCH 25/30] feat: add CodeGPT "native" API provider (#537) * feat: support codegpt client * feat: add basic request handler test * refactor: minor cleanup --- gradle/libs.versions.toml | 2 +- .../java/ee/carlrobert/codegpt/Icons.java | 2 + .../completions/CompletionClientProvider.java | 11 +- .../CompletionRequestProvider.java | 2 +- .../completions/CompletionRequestService.java | 49 +++--- .../codegpt/conversations/Conversation.java | 4 +- .../conversations/ConversationService.java | 17 ++- .../codegpt/settings/GeneralSettings.java | 6 + .../settings/GeneralSettingsComponent.java | 18 ++- .../settings/GeneralSettingsConfigurable.java | 9 ++ .../settings/GeneralSettingsState.java | 2 +- .../codegpt/settings/service/ServiceType.java | 1 + .../service/anthropic/AnthropicSettings.java | 2 +- .../anthropic/AnthropicSettingsForm.java | 4 +- .../settings/service/azure/AzureSettings.java | 4 +- .../service/azure/AzureSettingsForm.java | 10 +- .../settings/service/llama/LlamaSettings.java | 2 +- .../form/LlamaServerPreferencesForm.java | 4 +- .../service/openai/OpenAISettings.java | 2 +- .../service/openai/OpenAISettingsForm.java | 8 +- .../settings/service/you/YouSettingsForm.java | 2 +- .../ui/ChatToolWindowScrollablePanel.java | 23 +++ .../chat/ui/textarea/ModelComboBoxAction.java | 128 +++++++++++----- .../CodeCompletionFeatureToggleActions.kt | 23 +-- .../CodeCompletionRequestFactory.kt | 13 ++ .../codecompletions/CodeCompletionService.kt | 59 ++++++++ .../CodeGPTInlineCompletionProvider.kt | 8 +- .../codegpt/credentials/CredentialsStore.kt | 5 +- .../service/codegpt/CodeGPTServiceForm.kt | 141 ++++++++++++++++++ .../service/codegpt/CodeGPTServiceSettings.kt | 26 ++++ src/main/resources/icons/codegpt-model.svg | 2 + .../resources/icons/codegpt-model_dark.svg | 2 + .../resources/messages/codegpt.properties | 4 +- .../CompletionRequestProviderTest.kt | 8 +- .../DefaultCompletionRequestHandlerTest.kt | 31 ++++ .../testsupport/mixin/ShortcutsTestMixin.kt | 9 +- 36 files changed, 510 insertions(+), 133 deletions(-) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt create mode 100644 src/main/resources/icons/codegpt-model.svg create mode 100644 src/main/resources/icons/codegpt-model_dark.svg diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1dc9d9a5..58704e27 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2" kotlin = "1.9.23" -llm-client = "0.8.0" +llm-client = "0.8.1" okio = "3.9.0" tree-sitter = "0.22.5" diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index 89467c2c..34311e2a 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -8,6 +8,8 @@ public final class Icons { public static final Icon Default = IconLoader.getIcon("/icons/codegpt.svg", Icons.class); public static final Icon DefaultSmall = IconLoader.getIcon("/icons/codegpt-small.svg", Icons.class); + public static final Icon CodeGPTModel = + IconLoader.getIcon("/icons/codegpt-model.svg", Icons.class); public static final Icon Anthropic = IconLoader.getIcon("/icons/anthropic.svg", Icons.class); public static final Icon Azure = IconLoader.getIcon("/icons/azure.svg", Icons.class); public static final Icon Google = IconLoader.getIcon("/icons/google.svg", Icons.class); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index 40435aa0..6f4ce8a9 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -1,9 +1,10 @@ package ee.carlrobert.codegpt.completions; +import static ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential; + import com.intellij.openapi.application.ApplicationManager; import ee.carlrobert.codegpt.CodeGPTPlugin; import ee.carlrobert.codegpt.completions.you.YouUserManager; -import ee.carlrobert.codegpt.credentials.CredentialsStore; import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; import ee.carlrobert.codegpt.settings.advanced.AdvancedSettings; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; @@ -14,6 +15,7 @@ import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import ee.carlrobert.llm.client.anthropic.ClaudeClient; import ee.carlrobert.llm.client.azure.AzureClient; import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams; +import ee.carlrobert.llm.client.codegpt.CodeGPTClient; import ee.carlrobert.llm.client.google.GoogleClient; import ee.carlrobert.llm.client.llama.LlamaClient; import ee.carlrobert.llm.client.ollama.OllamaClient; @@ -25,12 +27,13 @@ import java.net.Proxy; import java.util.concurrent.TimeUnit; import okhttp3.Credentials; import okhttp3.OkHttpClient; -import org.jetbrains.annotations.Nullable; public class CompletionClientProvider { - private static @Nullable String getCredential(CredentialKey key) { - return CredentialsStore.INSTANCE.getCredential(key); + public static CodeGPTClient getCodeGPTClient() { + return new CodeGPTClient( + getCredential(CredentialKey.CODEGPT_API_KEY), + getDefaultClientBuilder()); } public static OpenAIClient getOpenAIClient() { diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index 9742b245..376df97e 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -251,7 +251,7 @@ public class CompletionRequestProvider { List messages, boolean streamRequest) { return buildCustomOpenAIChatCompletionRequest(settings, messages, streamRequest, - CredentialsStore.INSTANCE.getCredential(CUSTOM_SERVICE_API_KEY)); + CredentialsStore.getCredential(CUSTOM_SERVICE_API_KEY)); } private static Request buildCustomOpenAIChatCompletionRequest( diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index 47e117a9..d3592f69 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -8,8 +8,6 @@ import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; import com.intellij.openapi.diagnostic.Logger; -import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory; -import ee.carlrobert.codegpt.codecompletions.InfillRequestDetails; import ee.carlrobert.codegpt.completions.llama.LlamaModel; import ee.carlrobert.codegpt.completions.llama.PromptTemplate; import ee.carlrobert.codegpt.credentials.CredentialsStore; @@ -19,6 +17,7 @@ import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings; import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings; import ee.carlrobert.codegpt.settings.service.google.GoogleSettings; import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsState; @@ -83,8 +82,18 @@ public final class CompletionRequestService { public EventSource getChatCompletionAsync( CallParameters callParameters, CompletionEventListener eventListener) { + var application = ApplicationManager.getApplication(); var requestProvider = new CompletionRequestProvider(callParameters.getConversation()); return switch (GeneralSettings.getCurrentState().getSelectedService()) { + case CODEGPT -> CompletionClientProvider.getCodeGPTClient().getChatCompletionAsync( + requestProvider.buildOpenAIChatCompletionRequest( + application.getService(CodeGPTServiceSettings.class) + .getState() + .getChatCompletionSettings() + .getModel(), + callParameters), + eventListener + ); case OPENAI -> CompletionClientProvider.getOpenAIClient().getChatCompletionAsync( requestProvider.buildOpenAIChatCompletionRequest( OpenAISettings.getCurrentState().getModel(), @@ -92,8 +101,7 @@ public final class CompletionRequestService { eventListener); case CUSTOM_OPENAI -> getCustomOpenAIChatCompletionAsync( requestProvider.buildCustomOpenAIChatCompletionRequest( - ApplicationManager.getApplication() - .getService(CustomServiceSettings.class) + application.getService(CustomServiceSettings.class) .getState() .getChatCompletionSettings(), callParameters), @@ -116,8 +124,7 @@ public final class CompletionRequestService { requestProvider.buildOllamaChatCompletionRequest(callParameters), eventListener); case GOOGLE -> { - var settings = ApplicationManager.getApplication() - .getService(GoogleSettings.class).getState(); + var settings = application.getService(GoogleSettings.class).getState(); yield CompletionClientProvider.getGoogleClient().getChatCompletionAsync( requestProvider.buildGoogleChatCompletionRequest( settings.getModel(), @@ -128,30 +135,6 @@ public final class CompletionRequestService { }; } - public EventSource getCodeCompletionAsync( - InfillRequestDetails requestDetails, - CompletionEventListener eventListener) { - var httpClient = CompletionClientProvider.getDefaultClientBuilder().build(); - return switch (GeneralSettings.getCurrentState().getSelectedService()) { - case OPENAI -> CompletionClientProvider.getOpenAIClient() - .getCompletionAsync( - CodeCompletionRequestFactory.buildOpenAIRequest(requestDetails), - eventListener); - case CUSTOM_OPENAI -> EventSources.createFactory(httpClient).newEventSource( - CodeCompletionRequestFactory.buildCustomRequest(requestDetails), - new OpenAITextCompletionEventSourceListener(eventListener)); - case LLAMA_CPP -> CompletionClientProvider.getLlamaClient() - .getChatCompletionAsync( - CodeCompletionRequestFactory.buildLlamaRequest(requestDetails), - eventListener); - case OLLAMA -> CompletionClientProvider.getOllamaClient().getCompletionAsync( - CodeCompletionRequestFactory.INSTANCE.buildOllamaRequest(requestDetails), - eventListener); - default -> - throw new IllegalArgumentException("Code completion not supported for selected service"); - }; - } - public void generateCommitMessageAsync( String systemPrompt, String gitDiff, @@ -164,6 +147,10 @@ public final class CompletionRequestService { .build(); var selectedService = GeneralSettings.getCurrentState().getSelectedService(); switch (selectedService) { + case CODEGPT: + CompletionClientProvider.getCodeGPTClient() + .getChatCompletionAsync(openaiRequest, eventListener); + break; case OPENAI: CompletionClientProvider.getOpenAIClient() .getChatCompletionAsync(openaiRequest, eventListener); @@ -283,7 +270,7 @@ public final class CompletionRequestService { AzureSettings.getCurrentState().isUseAzureApiKeyAuthentication() ? CredentialKey.AZURE_OPENAI_API_KEY : CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN); - case CUSTOM_OPENAI, ANTHROPIC, LLAMA_CPP, OLLAMA -> true; + case CODEGPT, CUSTOM_OPENAI, ANTHROPIC, LLAMA_CPP, OLLAMA -> true; case YOU -> false; case GOOGLE -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.GOOGLE_API_KEY); }; diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java b/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java index 7d3b0265..83a32085 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java @@ -29,7 +29,7 @@ public class Conversation { } public void setMessages(List messages) { - this.messages = messages; + this.messages = new ArrayList<>(messages); } public String getClientCode() { @@ -77,7 +77,7 @@ public class Conversation { } public void removeMessage(UUID messageId) { - setMessages(messages.stream() + messages = new ArrayList<>(messages.stream() .filter(message -> !message.getId().equals(messageId)) .toList()); } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java index d11a29e3..3d5352b7 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java @@ -8,6 +8,7 @@ import ee.carlrobert.codegpt.settings.GeneralSettings; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings; import ee.carlrobert.codegpt.settings.service.google.GoogleSettings; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; @@ -188,7 +189,12 @@ public final class ConversationService { } private static String getModelForSelectedService(ServiceType serviceType) { + var application = ApplicationManager.getApplication(); return switch (serviceType) { + case CODEGPT -> application.getService(CodeGPTServiceSettings.class) + .getState() + .getChatCompletionSettings() + .getModel(); case OPENAI -> OpenAISettings.getCurrentState().getModel(); case CUSTOM_OPENAI -> "CustomService"; case ANTHROPIC -> AnthropicSettings.getCurrentState().getModel(); @@ -200,15 +206,12 @@ public final class ConversationService { ? llamaSettings.getCustomLlamaModelPath() : llamaSettings.getHuggingFaceModel().getCode(); } - case OLLAMA -> ApplicationManager.getApplication() - .getService(OllamaSettings.class) + case OLLAMA -> application.getService(OllamaSettings.class) + .getState() + .getModel(); + case GOOGLE -> application.getService(GoogleSettings.class) .getState() .getModel(); - case GOOGLE -> - ApplicationManager.getApplication() - .getService(GoogleSettings.class) - .getState() - .getModel(); }; } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java index b72ee03d..39a55745 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java @@ -10,6 +10,7 @@ import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.settings.service.ServiceType; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings; import ee.carlrobert.codegpt.settings.service.google.GoogleSettings; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; @@ -78,6 +79,11 @@ public class GeneralSettings implements PersistentStateComponent serviceComboBox; + private final CodeGPTServiceForm codeGPTSettingsForm; private final OpenAISettingsForm openAISettingsForm; private final CustomServiceForm customConfigurationSettingsForm; private final AnthropicSettingsForm anthropicSettingsForm; @@ -54,6 +56,7 @@ public class GeneralSettingsComponent { public GeneralSettingsComponent(Disposable parentDisposable, GeneralSettings settings) { displayNameField = new JBTextField(settings.getState().getDisplayName(), 20); + codeGPTSettingsForm = new CodeGPTServiceForm(); openAISettingsForm = new OpenAISettingsForm(OpenAISettings.getCurrentState()); customConfigurationSettingsForm = new CustomServiceForm(); anthropicSettingsForm = new AnthropicSettingsForm(AnthropicSettings.getCurrentState()); @@ -65,6 +68,7 @@ public class GeneralSettingsComponent { var cardLayout = new DynamicCardLayout(); var cards = new JPanel(cardLayout); + cards.add(codeGPTSettingsForm.getForm(), CODEGPT.getCode()); cards.add(openAISettingsForm.getForm(), OPENAI.getCode()); cards.add(customConfigurationSettingsForm.getForm(), CUSTOM_OPENAI.getCode()); cards.add(anthropicSettingsForm.getForm(), ANTHROPIC.getCode()); @@ -73,10 +77,9 @@ public class GeneralSettingsComponent { cards.add(llamaSettingsForm, LLAMA_CPP.getCode()); cards.add(ollamaSettingsForm.getForm(), OLLAMA.getCode()); cards.add(googleSettingsForm.getForm(), GOOGLE.getCode()); - var serviceComboBoxModel = new DefaultComboBoxModel(); - serviceComboBoxModel.addAll(Arrays.stream(ServiceType.values()).toList()); - serviceComboBox = new ComboBox<>(serviceComboBoxModel); - serviceComboBox.setSelectedItem(OPENAI); + cardLayout.show(cards, settings.getState().getSelectedService().getCode()); + serviceComboBox = new ComboBox<>(new DefaultComboBoxModel<>(ServiceType.values())); + serviceComboBox.setSelectedItem(settings.getState().getSelectedService()); serviceComboBox.setPreferredSize(displayNameField.getPreferredSize()); serviceComboBox.addItemListener(e -> { ServiceType selectedService = (ServiceType) e.getItem(); @@ -97,6 +100,10 @@ public class GeneralSettingsComponent { .getPanel(); } + public CodeGPTServiceForm getCodeGPTSettingsForm() { + return codeGPTSettingsForm; + } + public OpenAISettingsForm getOpenAISettingsForm() { return openAISettingsForm; } @@ -154,6 +161,7 @@ public class GeneralSettingsComponent { } public void resetForms() { + codeGPTSettingsForm.resetForm(); openAISettingsForm.resetForm(); customConfigurationSettingsForm.resetForm(); anthropicSettingsForm.resetForm(); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java index cef436f8..759788da 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java @@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.settings; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.ANTHROPIC_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_OPENAI_API_KEY; +import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CODEGPT_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CUSTOM_SERVICE_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.GOOGLE_API_KEY; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LLAMA_API_KEY; @@ -18,6 +19,7 @@ import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettingsForm; import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceForm; import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm; import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsForm; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; @@ -65,6 +67,7 @@ public class GeneralSettingsConfigurable implements Configurable { return !component.getDisplayName().equals(settings.getDisplayName()) || component.getSelectedService() != settings.getSelectedService() + || component.getCodeGPTSettingsForm().isModified() || OpenAISettings.getInstance().isModified(component.getOpenAISettingsForm()) || component.getCustomConfigurationSettingsForm().isModified() || AnthropicSettings.getInstance().isModified(component.getAnthropicSettingsForm()) @@ -81,6 +84,7 @@ public class GeneralSettingsConfigurable implements Configurable { settings.setDisplayName(component.getDisplayName()); settings.setSelectedService(component.getSelectedService()); + applyCodeGPTServiceSettings(component.getCodeGPTSettingsForm()); var openAISettingsForm = component.getOpenAISettingsForm(); applyOpenAISettings(openAISettingsForm); applyCustomOpenAISettings(component.getCustomConfigurationSettingsForm()); @@ -104,6 +108,11 @@ public class GeneralSettingsConfigurable implements Configurable { } } + private void applyCodeGPTServiceSettings(CodeGPTServiceForm form) { + CredentialsStore.INSTANCE.setCredential(CODEGPT_API_KEY, form.getApiKey()); + form.applyChanges(); + } + private void applyOpenAISettings(OpenAISettingsForm form) { CredentialsStore.INSTANCE.setCredential(OPENAI_API_KEY, form.getApiKey()); OpenAISettings.getInstance().loadState(form.getCurrentState()); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsState.java index 2c266c13..cd13df1f 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsState.java @@ -5,7 +5,7 @@ import ee.carlrobert.codegpt.settings.service.ServiceType; public class GeneralSettingsState { private String displayName = ""; - private ServiceType selectedService = ServiceType.OPENAI; + private ServiceType selectedService = ServiceType.CODEGPT; public String getDisplayName() { if (displayName == null || displayName.isEmpty()) { diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java index dc749997..514afbac 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java @@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.settings.service; import ee.carlrobert.codegpt.CodeGPTBundle; public enum ServiceType { + CODEGPT("CODEGPT", "service.codegpt.title", "codegpt.chat.completion"), OPENAI("OPENAI", "service.openai.title", "chat.completion"), CUSTOM_OPENAI("CUSTOM_OPENAI", "service.custom.openai.title", "custom.openai.chat.completion"), ANTHROPIC("ANTHROPIC", "service.anthropic.title", "anthropic.chat.completion"), diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java index 4ea11a0c..c488a8b8 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java @@ -37,6 +37,6 @@ public class AnthropicSettings implements PersistentStateComponent( new EnumComboBoxModel<>(OpenAIChatCompletionModel.class)); @@ -58,10 +58,10 @@ public class OpenAISettingsForm { .createPanel(); return FormBuilder.createFormBuilder() - .addComponent(new TitledSeparator(CodeGPTBundle.get("shared.codeCompletions"))) - .addComponent(withEmptyLeftBorder(codeCompletionConfigurationForm.getForm())) .addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration"))) .addComponent(withEmptyLeftBorder(configurationGrid)) + .addComponent(new TitledSeparator(CodeGPTBundle.get("shared.codeCompletions"))) + .addComponent(withEmptyLeftBorder(codeCompletionConfigurationForm.getForm())) .addComponentFillVertically(new JPanel(), 0) .getPanel(); } @@ -88,7 +88,7 @@ public class OpenAISettingsForm { public void resetForm() { var state = OpenAISettings.getCurrentState(); - apiKeyField.setText(CredentialsStore.INSTANCE.getCredential(OPENAI_API_KEY)); + apiKeyField.setText(CredentialsStore.getCredential(OPENAI_API_KEY)); completionModelComboBox.setSelectedItem( OpenAIChatCompletionModel.findByCode(state.getModel())); organizationField.setText(state.getOrganization()); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java index f3def2ed..a834b331 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java @@ -53,7 +53,7 @@ public class YouSettingsForm extends JPanel { passwordField = new JBPasswordField(); passwordField.setColumns(25); if (!settings.getEmail().isEmpty()) { - passwordField.setText(CredentialsStore.INSTANCE.getCredential(YOU_ACCOUNT_PASSWORD)); + passwordField.setText(CredentialsStore.getCredential(YOU_ACCOUNT_PASSWORD)); } signInButton = new JButton(CodeGPTBundle.get("settingsConfigurable.service.you.signIn.label")); signUpTextPane = createSignUpTextPane(); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java index 02deaabd..c4b0bfca 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatToolWindowScrollablePanel.java @@ -2,6 +2,13 @@ package ee.carlrobert.codegpt.toolwindow.chat.ui; import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel; import com.intellij.openapi.roots.ui.componentsList.layout.VerticalStackLayout; +import com.intellij.ui.JBColor; +import com.intellij.util.ui.JBUI; +import ee.carlrobert.codegpt.credentials.CredentialsStore; +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; +import ee.carlrobert.codegpt.settings.GeneralSettings; +import ee.carlrobert.codegpt.settings.service.ServiceType; +import ee.carlrobert.codegpt.ui.UIUtil; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -21,6 +28,22 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel { public void displayLandingView(JComponent landingView) { clearAll(); add(landingView); + if (GeneralSettings.getCurrentState().getSelectedService() == ServiceType.CODEGPT + && !CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.CODEGPT_API_KEY)) { + + var panel = new ResponsePanel() + .addContent(UIUtil.createTextPane(""" + +

+ It looks like you haven't configured your API key yet. Visit the CodeGPT settings to do so. +

+

+ Don't have an account? Sign up for free access to all open-source models. +

+ """, false, UIUtil::handleHyperlinkClicked)); + panel.setBorder(JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0)); + add(panel); + } } public ResponsePanel getMessageResponsePanel(UUID messageId) { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java index 5a22d28d..52930eaf 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/ModelComboBoxAction.java @@ -1,9 +1,11 @@ package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea; +import static ee.carlrobert.codegpt.settings.service.ServiceType.CODEGPT; import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI; import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA; import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI; import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU; +import static ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels.AVAILABLE_CHAT_MODELS; import static java.lang.String.format; import com.intellij.openapi.actionSystem.ActionUpdateThread; @@ -19,17 +21,17 @@ import ee.carlrobert.codegpt.Icons; import ee.carlrobert.codegpt.completions.llama.LlamaModel; import ee.carlrobert.codegpt.completions.you.YouUserManager; import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier; +import ee.carlrobert.codegpt.credentials.CredentialsStore; +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; import ee.carlrobert.codegpt.settings.GeneralSettings; -import ee.carlrobert.codegpt.settings.GeneralSettingsState; import ee.carlrobert.codegpt.settings.service.ServiceType; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings; import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings; -import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsState; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; -import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsState; import ee.carlrobert.codegpt.settings.service.you.YouSettings; -import ee.carlrobert.codegpt.settings.service.you.YouSettingsState; +import ee.carlrobert.llm.client.codegpt.CodeGPTModel; import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel; import ee.carlrobert.llm.client.you.completion.YouCompletionCustomModel; import ee.carlrobert.llm.client.you.completion.YouCompletionMode; @@ -41,19 +43,9 @@ import org.jetbrains.annotations.NotNull; public class ModelComboBoxAction extends ComboBoxAction { private final Runnable onModelChange; - private final GeneralSettingsState settings; - private final OpenAISettingsState openAISettings; - private final YouSettingsState youSettings; - private final OllamaSettingsState ollamaSettings; public ModelComboBoxAction(Runnable onModelChange, ServiceType selectedService) { this.onModelChange = onModelChange; - settings = GeneralSettings.getCurrentState(); - openAISettings = OpenAISettings.getCurrentState(); - youSettings = YouSettings.getCurrentState(); - ollamaSettings = ApplicationManager.getApplication() - .getService(OllamaSettings.class) - .getState(); updateTemplatePresentation(selectedService); subscribeToYouSignedOutTopic(ApplicationManager.getApplication().getMessageBus().connect()); @@ -73,18 +65,28 @@ public class ModelComboBoxAction extends ComboBoxAction { return button; } + private AnAction[] getCodeGPTModelActions(Presentation presentation) { + var apiKey = CredentialsStore.getCredential(CredentialKey.CODEGPT_API_KEY); + return AVAILABLE_CHAT_MODELS.stream() + .map(model -> { + var enabled = "codellama/CodeLlama-13b-Instruct-hf".equals(model.getCode()) + || (apiKey != null && !apiKey.isEmpty()); + return createCodeGPTModelAction(model, enabled, presentation); + }) + .toArray(AnAction[]::new); + } + @Override protected @NotNull DefaultActionGroup createPopupActionGroup(JComponent button) { var presentation = ((ComboBoxButton) button).getPresentation(); var actionGroup = new DefaultActionGroup(); + actionGroup.addSeparator("CodeGPT"); + actionGroup.addAll(getCodeGPTModelActions(presentation)); actionGroup.addSeparator("OpenAI"); List.of( OpenAIChatCompletionModel.GPT_4_VISION_PREVIEW, OpenAIChatCompletionModel.GPT_4_0125_128k, - OpenAIChatCompletionModel.GPT_3_5_0125_16k, - OpenAIChatCompletionModel.GPT_4_32k, - OpenAIChatCompletionModel.GPT_4, - OpenAIChatCompletionModel.GPT_3_5) + OpenAIChatCompletionModel.GPT_3_5_0125_16k) .forEach(model -> actionGroup.add(createOpenAIModelAction(model, presentation))); actionGroup.addSeparator("Custom OpenAI Service"); actionGroup.add(createModelAction( @@ -111,8 +113,12 @@ public class ModelComboBoxAction extends ComboBoxAction { Icons.Llama, presentation)); actionGroup.addSeparator("Ollama"); - ollamaSettings.getAvailableModels().forEach(model -> - actionGroup.add(createOllamaModelAction(model, presentation))); + ApplicationManager.getApplication() + .getService(OllamaSettings.class) + .getState() + .getAvailableModels() + .forEach(model -> + actionGroup.add(createOllamaModelAction(model, presentation))); actionGroup.addSeparator(); actionGroup.add(createModelAction( ServiceType.GOOGLE, @@ -160,20 +166,33 @@ public class ModelComboBoxAction extends ComboBoxAction { } private void updateTemplatePresentation(ServiceType selectedService) { + var application = ApplicationManager.getApplication(); var templatePresentation = getTemplatePresentation(); switch (selectedService) { + case CODEGPT: + var model = application.getService(CodeGPTServiceSettings.class) + .getState() + .getChatCompletionSettings() + .getModel(); + var modelName = AVAILABLE_CHAT_MODELS.stream() + .filter(it -> it.getCode().equals(model)) + .map(CodeGPTModel::getName) + .findFirst().orElse("Unknown"); + templatePresentation.setIcon(Icons.CodeGPTModel); + templatePresentation.setText(modelName); + break; case OPENAI: templatePresentation.setIcon(Icons.OpenAI); templatePresentation.setText( - OpenAIChatCompletionModel.findByCode(openAISettings.getModel()).getDescription()); + OpenAIChatCompletionModel.findByCode(OpenAISettings.getCurrentState().getModel()) + .getDescription()); break; case CUSTOM_OPENAI: templatePresentation.setIcon(Icons.OpenAI); - templatePresentation.setText( - ApplicationManager.getApplication().getService(CustomServiceSettings.class) - .getState() - .getTemplate() - .getProviderName()); + templatePresentation.setText(application.getService(CustomServiceSettings.class) + .getState() + .getTemplate() + .getProviderName()); break; case ANTHROPIC: templatePresentation.setIcon(Icons.Anthropic); @@ -184,11 +203,12 @@ public class ModelComboBoxAction extends ComboBoxAction { templatePresentation.setText("Azure OpenAI"); break; case YOU: + var settings = YouSettings.getCurrentState(); templatePresentation.setIcon(Icons.YouSmall); templatePresentation.setText( - youSettings.getChatMode() == YouCompletionMode.CUSTOM - ? youSettings.getCustomModel().getDescription() - : youSettings.getChatMode().getDescription() + settings.getChatMode() == YouCompletionMode.CUSTOM + ? settings.getCustomModel().getDescription() + : settings.getChatMode().getDescription() ); break; case LLAMA_CPP: @@ -197,7 +217,9 @@ public class ModelComboBoxAction extends ComboBoxAction { break; case OLLAMA: templatePresentation.setIcon(Icons.Ollama); - templatePresentation.setText(ollamaSettings.getModel()); + templatePresentation.setText(application.getService(OllamaSettings.class) + .getState() + .getModel()); break; case GOOGLE: templatePresentation.setText("Google (Gemini)"); @@ -254,12 +276,42 @@ public class ModelComboBoxAction extends ComboBoxAction { String label, Icon icon, Presentation comboBoxPresentation) { - settings.setSelectedService(serviceType); + GeneralSettings.getCurrentState().setSelectedService(serviceType); comboBoxPresentation.setIcon(icon); comboBoxPresentation.setText(label); onModelChange.run(); } + private AnAction createCodeGPTModelAction(CodeGPTModel model, boolean enabled, + Presentation comboBoxPresentation) { + return new DumbAwareAction(model.getName(), "", Icons.CodeGPTModel) { + @Override + public void update(@NotNull AnActionEvent event) { + var presentation = event.getPresentation(); + presentation.setEnabled( + enabled && !presentation.getText().equals(comboBoxPresentation.getText())); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + ApplicationManager.getApplication().getService(CodeGPTServiceSettings.class) + .getState() + .getChatCompletionSettings() + .setModel(model.getCode()); + handleModelChange( + CODEGPT, + model.getName(), + Icons.OpenAI, + comboBoxPresentation); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + }; + } + private AnAction createOllamaModelAction( String model, Presentation comboBoxPresentation @@ -273,7 +325,10 @@ public class ModelComboBoxAction extends ComboBoxAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - ollamaSettings.setModel(model); + ApplicationManager.getApplication() + .getService(OllamaSettings.class) + .getState() + .setModel(model); handleModelChange( OLLAMA, model, @@ -302,7 +357,7 @@ public class ModelComboBoxAction extends ComboBoxAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - openAISettings.setModel(model.getCode()); + OpenAISettings.getCurrentState().setModel(model.getCode()); handleModelChange( OPENAI, model.getDescription(), @@ -331,7 +386,7 @@ public class ModelComboBoxAction extends ComboBoxAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - youSettings.setChatMode(mode); + YouSettings.getCurrentState().setChatMode(mode); handleModelChange( YOU, mode.getDescription(), @@ -360,8 +415,9 @@ public class ModelComboBoxAction extends ComboBoxAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - youSettings.setCustomModel(model); - youSettings.setChatMode(YouCompletionMode.CUSTOM); + var settings = YouSettings.getCurrentState(); + settings.setCustomModel(model); + settings.setChatMode(YouCompletionMode.CUSTOM); handleModelChange( YOU, model.getDescription(), diff --git a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt index 121c4a54..6a0b29d0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt @@ -4,9 +4,10 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.components.service import com.intellij.openapi.project.DumbAwareAction +import ee.carlrobert.codegpt.codecompletions.CodeCompletionService import ee.carlrobert.codegpt.settings.GeneralSettings -import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.settings.service.ServiceType.* +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings @@ -18,6 +19,9 @@ abstract class CodeCompletionFeatureToggleActions( override fun actionPerformed(e: AnActionEvent) { when (GeneralSettings.getCurrentState().selectedService) { + CODEGPT -> + service().state.codeCompletionSettings.codeCompletionsEnabled + OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction @@ -40,9 +44,11 @@ abstract class CodeCompletionFeatureToggleActions( override fun update(e: AnActionEvent) { val selectedService = GeneralSettings.getCurrentState().selectedService - val codeCompletionEnabled = isCodeCompletionsEnabled(selectedService) + val codeCompletionEnabled = + service().isCodeCompletionsEnabled(selectedService) e.presentation.isVisible = codeCompletionEnabled != enableFeatureAction e.presentation.isEnabled = when (selectedService) { + CODEGPT, OPENAI, CUSTOM_OPENAI, LLAMA_CPP, @@ -59,19 +65,6 @@ abstract class CodeCompletionFeatureToggleActions( override fun getActionUpdateThread(): ActionUpdateThread { return ActionUpdateThread.BGT } - - private fun isCodeCompletionsEnabled(serviceType: ServiceType): Boolean { - return when (serviceType) { - OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled - CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled - LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible() - OLLAMA -> service().state.codeCompletionsEnabled - ANTHROPIC, - AZURE, - GOOGLE, - YOU -> false - } - } } class EnableCompletionsAction : CodeCompletionFeatureToggleActions(true) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt index a743a76c..3fe627dc 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt @@ -7,6 +7,7 @@ import ee.carlrobert.codegpt.completions.llama.LlamaModel import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential import ee.carlrobert.codegpt.settings.configuration.Placeholder +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState @@ -23,6 +24,18 @@ import java.nio.charset.StandardCharsets object CodeCompletionRequestFactory { + @JvmStatic + fun buildCodeGPTRequest(details: InfillRequestDetails): OpenAITextCompletionRequest { + val settings = service().state.codeCompletionSettings + return OpenAITextCompletionRequest.Builder(details.prefix) + .setSuffix(details.suffix) + .setStream(true) + .setModel(settings.model) + .setMaxTokens(settings.maxTokens) + .setTemperature(0.4) + .build() + } + @JvmStatic fun buildOpenAIRequest(details: InfillRequestDetails): OpenAITextCompletionRequest { return OpenAITextCompletionRequest.Builder(details.prefix) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt new file mode 100644 index 00000000..a2093b31 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.kt @@ -0,0 +1,59 @@ +package ee.carlrobert.codegpt.codecompletions + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildCodeGPTRequest +import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildCustomRequest +import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildLlamaRequest +import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildOpenAIRequest +import ee.carlrobert.codegpt.completions.CompletionClientProvider +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.ServiceType +import ee.carlrobert.codegpt.settings.service.ServiceType.* +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings +import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings +import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings +import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings +import ee.carlrobert.llm.client.openai.completion.OpenAITextCompletionEventSourceListener +import ee.carlrobert.llm.completion.CompletionEventListener +import okhttp3.sse.EventSource +import okhttp3.sse.EventSources.createFactory + +@Service(Service.Level.PROJECT) +class CodeCompletionService { + + fun isCodeCompletionsEnabled(selectedService: ServiceType): Boolean = + when (selectedService) { + CODEGPT -> service().state.codeCompletionSettings.codeCompletionsEnabled + OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled + CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled + LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible() + OLLAMA -> service().state.codeCompletionsEnabled + else -> false + } + + fun getCodeCompletionAsync( + requestDetails: InfillRequestDetails, + eventListener: CompletionEventListener + ): EventSource = + when (val selectedService = GeneralSettings.getCurrentState().selectedService) { + CODEGPT -> CompletionClientProvider.getCodeGPTClient() + .getCompletionAsync(buildCodeGPTRequest(requestDetails), eventListener) + + OPENAI -> CompletionClientProvider.getOpenAIClient() + .getCompletionAsync(buildOpenAIRequest(requestDetails), eventListener) + + CUSTOM_OPENAI -> createFactory( + CompletionClientProvider.getDefaultClientBuilder().build() + ).newEventSource( + buildCustomRequest(requestDetails), + OpenAITextCompletionEventSourceListener(eventListener) + ) + + LLAMA_CPP -> CompletionClientProvider.getLlamaClient() + .getChatCompletionAsync(buildLlamaRequest(requestDetails), eventListener) + + else -> throw IllegalArgumentException("Code completion not supported for ${selectedService.name}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt index 9e632510..04895b3f 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt @@ -7,9 +7,9 @@ import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.thisLogger import ee.carlrobert.codegpt.CodeGPTKeys -import ee.carlrobert.codegpt.completions.CompletionRequestService import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.service.ServiceType +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings @@ -36,7 +36,8 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider { get() = InlineCompletionProviderID("CodeGPTInlineCompletionProvider") override suspend fun getSuggestion(request: InlineCompletionRequest): InlineCompletionSuggestion { - if (request.editor.project == null) { + val project = request.editor.project + if (project == null) { logger.error("Could not find project") return InlineCompletionSuggestion.empty() } @@ -46,7 +47,7 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider { InfillRequestDetails.fromInlineCompletionRequest(request) } currentCall.set( - CompletionRequestService.getInstance().getCodeCompletionAsync( + project.service().getCodeCompletionAsync( infillRequest, CodeCompletionEventListener { val inlineText = it.takeWhile { message -> message != '\n' }.toString() @@ -68,6 +69,7 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider { override fun isEnabled(event: InlineCompletionEvent): Boolean { val selectedService = GeneralSettings.getCurrentState().selectedService val codeCompletionsEnabled = when (selectedService) { + ServiceType.CODEGPT -> service().state.codeCompletionSettings.codeCompletionsEnabled ServiceType.OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled ServiceType.CUSTOM_OPENAI -> service().state.codeCompletionSettings.codeCompletionsEnabled ServiceType.LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled diff --git a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt index de6f5055..72233af6 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt @@ -19,6 +19,7 @@ object CredentialsStore { } } + @JvmStatic fun getCredential(key: CredentialKey): String? = credentialsMap[key] fun setCredential(key: CredentialKey, password: String?) { @@ -26,7 +27,8 @@ object CredentialsStore { credentialsMap[key] = password if (prevPassword != password) { - val credentialAttributes = CredentialAttributes(generateServiceName("CodeGPT", key.name)) + val credentialAttributes = + CredentialAttributes(generateServiceName("CodeGPT", key.name)) PasswordSafe.instance.setPassword(credentialAttributes, password) } } @@ -34,6 +36,7 @@ object CredentialsStore { fun isCredentialSet(key: CredentialKey): Boolean = !getCredential(key).isNullOrEmpty() enum class CredentialKey { + CODEGPT_API_KEY, OPENAI_API_KEY, CUSTOM_SERVICE_API_KEY, ANTHROPIC_API_KEY, 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 new file mode 100644 index 00000000..3336dc70 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt @@ -0,0 +1,141 @@ +package ee.carlrobert.codegpt.settings.service.codegpt + +import com.intellij.openapi.components.service +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.panel.ComponentPanelBuilder +import com.intellij.ui.TitledSeparator +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.components.JBPasswordField +import com.intellij.ui.components.fields.IntegerField +import com.intellij.util.ui.FormBuilder +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CODEGPT_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.ui.UIUtil +import ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels +import ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels.AVAILABLE_CHAT_MODELS +import ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels.AVAILABLE_CODE_MODELS +import ee.carlrobert.llm.client.codegpt.CodeGPTModel +import org.jdesktop.swingx.combobox.ListComboBoxModel +import java.awt.Component +import javax.swing.DefaultListCellRenderer +import javax.swing.JList +import javax.swing.JPanel + +class CodeGPTServiceForm { + + private val apiKeyField = JBPasswordField().apply { + columns = 30 + text = getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) + } + + private val chatCompletionModelComboBox = + ComboBox(ListComboBoxModel(AVAILABLE_CHAT_MODELS)).apply { + selectedItem = + CodeGPTAvailableModels.findByCode(service().state.chatCompletionSettings.model) + renderer = CustomComboBoxRenderer() + } + + private val codeCompletionsEnabledCheckBox = JBCheckBox( + CodeGPTBundle.get("codeCompletionsForm.enableFeatureText"), + service().state.codeCompletionSettings.codeCompletionsEnabled + ) + + private val codeCompletionModelComboBox = + ComboBox(ListComboBoxModel(AVAILABLE_CODE_MODELS)).apply { + selectedItem = + CodeGPTAvailableModels.findByCode(service().state.codeCompletionSettings.model) + renderer = CustomComboBoxRenderer() + } + + private val codeCompletionMaxTokensField = + IntegerField("completion_max_tokens", 8, 4096).apply { + columns = 12 + value = service().state.codeCompletionSettings.maxTokens + } + + fun getForm(): JPanel = FormBuilder.createFormBuilder() + .addComponent(TitledSeparator(CodeGPTBundle.get("shared.configuration"))) + .addComponent( + FormBuilder.createFormBuilder() + .setFormLeftIndent(16) + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"), + apiKeyField + ) + .addComponentToRightColumn( + UIUtil.createComment("settingsConfigurable.service.codegpt.apiKey.comment") + ) + .addLabeledComponent("Model:", chatCompletionModelComboBox) + .addVerticalGap(4) + .panel + ) + .addComponent(TitledSeparator("Code Completions")) + .addComponent( + FormBuilder.createFormBuilder() + .setFormLeftIndent(16) + .addComponent(codeCompletionsEnabledCheckBox) + .addLabeledComponent("Model:", codeCompletionModelComboBox) + .addLabeledComponent("Max tokens:", codeCompletionMaxTokensField) + .addComponentToRightColumn( + ComponentPanelBuilder.createCommentComponent( + CodeGPTBundle.get("codeCompletionsForm.maxTokensComment"), true, 48, true + ) + ) + .panel + ) + .panel + + fun getApiKey() = String(apiKeyField.password).ifEmpty { null } + + fun isModified() = service().state.run { + (chatCompletionModelComboBox.selectedItem as CodeGPTModel).code != chatCompletionSettings.model + || (codeCompletionModelComboBox.selectedItem as CodeGPTModel).code != codeCompletionSettings.model + || codeCompletionMaxTokensField.value != codeCompletionSettings.maxTokens + || codeCompletionsEnabledCheckBox.isSelected != codeCompletionSettings.codeCompletionsEnabled + || getApiKey() != getCredential(CODEGPT_API_KEY) + } + + fun applyChanges() { + service().state.run { + chatCompletionSettings.model = + (chatCompletionModelComboBox.selectedItem as CodeGPTModel).code + codeCompletionSettings.codeCompletionsEnabled = + codeCompletionsEnabledCheckBox.isSelected + codeCompletionSettings.maxTokens = codeCompletionMaxTokensField.value + codeCompletionSettings.model = + (codeCompletionModelComboBox.selectedItem as CodeGPTModel).code + } + setCredential(CODEGPT_API_KEY, getApiKey()) + } + + fun resetForm() { + service().state.run { + chatCompletionModelComboBox.selectedItem = chatCompletionSettings.model + codeCompletionModelComboBox.selectedItem = codeCompletionSettings.model + codeCompletionMaxTokensField.value = codeCompletionSettings.maxTokens + codeCompletionsEnabledCheckBox.isSelected = + codeCompletionSettings.codeCompletionsEnabled + } + apiKeyField.text = getCredential(CODEGPT_API_KEY) + } + + private class CustomComboBoxRenderer : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + val component = + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus) + if (value is CodeGPTModel) { + text = value.name + } + return component + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..7aa9bd62 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt @@ -0,0 +1,26 @@ +package ee.carlrobert.codegpt.settings.service.codegpt + +import com.intellij.openapi.components.* + +@Service +@State( + name = "CodeGPT_CodeGPTServiceSettings", + storages = [Storage("CodeGPT_CodeGPTServiceSettings.xml")] +) +class CodeGPTServiceSettings : + SimplePersistentStateComponent(CodeGPTServiceSettingsState()) + +class CodeGPTServiceSettingsState : BaseState() { + var chatCompletionSettings by property(CodeGPTServiceChatCompletionSettingsState()) + var codeCompletionSettings by property(CodeGPTServiceCodeCompletionSettingsState()) +} + +class CodeGPTServiceChatCompletionSettingsState : BaseState() { + var model by string("meta-llama/Llama-3-70b-chat-hf") +} + +class CodeGPTServiceCodeCompletionSettingsState : BaseState() { + var codeCompletionsEnabled by property(true) + var model by string("codellama/CodeLlama-70b-hf") + var maxTokens by property(128) +} diff --git a/src/main/resources/icons/codegpt-model.svg b/src/main/resources/icons/codegpt-model.svg new file mode 100644 index 00000000..89919566 --- /dev/null +++ b/src/main/resources/icons/codegpt-model.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/icons/codegpt-model_dark.svg b/src/main/resources/icons/codegpt-model_dark.svg new file mode 100644 index 00000000..00581547 --- /dev/null +++ b/src/main/resources/icons/codegpt-model_dark.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 04b1d7e5..3ad8afcb 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -18,6 +18,7 @@ settings.displayName=CodeGPT: Settings settings.openaiQuotaExceeded=OpenAI quota exceeded. settingsConfigurable.displayName.label=Display name: settingsConfigurable.service.label=Service: +settingsConfigurable.service.codegpt.apiKey.comment=You can find the API key in your User settings. 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.openai.apiKey.comment=You can find the API key in your User settings. settingsConfigurable.service.openai.customModel.label=Custom model: @@ -170,6 +171,7 @@ toolwindow.chat.youProCheckBox.enable=Turn on for complex queries toolwindow.chat.youProCheckBox.disable=Turn off for faster responses toolwindow.chat.youProCheckBox.notAllowed=Enable by subscribing to YouPro plan toolwindow.chat.textArea.emptyText=Ask me anything... +service.codegpt.title=CodeGPT Service service.openai.title=OpenAI Service service.custom.openai.title=Custom OpenAI Service service.anthropic.title=Anthropic Service @@ -209,4 +211,4 @@ shared.chatCompletions=Chat Completions shared.codeCompletions=Code Completions codeCompletionsForm.enableFeatureText=Enable code completions codeCompletionsForm.maxTokensLabel=Max tokens: -codeCompletionsForm.maxTokensComment=The maximum number of tokens that can be generated in the code completion. +codeCompletionsForm.maxTokensComment=The maximum number of tokens that will be generated in the code completion. \ No newline at end of file diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt index ef874ecf..bb18c9c4 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.kt @@ -3,8 +3,6 @@ package ee.carlrobert.codegpt.completions import ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT import ee.carlrobert.codegpt.conversations.ConversationService import ee.carlrobert.codegpt.conversations.message.Message -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey -import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel import org.assertj.core.api.Assertions.assertThat @@ -14,7 +12,7 @@ import testsupport.IntegrationTest class CompletionRequestProviderTest : IntegrationTest() { fun testChatCompletionRequestWithSystemPromptOverride() { - setCredential(CredentialKey.OPENAI_API_KEY, "TEST_API_KEY") + useOpenAIService() ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT" val conversation = ConversationService.getInstance().startConversation() val firstMessage = createDummyMessage(500) @@ -43,6 +41,7 @@ class CompletionRequestProviderTest : IntegrationTest() { } fun testChatCompletionRequestWithoutSystemPromptOverride() { + useOpenAIService() ConfigurationSettings.getCurrentState().systemPrompt = COMPLETION_SYSTEM_PROMPT val conversation = ConversationService.getInstance().startConversation() val firstMessage = createDummyMessage(500) @@ -71,6 +70,7 @@ class CompletionRequestProviderTest : IntegrationTest() { } fun testChatCompletionRequestRetry() { + useOpenAIService() ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT" val conversation = ConversationService.getInstance().startConversation() val firstMessage = createDummyMessage("FIRST_TEST_PROMPT", 500) @@ -97,6 +97,7 @@ class CompletionRequestProviderTest : IntegrationTest() { } fun testReducedChatCompletionRequest() { + useOpenAIService() ConfigurationSettings.getCurrentState().systemPrompt = COMPLETION_SYSTEM_PROMPT val conversation = ConversationService.getInstance().startConversation() conversation.addMessage(createDummyMessage(50)) @@ -126,6 +127,7 @@ class CompletionRequestProviderTest : IntegrationTest() { } fun testTotalUsageExceededException() { + useOpenAIService() val conversation = ConversationService.getInstance().startConversation() conversation.addMessage(createDummyMessage(1500)) conversation.addMessage(createDummyMessage(1500)) diff --git a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt index 9cbbdea7..5e99ec2a 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.kt @@ -205,6 +205,37 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() { waitExpecting { "Hello!" == message.response } } + fun testCodeGPTServiceChatCompletionCall() { + useCodeGPTService() + ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT" + val message = Message("TEST_PROMPT") + val conversation = ConversationService.getInstance().startConversation() + val requestHandler = CompletionRequestHandler(getRequestEventListener(message)) + expectCodeGPT(StreamHttpExchange { request: RequestEntity -> + assertThat(request.uri.path).isEqualTo("/v1/chat/completions") + assertThat(request.method).isEqualTo("POST") + assertThat(request.headers[HttpHeaders.AUTHORIZATION]!![0]).isEqualTo("Bearer TEST_API_KEY") + assertThat(request.body) + .extracting( + "model", + "messages") + .containsExactly( + "TEST_MODEL", + listOf( + mapOf("role" to "system", "content" to "TEST_SYSTEM_PROMPT"), + mapOf("role" to "user", "content" to "TEST_PROMPT"))) + listOf( + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "lo")))), + jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!"))))) + }) + + requestHandler.call(CallParameters(conversation, ConversationType.DEFAULT, message, false)) + + waitExpecting { "Hello!" == message.response } + } + private fun getRequestEventListener(message: Message): CompletionResponseEventListener { return object : CompletionResponseEventListener { override fun handleCompleted(fullMessage: String, callParameters: CallParameters) { diff --git a/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt b/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt index 2ce62938..2273ca08 100644 --- a/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt +++ b/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt @@ -7,6 +7,7 @@ import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.settings.service.azure.AzureSettings +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings import ee.carlrobert.codegpt.settings.service.google.GoogleSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings @@ -15,6 +16,12 @@ import java.util.function.BooleanSupplier interface ShortcutsTestMixin { + fun useCodeGPTService() { + GeneralSettings.getCurrentState().selectedService = ServiceType.CODEGPT + setCredential(CODEGPT_API_KEY, "TEST_API_KEY") + service().state.chatCompletionSettings.model = "TEST_MODEL" + } + fun useOpenAIService() { useOpenAIService("gpt-4") } @@ -49,7 +56,6 @@ interface ShortcutsTestMixin { service().state.model = GoogleModel.GEMINI_PRO.code } - fun waitExpecting(condition: BooleanSupplier?) { PlatformTestUtil.waitWithEventsDispatching( "Waiting for message response timed out or did not meet expected conditions", @@ -57,5 +63,4 @@ interface ShortcutsTestMixin { 5 ) } - } From 7bee59a90e56b5c4102ba778015e6da05596624d Mon Sep 17 00:00:00 2001 From: Carl-Robert Date: Thu, 9 May 2024 11:16:09 +0300 Subject: [PATCH 26/30] feat: extract providers into their standalone configurables (#538) * fix: extract services to their own configurables * feat: switch to selected provider automatically upon apply * fix: credentials loading at once * fix: rename llama.cpp title --- .../codegpt/actions/OpenSettingsAction.java | 5 +- .../settings/GeneralSettingsComponent.java | 176 +----------------- .../settings/GeneralSettingsConfigurable.java | 143 +------------- .../codegpt/settings/service/ServiceType.java | 4 +- .../service/anthropic/AnthropicSettings.java | 10 - .../anthropic/AnthropicSettingsForm.java | 7 +- .../settings/service/azure/AzureSettings.java | 15 -- .../custom/CustomServiceFormTabbedPane.java | 4 +- .../service/openai/OpenAISettings.java | 11 -- .../settings/service/you/YouSettings.java | 4 - .../codegpt/CodeGPTProjectActivity.kt | 1 - .../codegpt/credentials/CredentialsStore.kt | 17 +- .../service/AnthropicServiceConfigurable.kt | 40 ++++ .../service/AzureServiceConfigurable.kt | 43 +++++ .../service/LlamaServiceConfigurable.kt | 40 ++++ .../service/OpenAIServiceConfigurable.kt | 40 ++++ .../settings/service/ServiceConfigurable.kt | 52 ++++++ .../service/ServiceConfigurableComponent.kt | 72 +++++++ .../service/YouServiceConfigurable.kt | 42 +++++ .../codegpt/CodeGPTServiceConfigurable.kt | 38 ++++ .../service/codegpt/CodeGPTServiceForm.kt | 1 + .../custom/CustomServiceConfigurable.kt | 40 ++++ .../service/custom/CustomServiceSettings.kt | 3 + .../CustomServiceChatCompletionForm.kt | 4 +- .../CustomServiceCodeCompletionForm.kt | 4 +- .../custom/{ => form}/CustomServiceForm.kt | 54 +++--- .../CustomServiceChatCompletionTemplate.kt | 2 +- .../CustomServiceCodeCompletionTemplate.kt | 2 +- .../{ => template}/CustomServiceTemplate.kt | 2 +- .../settings/service/google/GoogleSettings.kt | 1 + .../google/GoogleSettingsConfigurable.kt | 38 ++++ .../service/google/GoogleSettingsForm.kt | 5 +- .../ollama/OllamaSettingsConfigurable.kt | 35 ++++ .../service/ollama/OllamaSettingsForm.kt | 1 + src/main/resources/META-INF/plugin.xml | 21 ++- .../resources/messages/codegpt.properties | 20 +- 36 files changed, 579 insertions(+), 418 deletions(-) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/AnthropicServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/AzureServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/LlamaServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/OpenAIServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/YouServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceConfigurable.kt rename src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/{ => form}/CustomServiceChatCompletionForm.kt (94%) rename src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/{ => form}/CustomServiceCodeCompletionForm.kt (96%) rename src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/{ => form}/CustomServiceForm.kt (77%) rename src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/{ => template}/CustomServiceChatCompletionTemplate.kt (98%) rename src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/{ => template}/CustomServiceCodeCompletionTemplate.kt (97%) rename src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/{ => template}/CustomServiceTemplate.kt (97%) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsConfigurable.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsConfigurable.kt diff --git a/src/main/java/ee/carlrobert/codegpt/actions/OpenSettingsAction.java b/src/main/java/ee/carlrobert/codegpt/actions/OpenSettingsAction.java index 7f4629da..abba9064 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/OpenSettingsAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/OpenSettingsAction.java @@ -5,7 +5,7 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.options.ShowSettingsUtil; import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable; +import ee.carlrobert.codegpt.settings.service.ServiceConfigurable; import org.jetbrains.annotations.NotNull; public class OpenSettingsAction extends AnAction { @@ -18,7 +18,6 @@ public class OpenSettingsAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - ShowSettingsUtil.getInstance() - .showSettingsDialog(e.getProject(), GeneralSettingsConfigurable.class); + ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), ServiceConfigurable.class); } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java index 5088ce6c..f9a30a74 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsComponent.java @@ -1,151 +1,26 @@ package ee.carlrobert.codegpt.settings; -import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC; -import static ee.carlrobert.codegpt.settings.service.ServiceType.AZURE; -import static ee.carlrobert.codegpt.settings.service.ServiceType.CODEGPT; -import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI; -import static ee.carlrobert.codegpt.settings.service.ServiceType.GOOGLE; -import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP; -import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA; -import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI; -import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.ui.ComboBox; import com.intellij.ui.components.JBTextField; import com.intellij.util.ui.FormBuilder; import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.settings.service.ServiceType; -import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; -import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettingsForm; -import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; -import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm; -import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceForm; -import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm; -import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsForm; -import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; -import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm; -import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsForm; -import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; -import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm; -import ee.carlrobert.codegpt.settings.service.you.YouSettings; -import ee.carlrobert.codegpt.settings.service.you.YouSettingsForm; -import java.awt.CardLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Insets; -import javax.swing.DefaultComboBoxModel; import javax.swing.JComponent; import javax.swing.JPanel; public class GeneralSettingsComponent { - private final JPanel mainPanel; private final JBTextField displayNameField; - private final ComboBox serviceComboBox; - private final CodeGPTServiceForm codeGPTSettingsForm; - private final OpenAISettingsForm openAISettingsForm; - private final CustomServiceForm customConfigurationSettingsForm; - private final AnthropicSettingsForm anthropicSettingsForm; - private final AzureSettingsForm azureSettingsForm; - private final YouSettingsForm youSettingsForm; - private final LlamaSettingsForm llamaSettingsForm; - private final OllamaSettingsForm ollamaSettingsForm; - private final GoogleSettingsForm googleSettingsForm; - public GeneralSettingsComponent(Disposable parentDisposable, GeneralSettings settings) { + public GeneralSettingsComponent(GeneralSettings settings) { displayNameField = new JBTextField(settings.getState().getDisplayName(), 20); - codeGPTSettingsForm = new CodeGPTServiceForm(); - openAISettingsForm = new OpenAISettingsForm(OpenAISettings.getCurrentState()); - customConfigurationSettingsForm = new CustomServiceForm(); - anthropicSettingsForm = new AnthropicSettingsForm(AnthropicSettings.getCurrentState()); - azureSettingsForm = new AzureSettingsForm(AzureSettings.getCurrentState()); - youSettingsForm = new YouSettingsForm(YouSettings.getCurrentState(), parentDisposable); - llamaSettingsForm = new LlamaSettingsForm(LlamaSettings.getCurrentState()); - ollamaSettingsForm = new OllamaSettingsForm(); - googleSettingsForm = new GoogleSettingsForm(); - - var cardLayout = new DynamicCardLayout(); - var cards = new JPanel(cardLayout); - cards.add(codeGPTSettingsForm.getForm(), CODEGPT.getCode()); - cards.add(openAISettingsForm.getForm(), OPENAI.getCode()); - cards.add(customConfigurationSettingsForm.getForm(), CUSTOM_OPENAI.getCode()); - cards.add(anthropicSettingsForm.getForm(), ANTHROPIC.getCode()); - cards.add(azureSettingsForm.getForm(), AZURE.getCode()); - cards.add(youSettingsForm, YOU.getCode()); - cards.add(llamaSettingsForm, LLAMA_CPP.getCode()); - cards.add(ollamaSettingsForm.getForm(), OLLAMA.getCode()); - cards.add(googleSettingsForm.getForm(), GOOGLE.getCode()); - cardLayout.show(cards, settings.getState().getSelectedService().getCode()); - serviceComboBox = new ComboBox<>(new DefaultComboBoxModel<>(ServiceType.values())); - serviceComboBox.setSelectedItem(settings.getState().getSelectedService()); - serviceComboBox.setPreferredSize(displayNameField.getPreferredSize()); - serviceComboBox.addItemListener(e -> { - ServiceType selectedService = (ServiceType) e.getItem(); - cardLayout.show(cards, selectedService.getCode()); - if (selectedService == OLLAMA) { - ollamaSettingsForm.refreshModels(); - } - }); - mainPanel = FormBuilder.createFormBuilder() - .addLabeledComponent( - CodeGPTBundle.get("settingsConfigurable.displayName.label"), - displayNameField) - .addLabeledComponent( - CodeGPTBundle.get("settingsConfigurable.service.label"), - serviceComboBox) - .addComponent(cards) - .addComponentFillVertically(new JPanel(), 0) - .getPanel(); - } - - public CodeGPTServiceForm getCodeGPTSettingsForm() { - return codeGPTSettingsForm; - } - - public OpenAISettingsForm getOpenAISettingsForm() { - return openAISettingsForm; - } - - public CustomServiceForm getCustomConfigurationSettingsForm() { - return customConfigurationSettingsForm; - } - - public AnthropicSettingsForm getAnthropicSettingsForm() { - return anthropicSettingsForm; - } - - public AzureSettingsForm getAzureSettingsForm() { - return azureSettingsForm; - } - - public LlamaSettingsForm getLlamaSettingsForm() { - return llamaSettingsForm; - } - - public YouSettingsForm getYouSettingsForm() { - return youSettingsForm; - } - - public OllamaSettingsForm getOllamaSettingsForm() { - return ollamaSettingsForm; - } - - public GoogleSettingsForm getGoogleSettingsForm() { - return googleSettingsForm; - } - - public ServiceType getSelectedService() { - return serviceComboBox.getItem(); - } - - public void setSelectedService(ServiceType serviceType) { - serviceComboBox.setSelectedItem(serviceType); } public JPanel getPanel() { - return mainPanel; + return FormBuilder.createFormBuilder() + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.displayName.label"), + displayNameField) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); } public JComponent getPreferredFocusedComponent() { @@ -159,41 +34,4 @@ public class GeneralSettingsComponent { public void setDisplayName(String displayName) { displayNameField.setText(displayName); } - - public void resetForms() { - codeGPTSettingsForm.resetForm(); - openAISettingsForm.resetForm(); - customConfigurationSettingsForm.resetForm(); - anthropicSettingsForm.resetForm(); - azureSettingsForm.resetForm(); - youSettingsForm.resetForm(); - llamaSettingsForm.resetForm(); - ollamaSettingsForm.resetForm(); - googleSettingsForm.resetForm(); - } - - static class DynamicCardLayout extends CardLayout { - - @Override - public Dimension preferredLayoutSize(Container parent) { - Component current = findVisibleComponent(parent); - if (current != null) { - Insets insets = parent.getInsets(); - Dimension preferredSize = current.getPreferredSize(); - preferredSize.width += insets.left + insets.right; - preferredSize.height += insets.top + insets.bottom; - return preferredSize; - } - return super.preferredLayoutSize(parent); - } - - private Component findVisibleComponent(Container parent) { - for (Component comp : parent.getComponents()) { - if (comp.isVisible()) { - return comp; - } - } - return null; - } - } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java index 759788da..88f974c3 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java @@ -1,44 +1,13 @@ package ee.carlrobert.codegpt.settings; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.ANTHROPIC_API_KEY; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_OPENAI_API_KEY; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CODEGPT_API_KEY; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CUSTOM_SERVICE_API_KEY; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.GOOGLE_API_KEY; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LLAMA_API_KEY; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY; - -import com.intellij.openapi.Disposable; import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.util.Disposer; import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.conversations.ConversationsState; -import ee.carlrobert.codegpt.credentials.CredentialsStore; -import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings; -import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettingsForm; -import ee.carlrobert.codegpt.settings.service.azure.AzureSettings; -import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm; -import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceForm; -import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm; -import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsForm; -import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; -import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm; -import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; -import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm; -import ee.carlrobert.codegpt.settings.service.you.YouSettings; -import ee.carlrobert.codegpt.settings.service.you.YouSettingsForm; -import ee.carlrobert.codegpt.telemetry.TelemetryAction; -import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager; -import ee.carlrobert.codegpt.util.ApplicationUtil; import javax.swing.JComponent; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; public class GeneralSettingsConfigurable implements Configurable { - private Disposable parentDisposable; - private GeneralSettingsComponent component; @Nls(capitalization = Nls.Capitalization.Title) @@ -55,127 +24,23 @@ public class GeneralSettingsConfigurable implements Configurable { @Nullable @Override public JComponent createComponent() { - var settings = GeneralSettings.getInstance(); - parentDisposable = Disposer.newDisposable(); - component = new GeneralSettingsComponent(parentDisposable, settings); + component = new GeneralSettingsComponent(GeneralSettings.getInstance()); return component.getPanel(); } @Override public boolean isModified() { var settings = GeneralSettings.getCurrentState(); - - return !component.getDisplayName().equals(settings.getDisplayName()) - || component.getSelectedService() != settings.getSelectedService() - || component.getCodeGPTSettingsForm().isModified() - || OpenAISettings.getInstance().isModified(component.getOpenAISettingsForm()) - || component.getCustomConfigurationSettingsForm().isModified() - || AnthropicSettings.getInstance().isModified(component.getAnthropicSettingsForm()) - || AzureSettings.getInstance().isModified(component.getAzureSettingsForm()) - || YouSettings.getInstance().isModified(component.getYouSettingsForm()) - || LlamaSettings.getInstance().isModified(component.getLlamaSettingsForm()) - || component.getOllamaSettingsForm().isModified() - || component.getGoogleSettingsForm().isModified(); + return !component.getDisplayName().equals(settings.getDisplayName()); } @Override public void apply() { - var settings = GeneralSettings.getCurrentState(); - settings.setDisplayName(component.getDisplayName()); - settings.setSelectedService(component.getSelectedService()); - - applyCodeGPTServiceSettings(component.getCodeGPTSettingsForm()); - var openAISettingsForm = component.getOpenAISettingsForm(); - applyOpenAISettings(openAISettingsForm); - applyCustomOpenAISettings(component.getCustomConfigurationSettingsForm()); - applyAnthropicSettings(component.getAnthropicSettingsForm()); - applyAzureSettings(component.getAzureSettingsForm()); - applyYouSettings(component.getYouSettingsForm()); - applyLlamaSettings(component.getLlamaSettingsForm()); - component.getOllamaSettingsForm().applyChanges(); - applyGoogleSettings(component.getGoogleSettingsForm()); - - var serviceChanged = component.getSelectedService() != settings.getSelectedService(); - var modelChanged = !OpenAISettings.getCurrentState().getModel() - .equals(openAISettingsForm.getModel()); - if (serviceChanged || modelChanged) { - resetActiveTab(); - if (serviceChanged) { - TelemetryAction.SETTINGS_CHANGED.createActionMessage() - .property("service", component.getSelectedService().getCode().toLowerCase()) - .send(); - } - } - } - - private void applyCodeGPTServiceSettings(CodeGPTServiceForm form) { - CredentialsStore.INSTANCE.setCredential(CODEGPT_API_KEY, form.getApiKey()); - form.applyChanges(); - } - - private void applyOpenAISettings(OpenAISettingsForm form) { - CredentialsStore.INSTANCE.setCredential(OPENAI_API_KEY, form.getApiKey()); - OpenAISettings.getInstance().loadState(form.getCurrentState()); - } - - private void applyCustomOpenAISettings(CustomServiceForm form) { - CredentialsStore.INSTANCE.setCredential(CUSTOM_SERVICE_API_KEY, form.getApiKey()); - form.applyChanges(); - } - - private void applyLlamaSettings(LlamaSettingsForm form) { - CredentialsStore.INSTANCE.setCredential( - LLAMA_API_KEY, - form.getLlamaServerPreferencesForm().getApiKey()); - - LlamaSettings.getInstance().loadState(form.getCurrentState()); - } - - private void applyYouSettings(YouSettingsForm form) { - YouSettings.getInstance().loadState(form.getCurrentState()); - } - - private void applyAnthropicSettings(AnthropicSettingsForm form) { - CredentialsStore.INSTANCE.setCredential(ANTHROPIC_API_KEY, form.getApiKey()); - AnthropicSettings.getInstance().loadState(form.getCurrentState()); - } - - private void applyAzureSettings(AzureSettingsForm form) { - AzureSettings.getInstance().loadState(form.getCurrentState()); - CredentialsStore.INSTANCE.setCredential(AZURE_OPENAI_API_KEY, form.getApiKey()); - CredentialsStore.INSTANCE.setCredential( - AZURE_ACTIVE_DIRECTORY_TOKEN, - form.getActiveDirectoryToken()); - } - - private void applyGoogleSettings(GoogleSettingsForm form) { - form.applyChanges(); - CredentialsStore.INSTANCE.setCredential(GOOGLE_API_KEY, form.getApiKey()); + GeneralSettings.getCurrentState().setDisplayName(component.getDisplayName()); } @Override public void reset() { - var settings = GeneralSettings.getCurrentState(); - component.setDisplayName(settings.getDisplayName()); - component.setSelectedService(settings.getSelectedService()); - component.resetForms(); - } - - @Override - public void disposeUIResources() { - if (parentDisposable != null) { - Disposer.dispose(parentDisposable); - } - component = null; - } - - private void resetActiveTab() { - ConversationsState.getInstance().setCurrentConversation(null); - var project = ApplicationUtil.findCurrentProject(); - if (project == null) { - throw new RuntimeException("Could not find current project."); - } - - project.getService(ChatToolWindowContentManager.class).resetAll(); + component.setDisplayName(GeneralSettings.getCurrentState().getDisplayName()); } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java index 514afbac..4852d1c0 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java @@ -8,10 +8,10 @@ public enum ServiceType { CUSTOM_OPENAI("CUSTOM_OPENAI", "service.custom.openai.title", "custom.openai.chat.completion"), ANTHROPIC("ANTHROPIC", "service.anthropic.title", "anthropic.chat.completion"), AZURE("AZURE", "service.azure.title", "azure.chat.completion"), + GOOGLE("GOOGLE", "service.google.title", "google.chat.completion"), YOU("YOU", "service.you.title", "you.chat.completion"), LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion"), - OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion"), - GOOGLE("GOOGLE", "service.google.title", "google.chat.completion"); + OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion"); private final String code; private final String label; diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java index c488a8b8..59c78596 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java @@ -4,9 +4,6 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; -import ee.carlrobert.codegpt.credentials.CredentialsStore; -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @State(name = "CodeGPT_AnthropicSettings", storages = @Storage("CodeGPT_AnthropicSettings.xml")) @@ -32,11 +29,4 @@ public class AnthropicSettings implements PersistentStateComponent headers, Map body) { + public CustomServiceFormTabbedPane(Map headers, Map body) { headersTable = new JBTable( new DefaultTableModel(toArray(headers), new Object[]{"Key", "Value"})); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettings.java index 2bdb024b..b457fccf 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettings.java @@ -1,13 +1,9 @@ package ee.carlrobert.codegpt.settings.service.openai; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY; - import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; -import ee.carlrobert.codegpt.credentials.CredentialsStore; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @State(name = "CodeGPT_OpenAISettings_210", storages = @Storage("CodeGPT_OpenAISettings_210.xml")) @@ -33,11 +29,4 @@ public class OpenAISettings implements PersistentStateComponent { public static YouSettings getInstance() { return ApplicationManager.getApplication().getService(YouSettings.class); } - - public boolean isModified(YouSettingsForm form) { - return !form.getCurrentState().equals(state); - } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt index 02f7dce2..9fb4dea9 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt @@ -29,7 +29,6 @@ class CodeGPTProjectActivity : ProjectActivity { override suspend fun execute(project: Project) { EditorActionsUtil.refreshActions() - CredentialsStore.loadAll() if (YouUserManager.getInstance().authenticationResponse == null) { handleYouServiceAuthenticationAsync() diff --git a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt index 72233af6..d65953da 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt @@ -8,19 +8,12 @@ object CredentialsStore { private val credentialsMap = mutableMapOf() - fun loadAll() { - CredentialKey.values().forEach { - val credentialAttributes = CredentialAttributes(generateServiceName("CodeGPT", it.name)) - val password = PasswordSafe.instance.getPassword(credentialAttributes) - - // Avoid calling setCredential here since it will persist - // the password back into the PasswordSafe unnecessarily. - credentialsMap[it] = password - } - } - @JvmStatic - fun getCredential(key: CredentialKey): String? = credentialsMap[key] + fun getCredential(key: CredentialKey): String? = credentialsMap.getOrPut(key) { + PasswordSafe.instance.getPassword( + CredentialAttributes(generateServiceName("CodeGPT", key.name)) + ) + } fun setCredential(key: CredentialKey, password: String?) { val prevPassword = credentialsMap[key] diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/AnthropicServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/AnthropicServiceConfigurable.kt new file mode 100644 index 00000000..ccbd07dc --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/AnthropicServiceConfigurable.kt @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.ANTHROPIC_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings +import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettingsForm +import javax.swing.JComponent + +class AnthropicServiceConfigurable : Configurable { + + private lateinit var component: AnthropicSettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: Anthropic Service" + } + + override fun createComponent(): JComponent { + component = AnthropicSettingsForm(service().state) + return component.form + } + + override fun isModified(): Boolean { + return component.getCurrentState() != service().state + || component.getApiKey() != getCredential(ANTHROPIC_API_KEY) + } + + override fun apply() { + setCredential(ANTHROPIC_API_KEY, component.getApiKey()) + service().state.selectedService = ServiceType.ANTHROPIC + service().loadState(component.getCurrentState()) + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/AzureServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/AzureServiceConfigurable.kt new file mode 100644 index 00000000..3691ef7a --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/AzureServiceConfigurable.kt @@ -0,0 +1,43 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_OPENAI_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.azure.AzureSettings +import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm +import javax.swing.JComponent + +class AzureServiceConfigurable : Configurable { + + private lateinit var component: AzureSettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: Azure Service" + } + + override fun createComponent(): JComponent { + component = AzureSettingsForm(service().state) + return component.getForm() + } + + override fun isModified(): Boolean { + return component.getCurrentState() != service().state + || component.getActiveDirectoryToken() != getCredential(AZURE_ACTIVE_DIRECTORY_TOKEN) + || component.getApiKey() != getCredential(AZURE_OPENAI_API_KEY) + } + + override fun apply() { + service().state.selectedService = ServiceType.AZURE + service().loadState(component.currentState) + setCredential(AZURE_OPENAI_API_KEY, component.getApiKey()) + setCredential(AZURE_ACTIVE_DIRECTORY_TOKEN, component.getActiveDirectoryToken()) + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/LlamaServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/LlamaServiceConfigurable.kt new file mode 100644 index 00000000..921fda1f --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/LlamaServiceConfigurable.kt @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.LLAMA_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings +import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm +import javax.swing.JComponent + +class LlamaServiceConfigurable : Configurable { + + private lateinit var component: LlamaSettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: Custom Service" + } + + override fun createComponent(): JComponent { + component = LlamaSettingsForm(service().state) + return component + } + + override fun isModified(): Boolean { + return component.getCurrentState() != service().state + || component.llamaServerPreferencesForm.getApiKey() != getCredential(LLAMA_API_KEY) + } + + override fun apply() { + service().state.selectedService = ServiceType.LLAMA_CPP + setCredential(LLAMA_API_KEY, component.llamaServerPreferencesForm.getApiKey()) + service().loadState(component.currentState) + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/OpenAIServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/OpenAIServiceConfigurable.kt new file mode 100644 index 00000000..811be586 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/OpenAIServiceConfigurable.kt @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings +import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm +import javax.swing.JComponent + +class OpenAIServiceConfigurable : Configurable { + + private lateinit var component: OpenAISettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: OpenAI Service" + } + + override fun createComponent(): JComponent { + component = OpenAISettingsForm(service().state) + return component.getForm() + } + + override fun isModified(): Boolean { + return component.getCurrentState() != service().state + || component.getApiKey() != getCredential(OPENAI_API_KEY) + } + + override fun apply() { + service().state.selectedService = ServiceType.OPENAI + setCredential(OPENAI_API_KEY, component.getApiKey()) + service().loadState(component.getCurrentState()) + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurable.kt new file mode 100644 index 00000000..7eb00b32 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurable.kt @@ -0,0 +1,52 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.conversations.ConversationsState +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.telemetry.TelemetryAction +import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager +import ee.carlrobert.codegpt.util.ApplicationUtil.findCurrentProject +import javax.swing.JComponent + +class ServiceConfigurable : Configurable { + + private lateinit var component: ServiceConfigurableComponent + + override fun getDisplayName(): String { + return "CodeGPT: Services" + } + + override fun createComponent(): JComponent { + component = ServiceConfigurableComponent() + return component.getPanel() + } + + override fun isModified(): Boolean { + return component.getSelectedService() != service().state.selectedService + } + + override fun apply() { + val state = service().state + state.selectedService = component.getSelectedService() + + val serviceChanged = component.getSelectedService() != state.selectedService + if (serviceChanged) { + resetActiveTab() + TelemetryAction.SETTINGS_CHANGED.createActionMessage() + .property("service", component.getSelectedService().code.lowercase()) + .send() + } + } + + override fun reset() { + component.setSelectedService(service().state.selectedService) + } + + private fun resetActiveTab() { + service().currentConversation = null + val project = findCurrentProject() + ?: throw RuntimeException("Could not find current project.") + project.getService(ChatToolWindowContentManager::class.java).resetAll() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt new file mode 100644 index 00000000..0e96250e --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ServiceConfigurableComponent.kt @@ -0,0 +1,72 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.ide.DataManager +import com.intellij.openapi.components.service +import com.intellij.openapi.options.ex.Settings +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.EnumComboBoxModel +import com.intellij.ui.components.ActionLink +import com.intellij.ui.components.JBLabel +import com.intellij.util.ui.FormBuilder +import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceConfigurable +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceForm +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceConfigurable +import ee.carlrobert.codegpt.settings.service.google.GoogleSettingsConfigurable +import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsConfigurable +import javax.swing.JPanel + +class ServiceConfigurableComponent { + + var form: CodeGPTServiceForm = CodeGPTServiceForm() + + private var serviceComboBox: ComboBox = + ComboBox(EnumComboBoxModel(ServiceType::class.java)).apply { + selectedItem = service().state.selectedService + } + + fun getSelectedService(): ServiceType { + return serviceComboBox.item + } + + fun setSelectedService(serviceType: ServiceType?) { + serviceComboBox.selectedItem = serviceType + } + + fun getPanel(): JPanel = FormBuilder.createFormBuilder() + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.service.label"), + serviceComboBox + ) + .addVerticalGap(8) + .addComponent(JBLabel("All available providers that can be used with CodeGPT:")) + .addVerticalGap(8) + .addComponent(FormBuilder.createFormBuilder() + .setFormLeftIndent(16).apply { + addLinks(this) + } + .panel) + .addComponentFillVertically(JPanel(), 0) + .panel + + private fun addLinks(formBuilder: FormBuilder) { + mapOf( + "CodeGPT" to CodeGPTServiceConfigurable::class.java, + "OpenAI" to OpenAIServiceConfigurable::class.java, + "Custom OpenAI" to CustomServiceConfigurable::class.java, + "Azure" to AzureServiceConfigurable::class.java, + "Anthropic" to AnthropicServiceConfigurable::class.java, + "Google" to GoogleSettingsConfigurable::class.java, + "You.com" to YouServiceConfigurable::class.java, + "LLaMA C/C++ (Local)" to LlamaServiceConfigurable::class.java, + "Ollama (Local)" to OllamaSettingsConfigurable::class.java, + ).entries.forEach { (name, configurableClass) -> + formBuilder.addComponent(ActionLink(name) { + val context = service().getDataContext(it.source as ActionLink) + val settings = Settings.KEY.getData(context) + settings?.select(settings.find(configurableClass)) + }) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/YouServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/YouServiceConfigurable.kt new file mode 100644 index 00000000..80178a92 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/YouServiceConfigurable.kt @@ -0,0 +1,42 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.util.Disposer +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.you.YouSettings +import ee.carlrobert.codegpt.settings.service.you.YouSettingsForm +import javax.swing.JComponent + +class YouServiceConfigurable : Configurable { + + private var parentDisposable = Disposer.newDisposable() + private lateinit var component: YouSettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: You.com Service" + } + + override fun createComponent(): JComponent { + parentDisposable = Disposer.newDisposable(); + component = YouSettingsForm(service().state, parentDisposable) + return component + } + + override fun isModified(): Boolean { + return component.getCurrentState() != service().state + } + + override fun apply() { + service().state.selectedService = ServiceType.YOU + service().loadState(component.currentState) + } + + override fun disposeUIResources() { + Disposer.dispose(parentDisposable) + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceConfigurable.kt new file mode 100644 index 00000000..975b1983 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceConfigurable.kt @@ -0,0 +1,38 @@ +package ee.carlrobert.codegpt.settings.service.codegpt + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CODEGPT_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.ServiceType +import javax.swing.JComponent + +class CodeGPTServiceConfigurable : Configurable { + + private lateinit var component: CodeGPTServiceForm + + override fun getDisplayName(): String { + return "CodeGPT: CodeGPT Service" + } + + override fun createComponent(): JComponent { + component = CodeGPTServiceForm() + return component.getForm() + } + + override fun isModified(): Boolean { + return component.isModified() || component.getApiKey() != getCredential(CODEGPT_API_KEY) + } + + override fun apply() { + setCredential(CODEGPT_API_KEY, component.getApiKey()) + service().state.selectedService = ServiceType.CODEGPT + component.applyChanges() + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file 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 3336dc70..0e6d7704 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 @@ -86,6 +86,7 @@ class CodeGPTServiceForm { ) .panel ) + .addComponentFillVertically(JPanel(), 0) .panel fun getApiKey() = String(apiKeyField.password).ifEmpty { null } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceConfigurable.kt new file mode 100644 index 00000000..2377160f --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceConfigurable.kt @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.settings.service.custom + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CUSTOM_SERVICE_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.ServiceType +import ee.carlrobert.codegpt.settings.service.custom.form.CustomServiceForm +import javax.swing.JComponent + +class CustomServiceConfigurable : Configurable { + + private lateinit var component: CustomServiceForm + + override fun getDisplayName(): String { + return "CodeGPT: Custom Service" + } + + override fun createComponent(): JComponent { + component = CustomServiceForm() + return component.getForm() + } + + override fun isModified(): Boolean { + return component.isModified() + || component.getApiKey() != getCredential(CUSTOM_SERVICE_API_KEY) + } + + override fun apply() { + setCredential(CUSTOM_SERVICE_API_KEY, component.getApiKey()) + service().state.selectedService = ServiceType.CUSTOM_OPENAI + component.applyChanges() + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceSettings.kt index baa46482..4666838f 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceSettings.kt @@ -3,6 +3,9 @@ package ee.carlrobert.codegpt.settings.service.custom import com.intellij.openapi.components.* import com.intellij.util.xmlb.annotations.OptionTag import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate +import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceChatCompletionTemplate +import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceCodeCompletionTemplate +import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTemplate import ee.carlrobert.codegpt.util.MapConverter @Service diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceChatCompletionForm.kt similarity index 94% rename from src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt rename to src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceChatCompletionForm.kt index b57917aa..24b97408 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceChatCompletionForm.kt @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.settings.service.custom +package ee.carlrobert.codegpt.settings.service.custom.form import com.intellij.openapi.ui.MessageType import com.intellij.ui.components.JBTextField @@ -6,6 +6,8 @@ import com.intellij.util.ui.FormBuilder import ee.carlrobert.codegpt.CodeGPTBundle import ee.carlrobert.codegpt.completions.CompletionRequestProvider import ee.carlrobert.codegpt.completions.CompletionRequestService +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceFormTabbedPane import ee.carlrobert.codegpt.ui.OverlayUtil import ee.carlrobert.llm.client.openai.completion.ErrorDetails import ee.carlrobert.llm.completion.CompletionEventListener diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceCodeCompletionForm.kt similarity index 96% rename from src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt rename to src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceCodeCompletionForm.kt index b2938efd..ce805a04 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceCodeCompletionForm.kt @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.settings.service.custom +package ee.carlrobert.codegpt.settings.service.custom.form import com.intellij.icons.AllIcons.General import com.intellij.ide.HelpTooltip @@ -16,6 +16,8 @@ import ee.carlrobert.codegpt.codecompletions.InfillPromptTemplate import ee.carlrobert.codegpt.codecompletions.InfillRequestDetails import ee.carlrobert.codegpt.completions.CompletionRequestService import ee.carlrobert.codegpt.settings.configuration.Placeholder +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceCodeCompletionSettingsState +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceFormTabbedPane import ee.carlrobert.codegpt.ui.OverlayUtil import ee.carlrobert.llm.client.openai.completion.ErrorDetails import ee.carlrobert.llm.completion.CompletionEventListener diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt similarity index 77% rename from src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt rename to src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt index be995355..83085c3e 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt @@ -1,17 +1,20 @@ -package ee.carlrobert.codegpt.settings.service.custom +package ee.carlrobert.codegpt.settings.service.custom.form import com.intellij.icons.AllIcons.General import com.intellij.ide.HelpTooltip import com.intellij.openapi.components.service import com.intellij.openapi.ui.ComboBox import com.intellij.ui.EnumComboBoxModel -import com.intellij.ui.TitledSeparator import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBPasswordField import com.intellij.util.ui.FormBuilder import ee.carlrobert.codegpt.CodeGPTBundle import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceCodeCompletionSettingsState +import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings +import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTemplate import ee.carlrobert.codegpt.ui.UIUtil import java.awt.FlowLayout import java.net.MalformedURLException @@ -34,8 +37,10 @@ class CustomServiceForm { init { val state = service().state - chatCompletionsForm = CustomServiceChatCompletionForm(state.chatCompletionSettings, this::getApiKey) - codeCompletionsForm = CustomServiceCodeCompletionForm(state.codeCompletionSettings, this::getApiKey) + chatCompletionsForm = + CustomServiceChatCompletionForm(state.chatCompletionSettings, this::getApiKey) + codeCompletionsForm = + CustomServiceCodeCompletionForm(state.codeCompletionSettings, this::getApiKey) tabbedPane = JTabbedPane().apply { add(CodeGPTBundle.get("shared.chatCompletions"), chatCompletionsForm.form) add(CodeGPTBundle.get("shared.codeCompletions"), codeCompletionsForm.form) @@ -65,29 +70,24 @@ class CustomServiceForm { } fun getForm(): JPanel = FormBuilder.createFormBuilder() - .addComponent(TitledSeparator(CodeGPTBundle.get("shared.configuration"))) - .addComponent( - FormBuilder.createFormBuilder() - .setFormLeftIndent(16) - .addLabeledComponent( - CodeGPTBundle.get("settingsConfigurable.service.custom.openai.presetTemplate.label"), - JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)).apply { - add(templateComboBox) - add(Box.createHorizontalStrut(8)) - add(templateHelpText) - } - ) - .addLabeledComponent( - CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"), - apiKeyField - ) - .addComponentToRightColumn( - UIUtil.createComment("settingsConfigurable.service.custom.openai.apiKey.comment") - ) - .addVerticalGap(4) - .addComponent(tabbedPane) - .panel + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.service.custom.openai.presetTemplate.label"), + JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)).apply { + add(templateComboBox) + add(Box.createHorizontalStrut(8)) + add(templateHelpText) + } ) + .addLabeledComponent( + CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"), + apiKeyField + ) + .addComponentToRightColumn( + UIUtil.createComment("settingsConfigurable.service.custom.openai.apiKey.comment") + ) + .addVerticalGap(4) + .addComponent(tabbedPane) + .addComponentFillVertically(JPanel(), 0) .panel fun getApiKey() = String(apiKeyField.password).ifEmpty { null } @@ -102,7 +102,6 @@ class CustomServiceForm { || codeCompletionsForm.url != codeCompletionSettings.url || codeCompletionsForm.headers != codeCompletionSettings.headers || codeCompletionsForm.body != codeCompletionSettings.body - || getApiKey() != getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) } fun applyChanges() { @@ -126,6 +125,7 @@ class CustomServiceForm { fun resetForm() { service().state.run { templateComboBox.item = template + apiKeyField.text = getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) chatCompletionsForm.resetForm(chatCompletionSettings) codeCompletionsForm.resetForm(codeCompletionSettings) } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceChatCompletionTemplate.kt similarity index 98% rename from src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt rename to src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceChatCompletionTemplate.kt index c90369c5..8e873571 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceChatCompletionTemplate.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceChatCompletionTemplate.kt @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.settings.service.custom +package ee.carlrobert.codegpt.settings.service.custom.template enum class CustomServiceChatCompletionTemplate( val url: String, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceCodeCompletionTemplate.kt similarity index 97% rename from src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionTemplate.kt rename to src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceCodeCompletionTemplate.kt index 98ca97ab..653caf9a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceCodeCompletionTemplate.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceCodeCompletionTemplate.kt @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.settings.service.custom +package ee.carlrobert.codegpt.settings.service.custom.template enum class CustomServiceCodeCompletionTemplate( val url: String, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceTemplate.kt similarity index 97% rename from src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt rename to src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceTemplate.kt index f2961943..e80c8121 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/CustomServiceTemplate.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/template/CustomServiceTemplate.kt @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.settings.service.custom +package ee.carlrobert.codegpt.settings.service.custom.template enum class CustomServiceTemplate( val providerName: String, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt index 5ca647a9..34131de0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt @@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.settings.service.google import com.intellij.openapi.components.* import ee.carlrobert.llm.client.google.models.GoogleModel +@Service @State(name = "CodeGPT_GoogleSettings_210", storages = [Storage("CodeGPT_GoogleSettings_210.xml")]) class GoogleSettings : SimplePersistentStateComponent(GoogleSettingsState()) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsConfigurable.kt new file mode 100644 index 00000000..2dc49641 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsConfigurable.kt @@ -0,0 +1,38 @@ +package ee.carlrobert.codegpt.settings.service.google; + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.GOOGLE_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.ServiceType +import javax.swing.JComponent + +class GoogleSettingsConfigurable : Configurable { + + private lateinit var component: GoogleSettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: Google Service" + } + + override fun createComponent(): JComponent { + component = GoogleSettingsForm() + return component.getForm() + } + + override fun isModified(): Boolean { + return component.isModified() || component.getApiKey() != getCredential(GOOGLE_API_KEY) + } + + override fun apply() { + setCredential(GOOGLE_API_KEY, component.getApiKey()) + service().state.selectedService = ServiceType.GOOGLE + component.applyChanges() + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt index 3491a4df..060783b6 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt @@ -16,6 +16,7 @@ import javax.swing.JPanel import javax.swing.event.HyperlinkEvent class GoogleSettingsForm { + private val apiKeyField = JBPasswordField() private val completionModelComboBox: ComboBox @@ -67,8 +68,8 @@ class GoogleSettingsForm { fun getApiKey(): String? = String(apiKeyField.password).ifEmpty { null } fun getModel(): String = (completionModelComboBox.model - .selectedItem as GoogleModel) - .code + .selectedItem as GoogleModel) + .code fun getCurrentState() = GoogleSettingsState().apply { model = getModel() } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsConfigurable.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsConfigurable.kt new file mode 100644 index 00000000..f88fca9a --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsConfigurable.kt @@ -0,0 +1,35 @@ +package ee.carlrobert.codegpt.settings.service.ollama + + +import com.intellij.openapi.components.service +import com.intellij.openapi.options.Configurable +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.ServiceType +import javax.swing.JComponent + +class OllamaSettingsConfigurable : Configurable { + + private lateinit var component: OllamaSettingsForm + + override fun getDisplayName(): String { + return "CodeGPT: Ollama Service" + } + + override fun createComponent(): JComponent { + component = OllamaSettingsForm() + return component.getForm() + } + + override fun isModified(): Boolean { + return component.isModified() + } + + override fun apply() { + component.applyChanges() + service().state.selectedService = ServiceType.OLLAMA + } + + override fun reset() { + component.resetForm() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt index 8ede4ecf..71df61e4 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ollama/OllamaSettingsForm.kt @@ -77,6 +77,7 @@ class OllamaSettingsForm { ) .addComponent(TitledSeparator(CodeGPTBundle.get("shared.codeCompletions"))) .addComponent(UIUtil.withEmptyLeftBorder(codeCompletionConfigurationForm.getForm())) + .addComponentFillVertically(JPanel(), 0) .panel fun getModel(): String { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index bf0503bb..746a6b8d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -19,6 +19,26 @@ + + + + + + + + + + - diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 3ad8afcb..fdf406b8 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -17,7 +17,7 @@ action.statusbar.disableCompletions=Disable Completions settings.displayName=CodeGPT: Settings settings.openaiQuotaExceeded=OpenAI quota exceeded. settingsConfigurable.displayName.label=Display name: -settingsConfigurable.service.label=Service: +settingsConfigurable.service.label=Selected provider: settingsConfigurable.service.codegpt.apiKey.comment=You can find the API key in your User settings. 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.openai.apiKey.comment=You can find the API key in your User settings. @@ -171,15 +171,15 @@ toolwindow.chat.youProCheckBox.enable=Turn on for complex queries toolwindow.chat.youProCheckBox.disable=Turn off for faster responses toolwindow.chat.youProCheckBox.notAllowed=Enable by subscribing to YouPro plan toolwindow.chat.textArea.emptyText=Ask me anything... -service.codegpt.title=CodeGPT Service -service.openai.title=OpenAI Service -service.custom.openai.title=Custom OpenAI Service -service.anthropic.title=Anthropic Service -service.azure.title=Azure Service -service.you.title=You.com Service (Free, Cloud) -service.llama.title=LLaMA C/C++ Port (Free, Local) -service.ollama.title=Ollama (Free, Local) -service.google.title=Google Service +service.codegpt.title=CodeGPT +service.openai.title=OpenAI +service.custom.openai.title=Custom OpenAI +service.anthropic.title=Anthropic +service.azure.title=Azure +service.google.title=Google +service.you.title=You.com +service.llama.title=LLaMA C/C++ (Local) +service.ollama.title=Ollama (Local) validation.error.fieldRequired=This field is required. validation.error.invalidEmail=The email you entered is invalid. validation.error.mustBeNumber=Value must be number. From fedbe11fd266ff19e981f3d74bed33f49da231f3 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Thu, 9 May 2024 13:03:38 +0300 Subject: [PATCH 27/30] fix: long-running tasks on EDT when initializing forms --- .../anthropic/AnthropicSettingsForm.java | 7 +++- .../service/azure/AzureSettingsForm.java | 13 ++++-- .../form/LlamaServerPreferencesForm.java | 41 +++++++++++-------- .../service/openai/OpenAISettingsForm.java | 8 +++- .../settings/service/you/YouSettingsForm.java | 7 +++- .../codegpt/credentials/CredentialsStore.kt | 4 +- .../service/codegpt/CodeGPTServiceForm.kt | 10 ++++- .../service/custom/form/CustomServiceForm.kt | 6 ++- .../service/google/GoogleSettingsForm.kt | 15 +++---- 9 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java index 999128fb..ed11ec5c 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java @@ -2,6 +2,7 @@ package ee.carlrobert.codegpt.settings.service.anthropic; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.ANTHROPIC_API_KEY; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.ui.components.JBPasswordField; import com.intellij.ui.components.JBTextField; import com.intellij.util.ui.FormBuilder; @@ -10,6 +11,7 @@ import ee.carlrobert.codegpt.CodeGPTBundle; import ee.carlrobert.codegpt.credentials.CredentialsStore; import ee.carlrobert.codegpt.ui.UIUtil; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import org.jetbrains.annotations.Nullable; public class AnthropicSettingsForm { @@ -21,7 +23,10 @@ public class AnthropicSettingsForm { public AnthropicSettingsForm(AnthropicSettingsState settings) { apiKeyField = new JBPasswordField(); apiKeyField.setColumns(30); - apiKeyField.setText(CredentialsStore.getCredential(ANTHROPIC_API_KEY)); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + var apiKey = CredentialsStore.getCredential(ANTHROPIC_API_KEY); + SwingUtilities.invokeLater(() -> apiKeyField.setText(apiKey)); + }); apiVersionField = new JBTextField(settings.getApiVersion(), 35); modelField = new JBTextField(settings.getModel(), 35); } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java index 1b2b4391..10f443a2 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java @@ -2,6 +2,7 @@ package ee.carlrobert.codegpt.settings.service.azure; import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.ui.TitledSeparator; import com.intellij.ui.components.JBPasswordField; import com.intellij.ui.components.JBRadioButton; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Map; import javax.swing.ButtonGroup; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import org.jetbrains.annotations.Nullable; public class AzureSettingsForm { @@ -38,15 +40,20 @@ public class AzureSettingsForm { settings.isUseAzureActiveDirectoryAuthentication()); azureApiKeyField = new JBPasswordField(); azureApiKeyField.setColumns(30); - azureApiKeyField.setText(CredentialsStore.getCredential(CredentialKey.AZURE_OPENAI_API_KEY)); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + var apiKey = CredentialsStore.getCredential(CredentialKey.AZURE_OPENAI_API_KEY); + SwingUtilities.invokeLater(() -> azureApiKeyField.setText(apiKey)); + }); azureApiKeyFieldPanel = UI.PanelFactory.panel(azureApiKeyField) .withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label")) .resizeX(false) .createPanel(); azureActiveDirectoryTokenField = new JBPasswordField(); azureActiveDirectoryTokenField.setColumns(30); - azureActiveDirectoryTokenField.setText( - CredentialsStore.getCredential(CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN)); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + var apiKey = CredentialsStore.getCredential(CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN); + SwingUtilities.invokeLater(() -> azureActiveDirectoryTokenField.setText(apiKey)); + }); azureActiveDirectoryTokenFieldPanel = UI.PanelFactory.panel(azureActiveDirectoryTokenField) .withLabel(CodeGPTBundle.get("settingsConfigurable.service.azure.bearerToken.label")) .resizeX(false) diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaServerPreferencesForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaServerPreferencesForm.java index 00c2e822..172a302b 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaServerPreferencesForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/form/LlamaServerPreferencesForm.java @@ -28,6 +28,7 @@ import ee.carlrobert.codegpt.completions.llama.LlamaServerAgent; import ee.carlrobert.codegpt.completions.llama.LlamaServerStartupParams; import ee.carlrobert.codegpt.completions.llama.PromptTemplate; import ee.carlrobert.codegpt.credentials.CredentialsStore; +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState; import ee.carlrobert.codegpt.ui.OverlayUtil; import ee.carlrobert.codegpt.ui.UIUtil; @@ -40,6 +41,7 @@ import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import org.jetbrains.annotations.Nullable; public class LlamaServerPreferencesForm { @@ -86,7 +88,10 @@ public class LlamaServerPreferencesForm { baseHostField = new JBTextField(settings.getBaseHost(), 30); apiKeyField = new JBPasswordField(); apiKeyField.setColumns(30); - apiKeyField.setText(CredentialsStore.getCredential(LLAMA_API_KEY)); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + var apiKey = CredentialsStore.getCredential(CredentialKey.LLAMA_API_KEY); + SwingUtilities.invokeLater(() -> apiKeyField.setText(apiKey)); + }); llamaModelPreferencesForm = new LlamaModelPreferencesForm(); runLocalServerRadioButton = new JBRadioButton("Run local server", @@ -189,17 +194,17 @@ public class LlamaServerPreferencesForm { createComment("settingsConfigurable.service.llama.threads.comment")) .addLabeledComponent( CodeGPTBundle.get("settingsConfigurable.service.llama.additionalParameters.label"), - additionalParametersField) - .addComponentToRightColumn( - createComment( - "settingsConfigurable.service.llama.additionalParameters.comment")) - .addLabeledComponent( - CodeGPTBundle.get( - "settingsConfigurable.service.llama.additionalBuildParameters.label"), - additionalBuildParametersField) - .addComponentToRightColumn( - createComment( - "settingsConfigurable.service.llama.additionalBuildParameters.comment")) + additionalParametersField) + .addComponentToRightColumn( + createComment( + "settingsConfigurable.service.llama.additionalParameters.comment")) + .addLabeledComponent( + CodeGPTBundle.get( + "settingsConfigurable.service.llama.additionalBuildParameters.label"), + additionalBuildParametersField) + .addComponentToRightColumn( + createComment( + "settingsConfigurable.service.llama.additionalBuildParameters.comment")) .addVerticalGap(4) .addComponentFillVertically(new JPanel(), 0) .getPanel())) @@ -354,9 +359,9 @@ public class LlamaServerPreferencesForm { public List getListOfAdditionalParameters() { return Arrays.stream(additionalParametersField.getText().split(",")) - .map(String::trim) - .filter(s -> !s.isBlank()) - .toList(); + .map(String::trim) + .filter(s -> !s.isBlank()) + .toList(); } public String getAdditionalBuildParameters() { @@ -365,9 +370,9 @@ public class LlamaServerPreferencesForm { public List getListOfAdditionalBuildParameters() { return Arrays.stream(additionalBuildParametersField.getText().split(",")) - .map(String::trim) - .filter(s -> !s.isBlank()) - .toList(); + .map(String::trim) + .filter(s -> !s.isBlank()) + .toList(); } public PromptTemplate getPromptTemplate() { diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java index e3663bb9..ac65ef41 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java @@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.settings.service.openai; import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY; import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.ui.EnumComboBoxModel; import com.intellij.ui.TitledSeparator; @@ -12,10 +13,12 @@ import com.intellij.util.ui.FormBuilder; import com.intellij.util.ui.UI; import ee.carlrobert.codegpt.CodeGPTBundle; import ee.carlrobert.codegpt.credentials.CredentialsStore; +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey; import ee.carlrobert.codegpt.settings.service.CodeCompletionConfigurationForm; import ee.carlrobert.codegpt.ui.UIUtil; import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import org.jetbrains.annotations.Nullable; public class OpenAISettingsForm { @@ -28,7 +31,10 @@ public class OpenAISettingsForm { public OpenAISettingsForm(OpenAISettingsState settings) { apiKeyField = new JBPasswordField(); apiKeyField.setColumns(30); - apiKeyField.setText(CredentialsStore.getCredential(OPENAI_API_KEY)); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + var apiKey = CredentialsStore.getCredential(CredentialKey.OPENAI_API_KEY); + SwingUtilities.invokeLater(() -> apiKeyField.setText(apiKey)); + }); organizationField = new JBTextField(settings.getOrganization(), 30); completionModelComboBox = new ComboBox<>( new EnumComboBoxModel<>(OpenAIChatCompletionModel.class)); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java index a834b331..e6936bbb 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java @@ -1,9 +1,9 @@ package ee.carlrobert.codegpt.settings.service.you; -import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.YOU_ACCOUNT_PASSWORD; import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ComponentValidator; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.text.StringUtil; @@ -53,7 +53,10 @@ public class YouSettingsForm extends JPanel { passwordField = new JBPasswordField(); passwordField.setColumns(25); if (!settings.getEmail().isEmpty()) { - passwordField.setText(CredentialsStore.getCredential(YOU_ACCOUNT_PASSWORD)); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + var apiKey = CredentialsStore.getCredential(CredentialKey.YOU_ACCOUNT_PASSWORD); + SwingUtilities.invokeLater(() -> passwordField.setText(apiKey)); + }); } signInButton = new JButton(CodeGPTBundle.get("settingsConfigurable.service.you.signIn.label")); signUpTextPane = createSignUpTextPane(); diff --git a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt index d65953da..3f8433d8 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt @@ -3,16 +3,18 @@ package ee.carlrobert.codegpt.credentials import com.intellij.credentialStore.CredentialAttributes import com.intellij.credentialStore.generateServiceName import com.intellij.ide.passwordSafe.PasswordSafe +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread object CredentialsStore { private val credentialsMap = mutableMapOf() @JvmStatic + @RequiresBackgroundThread fun getCredential(key: CredentialKey): String? = credentialsMap.getOrPut(key) { PasswordSafe.instance.getPassword( CredentialAttributes(generateServiceName("CodeGPT", key.name)) - ) + ) ?: "" } fun setCredential(key: CredentialKey, password: String?) { 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 0e6d7704..2bb4870d 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 @@ -9,7 +9,6 @@ import com.intellij.ui.components.JBPasswordField import com.intellij.ui.components.fields.IntegerField import com.intellij.util.ui.FormBuilder import ee.carlrobert.codegpt.CodeGPTBundle -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CODEGPT_API_KEY import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential @@ -18,6 +17,8 @@ import ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels import ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels.AVAILABLE_CHAT_MODELS import ee.carlrobert.llm.client.codegpt.CodeGPTAvailableModels.AVAILABLE_CODE_MODELS import ee.carlrobert.llm.client.codegpt.CodeGPTModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import org.jdesktop.swingx.combobox.ListComboBoxModel import java.awt.Component import javax.swing.DefaultListCellRenderer @@ -28,7 +29,6 @@ class CodeGPTServiceForm { private val apiKeyField = JBPasswordField().apply { columns = 30 - text = getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) } private val chatCompletionModelComboBox = @@ -56,6 +56,12 @@ class CodeGPTServiceForm { value = service().state.codeCompletionSettings.maxTokens } + init { + apiKeyField.text = runBlocking(Dispatchers.IO) { + getCredential(CODEGPT_API_KEY) + } + } + fun getForm(): JPanel = FormBuilder.createFormBuilder() .addComponent(TitledSeparator(CodeGPTBundle.get("shared.configuration"))) .addComponent( diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt index 83085c3e..c56d4618 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/custom/form/CustomServiceForm.kt @@ -16,6 +16,8 @@ import ee.carlrobert.codegpt.settings.service.custom.CustomServiceCodeCompletion import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTemplate import ee.carlrobert.codegpt.ui.UIUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import java.awt.FlowLayout import java.net.MalformedURLException import java.net.URL @@ -27,7 +29,6 @@ class CustomServiceForm { private val apiKeyField = JBPasswordField().apply { columns = 30 - text = getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) } private val templateHelpText = JBLabel(General.ContextHelp) private val templateComboBox = ComboBox(EnumComboBoxModel(CustomServiceTemplate::class.java)) @@ -37,6 +38,9 @@ class CustomServiceForm { init { val state = service().state + apiKeyField.text = runBlocking(Dispatchers.IO) { + getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) + } chatCompletionsForm = CustomServiceChatCompletionForm(state.chatCompletionSettings, this::getApiKey) codeCompletionsForm = diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt index 060783b6..a04a26d9 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt @@ -8,10 +8,12 @@ import com.intellij.ui.components.JBPasswordField import com.intellij.util.ui.FormBuilder import com.intellij.util.ui.UI import ee.carlrobert.codegpt.CodeGPTBundle -import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.GOOGLE_API_KEY import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential import ee.carlrobert.codegpt.ui.UIUtil import ee.carlrobert.llm.client.google.models.GoogleModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import javax.swing.JPanel import javax.swing.event.HyperlinkEvent @@ -23,8 +25,9 @@ class GoogleSettingsForm { init { val state = service().state apiKeyField.columns = 30 - apiKeyField.text = - getCredential(CredentialKey.GOOGLE_API_KEY) + apiKeyField.text = runBlocking(Dispatchers.IO) { + getCredential(GOOGLE_API_KEY) + } completionModelComboBox = ComboBox( EnumComboBoxModel(GoogleModel::class.java) ) @@ -75,13 +78,12 @@ class GoogleSettingsForm { fun resetForm() { val state = service().state - apiKeyField.text = - getCredential(CredentialKey.GOOGLE_API_KEY) + apiKeyField.text = getCredential(GOOGLE_API_KEY) completionModelComboBox.selectedItem = GoogleModel.findByCode(state.model) } fun isModified(): Boolean = service().state.run { - model != getModel() || getApiKey() != getCredential(CredentialKey.GOOGLE_API_KEY) + model != getModel() || getApiKey() != getCredential(GOOGLE_API_KEY) } fun applyChanges() { @@ -89,5 +91,4 @@ class GoogleSettingsForm { model = getModel() } } - } From 59acb59843bb99b9b093126e14f104485af998c5 Mon Sep 17 00:00:00 2001 From: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com> Date: Thu, 9 May 2024 12:08:55 +0200 Subject: [PATCH 28/30] chore: Update to CodeGemma 1.1 7b Instruct (#534) --- .../codegpt/completions/HuggingFaceModel.java | 12 ++++++------ .../codegpt/completions/llama/LlamaModel.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index f11c8419..f3e98e12 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -94,15 +94,15 @@ public enum HuggingFaceModel { PHI_3_3_8B_4K_FP16(4, 16, "Phi-3-mini-4k-instruct-GGUF", "Phi-3-mini-4k-instruct-fp16.gguf", "lmstudio-community", 7.64), - CODE_GEMMA_7B_Q3_K_M(7, 3, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q3_K_M.gguf", - "lmstudio-community", 4.37), - CODE_GEMMA_7B_Q4_K_M(7, 4, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q4_K_M.gguf", + CODE_GEMMA_7B_Q3_K_L(7, 3, "codegemma-1.1-7b-it-GGUF", "codegemma-1.1-7b-it-Q3_K_L.gguf", + "lmstudio-community", 4.71), + CODE_GEMMA_7B_Q4_K_M(7, 4, "codegemma-1.1-7b-it-GGUF", "codegemma-1.1-7b-it-Q4_K_M.gguf", "lmstudio-community", 5.33), - CODE_GEMMA_7B_Q5_K_M(7, 5, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q5_K_M.gguf", + CODE_GEMMA_7B_Q5_K_M(7, 5, "codegemma-1.1-7b-it-GGUF", "codegemma-1.1-7b-it-Q5_K_M.gguf", "lmstudio-community", 6.14), - CODE_GEMMA_7B_Q6_K(7, 6, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q6_K.gguf", + CODE_GEMMA_7B_Q6_K(7, 6, "codegemma-1.1-7b-it-GGUF", "codegemma-1.1-7b-it-Q6_K.gguf", "lmstudio-community", 7.01), - CODE_GEMMA_7B_Q8_0(7, 8, "codegemma-7b-it-GGUF", "codegemma-7b-it-Q8_0.gguf", + CODE_GEMMA_7B_Q8_0(7, 8, "codegemma-1.1-7b-it-GGUF", "codegemma-1.1-7b-it-Q8_0.gguf", "lmstudio-community", 9.08), CODE_QWEN_1_5_7B_Q3_K_M(7, 3, "Qwen_-_CodeQwen1.5-7B-Chat-gguf", diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java index 69840481..7ea7e024 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java @@ -122,7 +122,7 @@ public enum LlamaModel { PromptTemplate.CODE_GEMMA, InfillPromptTemplate.CODE_GEMMA, List.of( - HuggingFaceModel.CODE_GEMMA_7B_Q3_K_M, + HuggingFaceModel.CODE_GEMMA_7B_Q3_K_L, HuggingFaceModel.CODE_GEMMA_7B_Q4_K_M, HuggingFaceModel.CODE_GEMMA_7B_Q5_K_M, HuggingFaceModel.CODE_GEMMA_7B_Q6_K, From 69bef7099d300107535da3a2fb93e29be4a21997 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 13:09:07 +0300 Subject: [PATCH 29/30] chore(deps): bump org.jetbrains.kotlin:kotlin-gradle-plugin (#540) Bumps [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.9.23 to 1.9.24. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.23...v1.9.24) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 58704e27..f37f9b1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ jackson = "2.17.0" jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2" -kotlin = "1.9.23" +kotlin = "1.9.24" llm-client = "0.8.1" okio = "3.9.0" tree-sitter = "0.22.5" From 6344f225c999d87dc407c750245922c44eb8b317 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 13:09:19 +0300 Subject: [PATCH 30/30] chore(deps): bump com.fasterxml.jackson:jackson-bom (#539) Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.17.0 to 2.17.1. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.17.0...jackson-bom-2.17.1) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f37f9b1a..a68e1b61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ commons-text = "1.12.0" flexmark = "0.64.8" gradle-intellij-plugin-version = "1.17.3" gson = "2.10.1" -jackson = "2.17.0" +jackson = "2.17.1" jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2"