diff --git a/codegpt-treesitter/build.gradle.kts b/codegpt-treesitter/build.gradle.kts index 532f50f5..7df5b0fd 100644 --- a/codegpt-treesitter/build.gradle.kts +++ b/codegpt-treesitter/build.gradle.kts @@ -9,15 +9,15 @@ 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-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") @@ -25,16 +25,16 @@ 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") 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 49c0c4c7..a68e1b61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,18 +3,18 @@ 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" -jackson = "2.17.0" +jackson = "2.17.1" jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2" -kotlin = "1.9.23" -llm-client = "0.7.2" +kotlin = "1.9.24" +llm-client = "0.8.1" 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" } 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/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index 6dbeaf12..34311e2a 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -8,14 +8,18 @@ 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); 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); 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/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/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index ee1506d4..6f4ce8a9 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -1,18 +1,24 @@ 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; 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.codegpt.CodeGPTClient; +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; import ee.carlrobert.llm.client.you.UTMParameters; import ee.carlrobert.llm.client.you.YouClient; @@ -21,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() { @@ -92,6 +99,22 @@ 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 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 58f28c3a..376df97e 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; @@ -40,7 +40,15 @@ 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; 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 +64,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; @@ -116,7 +125,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), @@ -124,18 +134,23 @@ 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) { return buildCustomOpenAIChatCompletionRequest( - ApplicationManager.getApplication().getService(CustomServiceState.class) + ApplicationManager.getApplication().getService(CustomServiceSettings.class) + .getState() .getChatCompletionSettings(), List.of( new OpenAIChatCompletionStandardMessage( @@ -205,19 +220,29 @@ 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) .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) { return buildCustomOpenAIChatCompletionRequest( settings, - buildMessages(callParameters), + buildOpenAIMessages(callParameters), true); } @@ -225,8 +250,16 @@ public class CompletionRequestProvider { CustomServiceChatCompletionSettingsState settings, List messages, boolean streamRequest) { + return buildCustomOpenAIChatCompletionRequest(settings, messages, streamRequest, + CredentialsStore.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")) { @@ -294,7 +327,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) { @@ -326,7 +420,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) { @@ -342,10 +438,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) { @@ -368,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, @@ -393,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 edacf655..d3592f69 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; @@ -10,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; @@ -21,16 +17,25 @@ 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; 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; @@ -77,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(), @@ -86,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), @@ -106,27 +120,18 @@ public final class CompletionRequestService { callParameters.getMessage(), callParameters.getConversationType()), eventListener); - }; - } - - 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); - default -> - throw new IllegalArgumentException("Code completion not supported for selected service"); + case OLLAMA -> CompletionClientProvider.getOllamaClient().getChatCompletionAsync( + requestProvider.buildOllamaChatCompletionRequest(callParameters), + eventListener); + case GOOGLE -> { + var settings = application.getService(GoogleSettings.class).getState(); + yield CompletionClientProvider.getGoogleClient().getChatCompletionAsync( + requestProvider.buildGoogleChatCompletionRequest( + settings.getModel(), + callParameters), + settings.getModel(), + eventListener); + } }; } @@ -135,13 +140,17 @@ 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()) .build(); var selectedService = GeneralSettings.getCurrentState().getSelectedService(); switch (selectedService) { + case CODEGPT: + CompletionClientProvider.getCodeGPTClient() + .getChatCompletionAsync(openaiRequest, eventListener); + break; case OPENAI: CompletionClientProvider.getOpenAIClient() .getChatCompletionAsync(openaiRequest, eventListener); @@ -191,6 +200,35 @@ 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; + 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; @@ -226,19 +264,16 @@ 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 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/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index fc9f06b8..f3e98e12 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -7,67 +7,138 @@ import java.net.URL; 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-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-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-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-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_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_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-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-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-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-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"), - 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-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-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_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-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-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-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", + "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"); + HuggingFaceModel(int parameterSize, int quantization, String directory, String fileName) { + this(parameterSize, quantization, directory, fileName, "TheBloke", null); } - 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; } public int getParameterSize() { @@ -82,17 +153,18 @@ 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)); - } - 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); } @@ -100,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() { - 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; - } - - @Override - public String toString() { + public String getQuantizationLabel() { return format("%d-bit precision", 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..7ea7e024 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, @@ -99,7 +99,52 @@ 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_Q6_K, + HuggingFaceModel.PHI_3_3_8B_4K_Q8_0, + HuggingFaceModel.PHI_3_3_8B_4K_FP16)), + CODE_GEMMA( + "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_Q3_K_L, + HuggingFaceModel.CODE_GEMMA_7B_Q4_K_M, + 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; 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..9e7c161b 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,67 @@ 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(); + } + }, + CODE_GEMMA("CodeGemma 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(); + } + }, + 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/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 8e5757fd..3d5352b7 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java @@ -8,7 +8,10 @@ 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; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import java.time.LocalDateTime; import java.util.ArrayList; @@ -186,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(); @@ -195,9 +203,15 @@ 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 -> application.getService(OllamaSettings.class) + .getState() + .getModel(); + case GOOGLE -> application.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 56d9ba3c..39a55745 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java @@ -10,7 +10,10 @@ 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; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import org.jetbrains.annotations.NotNull; @@ -69,10 +72,18 @@ public class GeneralSettings implements PersistentStateComponent serviceComboBox; - private final OpenAISettingsForm openAISettingsForm; - private final CustomServiceForm customConfigurationSettingsForm; - private final AnthropicSettingsForm anthropicSettingsForm; - private final AzureSettingsForm azureSettingsForm; - private final YouSettingsForm youSettingsForm; - private final LlamaSettingsForm llamaSettingsForm; - public GeneralSettingsComponent(Disposable parentDisposable, GeneralSettings settings) { + public GeneralSettingsComponent(GeneralSettings settings) { displayNameField = new JBTextField(settings.getState().getDisplayName(), 20); - 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()); - - var cardLayout = new DynamicCardLayout(); - var cards = new JPanel(cardLayout); - 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()); - var serviceComboBoxModel = new DefaultComboBoxModel(); - serviceComboBoxModel.addAll(Arrays.stream(ServiceType.values()).toList()); - serviceComboBox = new ComboBox<>(serviceComboBoxModel); - serviceComboBox.setSelectedItem(OPENAI); - serviceComboBox.setPreferredSize(displayNameField.getPreferredSize()); - serviceComboBox.addItemListener(e -> - cardLayout.show(cards, ((ServiceType) e.getItem()).getCode())); - 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 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 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() { @@ -129,38 +34,4 @@ public class GeneralSettingsComponent { public void setDisplayName(String displayName) { displayNameField.setText(displayName); } - - public void resetForms() { - openAISettingsForm.resetForm(); - customConfigurationSettingsForm.resetForm(); - anthropicSettingsForm.resetForm(); - azureSettingsForm.resetForm(); - youSettingsForm.resetForm(); - llamaSettingsForm.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 3d12089a..88f974c3 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettingsConfigurable.java @@ -1,40 +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.CUSTOM_SERVICE_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.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.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) @@ -51,111 +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() - || 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()); + return !component.getDisplayName().equals(settings.getDisplayName()); } @Override public void apply() { - var settings = GeneralSettings.getCurrentState(); - settings.setDisplayName(component.getDisplayName()); - settings.setSelectedService(component.getSelectedService()); - - var openAISettingsForm = component.getOpenAISettingsForm(); - applyOpenAISettings(openAISettingsForm); - applyCustomOpenAISettings(component.getCustomConfigurationSettingsForm()); - applyAnthropicSettings(component.getAnthropicSettingsForm()); - applyAzureSettings(component.getAzureSettingsForm()); - applyYouSettings(component.getYouSettingsForm()); - applyLlamaSettings(component.getLlamaSettingsForm()); - - 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 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()); + 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/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/configuration/ConfigurationComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java index 77f54226..26c6be61 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java @@ -190,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 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..4852d1c0 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/ServiceType.java @@ -3,12 +3,15 @@ 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"), 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"); + 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/anthropic/AnthropicSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettings.java index 4ea11a0c..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 { + var apiKey = CredentialsStore.getCredential(ANTHROPIC_API_KEY); + SwingUtilities.invokeLater(() -> apiKeyField.setText(apiKey)); + }); apiVersionField = new JBTextField(settings.getApiVersion(), 35); modelField = new JBTextField(settings.getModel(), 35); } public JPanel getForm() { return FormBuilder.createFormBuilder() - .addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration"))) - .addComponent(withEmptyLeftBorder(UI.PanelFactory.grid() + .addComponent(UI.PanelFactory.grid() .add(UI.PanelFactory.panel(apiKeyField) .withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label")) .resizeX(false) @@ -49,7 +51,7 @@ public class AnthropicSettingsForm { .withComment(CodeGPTBundle.get( "settingsConfigurable.service.anthropic.model.comment")) .resizeX(false)) - .createPanel())) + .createPanel()) .addComponentFillVertically(new JPanel(), 0) .getPanel(); } @@ -63,7 +65,7 @@ public class AnthropicSettingsForm { public void resetForm() { var state = AnthropicSettings.getCurrentState(); - apiKeyField.setText(CredentialsStore.INSTANCE.getCredential(ANTHROPIC_API_KEY)); + apiKeyField.setText(CredentialsStore.getCredential(ANTHROPIC_API_KEY)); apiVersionField.setText(state.getApiVersion()); modelField.setText(state.getModel()); } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettings.java index 7df50c07..d090998f 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettings.java @@ -1,14 +1,9 @@ package ee.carlrobert.codegpt.settings.service.azure; -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 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_AzureSettings_210", storages = @Storage("CodeGPT_AzureSettings_210.xml")) @@ -34,14 +29,4 @@ public class AzureSettings implements PersistentStateComponent { + 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.INSTANCE.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) @@ -119,10 +125,9 @@ public class AzureSettingsForm { public void resetForm() { var state = AzureSettings.getCurrentState(); - azureApiKeyField.setText( - CredentialsStore.INSTANCE.getCredential(CredentialKey.AZURE_OPENAI_API_KEY)); + azureApiKeyField.setText(CredentialsStore.getCredential(CredentialKey.AZURE_OPENAI_API_KEY)); azureActiveDirectoryTokenField.setText( - CredentialsStore.INSTANCE.getCredential(CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN)); + CredentialsStore.getCredential(CredentialKey.AZURE_ACTIVE_DIRECTORY_TOKEN)); useAzureApiKeyAuthenticationRadioButton.setSelected(state.isUseAzureApiKeyAuthentication()); useAzureActiveDirectoryAuthenticationRadioButton.setSelected( state.isUseAzureActiveDirectoryAuthentication()); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceFormTabbedPane.java b/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceFormTabbedPane.java index 2b5d828f..581b1c29 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceFormTabbedPane.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceFormTabbedPane.java @@ -13,12 +13,12 @@ import java.util.Map.Entry; import javax.swing.JPanel; import javax.swing.table.DefaultTableModel; -class CustomServiceFormTabbedPane extends JBTabbedPane { +public class CustomServiceFormTabbedPane extends JBTabbedPane { private final JBTable headersTable; private final JBTable bodyTable; - CustomServiceFormTabbedPane(Map 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/llama/LlamaSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/llama/LlamaSettings.java index ef952d2f..c4139391 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,9 @@ 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; import org.apache.commons.lang3.StringUtils; @@ -25,12 +28,32 @@ public class LlamaSettings implements PersistentStateComponent" + + "

File Size: %.2f GB

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

File Size: %.2f GB

" + "

Max RAM Required: %.2f GB

" @@ -364,7 +368,7 @@ public class LlamaModelPreferencesForm { return comboBox; } - private ComboBox createHuggingFaceComboBox( + private ComboBox createModelQuantizationComboBox( DefaultComboBoxModel huggingFaceComboBoxModel, JBLabel modelExistsIcon, JBLabel modelDetailsLabel, @@ -379,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; } 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 cbd2bae3..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.INSTANCE.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", @@ -131,7 +136,7 @@ public class LlamaServerPreferencesForm { additionalBuildParametersField.setText(state.getAdditionalBuildParameters()); remotePromptTemplatePanel.setPromptTemplate(state.getRemoteModelPromptTemplate()); // ? infillPromptTemplatePanel.setPromptTemplate(state.getRemoteModelInfillPromptTemplate()); - apiKeyField.setText(CredentialsStore.INSTANCE.getCredential(LLAMA_API_KEY)); + apiKeyField.setText(CredentialsStore.getCredential(LLAMA_API_KEY)); } public JComponent createUseExistingServerForm() { @@ -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/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/OpenAISettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettings.java index d65a1576..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 { + 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)); @@ -36,7 +42,8 @@ public class OpenAISettingsForm { OpenAIChatCompletionModel.findByCode(settings.getModel())); codeCompletionConfigurationForm = new CodeCompletionConfigurationForm( settings.isCodeCompletionsEnabled(), - settings.getCodeCompletionMaxTokens()); + settings.getCodeCompletionMaxTokens(), + null); } public JPanel getForm() { @@ -57,10 +64,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(); } @@ -87,7 +94,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/YouSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettings.java index 0293d127..3bf13625 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettings.java @@ -29,8 +29,4 @@ public class YouSettings 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/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/you/YouSettingsForm.java index f3def2ed..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.INSTANCE.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/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 71aef244..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,8 +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; @@ -18,15 +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.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; @@ -38,15 +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; public ModelComboBoxAction(Runnable onModelChange, ServiceType selectedService) { this.onModelChange = onModelChange; - settings = GeneralSettings.getCurrentState(); - openAISettings = OpenAISettings.getCurrentState(); - youSettings = YouSettings.getCurrentState(); updateTemplatePresentation(selectedService); subscribeToYouSignedOutTopic(ApplicationManager.getApplication().getMessageBus().connect()); @@ -66,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( @@ -103,6 +112,19 @@ public class ModelComboBoxAction extends ComboBoxAction { getLlamaCppPresentationText(), Icons.Llama, presentation)); + actionGroup.addSeparator("Ollama"); + ApplicationManager.getApplication() + .getService(OllamaSettings.class) + .getState() + .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"); @@ -144,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); @@ -168,18 +203,30 @@ 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: templatePresentation.setText(getLlamaCppPresentationText()); templatePresentation.setIcon(Icons.Llama); break; + case OLLAMA: + templatePresentation.setIcon(Icons.Ollama); + templatePresentation.setText(application.getService(OllamaSettings.class) + .getState() + .getModel()); + break; + case GOOGLE: + templatePresentation.setText("Google (Gemini)"); + templatePresentation.setIcon(Icons.Google); + break; default: + break; } } @@ -229,12 +276,73 @@ 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 + ) { + 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) { + ApplicationManager.getApplication() + .getService(OllamaSettings.class) + .getState() + .setModel(model); + handleModelChange( + OLLAMA, + model, + Icons.Ollama, + comboBoxPresentation); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + }; + } + private AnAction createOpenAIModelAction( OpenAIChatCompletionModel model, Presentation comboBoxPresentation) { @@ -249,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(), @@ -278,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(), @@ -307,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/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/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/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/actions/CodeCompletionFeatureToggleActions.kt b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt index bf5d7145..6a0b29d0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/actions/CodeCompletionFeatureToggleActions.kt @@ -4,57 +4,69 @@ 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 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings abstract class CodeCompletionFeatureToggleActions( private val enableFeatureAction: Boolean ) : DumbAwareAction() { - 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) { + CODEGPT -> + service().state.codeCompletionSettings.codeCompletionsEnabled + + 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, + GOOGLE, + 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 - ) + val codeCompletionEnabled = + service().isCodeCompletionsEnabled(selectedService) + e.presentation.isVisible = codeCompletionEnabled != enableFeatureAction + e.presentation.isEnabled = when (selectedService) { + CODEGPT, + OPENAI, + CUSTOM_OPENAI, + LLAMA_CPP, + OLLAMA -> true + + ANTHROPIC, + AZURE, + YOU, + GOOGLE, + null -> false + } } 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.getCurrentState().isCodeCompletionsEnabled - else -> false - } - } } class EnableCompletionsAction : CodeCompletionFeatureToggleActions(true) -class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false) \ No newline at end of file +class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt index d7723f0d..3fe627dc 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionRequestFactory.kt @@ -7,11 +7,15 @@ 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 +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.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 @@ -20,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) @@ -33,17 +49,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 { @@ -71,6 +106,22 @@ object CodeCompletionRequestFactory { .build() } + 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 { if (!settings.isRunLocalServer) { return settings.remoteModelInfillPromptTemplate @@ -94,4 +145,4 @@ object CodeCompletionRequestFactory { else -> value } } -} \ No newline at end of file +} 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 79de4962..7494e875 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/CodeGPTInlineCompletionProvider.kt @@ -16,11 +16,12 @@ 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 import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings import ee.carlrobert.codegpt.ui.OverlayUtil.showNotification import ee.carlrobert.llm.client.openai.completion.ErrorDetails @@ -48,7 +49,8 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider { get() = CodeCompletionSuggestionUpdateAdapter() override suspend fun getSuggestion(request: InlineCompletionRequest): InlineCompletionSingleSuggestion { - if (request.editor.project == null) { + val project = request.editor.project + if (project == null) { logger.error("Could not find project") return InlineCompletionSingleSuggestion.build(elements = emptyFlow()) } @@ -58,7 +60,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() @@ -80,10 +82,16 @@ 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 - else -> false + ServiceType.OLLAMA -> service().state.codeCompletionsEnabled + 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/codecompletions/InfillPromptTemplate.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt index ea68aa40..e434418d 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/InfillPromptTemplate.kt @@ -7,11 +7,24 @@ 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|>"
+        }
+    },
+    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/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt
index 284c6084..3f8433d8 100644
--- a/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt
+++ b/src/main/kotlin/ee/carlrobert/codegpt/credentials/CredentialsStore.kt
@@ -3,30 +3,27 @@ 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()
 
