From 4c8b8d4e4f42c6eefd0383674768840e6a34921d Mon Sep 17 00:00:00 2001 From: keith siilats Date: Wed, 4 Oct 2023 17:18:50 -0400 Subject: [PATCH] on quota exceeded suggest user switch to different LLM provider (#221) * on quota exceeded suggest user switch to different LLM provider * Improve insufficient quota handling, add more telemetry actions --------- Co-authored-by: Carl-Robert Linnupuu --- .../codegpt/telemetry/TelemetryAction.java | 3 +- .../codegpt/actions/ActionType.java | 3 +- .../completions/CompletionRequestHandler.java | 19 ++++--- .../settings/SettingsConfigurable.java | 26 +++++++++- .../chat/BaseChatToolWindowTabPanel.java | 13 +++-- .../components/ChatMessageResponseBody.java | 52 ++++++++++++++++--- 6 files changed, 95 insertions(+), 21 deletions(-) diff --git a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/TelemetryAction.java b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/TelemetryAction.java index d3d50625..57bf65d5 100644 --- a/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/TelemetryAction.java +++ b/codegpt-telemetry/src/main/java/ee/carlrobert/codegpt/telemetry/TelemetryAction.java @@ -7,7 +7,8 @@ public enum TelemetryAction { COMPLETION("CodeGPT-Completion"), COMPLETION_ERROR("CodeGPT-Completion-Error"), IDE_ACTION("CodeGPT-Action"), - IDE_ACTION_ERROR("CodeGPT-Action-Error"); + IDE_ACTION_ERROR("CodeGPT-Action-Error"), + SETTINGS_CHANGED("CodeGPT-Settings-Changed"); private final String code; diff --git a/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java b/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java index 431e98e3..0ba9b313 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/ActionType.java @@ -13,5 +13,6 @@ public enum ActionType { CREATE_NEW_FILE, COPY_CODE, REPLACE_IN_MAIN_EDITOR, - RELOAD_MESSAGE + RELOAD_MESSAGE, + CHANGE_PROVIDER } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java index 626e119c..97372815 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java @@ -1,11 +1,11 @@ package ee.carlrobert.codegpt.completions; -import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.llm.client.openai.completion.ErrorDetails; import ee.carlrobert.llm.client.you.completion.YouCompletionEventListener; import ee.carlrobert.llm.client.you.completion.YouSerpResult; @@ -230,11 +230,18 @@ public class CompletionRequestHandler { } private void sendError(ErrorDetails error, Throwable ex) { - TelemetryAction.COMPLETION_ERROR.createActionMessage() - .property("conversationId", conversation.getId().toString()) - .property("model", conversation.getModel()) - .error(new RuntimeException(error.toString(), ex)) - .send(); + var telemetryMessage = TelemetryAction.COMPLETION_ERROR.createActionMessage(); + if ("insufficient_quota".equals(error.getCode())) { + telemetryMessage + .property("type", "USER") + .property("code", "INSUFFICIENT_QUOTA"); + } else { + telemetryMessage + .property("conversationId", conversation.getId().toString()) + .property("model", conversation.getModel()) + .error(new RuntimeException(error.toString(), ex)); + } + telemetryMessage.send(); } } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java index 08ab8af0..14bdab41 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java @@ -9,6 +9,7 @@ import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; import ee.carlrobert.codegpt.util.ApplicationUtils; import javax.swing.JComponent; @@ -64,7 +65,8 @@ public class SettingsConfigurable implements Configurable, Disposable { OpenAICredentialsManager.getInstance().setApiKey(serviceSelectionForm.getOpenAIApiKey()); AzureCredentialsManager.getInstance().setApiKey(serviceSelectionForm.getAzureOpenAIApiKey()); - AzureCredentialsManager.getInstance().setAzureActiveDirectoryToken(serviceSelectionForm.getAzureActiveDirectoryToken()); + AzureCredentialsManager.getInstance() + .setAzureActiveDirectoryToken(serviceSelectionForm.getAzureActiveDirectoryToken()); settings.setDisplayName(settingsComponent.getDisplayName()); settings.setUseOpenAIService(serviceSelectionForm.isOpenAIServiceSelected()); @@ -77,6 +79,11 @@ public class SettingsConfigurable implements Configurable, Disposable { if (serviceChanged || modelChanged) { resetActiveTab(); + if (serviceChanged) { + TelemetryAction.SETTINGS_CHANGED.createActionMessage() + .property("service", getServiceCode(serviceSelectionForm)) + .send(); + } } } @@ -109,7 +116,9 @@ public class SettingsConfigurable implements Configurable, Disposable { public void dispose() { } - private boolean isServiceChanged(ServiceSelectionForm serviceSelectionForm, SettingsState settings) { + private boolean isServiceChanged( + ServiceSelectionForm serviceSelectionForm, + SettingsState settings) { return serviceSelectionForm.isOpenAIServiceSelected() != settings.isUseOpenAIService() || serviceSelectionForm.isAzureServiceSelected() != settings.isUseAzureService() || serviceSelectionForm.isYouServiceSelected() != settings.isUseYouService(); @@ -124,4 +133,17 @@ public class SettingsConfigurable implements Configurable, Disposable { project.getService(StandardChatToolWindowContentManager.class).resetActiveTab(); } + + private String getServiceCode(ServiceSelectionForm serviceSelectionForm) { + if (serviceSelectionForm.isOpenAIServiceSelected()) { + return "openai"; + } + if (serviceSelectionForm.isAzureServiceSelected()) { + return "azure"; + } + if (serviceSelectionForm.isYouServiceSelected()) { + return "you"; + } + return null; + } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java index a410da98..f2d352cb 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java @@ -200,9 +200,16 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan } })); requestHandler.addErrorListener((error, ex) -> { - responsePanel.enableActions(); - responseContainer.displayError(error.getMessage()); - stopStreaming(responseContainer); + try { + if ("insufficient_quota".equals(error.getCode())) { + responseContainer.displayQuotaExceeded(); + } else { + responseContainer.displayError(error.getMessage()); + } + } finally { + responsePanel.enableActions(); + stopStreaming(responseContainer); + } }); requestHandler.addSerpResultsListener( serpResults -> serpResultsMapping.put(message.getId(), serpResults.stream() diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java index ee962f58..44bc14fb 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java @@ -19,9 +19,11 @@ import com.vladsch.flexmark.ast.FencedCodeBlock; import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.data.MutableDataSet; +import ee.carlrobert.codegpt.actions.ActionType; import ee.carlrobert.codegpt.completions.SerpResult; import ee.carlrobert.codegpt.settings.SettingsConfigurable; import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.codegpt.toolwindow.chat.editor.ChatToolWindowTabPanelEditor; import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer; import ee.carlrobert.codegpt.toolwindow.chat.StreamParser; @@ -52,11 +54,18 @@ public class ChatMessageResponseBody extends JPanel { this(project, getPanelBackgroundColor(), false, parentDisposable); } - public ChatMessageResponseBody(Project project, boolean withGhostText, Disposable parentDisposable) { + public ChatMessageResponseBody( + Project project, + boolean withGhostText, + Disposable parentDisposable) { this(project, getPanelBackgroundColor(), withGhostText, parentDisposable); } - public ChatMessageResponseBody(Project project, Color backgroundColor, boolean withGhostText, Disposable parentDisposable) { + public ChatMessageResponseBody( + Project project, + Color backgroundColor, + boolean withGhostText, + Disposable parentDisposable) { super(new BorderLayout()); this.project = project; this.parentDisposable = parentDisposable; @@ -66,7 +75,8 @@ public class ChatMessageResponseBody extends JPanel { if (withGhostText) { prepareProcessingTextResponse(); - currentlyProcessedTextPane.setText("

"); + currentlyProcessedTextPane.setText( + "

"); } UIManager.addPropertyChangeListener(propertyChangeEvent -> setBackground(backgroundColor)); @@ -95,7 +105,8 @@ public class ChatMessageResponseBody extends JPanel { var message = SettingsState.getInstance().isUseYouService() ? "Please log in to access the chat feature." : "API key not provided. Open Settings to set one."; - currentlyProcessedTextPane.setText(String.format("

%s

", message)); + currentlyProcessedTextPane.setText( + format("

%s

", message)); currentlyProcessedTextPane.addHyperlinkListener(e -> { if (e.getEventType() == ACTIVATED) { ShowSettingsUtil.getInstance().showSettingsDialog(project, SettingsConfigurable.class); @@ -104,6 +115,23 @@ public class ChatMessageResponseBody extends JPanel { currentlyProcessedTextPane.getCaret().setVisible(false); } + public void displayQuotaExceeded() { + currentlyProcessedTextPane.setText("" + + "

" + + "You exceeded your current quota, please check your plan and billing details, " + + "or change to a different LLM provider.

" + + ""); + currentlyProcessedTextPane.addHyperlinkListener(e -> { + if (e.getEventType() == ACTIVATED) { + ShowSettingsUtil.getInstance().showSettingsDialog(project, SettingsConfigurable.class); + TelemetryAction.IDE_ACTION.createActionMessage() + .property("action", ActionType.CHANGE_PROVIDER.name()) + .send(); + } + }); + currentlyProcessedTextPane.getCaret().setVisible(false); + } + public void hideCarets() { if (currentlyProcessedEditor != null) { ((EditorEx) currentlyProcessedEditor.getEditor()).setCaretVisible(false); @@ -115,7 +143,9 @@ public class ChatMessageResponseBody extends JPanel { } public void displayError(String message) { - var errorText = format("

%s

", message); + var errorText = format( + "

%s

", + message); if (responseReceived) { var errorPane = createTextPane(); errorPane.setText(errorText); @@ -131,7 +161,8 @@ public class ChatMessageResponseBody extends JPanel { public void displaySerpResults(List serpResults) { var titles = serpResults.stream() - .map(result -> format("
  • %s
  • ", result.getUrl(), result.getName())) + .map(result -> format("
  • %s
  • ", + result.getUrl(), result.getName())) .collect(Collectors.joining()); var html = format( "" + @@ -153,7 +184,8 @@ public class ChatMessageResponseBody extends JPanel { streamParser.clear(); // TODO: First message might be code block prepareProcessingTextResponse(); - currentlyProcessedTextPane.setText("

    "); + currentlyProcessedTextPane.setText( + "

    "); repaint(); revalidate(); @@ -203,7 +235,11 @@ public class ChatMessageResponseBody extends JPanel { private void prepareProcessingCodeResponse(String code, String language) { hideCarets(); currentlyProcessedTextPane = null; - currentlyProcessedEditor = new ChatToolWindowTabPanelEditor(project, code, language, parentDisposable); + currentlyProcessedEditor = new ChatToolWindowTabPanelEditor( + project, + code, + language, + parentDisposable); currentlyProcessedElement = new ResponseWrapper(); currentlyProcessedElement.add(currentlyProcessedEditor.getComponent());