diff --git a/CHANGELOG.md b/CHANGELOG.md index 53dc722f..2b124df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Support for Anthropic Claude service + ## [2.4.0] - 2024-02-26 ### Added diff --git a/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts index 2552d55d..cd35004a 100644 --- a/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts @@ -23,7 +23,7 @@ checkstyle { } dependencies { - implementation("ee.carlrobert:llm-client:0.5.0") + implementation("ee.carlrobert:llm-client:0.6.0") } tasks { diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index b61dcb04..d7aae08b 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -8,6 +8,7 @@ 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 Anthropic = IconLoader.getIcon("/icons/anthropic.svg", Icons.class); public static final Icon Azure = IconLoader.getIcon("/icons/azure.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); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index 8ecffe42..d2db4b2a 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -4,13 +4,16 @@ import static java.lang.String.format; import ee.carlrobert.codegpt.CodeGPTPlugin; import ee.carlrobert.codegpt.completions.you.YouUserManager; +import ee.carlrobert.codegpt.credentials.AnthropicCredentialsManager; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.LlamaCredentialManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialManager; 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.openai.OpenAISettings; +import ee.carlrobert.llm.client.anthropic.ClaudeClient; import ee.carlrobert.llm.client.azure.AzureClient; import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams; import ee.carlrobert.llm.client.llama.LlamaClient; @@ -31,6 +34,13 @@ public class CompletionClientProvider { .build(getDefaultClientBuilder()); } + public static ClaudeClient getClaudeClient() { + return new ClaudeClient( + AnthropicCredentialsManager.getInstance().getCredential(), + AnthropicSettings.getCurrentState().getApiVersion(), + getDefaultClientBuilder()); + } + public static AzureClient getAzureClient() { var settings = AzureSettings.getCurrentState(); var params = new AzureCompletionRequestParams( diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index a22fdcfb..c3e9c71f 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -21,6 +21,7 @@ import ee.carlrobert.codegpt.settings.GeneralSettings; import ee.carlrobert.codegpt.settings.IncludedFilesSettings; 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.custom.CustomServiceSettings; import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettingsState; import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings; @@ -30,6 +31,8 @@ import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration import ee.carlrobert.codegpt.telemetry.core.service.UserId; import ee.carlrobert.embedding.EmbeddingsService; import ee.carlrobert.embedding.ReferencedFile; +import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest; +import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequestMessage; import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest; import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel; import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage; @@ -44,6 +47,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; import okhttp3.Request; import okhttp3.RequestBody; import org.jetbrains.annotations.Nullable; @@ -236,7 +240,29 @@ public class CompletionRequestProvider { } } - public List buildMessages( + public ClaudeCompletionRequest buildAnthropicChatCompletionRequest( + CallParameters callParameters) { + var configuration = ConfigurationSettings.getCurrentState(); + var settings = AnthropicSettings.getCurrentState(); + var request = new ClaudeCompletionRequest(); + request.setModel(settings.getModel()); + request.setMaxTokens(configuration.getMaxTokens()); + request.setStream(true); + request.setSystem(COMPLETION_SYSTEM_PROMPT); + var messages = conversation.getMessages().stream() + .filter(prevMessage -> prevMessage.getResponse() != null + && !prevMessage.getResponse().isEmpty()) + .flatMap(prevMessage -> Stream.of( + new ClaudeCompletionRequestMessage("user", prevMessage.getPrompt()), + new ClaudeCompletionRequestMessage("assistant", prevMessage.getResponse()))) + .collect(toList()); + messages.add( + new ClaudeCompletionRequestMessage("user", callParameters.getMessage().getPrompt())); + request.setMessages(messages); + return request; + } + + private List buildMessages( CallParameters callParameters, boolean useContextualSearch) { var message = callParameters.getMessage(); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java index cd9c7d4e..02964cb1 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -75,6 +75,10 @@ public final class CompletionRequestService { customConfiguration, callParameters), eventListener); + case ANTHROPIC: + return CompletionClientProvider.getClaudeClient().getCompletionAsync( + requestProvider.buildAnthropicChatCompletionRequest(callParameters), + eventListener); case AZURE: var azureSettings = AzureSettings.getCurrentState(); return CompletionClientProvider.getAzureClient().getChatCompletionAsync( diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java index ac2e5a7c..cf1bb8d0 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java @@ -8,6 +8,7 @@ import ee.carlrobert.codegpt.completions.CallParameters; import ee.carlrobert.codegpt.conversations.message.Message; 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.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; @@ -192,6 +193,8 @@ public final class ConversationService { return OpenAISettings.getCurrentState().getModel(); case CUSTOM_OPENAI: return "CustomService"; + case ANTHROPIC: + return AnthropicSettings.getCurrentState().getModel(); case AZURE: return AzureSettings.getCurrentState().getDeploymentId(); case YOU: diff --git a/src/main/java/ee/carlrobert/codegpt/credentials/AnthropicCredentialsManager.java b/src/main/java/ee/carlrobert/codegpt/credentials/AnthropicCredentialsManager.java new file mode 100644 index 00000000..58fc4fa5 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/credentials/AnthropicCredentialsManager.java @@ -0,0 +1,16 @@ +package ee.carlrobert.codegpt.credentials; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; + +@Service +public final class AnthropicCredentialsManager extends SingleCredentialManager { + + private AnthropicCredentialsManager() { + super("ANTHROPIC_API_KEY"); + } + + public static AnthropicCredentialsManager getInstance() { + return ApplicationManager.getApplication().getService(AnthropicCredentialsManager.class); + } +} \ No newline at end of file diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java index 4394410d..d6ca6070 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java @@ -8,6 +8,7 @@ import ee.carlrobert.codegpt.completions.HuggingFaceModel; import ee.carlrobert.codegpt.completions.llama.LlamaModel; 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.llama.LlamaSettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; @@ -43,6 +44,10 @@ public class GeneralSettings implements PersistentStateComponent { + + private AnthropicSettingsState state = new AnthropicSettingsState(); + + @Override + @NotNull + public AnthropicSettingsState getState() { + return state; + } + + @Override + public void loadState(@NotNull AnthropicSettingsState state) { + this.state = state; + } + + public static AnthropicSettingsState getCurrentState() { + return getInstance().getState(); + } + + public static AnthropicSettings getInstance() { + return ApplicationManager.getApplication().getService(AnthropicSettings.class); + } + + public boolean isModified(AnthropicSettingsForm form) { + return !form.getCurrentState().equals(state) + || !StringUtils.equals( + form.getApiKey(), + AnthropicCredentialsManager.getInstance().getCredential()); + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java new file mode 100644 index 00000000..fa5374ca --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsForm.java @@ -0,0 +1,74 @@ +package ee.carlrobert.codegpt.settings.service.anthropic; + +import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder; + +import com.intellij.ui.TitledSeparator; +import com.intellij.ui.components.JBPasswordField; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.UI; +import ee.carlrobert.codegpt.CodeGPTBundle; +import ee.carlrobert.codegpt.credentials.AnthropicCredentialsManager; +import ee.carlrobert.codegpt.ui.UIUtil; +import javax.swing.JPanel; +import org.jetbrains.annotations.Nullable; + +public class AnthropicSettingsForm { + + private final JBPasswordField apiKeyField; + private final JBTextField apiVersionField; + private final JBTextField modelField; + + public AnthropicSettingsForm(AnthropicSettingsState settings) { + apiKeyField = new JBPasswordField(); + apiKeyField.setColumns(30); + apiKeyField.setText(AnthropicCredentialsManager.getInstance().getCredential()); + 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() + .add(UI.PanelFactory.panel(apiKeyField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label")) + .resizeX(false) + .withComment( + CodeGPTBundle.get("settingsConfigurable.service.anthropic.apiKey.comment")) + .withCommentHyperlinkListener(UIUtil::handleHyperlinkClicked)) + .add(UI.PanelFactory.panel(apiVersionField) + .withLabel(CodeGPTBundle.get("shared.apiVersion")) + .withComment(CodeGPTBundle.get( + "settingsConfigurable.service.anthropic.apiVersion.comment")) + .withCommentHyperlinkListener(UIUtil::handleHyperlinkClicked) + .resizeX(false)) + .add(UI.PanelFactory.panel(modelField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.shared.model.label")) + .withComment(CodeGPTBundle.get( + "settingsConfigurable.service.anthropic.model.comment")) + .resizeX(false)) + .createPanel())) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); + } + + public AnthropicSettingsState getCurrentState() { + var state = new AnthropicSettingsState(); + state.setModel(modelField.getText()); + state.setApiVersion(apiVersionField.getText()); + return state; + } + + public void resetForm() { + var state = AnthropicSettings.getCurrentState(); + apiKeyField.setText(AnthropicCredentialsManager.getInstance().getCredential()); + apiVersionField.setText(state.getApiVersion()); + modelField.setText(state.getModel()); + } + + public @Nullable String getApiKey() { + var apiKey = new String(apiKeyField.getPassword()); + return apiKey.isEmpty() ? null : apiKey; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsState.java new file mode 100644 index 00000000..e13982b5 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/anthropic/AnthropicSettingsState.java @@ -0,0 +1,42 @@ +package ee.carlrobert.codegpt.settings.service.anthropic; + +import java.util.Objects; + +public class AnthropicSettingsState { + + private String apiVersion = ""; + private String model = "claude-3-opus-20240229"; + + public String getApiVersion() { + return apiVersion; + } + + public void setApiVersion(String apiVersion) { + this.apiVersion = apiVersion; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AnthropicSettingsState that = (AnthropicSettingsState) o; + return Objects.equals(apiVersion, that.apiVersion) && Objects.equals(model, that.model); + } + + @Override + public int hashCode() { + return Objects.hash(apiVersion, model); + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java index 04332473..ede099ae 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/azure/AzureSettingsForm.java @@ -91,7 +91,7 @@ public class AzureSettingsForm { "settingsConfigurable.service.azure.deploymentId.comment"))) .add(UI.PanelFactory.panel(azureApiVersionField) .withLabel(CodeGPTBundle.get( - "settingsConfigurable.service.azure.apiVersion.label")) + "shared.apiVersion")) .resizeX(false) .withComment(CodeGPTBundle.get( "settingsConfigurable.service.azure.apiVersion.comment"))) diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.java index 63b9edc4..8837ff37 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/custom/CustomServiceForm.java @@ -92,8 +92,7 @@ public class CustomServiceForm { .getPanel(); return FormBuilder.createFormBuilder() - .addComponent(new TitledSeparator( - CodeGPTBundle.get("settingsConfigurable.service.openai.configuration.title"))) + .addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration"))) .addComponent(withEmptyLeftBorder(form)) .addComponentFillVertically(new JPanel(), 0) .getPanel(); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java index d18047b1..b6a3fb0a 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/openai/OpenAISettingsForm.java @@ -51,8 +51,7 @@ public class OpenAISettingsForm { .createPanel(); return FormBuilder.createFormBuilder() - .addComponent(new TitledSeparator( - CodeGPTBundle.get("settingsConfigurable.service.openai.configuration.title"))) + .addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration"))) .addComponent(withEmptyLeftBorder(configurationGrid)) .addComponentFillVertically(new JPanel(), 0) .getPanel(); 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 7c6c71e2..1a9e38ab 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 @@ -73,6 +73,12 @@ public class ModelComboBoxAction extends ComboBoxAction { Icons.OpenAI, presentation)); actionGroup.addSeparator(); + actionGroup.add(createModelAction( + ServiceType.ANTHROPIC, + "Anthropic (Claude)", + Icons.Anthropic, + presentation)); + actionGroup.addSeparator(); actionGroup.add( createModelAction(ServiceType.AZURE, "Azure OpenAI", Icons.Azure, presentation)); actionGroup.addSeparator(); @@ -105,6 +111,10 @@ public class ModelComboBoxAction extends ComboBoxAction { .getTemplate() .getName()); break; + case ANTHROPIC: + templatePresentation.setIcon(Icons.Anthropic); + templatePresentation.setText("Anthropic (Claude)"); + break; case AZURE: templatePresentation.setIcon(Icons.Azure); templatePresentation.setText("Azure OpenAI"); diff --git a/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java b/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java index 75b6a100..02fdd5a7 100644 --- a/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java +++ b/src/main/java/ee/carlrobert/codegpt/ui/ModelIconLabel.java @@ -18,6 +18,9 @@ public class ModelIconLabel extends JBLabel { if ("chat.completion".equals(clientCode)) { setIcon(Icons.OpenAI); } + if ("anthropic.chat.completion".equals(clientCode)) { + setIcon(Icons.Anthropic); + } if ("azure.chat.completion".equals(clientCode)) { setIcon(Icons.Azure); } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b03f2d95..448ef27d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -29,6 +29,7 @@ serviceImplementation="ee.carlrobert.codegpt.telemetry.core.service.TelemetryServiceFactory"/> + diff --git a/src/main/resources/icons/anthropic.svg b/src/main/resources/icons/anthropic.svg new file mode 100644 index 00000000..dbbe5e81 --- /dev/null +++ b/src/main/resources/icons/anthropic.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index a90b1be0..9f71715c 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -18,16 +18,17 @@ settings.openaiQuotaExceeded=OpenAI quota exceeded. settingsConfigurable.displayName.label=Display name: settingsConfigurable.service.label=Service: 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.configuration.title=Configuration -settingsConfigurable.service.openai.apiKey.comment=You can find your Secret API key in your User settings. +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.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. settingsConfigurable.service.azure.resourceName.label=Resource name: settingsConfigurable.service.azure.resourceName.comment=The name of your Azure OpenAI resource. settingsConfigurable.service.azure.deploymentId.label=Deployment ID: settingsConfigurable.service.azure.deploymentId.comment=The name of your model deployment. You're required to first deploy a model before you can make calls. -settingsConfigurable.service.azure.apiVersion.label=API version: settingsConfigurable.service.azure.apiVersion.comment=The API version to use for this operation. This follows the YYYY-MM-DD format. settingsConfigurable.service.azure.bearerToken.label=Bearer token: settingsConfigurable.service.azure.useApiKeyAuth.label=Use API key authentication @@ -165,6 +166,7 @@ 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) @@ -185,5 +187,7 @@ notification.completionError.description=Completion failed:
%s statusBar.widget.tooltip=Status shared.promptTemplate=Prompt template: shared.infillPromptTemplate=Infill template: +shared.apiVersion=API version: +shared.configuration=Configuration shared.port=Port: codeCompletion.progress.title=Code completion in progress \ No newline at end of file