-    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
+    @RequiresBackgroundThread
+    fun getCredential(key: CredentialKey): String? = credentialsMap.getOrPut(key) {
+        PasswordSafe.instance.getPassword(
+            CredentialAttributes(generateServiceName("CodeGPT", key.name))
+        ) ?: ""
     }
 
-    fun getCredential(key: CredentialKey): String? = credentialsMap[key]
-
     fun setCredential(key: CredentialKey, password: String?) {
         val prevPassword = credentialsMap[key]
         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,12 +31,14 @@ 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,
         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/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/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/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 new file mode 100644 index 00000000..2bb4870d --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt @@ -0,0 +1,148 @@ +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.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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +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 + } + + 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 + } + + init { + apiKeyField.text = runBlocking(Dispatchers.IO) { + getCredential(CODEGPT_API_KEY) + } + } + + 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 + ) + .addComponentFillVertically(JPanel(), 0) + .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/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 86% 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 7bf46f95..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 @@ -15,7 +17,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 +72,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/form/CustomServiceCodeCompletionForm.kt similarity index 89% 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 fcac639b..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 @@ -28,7 +30,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"), @@ -37,7 +42,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) } @@ -138,7 +143,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/form/CustomServiceForm.kt similarity index 75% 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 b5c8344d..c56d4618 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,18 +1,23 @@ -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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import java.awt.FlowLayout import java.net.MalformedURLException import java.net.URL @@ -24,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)) @@ -34,8 +38,13 @@ class CustomServiceForm { init { val state = service().state - chatCompletionsForm = CustomServiceChatCompletionForm(state.chatCompletionSettings) - codeCompletionsForm = CustomServiceCodeCompletionForm(state.codeCompletionSettings) + apiKeyField.text = runBlocking(Dispatchers.IO) { + getCredential(CredentialKey.CUSTOM_SERVICE_API_KEY) + } + 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 +74,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 +106,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 +129,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 91% 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 f6d39271..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, @@ -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/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 90% 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 2d837d4b..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, @@ -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 { 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..34131de0 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettings.kt @@ -0,0 +1,12 @@ +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()) + +class GoogleSettingsState : BaseState() { + var model by string(GoogleModel.GEMINI_PRO.code) +} 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 new file mode 100644 index 00000000..a04a26d9 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/google/GoogleSettingsForm.kt @@ -0,0 +1,94 @@ +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.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 + +class GoogleSettingsForm { + + private val apiKeyField = JBPasswordField() + private val completionModelComboBox: ComboBox + + init { + val state = service().state + apiKeyField.columns = 30 + apiKeyField.text = runBlocking(Dispatchers.IO) { + getCredential(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(GOOGLE_API_KEY) + completionModelComboBox.selectedItem = GoogleModel.findByCode(state.model) + } + + fun isModified(): Boolean = service().state.run { + model != getModel() || getApiKey() != getCredential(GOOGLE_API_KEY) + } + + fun applyChanges() { + service().state.run { + model = getModel() + } + } +} 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/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 new file mode 100644 index 00000000..71df61e4 --- /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() } + } + + 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())) + .addComponentFillVertically(JPanel(), 0) + .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 + } + + 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"))) + } + } + + private fun disableModelComboBoxWithPlaceholder(placeholderModel: ComboBoxModel) { + invokeLater { + modelComboBox.apply { + model = placeholderModel + isEnabled = false + } + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index a8492b9c..62846758 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 + + + + + + + + + + + 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/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/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..fdf406b8 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -17,12 +17,15 @@ 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. 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. @@ -116,6 +119,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 @@ -167,12 +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.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.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. @@ -204,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/codecompletions/CodeCompletionServiceTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt index 7f6a6057..cd809ea2 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.kt @@ -39,7 +39,7 @@ class CodeCompletionServiceTest : IntegrationTest() { assertThat(request.method).isEqualTo("POST") assertThat(request.body) .extracting("prompt") - .isEqualTo(InfillPromptTemplate.LLAMA.buildPrompt(prefix, suffix)) + .isEqualTo(InfillPromptTemplate.CODE_LLAMA.buildPrompt(prefix, suffix)) listOf( jsonMapResponse( e("content", expectedCompletion), 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 86161a1e..5e99ec2a 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,74 @@ 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 } + } + + 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/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt index 84dad146..c78286b0 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/completions/PromptTemplateTest.kt @@ -2,8 +2,11 @@ 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 import ee.carlrobert.codegpt.completions.llama.PromptTemplate.TORA import ee.carlrobert.codegpt.conversations.message.Message import org.assertj.core.api.Assertions.assertThat @@ -104,6 +107,208 @@ 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 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 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) diff --git a/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt b/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt index 284a7ba9..2273ca08 100644 --- a/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt +++ b/src/test/kotlin/testsupport/mixin/ShortcutsTestMixin.kt @@ -1,18 +1,27 @@ 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.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 +import ee.carlrobert.llm.client.google.models.GoogleModel 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") } @@ -41,6 +50,12 @@ 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", @@ -48,5 +63,4 @@ interface ShortcutsTestMixin { 5 ) } - }