From fdb2fb98e08d72ad3c9168a48d5d947e63bfaa4b Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Thu, 6 Jun 2024 00:03:25 +0300 Subject: [PATCH] feat: introduce openai and anthropic models for subscribed users --- gradle/libs.versions.toml | 2 +- .../ee/carlrobert/codegpt/CodeGPTKeys.java | 3 + .../java/ee/carlrobert/codegpt/Icons.java | 2 + .../codegpt/settings/GeneralSettings.java | 80 ++++++++++++------- .../codegpt/settings/service/ServiceType.java | 18 +++++ .../toolwindow/chat/ChatToolWindowPanel.java | 54 ++++++++++--- .../chat/ChatToolWindowTabPanel.java | 4 +- .../ui/ChatToolWindowScrollablePanel.java | 2 +- .../chat/ui/textarea/ModelComboBoxAction.java | 46 +++++------ .../chat/ui/textarea/UserPromptTextArea.java | 11 +++ .../codegpt/CodeGPTProjectActivity.kt | 13 ++- .../service/ProviderChangeNotifier.kt | 14 ++++ .../service/codegpt/CodeGPTAvailableModels.kt | 80 +++++++++++++++---- .../service/codegpt/CodeGPTService.kt | 40 ++++++++++ .../codegpt/CodeGPTServiceConfigurable.kt | 8 +- .../service/codegpt/CodeGPTServiceForm.kt | 11 +-- .../service/codegpt/CodeGPTServiceSettings.kt | 8 +- .../codegpt/CodeGPTUserDetailsNotifier.kt | 14 ++++ src/main/resources/icons/dbrx.svg | 6 ++ src/main/resources/icons/meta.svg | 1 + 20 files changed, 317 insertions(+), 100 deletions(-) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/ProviderChangeNotifier.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTService.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTUserDetailsNotifier.kt create mode 100644 src/main/resources/icons/dbrx.svg create mode 100644 src/main/resources/icons/meta.svg diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d57b62e..78190925 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jsoup = "1.17.2" jtokkit = "1.0.0" junit = "5.10.2" kotlin = "2.0.0" -llm-client = "0.8.5" +llm-client = "0.8.6" okio = "3.9.0" tree-sitter = "0.22.5" diff --git a/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java b/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java index a96ade5b..94bbdea3 100644 --- a/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java +++ b/src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt; import com.intellij.openapi.util.Key; +import ee.carlrobert.llm.client.codegpt.CodeGPTUserDetails; import java.util.List; public class CodeGPTKeys { @@ -11,4 +12,6 @@ public class CodeGPTKeys { Key.create("codegpt.selectedFiles"); public static final Key IMAGE_ATTACHMENT_FILE_PATH = Key.create("codegpt.imageAttachmentFilePath"); + public static final Key CODEGPT_USER_DETAILS = + Key.create("codegpt.userDetails"); } diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index 34311e2a..b24f79d4 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -12,9 +12,11 @@ public final class Icons { 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 Databricks = IconLoader.getIcon("/icons/dbrx.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 Meta = IconLoader.getIcon("/icons/meta.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); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java index f4d178f3..f4e4e1e5 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/GeneralSettings.java @@ -4,17 +4,21 @@ 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.CodeGPTKeys; 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.ProviderChangeNotifier; 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.CodeGPTService; 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 ee.carlrobert.codegpt.util.ApplicationUtil; import org.jetbrains.annotations.NotNull; @State(name = "CodeGPT_GeneralSettings_270", storages = @Storage("CodeGPT_GeneralSettings_270.xml")) @@ -50,38 +54,52 @@ public class GeneralSettings implements PersistentStateComponent CLIENT_CODE_MAP = new HashMap<>(); + + static { + for (ServiceType type : values()) { + CLIENT_CODE_MAP.put(type.getCompletionCode(), type); + } + } + ServiceType(String code, String messageKey, String completionCode) { this.code = code; this.label = CodeGPTBundle.get(messageKey); @@ -39,4 +49,12 @@ public enum ServiceType { public String toString() { return label; } + + public static ServiceType fromClientCode(String clientCode) { + ServiceType serviceType = CLIENT_CODE_MAP.get(clientCode); + if (serviceType == null) { + throw new RuntimeException("Provided client code '" + clientCode + "' is not supported"); + } + return serviceType; + } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowPanel.java index 75a53d73..ea7a062a 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowPanel.java @@ -3,13 +3,16 @@ package ee.carlrobert.codegpt.toolwindow.chat; import static java.lang.String.format; import static java.util.Collections.emptyList; +import com.intellij.ide.BrowserUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.actionSystem.DefaultCompactActionGroup; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; +import com.intellij.ui.components.ActionLink; import com.intellij.util.ui.JBUI; import ee.carlrobert.codegpt.CodeGPTKeys; import ee.carlrobert.codegpt.ReferencedFile; @@ -19,8 +22,13 @@ import ee.carlrobert.codegpt.actions.toolwindow.CreateNewConversationAction; import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.ConversationsState; +import ee.carlrobert.codegpt.settings.GeneralSettings; +import ee.carlrobert.codegpt.settings.service.ProviderChangeNotifier; +import ee.carlrobert.codegpt.settings.service.ServiceType; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTUserDetailsNotifier; import ee.carlrobert.codegpt.toolwindow.chat.ui.ToolWindowFooterNotification; import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.AttachImageNotifier; +import ee.carlrobert.llm.client.codegpt.PricingPlan; import java.awt.BorderLayout; import java.nio.file.Path; import java.nio.file.Paths; @@ -34,6 +42,7 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel { private final ToolWindowFooterNotification selectedFilesNotification; private final ToolWindowFooterNotification imageFileAttachmentNotification; + private final ActionLink upgradePlanLink; private ChatToolWindowTabbedPane tabbedPane; public ChatToolWindowPanel( @@ -44,18 +53,42 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel { () -> clearSelectedFilesNotification(project)); imageFileAttachmentNotification = new ToolWindowFooterNotification(() -> project.putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, "")); + upgradePlanLink = new ActionLink("Upgrade your plan", event -> { + BrowserUtil.browse("https://codegpt.carlrobert.ee/#pricing"); + }); + upgradePlanLink.setFont(JBUI.Fonts.smallFont()); + upgradePlanLink.setExternalLinkIcon(); + upgradePlanLink.setVisible(false); + init(project, parentDisposable); - project.getMessageBus() - .connect() - .subscribe(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC, - (IncludeFilesInContextNotifier) this::displaySelectedFilesNotification); - project.getMessageBus() - .connect() - .subscribe(AttachImageNotifier.IMAGE_ATTACHMENT_FILE_PATH_TOPIC, - (AttachImageNotifier) filePath -> imageFileAttachmentNotification.show( - Path.of(filePath).getFileName().toString(), - "File path: " + filePath)); + var messageBusConnection = project.getMessageBus().connect(); + messageBusConnection.subscribe(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC, + (IncludeFilesInContextNotifier) this::displaySelectedFilesNotification); + messageBusConnection.subscribe(AttachImageNotifier.IMAGE_ATTACHMENT_FILE_PATH_TOPIC, + (AttachImageNotifier) filePath -> imageFileAttachmentNotification.show( + Path.of(filePath).getFileName().toString(), + "File path: " + filePath)); + messageBusConnection.subscribe(ProviderChangeNotifier.getPROVIDER_CHANGE_TOPIC(), + (ProviderChangeNotifier) provider -> { + if (provider == ServiceType.CODEGPT) { + var userDetails = CodeGPTKeys.CODEGPT_USER_DETAILS.get(project); + upgradePlanLink.setVisible( + userDetails != null && userDetails.getPricingPlan() != PricingPlan.INDIVIDUAL); + } else { + upgradePlanLink.setVisible(false); + } + }); + messageBusConnection.subscribe(CodeGPTUserDetailsNotifier.getCODEGPT_USER_DETAILS_TOPIC(), + (CodeGPTUserDetailsNotifier) userDetails -> { + if (userDetails != null) { + var provider = ApplicationManager.getApplication().getService(GeneralSettings.class) + .getState() + .getSelectedService(); + upgradePlanLink.setVisible(provider == ServiceType.CODEGPT + && userDetails.getPricingPlan() != PricingPlan.INDIVIDUAL); + } + }); } public ChatToolWindowTabbedPane getChatTabbedPane() { @@ -109,6 +142,7 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel { actionToolbarPanel.add( createActionToolbar(project, tabbedPane, onAddNewTab).getComponent(), BorderLayout.LINE_START); + actionToolbarPanel.add(upgradePlanLink, BorderLayout.LINE_END); setToolbar(actionToolbarPanel); var notificationContainer = new JPanel(new BorderLayout()); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index cb9a1f4d..15059607 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -274,6 +274,7 @@ public class ChatToolWindowTabPanel implements Disposable { JBUI.Borders.empty(8))); var contentManager = project.getService(ChatToolWindowContentManager.class); panel.add(JBUI.Panels.simplePanel(createUserPromptTextAreaHeader( + project, selectedService, () -> { ConversationService.getInstance().startConversation(); @@ -284,13 +285,14 @@ public class ChatToolWindowTabPanel implements Disposable { } private JPanel createUserPromptTextAreaHeader( + Project project, ServiceType selectedService, Runnable onModelChange) { return JBUI.Panels.simplePanel() .withBorder(Borders.emptyBottom(8)) .andTransparent() .addToLeft(totalTokensPanel) - .addToRight(new ModelComboBoxAction(onModelChange, selectedService) + .addToRight(new ModelComboBoxAction(project, onModelChange, selectedService) .createCustomComponent(ActionPlaces.UNKNOWN)); } 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 70ca682d..c1b21c22 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 @@ -40,7 +40,7 @@ public class ChatToolWindowScrollablePanel extends ScrollablePanel { .addContent(UIUtil.createTextPane("""

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

Don't have an account? Sign up for free access to all open-source models. 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 89e691d0..1b4ad0d9 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 @@ -15,13 +15,13 @@ import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ex.ComboBoxAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; import com.intellij.util.messages.MessageBusConnection; +import ee.carlrobert.codegpt.CodeGPTKeys; 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.service.ServiceType; import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTAvailableModels; @@ -43,8 +43,10 @@ import org.jetbrains.annotations.NotNull; public class ModelComboBoxAction extends ComboBoxAction { private final Runnable onModelChange; + private final Project project; - public ModelComboBoxAction(Runnable onModelChange, ServiceType selectedService) { + public ModelComboBoxAction(Project project, Runnable onModelChange, ServiceType selectedService) { + this.project = project; this.onModelChange = onModelChange; updateTemplatePresentation(selectedService); @@ -65,14 +67,11 @@ public class ModelComboBoxAction extends ComboBoxAction { return button; } - private AnAction[] getCodeGPTModelActions(Presentation presentation) { - var apiKey = CredentialsStore.getCredential(CredentialKey.CODEGPT_API_KEY); - return CodeGPTAvailableModels.getCHAT_MODELS().stream() - .map(model -> { - var enabled = "meta-llama/Llama-3-8b-chat-hf".equals(model.getCode()) - || (apiKey != null && !apiKey.isEmpty()); - return createCodeGPTModelAction(model, enabled, presentation); - }) + private AnAction[] getCodeGPTModelActions(Project project, Presentation presentation) { + var userDetails = CodeGPTKeys.CODEGPT_USER_DETAILS.get(project); + return CodeGPTAvailableModels.getToolWindowModels( + userDetails == null ? null : userDetails.getPricingPlan()).stream() + .map(model -> createCodeGPTModelAction(model, presentation)) .toArray(AnAction[]::new); } @@ -81,7 +80,7 @@ public class ModelComboBoxAction extends ComboBoxAction { var presentation = ((ComboBoxButton) button).getPresentation(); var actionGroup = new DefaultActionGroup(); actionGroup.addSeparator("CodeGPT"); - actionGroup.addAll(getCodeGPTModelActions(presentation)); + actionGroup.addAll(getCodeGPTModelActions(project, presentation)); actionGroup.addSeparator("OpenAI"); List.of( OpenAIChatCompletionModel.GPT_4_O, @@ -171,16 +170,15 @@ public class ModelComboBoxAction extends ComboBoxAction { var templatePresentation = getTemplatePresentation(); switch (selectedService) { case CODEGPT: - var model = application.getService(CodeGPTServiceSettings.class) + var modelCode = application.getService(CodeGPTServiceSettings.class) .getState() .getChatCompletionSettings() .getModel(); - var modelName = CodeGPTAvailableModels.getCHAT_MODELS().stream() - .filter(it -> it.getCode().equals(model)) - .map(CodeGPTModel::getName) - .findFirst().orElse("Unknown"); - templatePresentation.setIcon(Icons.CodeGPTModel); - templatePresentation.setText(modelName); + var model = CodeGPTAvailableModels.getALL_CHAT_MODELS().stream() + .filter(it -> it.getCode().equals(modelCode)) + .findFirst(); + templatePresentation.setIcon(model.map(CodeGPTModel::getIcon).orElse(Icons.CodeGPTModel)); + templatePresentation.setText(model.map(CodeGPTModel::getName).orElse("Unknown")); break; case OPENAI: templatePresentation.setIcon(Icons.OpenAI); @@ -284,14 +282,12 @@ public class ModelComboBoxAction extends ComboBoxAction { onModelChange.run(); } - private AnAction createCodeGPTModelAction(CodeGPTModel model, boolean enabled, - Presentation comboBoxPresentation) { - return new DumbAwareAction(model.getName(), "", Icons.CodeGPTModel) { + private AnAction createCodeGPTModelAction(CodeGPTModel model, Presentation comboBoxPresentation) { + return new DumbAwareAction(model.getName(), "", model.getIcon()) { @Override public void update(@NotNull AnActionEvent event) { var presentation = event.getPresentation(); - presentation.setEnabled( - enabled && !presentation.getText().equals(comboBoxPresentation.getText())); + presentation.setEnabled(!presentation.getText().equals(comboBoxPresentation.getText())); } @Override @@ -303,7 +299,7 @@ public class ModelComboBoxAction extends ComboBoxAction { handleModelChange( CODEGPT, model.getName(), - Icons.OpenAI, + model.getIcon(), comboBoxPresentation); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/UserPromptTextArea.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/textarea/UserPromptTextArea.java index 835260b5..7570f887 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.CODEGPT; 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_O; @@ -9,6 +10,7 @@ import static ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionMod import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.util.registry.Registry; @@ -21,6 +23,7 @@ import ee.carlrobert.codegpt.Icons; import ee.carlrobert.codegpt.actions.AttachImageAction; import ee.carlrobert.codegpt.completions.CompletionRequestHandler; import ee.carlrobert.codegpt.settings.GeneralSettings; +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings; import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings; import ee.carlrobert.codegpt.ui.IconActionButton; import ee.carlrobert.codegpt.ui.UIUtil; @@ -34,6 +37,7 @@ import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import javax.swing.AbstractAction; @@ -204,6 +208,13 @@ public class UserPromptTextArea extends JPanel { if (selectedService == ANTHROPIC || selectedService == OLLAMA) { return true; } + if (selectedService == CODEGPT) { + var model = ApplicationManager.getApplication().getService(CodeGPTServiceSettings.class) + .getState() + .getChatCompletionSettings() + .getModel(); + return List.of("gpt-4o", "claude-3-opus").contains(model); + } var model = OpenAISettings.getCurrentState().getModel(); return selectedService == OPENAI && ( diff --git a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt index ee28904c..4e848cb4 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/CodeGPTProjectActivity.kt @@ -14,7 +14,10 @@ import ee.carlrobert.codegpt.completions.you.auth.YouAuthenticationService import ee.carlrobert.codegpt.completions.you.auth.response.YouAuthenticationResponse import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.settings.GeneralSettings import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings +import ee.carlrobert.codegpt.settings.service.ServiceType +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTService import ee.carlrobert.codegpt.settings.service.you.YouSettings import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.AttachImageNotifier import ee.carlrobert.codegpt.ui.OverlayUtil @@ -29,6 +32,11 @@ class CodeGPTProjectActivity : ProjectActivity { override suspend fun execute(project: Project) { EditorActionsUtil.refreshActions() + val settings = service().state + if (settings.selectedService == ServiceType.CODEGPT) { + project.service().syncUserDetailsAsync() + } + if (YouUserManager.getInstance().authenticationResponse == null) { handleYouServiceAuthenticationAsync() } @@ -39,7 +47,10 @@ class CodeGPTProjectActivity : ProjectActivity { val desktopPath = Paths.get(System.getProperty("user.home"), "Desktop") project.service().watch(desktopPath) { if (watchExtensions.contains(it.extension.lowercase())) { - showImageAttachmentNotification(project, desktopPath.resolve(it).absolutePathString()) + showImageAttachmentNotification( + project, + desktopPath.resolve(it).absolutePathString() + ) } } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ProviderChangeNotifier.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ProviderChangeNotifier.kt new file mode 100644 index 00000000..75bee599 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/ProviderChangeNotifier.kt @@ -0,0 +1,14 @@ +package ee.carlrobert.codegpt.settings.service + +import com.intellij.util.messages.Topic + +interface ProviderChangeNotifier { + + fun providerChanged(provider: ServiceType) + + companion object { + @JvmStatic + val PROVIDER_CHANGE_TOPIC = + Topic.create("providerChange", ProviderChangeNotifier::class.java) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt index 50a10a86..ee7ce30a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt @@ -1,31 +1,81 @@ package ee.carlrobert.codegpt.settings.service.codegpt +import ee.carlrobert.codegpt.Icons +import ee.carlrobert.llm.client.codegpt.PricingPlan +import ee.carlrobert.llm.client.codegpt.PricingPlan.* +import javax.swing.Icon + object CodeGPTAvailableModels { @JvmStatic - val CHAT_MODELS: List = listOf( - CodeGPTModel("Llama 3 (70B)", "meta-llama/Llama-3-70b-chat-hf"), - CodeGPTModel("Llama 3 (8B)", "meta-llama/Llama-3-8b-chat-hf"), - CodeGPTModel("Code Llama (70B)", "codellama/CodeLlama-70b-Instruct-hf"), - CodeGPTModel("Mixtral (8x22B)", "mistralai/Mixtral-8x22B-Instruct-v0.1"), - CodeGPTModel("DBRX (132B)", "databricks/dbrx-instruct"), - CodeGPTModel("DeepSeek Coder (33B)", "deepseek-ai/deepseek-coder-33b-instruct"), - CodeGPTModel("WizardLM-2 (8x22B)", "microsoft/WizardLM-2-8x22B") + fun getToolWindowModels(pricingPlan: PricingPlan?): List { + val anonymousModels = BASE_CHAT_MODELS + CodeGPTModel( + "Llama 3 (8B) - FREE", + "llama-3-8b", + Icons.Meta, + ANONYMOUS + ) + if (pricingPlan == null) { + return anonymousModels + } + return when (pricingPlan) { + ANONYMOUS -> anonymousModels + + FREE -> BASE_CHAT_MODELS + listOf( + CodeGPTModel("Code Llama (70B)", "codellama:chat", Icons.Meta, FREE), + CodeGPTModel("Mixtral (8x22B)", "mixtral-8x22b", Icons.CodeGPTModel, FREE), + CodeGPTModel( + "DeepSeek Coder (33B)", + "deepseek-coder-33b", + Icons.CodeGPTModel, + FREE + ), + CodeGPTModel("WizardLM-2 (8x22B)", "wizardlm-2-8x22b", Icons.CodeGPTModel, FREE) + ) + + else -> BASE_CHAT_MODELS + } + } + + @JvmStatic + val BASE_CHAT_MODELS: List = listOf( + CodeGPTModel("GPT-4o", "gpt-4o", Icons.OpenAI, INDIVIDUAL), + CodeGPTModel("GPT-3.5 Turbo", "gpt-3.5-turbo", Icons.OpenAI, INDIVIDUAL), + CodeGPTModel("Claude 3 Opus", "claude-3-opus", Icons.Anthropic, INDIVIDUAL), + CodeGPTModel("Claude 3 Sonnet", "claude-3-sonnet", Icons.Anthropic, INDIVIDUAL), + CodeGPTModel("DBRX", "dbrx", Icons.Databricks, INDIVIDUAL), + CodeGPTModel("Llama 3 (70B)", "llama-3-70b", Icons.Meta, FREE), + ) + + @JvmStatic + val ALL_CHAT_MODELS: List = BASE_CHAT_MODELS + listOf( + CodeGPTModel("Llama 3 (8B) - FREE", "llama-3-8b", Icons.Meta, ANONYMOUS), + CodeGPTModel("Code Llama (70B)", "codellama:chat", Icons.Meta, FREE), + CodeGPTModel("Mixtral (8x22B)", "mixtral-8x22b", Icons.CodeGPTModel, FREE), + CodeGPTModel("DeepSeek Coder (33B)", "deepseek-coder-33b", Icons.CodeGPTModel, FREE), + CodeGPTModel("WizardLM-2 (8x22B)", "wizardlm-2-8x22b", Icons.CodeGPTModel, FREE) ) @JvmStatic val CODE_MODELS: List = listOf( - CodeGPTModel("StarCoder (16B)", "starcoder-16b"), - CodeGPTModel("Code Llama (70B)", "codellama/CodeLlama-70b-hf"), - CodeGPTModel("Code Llama Python (70B)", "codellama/CodeLlama-70b-Python-hf"), - CodeGPTModel("WizardCoder Python (34B)", "WizardLM/WizardCoder-Python-34B-V1.0"), - CodeGPTModel("Phind Code LLaMA v2 (34B)", "Phind/Phind-CodeLlama-34B-v2") + CodeGPTModel("GPT-3.5 Turbo Instruct", "gpt-3.5-turbo-instruct", Icons.OpenAI, INDIVIDUAL), + CodeGPTModel("StarCoder (16B)", "starcoder-16b", Icons.CodeGPTModel, FREE), + CodeGPTModel("StarCoder (7B) - FREE", "starcoder-7b", Icons.CodeGPTModel, FREE), + CodeGPTModel("Code Llama (70B)", "codellama:code", Icons.CodeGPTModel, FREE), + CodeGPTModel("Code Llama Python (70B)", "codellama-python", Icons.CodeGPTModel, FREE), + CodeGPTModel("WizardCoder Python (34B)", "wizardcoder-python", Icons.CodeGPTModel, FREE), + CodeGPTModel("Phind Code LLaMA v2 (34B)", "phind-codellama", Icons.CodeGPTModel, FREE) ) @JvmStatic fun findByCode(code: String?): CodeGPTModel? { - return CHAT_MODELS.union(CODE_MODELS).firstOrNull { it.code == code } + return ALL_CHAT_MODELS.union(CODE_MODELS).firstOrNull { it.code == code } } } -data class CodeGPTModel(val name: String, val code: String) \ No newline at end of file +data class CodeGPTModel( + val name: String, + val code: String, + val icon: Icon, + val individual: PricingPlan +) \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTService.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTService.kt new file mode 100644 index 00000000..d9becb43 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTService.kt @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.settings.service.codegpt + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import ee.carlrobert.codegpt.CodeGPTKeys.CODEGPT_USER_DETAILS +import ee.carlrobert.codegpt.completions.CompletionClientProvider +import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CODEGPT_API_KEY +import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential +import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTUserDetailsNotifier.Companion.CODEGPT_USER_DETAILS_TOPIC +import kotlinx.coroutines.* + +@Service +class CodeGPTService private constructor(val project: Project) { + + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + fun syncUserDetailsAsync() { + syncUserDetailsAsync(getCredential(CODEGPT_API_KEY)) + } + + fun syncUserDetailsAsync(apiKey: String?) { + serviceScope.launch { + val userDetails = withContext(Dispatchers.IO) { + if (apiKey.isNullOrEmpty()) null + else CompletionClientProvider.getCodeGPTClient().getUserDetails(apiKey) + } + if (userDetails != null && userDetails.pricingPlan != null) { + CODEGPT_USER_DETAILS.set(project, userDetails) + if (!userDetails.fullName.isNullOrEmpty()) { + service().state.displayName = userDetails.fullName + } + } + project.messageBus + .syncPublisher(CODEGPT_USER_DETAILS_TOPIC) + .userDetailsObtained(userDetails) + } + } +} \ 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 index 975b1983..3e767198 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceConfigurable.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceConfigurable.kt @@ -7,6 +7,7 @@ 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.util.ApplicationUtil import javax.swing.JComponent class CodeGPTServiceConfigurable : Configurable { @@ -28,7 +29,12 @@ class CodeGPTServiceConfigurable : Configurable { override fun apply() { setCredential(CODEGPT_API_KEY, component.getApiKey()) - service().state.selectedService = ServiceType.CODEGPT + service().state.apply { + selectedService = ServiceType.CODEGPT + } + ApplicationUtil.findCurrentProject() + ?.service() + ?.syncUserDetailsAsync(component.getApiKey()) component.applyChanges() } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt index cf9176a0..f19be1c2 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceForm.kt @@ -2,7 +2,6 @@ package ee.carlrobert.codegpt.settings.service.codegpt import com.intellij.openapi.components.service import com.intellij.openapi.ui.ComboBox -import com.intellij.ui.DocumentAdapter import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBPasswordField import com.intellij.util.ui.FormBuilder @@ -18,7 +17,6 @@ import java.awt.Component import javax.swing.DefaultListCellRenderer import javax.swing.JList import javax.swing.JPanel -import javax.swing.event.DocumentEvent class CodeGPTServiceForm { @@ -27,7 +25,7 @@ class CodeGPTServiceForm { } private val chatCompletionModelComboBox = - ComboBox(ListComboBoxModel(CodeGPTAvailableModels.CHAT_MODELS)).apply { + ComboBox(ListComboBoxModel(CodeGPTAvailableModels.ALL_CHAT_MODELS)).apply { selectedItem = CodeGPTAvailableModels.findByCode(service().state.chatCompletionSettings.model) renderer = CustomComboBoxRenderer() @@ -49,13 +47,6 @@ class CodeGPTServiceForm { apiKeyField.text = runBlocking(Dispatchers.IO) { getCredential(CODEGPT_API_KEY) } - val apiKeyDefined = apiKeyField.password.isNotEmpty() - codeCompletionModelComboBox.isEnabled = apiKeyDefined - apiKeyField.document.addDocumentListener(object : DocumentAdapter() { - override fun textChanged(e: DocumentEvent) { - codeCompletionModelComboBox.isEnabled = apiKeyDefined - } - }) } fun getForm(): JPanel = FormBuilder.createFormBuilder() diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt index 61b13aeb..f2ed4ee1 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTServiceSettings.kt @@ -4,8 +4,8 @@ import com.intellij.openapi.components.* @Service @State( - name = "CodeGPT_CodeGPTServiceSettings", - storages = [Storage("CodeGPT_CodeGPTServiceSettings.xml")] + name = "CodeGPT_CodeGPTServiceSettings_280", + storages = [Storage("CodeGPT_CodeGPTServiceSettings_280.xml")] ) class CodeGPTServiceSettings : SimplePersistentStateComponent(CodeGPTServiceSettingsState()) @@ -16,10 +16,10 @@ class CodeGPTServiceSettingsState : BaseState() { } class CodeGPTServiceChatCompletionSettingsState : BaseState() { - var model by string("meta-llama/Llama-3-70b-chat-hf") + var model by string("llama-3-8b") } class CodeGPTServiceCodeCompletionSettingsState : BaseState() { var codeCompletionsEnabled by property(false) - var model by string("codellama/CodeLlama-70b-hf") + var model by string("starcoder-7b") } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTUserDetailsNotifier.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTUserDetailsNotifier.kt new file mode 100644 index 00000000..47f4b372 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTUserDetailsNotifier.kt @@ -0,0 +1,14 @@ +package ee.carlrobert.codegpt.settings.service.codegpt + +import com.intellij.util.messages.Topic +import ee.carlrobert.llm.client.codegpt.CodeGPTUserDetails + +interface CodeGPTUserDetailsNotifier { + fun userDetailsObtained(userDetails: CodeGPTUserDetails?) + + companion object { + @JvmStatic + val CODEGPT_USER_DETAILS_TOPIC = + Topic.create("codegptUserDetails", CodeGPTUserDetailsNotifier::class.java) + } +} \ No newline at end of file diff --git a/src/main/resources/icons/dbrx.svg b/src/main/resources/icons/dbrx.svg new file mode 100644 index 00000000..f978c53d --- /dev/null +++ b/src/main/resources/icons/dbrx.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/icons/meta.svg b/src/main/resources/icons/meta.svg new file mode 100644 index 00000000..3630f30f --- /dev/null +++ b/src/main/resources/icons/meta.svg @@ -0,0 +1 @@ + \ No newline at end of file