From c3da76f2bdaee782b96c59db692b01f7a3dac775 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Thu, 9 Nov 2023 03:11:50 +0200 Subject: [PATCH] Clean up BaseChatToolWindowTabPanel code --- .../completions/CompletionClientProvider.java | 13 +- .../completions/CompletionRequestHandler.java | 135 +----- .../completions/CompletionRequestService.java | 78 ++++ .../ToolWindowCompletionEventListener.java | 24 ++ .../conversations/message/Message.java | 2 +- .../service/YouServiceSelectionForm.java | 9 +- .../codegpt/settings/state/SettingsState.java | 31 ++ .../chat/BaseChatToolWindowTabPanel.java | 405 ++++++++---------- .../components/ChatMessageResponseBody.java | 30 +- .../chat/components/YouProCheckbox.java | 40 ++ .../ContextualChatToolWindowLandingPanel.java | 2 +- .../chat/editor/ResponseEditor.java | 6 +- .../StandardChatToolWindowLandingPanel.java | 34 +- .../StandardChatToolWindowTabPanel.java | 8 +- .../carlrobert/codegpt/util/SwingUtils.java | 7 +- .../DefaultCompletionRequestHandlerTest.java | 26 +- 16 files changed, 450 insertions(+), 400 deletions(-) create mode 100644 src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java create mode 100644 src/main/java/ee/carlrobert/codegpt/completions/ToolWindowCompletionEventListener.java create mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/YouProCheckbox.java diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index 7f3767ca..1c11b732 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt.completions; import ee.carlrobert.codegpt.CodeGPTPlugin; +import ee.carlrobert.codegpt.completions.you.YouUserManager; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; import ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsState; @@ -30,12 +31,22 @@ public class CompletionClientProvider { return getAzureClientBuilder().build(); } - public static YouClient getYouClient(String sessionId, String accessToken) { + public static YouClient getYouClient() { var utmParameters = new UTMParameters(); utmParameters.setSource("ide"); utmParameters.setMedium("jetbrains"); utmParameters.setCampaign(CodeGPTPlugin.getVersion()); utmParameters.setContent("CodeGPT"); + + var sessionId = ""; + var accessToken = ""; + var youUserManager = YouUserManager.getInstance(); + if (youUserManager.isAuthenticated()) { + var authenticationResponse = youUserManager.getAuthenticationResponse().getData(); + sessionId = authenticationResponse.getSession().getSessionId(); + accessToken = authenticationResponse.getSessionJwt(); + } + // FIXME return (YouClient) new YouClient.Builder(sessionId, accessToken) .setUTMParameters(utmParameters) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java index 316b3f8b..77a1c6fc 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java @@ -1,12 +1,8 @@ package ee.carlrobert.codegpt.completions; import com.intellij.openapi.diagnostic.Logger; -import ee.carlrobert.codegpt.completions.you.YouUserManager; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.settings.service.ServiceType; -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; @@ -14,50 +10,25 @@ import ee.carlrobert.llm.client.you.completion.YouCompletionEventListener; import ee.carlrobert.llm.client.you.completion.YouSerpResult; import ee.carlrobert.llm.completion.CompletionEventListener; import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Consumer; import javax.swing.SwingWorker; import okhttp3.sse.EventSource; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public class CompletionRequestHandler { private static final Logger LOG = Logger.getInstance(CompletionRequestHandler.class); private final StringBuilder messageBuilder = new StringBuilder(); + private final boolean useContextualSearch; + private final ToolWindowCompletionEventListener toolWindowCompletionEventListener; private SwingWorker swingWorker; private EventSource eventSource; - private @Nullable Consumer messageListener; - private @Nullable BiConsumer errorListener; - private @Nullable Consumer completedListener; - private @Nullable Consumer> serpResultsListener; - private @Nullable Runnable tokensExceededListener; - private boolean useContextualSearch; - public CompletionRequestHandler withContextualSearch(boolean useContextualSearch) { + public CompletionRequestHandler( + boolean useContextualSearch, + ToolWindowCompletionEventListener toolWindowCompletionEventListener) { this.useContextualSearch = useContextualSearch; - return this; - } - - public void addMessageListener(Consumer messageListener) { - this.messageListener = messageListener; - } - - public void addErrorListener(BiConsumer errorListener) { - this.errorListener = errorListener; - } - - public void addRequestCompletedListener(Consumer completedListener) { - this.completedListener = completedListener; - } - - public void addTokensExceededListener(Runnable tokensExceededListener) { - this.tokensExceededListener = tokensExceededListener; - } - - public void addSerpResultsListener(Consumer> serpResultsListener) { - this.serpResultsListener = serpResultsListener; + this.toolWindowCompletionEventListener = toolWindowCompletionEventListener; } public void call(Conversation conversation, Message message, boolean isRetry) { @@ -75,60 +46,13 @@ public class CompletionRequestHandler { private EventSource startCall( @NotNull Conversation conversation, @NotNull Message message, - boolean isRetry, + boolean retry, CompletionEventListener eventListener) { - var settings = SettingsState.getInstance(); - var requestProvider = new CompletionRequestProvider(conversation); - try { - if (settings.getSelectedService() == ServiceType.LLAMA_CPP) { - return CompletionClientProvider.getLlamaClient() - .getChatCompletion(requestProvider.buildLlamaCompletionRequest(message), eventListener); - } - - if (settings.getSelectedService() == ServiceType.YOU) { - var sessionId = ""; - var accessToken = ""; - var youUserManager = YouUserManager.getInstance(); - if (youUserManager.isAuthenticated()) { - var authenticationResponse = - youUserManager.getAuthenticationResponse().getData(); - sessionId = authenticationResponse.getSession().getSessionId(); - accessToken = authenticationResponse.getSessionJwt(); - } - var request = requestProvider.buildYouCompletionRequest(message); - LOG.info("Initiating completion request using model: " + - (request.isUseGPT4Model() ? "GPT-4" : "YouBot")); - - return CompletionClientProvider.getYouClient(sessionId, accessToken) - .getChatCompletion(request, eventListener); - } - - if (settings.getSelectedService() == ServiceType.AZURE) { - var azureSettings = AzureSettingsState.getInstance(); - return CompletionClientProvider.getAzureClient().getChatCompletion( - requestProvider.buildOpenAIChatCompletionRequest( - azureSettings.getModel(), - message, - isRetry, - useContextualSearch, - azureSettings.isUsingCustomPath() ? azureSettings.getPath() : null), - eventListener); - } - - var openAISettings = OpenAISettingsState.getInstance(); - return CompletionClientProvider.getOpenAIClient().getChatCompletion( - requestProvider.buildOpenAIChatCompletionRequest( - openAISettings.getModel(), - message, - isRetry, - useContextualSearch, - openAISettings.isUsingCustomPath() ? openAISettings.getPath() : null), - eventListener); + return CompletionRequestService.getInstance() + .getChatCompletionAsync(conversation, message, retry, useContextualSearch, eventListener); } catch (Throwable t) { - if (errorListener != null) { - errorListener.accept(new ErrorDetails("Something went wrong"), t); - } + toolWindowCompletionEventListener.handleError(new ErrorDetails("Something went wrong"), t); throw t; } } @@ -152,13 +76,9 @@ public class CompletionRequestHandler { conversation, message, isRetry, - settings.getSelectedService() == ServiceType.YOU ? - new YouRequestCompletionEventListener() : - new BaseCompletionEventListener()); + new YouRequestCompletionEventListener()); } catch (TotalUsageExceededException e) { - if (tokensExceededListener != null) { - tokensExceededListener.run(); - } + toolWindowCompletionEventListener.handleTokensExceeded(conversation, message); } finally { sendInfo(settings); } @@ -169,13 +89,16 @@ public class CompletionRequestHandler { message.setResponse(messageBuilder.toString()); for (String text : chunks) { messageBuilder.append(text); - if (messageListener != null) { - messageListener.accept(text); - } + toolWindowCompletionEventListener.handleMessage(text); } } - class BaseCompletionEventListener implements CompletionEventListener { + class YouRequestCompletionEventListener implements YouCompletionEventListener { + + @Override + public void onSerpResults(List results) { + toolWindowCompletionEventListener.handleSerpResults(results, message); + } @Override public void onMessage(String message) { @@ -184,34 +107,20 @@ public class CompletionRequestHandler { @Override public void onComplete(StringBuilder messageBuilder) { - if (completedListener != null) { - completedListener.accept(messageBuilder.toString()); - } + toolWindowCompletionEventListener.handleCompleted(messageBuilder.toString(), message, + conversation, isRetry); } @Override public void onError(ErrorDetails error, Throwable ex) { try { - if (errorListener != null) { - errorListener.accept(error, ex); - } + toolWindowCompletionEventListener.handleError(error, ex); } finally { sendError(error, ex); } } } - class YouRequestCompletionEventListener extends BaseCompletionEventListener - implements YouCompletionEventListener { - - @Override - public void onSerpResults(List results) { - if (serpResultsListener != null) { - serpResultsListener.accept(results); - } - } - } - private void sendInfo(SettingsState settings) { TelemetryAction.COMPLETION.createActionMessage() .property("conversationId", conversation.getId().toString()) diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java new file mode 100644 index 00000000..c6c59b86 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java @@ -0,0 +1,78 @@ +package ee.carlrobert.codegpt.completions; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; +import ee.carlrobert.codegpt.conversations.Conversation; +import ee.carlrobert.codegpt.conversations.message.Message; +import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; +import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; +import ee.carlrobert.codegpt.settings.service.ServiceType; +import ee.carlrobert.codegpt.settings.state.AzureSettingsState; +import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; +import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.llm.completion.CompletionEventListener; +import okhttp3.sse.EventSource; +import org.jetbrains.annotations.NotNull; + +@Service +public final class CompletionRequestService { + + private CompletionRequestService() { + } + + public static CompletionRequestService getInstance() { + return ApplicationManager.getApplication().getService(CompletionRequestService.class); + } + + public EventSource getChatCompletionAsync( + @NotNull Conversation conversation, + @NotNull Message message, + boolean retry, + boolean useContextualSearch, + CompletionEventListener eventListener) { + var requestProvider = new CompletionRequestProvider(conversation); + switch (SettingsState.getInstance().getSelectedService()) { + case OPENAI: + var openAISettings = OpenAISettingsState.getInstance(); + return CompletionClientProvider.getOpenAIClient().getChatCompletion( + requestProvider.buildOpenAIChatCompletionRequest( + openAISettings.getModel(), + message, + retry, + useContextualSearch, + openAISettings.isUsingCustomPath() ? openAISettings.getPath() : null), + eventListener); + case AZURE: + var azureSettings = AzureSettingsState.getInstance(); + return CompletionClientProvider.getAzureClient().getChatCompletion( + requestProvider.buildOpenAIChatCompletionRequest( + azureSettings.getModel(), + message, + retry, + useContextualSearch, + azureSettings.isUsingCustomPath() ? azureSettings.getPath() : null), + eventListener); + case YOU: + return CompletionClientProvider.getYouClient().getChatCompletion( + requestProvider.buildYouCompletionRequest(message), + eventListener); + case LLAMA_CPP: + return CompletionClientProvider.getLlamaClient().getChatCompletion( + requestProvider.buildLlamaCompletionRequest(message), + eventListener); + default: + throw new IllegalArgumentException(); + } + } + + public boolean isRequestAllowed() { + var selectedService = SettingsState.getInstance().getSelectedService(); + if (selectedService == ServiceType.AZURE) { + return AzureCredentialsManager.getInstance().isCredentialSet(); + } + if (selectedService == ServiceType.OPENAI) { + return OpenAICredentialsManager.getInstance().isApiKeySet(); + } + return true; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/completions/ToolWindowCompletionEventListener.java b/src/main/java/ee/carlrobert/codegpt/completions/ToolWindowCompletionEventListener.java new file mode 100644 index 00000000..94d669af --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/completions/ToolWindowCompletionEventListener.java @@ -0,0 +1,24 @@ +package ee.carlrobert.codegpt.completions; + +import ee.carlrobert.codegpt.conversations.Conversation; +import ee.carlrobert.codegpt.conversations.message.Message; +import ee.carlrobert.llm.client.openai.completion.ErrorDetails; +import ee.carlrobert.llm.client.you.completion.YouSerpResult; +import java.util.List; + +public interface ToolWindowCompletionEventListener { + + default void handleMessage(String message) {} + + default void handleError(ErrorDetails error, Throwable ex) {} + + default void handleTokensExceeded(Conversation conversation, Message message) {} + + default void handleCompleted( + String fullMessage, + Message message, + Conversation conversation, + boolean isRetry) {} + + default void handleSerpResults(List results, Message message) {} +} diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java b/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java index 7f09a378..2e81e627 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java @@ -2,7 +2,7 @@ package ee.carlrobert.codegpt.conversations.message; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import ee.carlrobert.codegpt.completions.you.YouSerpResult; +import ee.carlrobert.llm.client.you.completion.YouSerpResult; import java.util.List; import java.util.Objects; import java.util.UUID; diff --git a/src/main/java/ee/carlrobert/codegpt/settings/service/YouServiceSelectionForm.java b/src/main/java/ee/carlrobert/codegpt/settings/service/YouServiceSelectionForm.java index 72c3e916..0c6a0209 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/service/YouServiceSelectionForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/service/YouServiceSelectionForm.java @@ -134,15 +134,10 @@ public class YouServiceSelectionForm extends JPanel { } private JTextPane createSignUpTextPane() { - var textPane = createTextPane( + var textPane = SwingUtils.createTextPane( "Don't have an account? Sign up"); textPane.setBorder(JBUI.Borders.emptyLeft(4)); - return textPane; - } - - private JTextPane createTextPane(String htmlContent) { - var textPane = SwingUtils.createTextPane(SwingUtils::handleHyperlinkClicked); - textPane.setText(htmlContent); + textPane.setOpaque(false); return textPane; } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java index 6fa4d4c4..48cd7484 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java @@ -6,6 +6,7 @@ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.XmlSerializerUtil; 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 org.jetbrains.annotations.NotNull; @@ -62,6 +63,36 @@ public class SettingsState implements PersistentStateComponent { } } + public String getModel() { + switch (selectedService) { + case OPENAI: + return OpenAISettingsState.getInstance().getModel(); + case AZURE: + return AzureSettingsState.getInstance().getModel(); + case YOU: + return "YouCode"; + case LLAMA_CPP: + var llamaSettings = LlamaSettingsState.getInstance(); + if (llamaSettings.isUseCustomModel()) { + var filePath = llamaSettings.getCustomLlamaModelPath(); + int lastSeparatorIndex = filePath.lastIndexOf('/'); + if (lastSeparatorIndex == -1) { + return filePath; + } + return filePath.substring(lastSeparatorIndex + 1); + } + var huggingFaceModel = llamaSettings.getHuggingFaceModel(); + var llamaModel = LlamaModel.findByHuggingFaceModel(huggingFaceModel); + return String.format( + "%s %dB (Q%d)", + llamaModel.getLabel(), + huggingFaceModel.getParameterSize(), + huggingFaceModel.getQuantization()); + default: + return "Unknown"; + } + } + public String getEmail() { return email; } 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 ec96a670..78e31129 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java @@ -3,35 +3,30 @@ package ee.carlrobert.codegpt.toolwindow.chat; import static com.intellij.openapi.ui.Messages.OK; import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor; import static java.lang.String.format; -import static java.util.stream.Collectors.toList; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; 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.ui.ScrollPaneFactory; import com.intellij.ui.components.JBCheckBox; -import com.intellij.ui.components.JBScrollPane; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.JBUI.Borders; -import ee.carlrobert.codegpt.CodeGPTBundle; import ee.carlrobert.codegpt.actions.ActionType; import ee.carlrobert.codegpt.completions.CompletionRequestHandler; -import ee.carlrobert.codegpt.completions.llama.LlamaModel; -import ee.carlrobert.codegpt.completions.you.YouSerpResult; +import ee.carlrobert.codegpt.completions.CompletionRequestService; +import ee.carlrobert.codegpt.completions.ToolWindowCompletionEventListener; import ee.carlrobert.codegpt.completions.you.YouSubscriptionNotifier; import ee.carlrobert.codegpt.completions.you.YouUserManager; import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; -import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; import ee.carlrobert.codegpt.settings.service.ServiceType; -import ee.carlrobert.codegpt.settings.state.AzureSettingsState; -import ee.carlrobert.codegpt.settings.state.LlamaSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.settings.state.YouSettingsState; @@ -42,10 +37,13 @@ import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel; import ee.carlrobert.codegpt.toolwindow.chat.components.SmartScroller; import ee.carlrobert.codegpt.toolwindow.chat.components.UserMessagePanel; import ee.carlrobert.codegpt.toolwindow.chat.components.UserPromptTextArea; +import ee.carlrobert.codegpt.toolwindow.chat.components.YouProCheckbox; import ee.carlrobert.codegpt.util.EditorUtils; import ee.carlrobert.codegpt.util.OverlayUtils; import ee.carlrobert.codegpt.util.SwingUtils; import ee.carlrobert.codegpt.util.file.FileUtils; +import ee.carlrobert.llm.client.openai.completion.ErrorDetails; +import ee.carlrobert.llm.client.you.completion.YouSerpResult; import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -57,6 +55,7 @@ import java.util.UUID; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JTextPane; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; @@ -67,6 +66,9 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan private static final Logger LOG = Logger.getInstance(BaseChatToolWindowTabPanel.class); + private final SettingsState settings; + private final YouUserManager youUserManager; + private final boolean useContextualSearch; private final JPanel rootPanel; private final ScrollablePanel scrollablePanel; @@ -85,11 +87,15 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan this.project = project; this.useContextualSearch = useContextualSearch; this.conversationService = ConversationService.getInstance(); - this.rootPanel = new JPanel(new GridBagLayout()); - this.scrollablePanel = new ScrollablePanel(); + this.scrollablePanel = new ScrollablePanel(new VerticalStackLayout()); this.userPromptTextArea = new UserPromptTextArea(this::handleSubmit); - this.gpt4CheckBox = createGPT4ModelCheckBox(); - init(); + this.gpt4CheckBox = new YouProCheckbox(project); + this.settings = SettingsState.getInstance(); + this.youUserManager = YouUserManager.getInstance(); + this.rootPanel = createRootPanel(); + + userPromptTextArea.requestFocusInWindow(); + userPromptTextArea.requestFocus(); } public void requestFocusForTextArea() { @@ -115,31 +121,14 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan public void displayLandingView() { scrollablePanel.removeAll(); scrollablePanel.add(getLandingView()); - var youUserManager = YouUserManager.getInstance(); - if (SettingsState.getInstance().getSelectedService() == ServiceType.YOU && + if (settings.getSelectedService() == ServiceType.YOU && (!youUserManager.isAuthenticated() || !youUserManager.isSubscribed())) { - scrollablePanel.add(new ResponsePanel().addContent(createTextPane())); + scrollablePanel.add(new ResponsePanel().addContent(createYouCouponTextPane())); } scrollablePanel.repaint(); scrollablePanel.revalidate(); } - private JTextPane createTextPane() { - var textPane = SwingUtils.createTextPane(SwingUtils::handleHyperlinkClicked); - textPane.setBackground(getPanelBackgroundColor()); - textPane.setFocusable(false); - textPane.setText( - "\n" - + "\n" - + "

Use CodeGPT coupon for free month of GPT-4.

\n" - + "

\n" - + " Sign up here\n" - + "

\n" - + "\n" - + ""); - return textPane; - } - @Override public void startNewConversation(Message message) { conversation = conversationService.startConversation(); @@ -160,9 +149,10 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan this)); var responsePanel = new ResponsePanel() .withReloadAction(() -> reloadMessage(message, conversation)) - .withDeleteAction(() -> deleteMessage(message.getId(), messageWrapper, conversation)) + .withDeleteAction(() -> removeMessage(message.getId(), messageWrapper, conversation)) .addContent(new ChatMessageResponseBody(project, true, this)); messageWrapper.add(responsePanel); + call(conversation, message, responsePanel, false); } @@ -170,106 +160,6 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan public void dispose() { } - private boolean isRequestAllowed() { - var settings = SettingsState.getInstance(); - if (settings.getSelectedService() == ServiceType.AZURE) { - return AzureCredentialsManager.getInstance().isCredentialSet(); - } - if (settings.getSelectedService() == ServiceType.OPENAI) { - return OpenAICredentialsManager.getInstance().isApiKeySet(); - } - return true; - } - - private void call( - Conversation conversation, - Message message, - ResponsePanel responsePanel, - boolean isRetry) { - ChatMessageResponseBody responseContainer = (ChatMessageResponseBody) responsePanel.getContent(); - - if (!isRequestAllowed()) { - responseContainer.displayMissingCredential(); - return; - } - - var requestHandler = new CompletionRequestHandler(); - requestHandler.withContextualSearch(useContextualSearch); - requestHandler.addMessageListener(partialMessage -> { - try { - LOG.debug(partialMessage); - ApplicationManager.getApplication() - .invokeLater(() -> responseContainer.update(partialMessage)); - } catch (Exception e) { - responseContainer.displayDefaultError(); - throw new RuntimeException("Error while updating the content", e); - } - }); - requestHandler.addRequestCompletedListener(completeMessage -> { - responsePanel.enableActions(); - conversationService.saveMessage(completeMessage, message, conversation, isRetry); - stopStreaming(responseContainer); - - var serpResults = serpResultsMapping.get(message.getId()); - var containsResults = serpResults != null && !serpResults.isEmpty(); - if (YouSettingsState.getInstance().isDisplayWebSearchResults()) { - if (containsResults) { - responseContainer.displaySerpResults(serpResults); - } - } - - if (containsResults) { - message.setSerpResults(serpResults.stream() - .map(result -> new YouSerpResult( - result.getUrl(), - result.getName(), - result.getSnippet(), - result.getSnippetSource())) - .collect(toList())); - } - }); - requestHandler.addTokensExceededListener(() -> SwingUtilities.invokeLater(() -> { - var answer = OverlayUtils.showTokenLimitExceededDialog(); - if (answer == OK) { - TelemetryAction.IDE_ACTION.createActionMessage() - .property("action", "DISCARD_TOKEN_LIMIT") - .property("model", conversation.getModel()) - .send(); - - conversationService.discardTokenLimits(conversation); - requestHandler.call(conversation, message, true); - } else { - stopStreaming(responseContainer); - } - })); - requestHandler.addErrorListener((error, ex) -> { - try { - if ("insufficient_quota".equals(error.getCode())) { - if (SettingsState.getInstance().getSelectedService() == ServiceType.OPENAI) { - OpenAISettingsState.getInstance().setOpenAIQuotaExceeded(true); - } - responseContainer.displayQuotaExceeded(); - } else { - responseContainer.displayError(error.getMessage()); - } - } finally { - responsePanel.enableActions(); - stopStreaming(responseContainer); - } - }); - requestHandler.addSerpResultsListener( - serpResults -> serpResultsMapping.put(message.getId(), serpResults.stream() - .map(result -> new YouSerpResult( - result.getUrl(), - result.getName(), - result.getSnippet(), - result.getSnippetSource())) - .collect(toList()))); - userPromptTextArea.setRequestHandler(requestHandler); - userPromptTextArea.setSubmitEnabled(false); - requestHandler.call(conversation, message, isRetry); - } - protected void reloadMessage(Message message, Conversation conversation) { ResponsePanel responsePanel = null; try { @@ -281,8 +171,10 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan scrollablePanel.revalidate(); scrollablePanel.repaint(); } catch (Exception e) { - throw new RuntimeException("Couldn't reload message", e); + throw new RuntimeException("Couldn't delete the existing message component", e); } finally { + LOG.debug("Reloading message: " + message.getId()); + if (responsePanel != null) { message.setResponse(""); conversationService.saveMessage(conversation, message); @@ -295,7 +187,7 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan } } - protected void deleteMessage(UUID messageId, JPanel messageWrapper, Conversation conversation) { + protected void removeMessage(UUID messageId, JPanel messageWrapper, Conversation conversation) { scrollablePanel.remove(messageWrapper); scrollablePanel.repaint(); scrollablePanel.revalidate(); @@ -327,11 +219,24 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan scrollablePanel.repaint(); } - private void stopStreaming(ChatMessageResponseBody responseContainer) { - SwingUtilities.invokeLater(() -> { - userPromptTextArea.setSubmitEnabled(true); - responseContainer.hideCarets(); - }); + private void call( + Conversation conversation, + Message message, + ResponsePanel responsePanel, + boolean isRetry) { + ChatMessageResponseBody responseContainer = (ChatMessageResponseBody) responsePanel.getContent(); + + if (!CompletionRequestService.getInstance().isRequestAllowed()) { + responseContainer.displayMissingCredential(); + return; + } + + var requestHandler = new CompletionRequestHandler( + useContextualSearch, + new ChatToolWindowCompletionEventListener(responsePanel)); + userPromptTextArea.setRequestHandler(requestHandler); + userPromptTextArea.setSubmitEnabled(false); + requestHandler.call(conversation, message, isRetry); } private void handleSubmit(String text) { @@ -356,46 +261,44 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan } } - private void init() { + private static JScrollPane createScrollPane(ScrollablePanel scrollablePanel) { + var scrollPane = ScrollPaneFactory.createScrollPane(scrollablePanel, true); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + new SmartScroller(scrollPane); + return scrollPane; + } + + private JPanel createRootPanel() { + var rootPanel = new JPanel(new GridBagLayout()); var gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.BOTH; gbc.weighty = 1; gbc.weightx = 1; gbc.gridx = 0; gbc.gridy = 0; - - scrollablePanel.setLayout(new BoxLayout(scrollablePanel, BoxLayout.Y_AXIS)); - JBScrollPane scrollPane = new JBScrollPane(scrollablePanel); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setBorder(null); - scrollPane.setViewportBorder(null); - rootPanel.add(scrollPane, gbc); - new SmartScroller(scrollPane); - - gbc.weighty = 0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.gridy = 1; - - var model = getModel(); - var modelIconWrapper = JBUI.Panels - .simplePanel(new ModelIconLabel( - SettingsState.getInstance() - .getSelectedService() - .getCompletionCode(), - model)) - .withBorder(Borders.emptyRight(4)) - .withBackground(getPanelBackgroundColor()); + rootPanel.add(createScrollPane(scrollablePanel), gbc); var wrapper = new JPanel(new BorderLayout()); wrapper.setBorder(JBUI.Borders.compound( JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0), JBUI.Borders.empty(8))); wrapper.setBackground(getPanelBackgroundColor()); + wrapper.add(createPromptTextAreaHeader(), BorderLayout.NORTH); wrapper.add(userPromptTextArea, BorderLayout.SOUTH); + gbc.weighty = 0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridy = 1; + rootPanel.add(wrapper, gbc); + + return rootPanel; + } + + private JPanel createPromptTextAreaHeader() { var header = new JPanel(new BorderLayout()); header.setBackground(getPanelBackgroundColor()); header.setBorder(JBUI.Borders.emptyBottom(8)); + var model = settings.getModel(); if ("YouCode".equals(model)) { var messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); subscribeToYouModelChangeTopic(); @@ -403,18 +306,13 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan subscribeToSignedOutTopic(messageBusConnection); header.add(gpt4CheckBox, BorderLayout.LINE_START); } - header.add(modelIconWrapper, BorderLayout.LINE_END); - wrapper.add(header); - - rootPanel.add(wrapper, gbc); - userPromptTextArea.requestFocusInWindow(); - userPromptTextArea.requestFocus(); - } - - private void subscribeToSignedOutTopic(MessageBusConnection messageBusConnection) { - messageBusConnection.subscribe( - SignedOutNotifier.SIGNED_OUT_TOPIC, - (SignedOutNotifier) () -> gpt4CheckBox.setEnabled(false)); + header.add(JBUI.Panels + .simplePanel( + new ModelIconLabel(settings.getSelectedService().getCompletionCode(), + model)) + .withBorder(Borders.emptyRight(4)) + .withBackground(getPanelBackgroundColor()), BorderLayout.LINE_END); + return header; } private void subscribeToYouModelChangeTopic() { @@ -425,6 +323,12 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan (YouModelChangeNotifier) gpt4CheckBox::setSelected); } + private void subscribeToSignedOutTopic(MessageBusConnection messageBusConnection) { + messageBusConnection.subscribe( + SignedOutNotifier.SIGNED_OUT_TOPIC, + (SignedOutNotifier) () -> gpt4CheckBox.setEnabled(false)); + } + private void subscribeToYouSubscriptionTopic(MessageBusConnection messageBusConnection) { messageBusConnection.subscribe( YouSubscriptionNotifier.SUBSCRIPTION_TOPIC, @@ -434,64 +338,111 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan }); } - private JBCheckBox createGPT4ModelCheckBox() { - var gpt4CheckBox = new JBCheckBox(CodeGPTBundle.get("toolwindow.chat.youProCheckBox.text")); - gpt4CheckBox.setOpaque(false); - gpt4CheckBox.setEnabled(YouUserManager.getInstance().isSubscribed()); - gpt4CheckBox.setSelected(YouSettingsState.getInstance().isUseGPT4Model()); - gpt4CheckBox.setToolTipText(getTooltipText(gpt4CheckBox.isSelected())); - gpt4CheckBox.addChangeListener(e -> { - var selected = ((JBCheckBox) e.getSource()).isSelected(); - var tooltipText = getTooltipText(selected); - gpt4CheckBox.setToolTipText(tooltipText); - // TODO: Remove - project.getMessageBus() - .syncPublisher(YouModelChangeNotifier.YOU_MODEL_CHANGE_NOTIFIER_TOPIC) - .modelChanged(selected); - YouSettingsState.getInstance().setUseGPT4Model(selected); - }); - return gpt4CheckBox; + private JTextPane createYouCouponTextPane() { + var textPane = SwingUtils.createTextPane( + "\n" + + "\n" + + "

Use CodeGPT coupon for free month of GPT-4.

\n" + + "

\n" + + " Sign up here\n" + + "

\n" + + "\n" + + "" + ); + textPane.setBackground(getPanelBackgroundColor()); + textPane.setFocusable(false); + return textPane; } - private String getTooltipText(boolean selected) { - if (YouUserManager.getInstance().isSubscribed()) { - return selected ? - CodeGPTBundle.get("toolwindow.chat.youProCheckBox.disable") : - CodeGPTBundle.get("toolwindow.chat.youProCheckBox.enable"); - } - return CodeGPTBundle.get("toolwindow.chat.youProCheckBox.notAllowed"); - } + private class ChatToolWindowCompletionEventListener implements ToolWindowCompletionEventListener { - private String getModel() { - var settings = SettingsState.getInstance(); - if (settings.getSelectedService() == ServiceType.OPENAI) { - return OpenAISettingsState.getInstance().getModel(); + private final Logger LOG = Logger.getInstance(ChatToolWindowCompletionEventListener.class); + + private final ResponsePanel responsePanel; + private final ChatMessageResponseBody responseContainer; + + public ChatToolWindowCompletionEventListener(ResponsePanel responsePanel) { + this.responsePanel = responsePanel; + this.responseContainer = (ChatMessageResponseBody) responsePanel.getContent(); } - if (settings.getSelectedService() == ServiceType.AZURE) { - return AzureSettingsState.getInstance().getModel(); - } - if (settings.getSelectedService() == ServiceType.YOU) { - return "YouCode"; - } - if (settings.getSelectedService() == ServiceType.LLAMA_CPP) { - var llamaSettings = LlamaSettingsState.getInstance(); - if (llamaSettings.isUseCustomModel()) { - var filePath = llamaSettings.getCustomLlamaModelPath(); - int lastSeparatorIndex = filePath.lastIndexOf('/'); - if (lastSeparatorIndex == -1) { - return filePath; - } - return filePath.substring(lastSeparatorIndex + 1); + + @Override + public void handleMessage(String message) { + try { + LOG.debug(message); + ApplicationManager.getApplication() + .invokeLater(() -> responseContainer.update(message)); + } catch (Exception e) { + responseContainer.displayDefaultError(); + throw new RuntimeException("Error while updating the content", e); } - var huggingFaceModel = llamaSettings.getHuggingFaceModel(); - var llamaModel = LlamaModel.findByHuggingFaceModel(huggingFaceModel); - return String.format( - "%s %dB (Q%d)", - llamaModel.getLabel(), - huggingFaceModel.getParameterSize(), - huggingFaceModel.getQuantization()); } - return "Unknown"; + @Override + public void handleError(ErrorDetails error, Throwable ex) { + try { + if ("insufficient_quota".equals(error.getCode())) { + if (SettingsState.getInstance().getSelectedService() == ServiceType.OPENAI) { + OpenAISettingsState.getInstance().setOpenAIQuotaExceeded(true); + } + responseContainer.displayQuotaExceeded(); + } else { + responseContainer.displayError(error.getMessage()); + } + } finally { + LOG.error(error.getMessage(), ex); + responsePanel.enableActions(); + stopStreaming(responseContainer); + } + } + + @Override + public void handleTokensExceeded(Conversation conversation, Message message) { + var answer = OverlayUtils.showTokenLimitExceededDialog(); + if (answer == OK) { + TelemetryAction.IDE_ACTION.createActionMessage() + .property("action", "DISCARD_TOKEN_LIMIT") + .property("model", conversation.getModel()) + .send(); + + conversationService.discardTokenLimits(conversation); + call(conversation, message, responsePanel, true); + } else { + stopStreaming(responseContainer); + } + } + + @Override + public void handleCompleted( + String fullMessage, + Message message, + Conversation conversation, + boolean isRetry) { + responsePanel.enableActions(); + conversationService.saveMessage(fullMessage, message, conversation, isRetry); + stopStreaming(responseContainer); + + var serpResults = serpResultsMapping.get(message.getId()); + var containsResults = serpResults != null && !serpResults.isEmpty(); + if (YouSettingsState.getInstance().isDisplayWebSearchResults() && containsResults) { + responseContainer.displaySerpResults(serpResults); + } + + if (containsResults) { + message.setSerpResults(serpResults); + } + } + + @Override + public void handleSerpResults(List results, Message message) { + serpResultsMapping.put(message.getId(), results); + } + + private void stopStreaming(ChatMessageResponseBody responseContainer) { + SwingUtilities.invokeLater(() -> { + userPromptTextArea.setSubmitEnabled(true); + responseContainer.hideCarets(); + }); + } } } 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 f7efdf3c..7a9e5be9 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 @@ -20,7 +20,6 @@ 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.you.YouSerpResult; import ee.carlrobert.codegpt.settings.SettingsConfigurable; import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer; @@ -29,6 +28,7 @@ import ee.carlrobert.codegpt.toolwindow.chat.StreamResponseType; import ee.carlrobert.codegpt.toolwindow.chat.editor.ResponseEditor; import ee.carlrobert.codegpt.util.MarkdownUtils; import ee.carlrobert.codegpt.util.SwingUtils; +import ee.carlrobert.llm.client.you.completion.YouSerpResult; import java.awt.BorderLayout; import java.awt.Color; import java.util.List; @@ -145,9 +145,7 @@ public class ChatMessageResponseBody extends JPanel { "

%s

", message); if (responseReceived) { - var errorPane = createTextPane(); - errorPane.setText(errorText); - add(new ResponseWrapper().add(errorPane)); + add(new ResponseWrapper().add(createTextPane(errorText))); } else { currentlyProcessedTextPane.setText(errorText); } @@ -158,22 +156,24 @@ public class ChatMessageResponseBody extends JPanel { } public void displaySerpResults(List serpResults) { + var html = getSearchResultsHtml(serpResults); + if (responseReceived) { + add(new ResponseWrapper().add(createTextPane(html))); + } else { + currentlyProcessedTextPane.setText(html); + } + } + + private String getSearchResultsHtml(List serpResults) { var titles = serpResults.stream() .map(result -> format("
  • %s
  • ", result.getUrl(), result.getName())) .collect(Collectors.joining()); - var html = format( + return format( "" + "

    Search results:

    " + "
      %s
    " + "", titles); - if (responseReceived) { - var textPane = createTextPane(); - textPane.setText(html); - add(new ResponseWrapper().add(textPane)); - } else { - currentlyProcessedTextPane.setText(html); - } } public void clear() { @@ -224,7 +224,7 @@ public class ChatMessageResponseBody extends JPanel { private void prepareProcessingTextResponse() { hideCarets(); currentlyProcessedEditor = null; - currentlyProcessedTextPane = createTextPane(); + currentlyProcessedTextPane = createTextPane(""); currentlyProcessedElement = new ResponseWrapper(); currentlyProcessedElement.add(currentlyProcessedTextPane); add(currentlyProcessedElement); @@ -263,8 +263,8 @@ public class ChatMessageResponseBody extends JPanel { } } - private JTextPane createTextPane() { - var textPane = SwingUtils.createTextPane(event -> { + private JTextPane createTextPane(String text) { + var textPane = SwingUtils.createTextPane(text, event -> { if (FileUtil.exists(event.getDescription()) && ACTIVATED.equals(event.getEventType())) { VirtualFile file = LocalFileSystem.getInstance().findFileByPath(event.getDescription()); FileEditorManager.getInstance(project).openFile(Objects.requireNonNull(file), true); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/YouProCheckbox.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/YouProCheckbox.java new file mode 100644 index 00000000..ce21bc34 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/YouProCheckbox.java @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.toolwindow.chat.components; + +import com.intellij.openapi.project.Project; +import com.intellij.ui.components.JBCheckBox; +import ee.carlrobert.codegpt.CodeGPTBundle; +import ee.carlrobert.codegpt.completions.you.YouUserManager; +import ee.carlrobert.codegpt.settings.state.YouSettingsState; +import ee.carlrobert.codegpt.toolwindow.chat.YouModelChangeNotifier; +import org.jetbrains.annotations.NotNull; + +public class YouProCheckbox extends JBCheckBox { + + public YouProCheckbox(@NotNull Project project) { + super(CodeGPTBundle.get("toolwindow.chat.youProCheckBox.text")); + var youSettings = YouSettingsState.getInstance(); + var youUserManager = YouUserManager.getInstance(); + setOpaque(false); + setEnabled(youUserManager.isSubscribed()); + setSelected(youSettings.isUseGPT4Model()); + setToolTipText(getTooltipText(youUserManager, isSelected())); + addChangeListener(e -> { + var selected = ((JBCheckBox) e.getSource()).isSelected(); + setToolTipText(getTooltipText(youUserManager, selected)); + // TODO: Remove + project.getMessageBus() + .syncPublisher(YouModelChangeNotifier.YOU_MODEL_CHANGE_NOTIFIER_TOPIC) + .modelChanged(selected); + youSettings.setUseGPT4Model(selected); + }); + } + + private String getTooltipText(YouUserManager youUserManager, boolean selected) { + if (youUserManager.isSubscribed()) { + return selected ? + CodeGPTBundle.get("toolwindow.chat.youProCheckBox.disable") : + CodeGPTBundle.get("toolwindow.chat.youProCheckBox.enable"); + } + return CodeGPTBundle.get("toolwindow.chat.youProCheckBox.notAllowed"); + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java index f8892c52..983863c9 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java @@ -71,7 +71,7 @@ class ContextualChatToolWindowLandingPanel extends ResponsePanel { } private JTextPane createTextPane() { - var textPane = SwingUtils.createTextPane(this::handleHyperlinkClicked); + var textPane = SwingUtils.createTextPane("", this::handleHyperlinkClicked); textPane.setBackground(getPanelBackgroundColor()); return textPane; } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditor.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditor.java index b22459b4..d8f04735 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditor.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/editor/ResponseEditor.java @@ -65,12 +65,12 @@ public class ResponseEditor extends JPanel implements Disposable { var settings = editorEx.getSettings(); settings.setAdditionalColumnsCount(0); - settings.setAdditionalLinesCount(0); + settings.setAdditionalLinesCount(1); settings.setAdditionalPageAtBottom(false); settings.setVirtualSpace(false); settings.setUseSoftWraps(false); - settings.setLineMarkerAreaShown(true); - settings.setGutterIconsShown(true); + settings.setLineMarkerAreaShown(false); + settings.setGutterIconsShown(false); add(createHeaderComponent(), BorderLayout.NORTH); add(editor.getComponent(), BorderLayout.SOUTH); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java index b13c7fb3..718ff11b 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt.toolwindow.chat.standard; import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor; +import static java.lang.String.format; import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED; import com.intellij.openapi.diagnostic.Logger; @@ -23,25 +24,20 @@ class StandardChatToolWindowLandingPanel extends ResponsePanel { } private JTextPane createContent() { - var description = createTextPane(); - description.setText("" + - String.format( - "

    Welcome %s, I'm your intelligent code companion, here to be your partner-in-crime for getting things done in a flash. Together, we'll tackle tasks swiftly and efficiently, making your coding experience a joy.

    ", - SettingsState.getInstance().getDisplayName()) + - "

    Feel free to ask me anything you'd like, but my true superpower lies in assisting you with your code! Here are a few examples of how I can assist you:

    " + - "
      " + - "
    • Generate unit tests for the selected codeExplain the selected code
    • " + - "
    • Find bugs in the selected code
    • " + - "Being an AI-powered assistant, I may occasionally have surprises or make mistakes. Therefore, it's wise to double-check any code or suggestions I provide.

      " + - ""); - - return description; - } - - private JTextPane createTextPane() { - var textPane = SwingUtils.createTextPane(this::handleHyperlinkClicked); + var textPane = SwingUtils.createTextPane( + "" + + format( + "

      Welcome %s, I'm your intelligent code companion, here to be your partner-in-crime for getting things done in a flash. Together, we'll tackle tasks swiftly and efficiently, making your coding experience a joy.

      ", + SettingsState.getInstance().getDisplayName()) + + "

      Feel free to ask me anything you'd like, but my true superpower lies in assisting you with your code! Here are a few examples of how I can assist you:

      " + + "
        " + + "
      • Generate unit tests for the selected codeExplain the selected code
      • " + + "
      • Find bugs in the selected code
      • " + + "Being an AI-powered assistant, I may occasionally have surprises or make mistakes. Therefore, it's wise to double-check any code or suggestions I provide.

        " + + "", + this::handleHyperlinkClicked); textPane.setBackground(getPanelBackgroundColor()); return textPane; } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java index 188c7d2a..1f78350f 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java @@ -6,15 +6,14 @@ import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.settings.state.YouSettingsState; import ee.carlrobert.codegpt.toolwindow.chat.BaseChatToolWindowTabPanel; import ee.carlrobert.codegpt.toolwindow.chat.components.ChatMessageResponseBody; import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel; import ee.carlrobert.codegpt.toolwindow.chat.components.UserMessagePanel; import ee.carlrobert.codegpt.util.EditorUtils; -import ee.carlrobert.codegpt.util.file.FileUtils; import ee.carlrobert.codegpt.util.OverlayUtils; +import ee.carlrobert.codegpt.util.file.FileUtils; import javax.swing.JComponent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -25,7 +24,8 @@ public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { this(project, null); } - public StandardChatToolWindowTabPanel(@NotNull Project project, + public StandardChatToolWindowTabPanel( + @NotNull Project project, @Nullable Conversation conversation) { super(project, false); if (conversation == null) { @@ -79,7 +79,7 @@ public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { messageWrapper.add(new UserMessagePanel(project, message, false, this)); messageWrapper.add(new ResponsePanel() .withReloadAction(() -> reloadMessage(message, conversation)) - .withDeleteAction(() -> deleteMessage(message.getId(), messageWrapper, conversation)) + .withDeleteAction(() -> removeMessage(message.getId(), messageWrapper, conversation)) .addContent(messageResponseBody)); }); setConversation(conversation); diff --git a/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java b/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java index 8cdc579d..0b4f1f83 100644 --- a/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java +++ b/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java @@ -22,12 +22,17 @@ import javax.swing.event.HyperlinkListener; public class SwingUtils { - public static JTextPane createTextPane(HyperlinkListener listener) { + public static JTextPane createTextPane(String text) { + return createTextPane(text, SwingUtils::handleHyperlinkClicked); + } + + public static JTextPane createTextPane(String text, HyperlinkListener listener) { var textPane = new JTextPane(); textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true); textPane.addHyperlinkListener(listener); textPane.setContentType("text/html"); textPane.setEditable(false); + textPane.setText(text); return textPane; } diff --git a/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java b/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java index 7fc79608..6143650b 100644 --- a/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java +++ b/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java @@ -13,6 +13,7 @@ import static org.awaitility.Awaitility.await; import com.intellij.testFramework.fixtures.BasePlatformTestCase; import ee.carlrobert.codegpt.CodeGPTPlugin; +import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; @@ -58,8 +59,7 @@ public class DefaultCompletionRequestHandlerTest extends BasePlatformTestCase { public void testOpenAIChatCompletionCall() { var message = new Message("TEST_PROMPT"); var conversation = ConversationService.getInstance().startConversation(); - var requestHandler = new CompletionRequestHandler(); - requestHandler.addRequestCompletedListener(message::setResponse); + var requestHandler = new CompletionRequestHandler(false, getRequestEventListener(message)); SettingsState.getInstance().setSelectedService(ServiceType.OPENAI); expectStreamRequest("/v1/chat/completions", request -> { assertThat(request.getMethod()).isEqualTo("POST"); @@ -94,9 +94,8 @@ public class DefaultCompletionRequestHandlerTest extends BasePlatformTestCase { azureSettings.setApiVersion("TEST_API_VERSION"); azureSettings.setDeploymentId("TEST_DEPLOYMENT_ID"); var conversationService = ConversationService.getInstance(); - var requestHandler = new CompletionRequestHandler(); var message = new Message("TEST_PROMPT"); - requestHandler.addRequestCompletedListener(message::setResponse); + var requestHandler = new CompletionRequestHandler(false, getRequestEventListener(message)); var prevMessage = new Message("TEST_PREV_PROMPT"); prevMessage.setResponse("TEST_PREV_RESPONSE"); var conversation = conversationService.startConversation(); @@ -132,8 +131,7 @@ public class DefaultCompletionRequestHandlerTest extends BasePlatformTestCase { var message = new Message("TEST_PROMPT"); var conversation = ConversationService.getInstance().startConversation(); conversation.addMessage(new Message("Ping", "Pong")); - var requestHandler = new CompletionRequestHandler(); - requestHandler.addRequestCompletedListener(message::setResponse); + var requestHandler = new CompletionRequestHandler(false, getRequestEventListener(message)); SettingsState.getInstance().setSelectedService(ServiceType.YOU); expectStreamRequest("/api/streamingSearch", request -> { assertThat(request.getMethod()).isEqualTo("GET"); @@ -182,8 +180,7 @@ public class DefaultCompletionRequestHandlerTest extends BasePlatformTestCase { var message = new Message("TEST_PROMPT"); var conversation = ConversationService.getInstance().startConversation(); conversation.addMessage(new Message("Ping", "Pong")); - var requestHandler = new CompletionRequestHandler(); - requestHandler.addRequestCompletedListener(message::setResponse); + var requestHandler = new CompletionRequestHandler(false, getRequestEventListener(message)); SettingsState.getInstance().setSelectedService(ServiceType.LLAMA_CPP); expectStreamRequest("/completion", request -> { assertThat(request.getBody()) @@ -214,4 +211,17 @@ public class DefaultCompletionRequestHandlerTest extends BasePlatformTestCase { private void expectStreamRequest(String path, StreamHttpExchange exchange) { server.addExpectation(new StreamExpectation(path, exchange)); } + + private ToolWindowCompletionEventListener getRequestEventListener(Message message) { + return new ToolWindowCompletionEventListener() { + @Override + public void handleCompleted( + String fullMessage, + Message conversationMessage, + Conversation conversation, + boolean isRetry) { + message.setResponse(fullMessage); + } + }; + } }