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 <carlrobertoh@gmail.com>
This commit is contained in:
keith siilats 2023-10-04 17:18:50 -04:00 committed by GitHub
parent a45646740e
commit 4c8b8d4e4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 21 deletions

View file

@ -13,5 +13,6 @@ public enum ActionType {
CREATE_NEW_FILE,
COPY_CODE,
REPLACE_IN_MAIN_EDITOR,
RELOAD_MESSAGE
RELOAD_MESSAGE,
CHANGE_PROVIDER
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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()

View file

@ -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("<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">&#8205;</p></html>");
currentlyProcessedTextPane.setText(
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">&#8205;</p></html>");
}
UIManager.addPropertyChangeListener(propertyChangeEvent -> setBackground(backgroundColor));
@ -95,7 +105,8 @@ public class ChatMessageResponseBody extends JPanel {
var message = SettingsState.getInstance().isUseYouService() ?
"Please <a href=\"#\">log in</a> to access the chat feature." :
"API key not provided. Open <a href=\"#\">Settings</a> to set one.";
currentlyProcessedTextPane.setText(String.format("<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">%s</p></html>", message));
currentlyProcessedTextPane.setText(
format("<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">%s</p></html>", 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("<html>"
+ "<p style=\"margin-top: 4px; margin-bottom: 8px;\">"
+ "You exceeded your current quota, please check your plan and billing details, "
+ "or <a href=\"#CHANGE_PROVIDER\">change</a> to a different LLM provider.</p>"
+ "</html>");
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("<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">%s</p></html>", message);
var errorText = format(
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">%s</p></html>",
message);
if (responseReceived) {
var errorPane = createTextPane();
errorPane.setText(errorText);
@ -131,7 +161,8 @@ public class ChatMessageResponseBody extends JPanel {
public void displaySerpResults(List<SerpResult> serpResults) {
var titles = serpResults.stream()
.map(result -> format("<li style=\"margin-bottom: 4px;\"><a href=\"%s\">%s</a></li>", result.getUrl(), result.getName()))
.map(result -> format("<li style=\"margin-bottom: 4px;\"><a href=\"%s\">%s</a></li>",
result.getUrl(), result.getName()))
.collect(Collectors.joining());
var html = format(
"<html>" +
@ -153,7 +184,8 @@ public class ChatMessageResponseBody extends JPanel {
streamParser.clear();
// TODO: First message might be code block
prepareProcessingTextResponse();
currentlyProcessedTextPane.setText("<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">&#8205;</p></html>");
currentlyProcessedTextPane.setText(
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">&#8205;</p></html>");
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());