From 29de8192fe84a5c6cc9dabf1e9fa50c4aa068f94 Mon Sep 17 00:00:00 2001 From: Stephan Janssen Date: Fri, 8 Nov 2024 14:36:28 +0100 Subject: [PATCH 1/2] Fix #324 Replace SwingUtilities.invokeLater() + Refactored ActionButtonsPanel with ActionPanelController --- build.gradle.kts | 11 +- .../controller/ActionPanelController.java | 180 ++++++++++++++ .../genie/service/ChatPromptExecutor.java | 7 +- .../genie/service/MessageCreationService.java | 1 - .../streaming/StreamingResponseHandler.java | 2 +- .../ui/DevoxxGenieToolWindowContent.java | 7 +- .../genie/ui/EditorFileButtonManager.java | 3 + .../CommandAutoCompleteTextField.java | 2 +- .../genie/ui/component/ContextPopupMenu.java | 3 +- .../genie/ui/component/PromptInputArea.java | 3 +- .../genie/ui/panel/ActionButtonsPanel.java | 221 ++++-------------- .../ui/panel/ChatStreamingResponsePanel.java | 2 +- .../genie/ui/panel/ConversationPanel.java | 8 +- .../com/devoxx/genie/ui/panel/HelpPanel.java | 3 +- .../genie/ui/panel/PromptOutputPanel.java | 5 +- .../devoxx/genie/ui/panel/WelcomePanel.java | 2 +- .../LanguageModelCostSettingsComponent.java | 3 +- src/main/resources/META-INF/plugin.xml | 5 + 18 files changed, 274 insertions(+), 194 deletions(-) create mode 100644 src/main/java/com/devoxx/genie/controller/ActionPanelController.java diff --git a/build.gradle.kts b/build.gradle.kts index 9b0ebc8a..5dbe8e07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } group = "com.devoxx.genie" -version = "0.2.26" +version = "0.2.27" repositories { mavenCentral() @@ -56,6 +56,15 @@ dependencies { implementation("com.knuddels:jtokkit:1.0.0") implementation("org.commonmark:commonmark:0.22.0") + // TDG : Add Log4j dependencies + implementation("org.apache.logging.log4j:log4j-api:2.22.1") + implementation("org.apache.logging.log4j:log4j-core:2.22.1") + + // TDG : Add other TDG dependencies + implementation("org.junit.jupiter:junit-jupiter-api:5.11.3") + implementation("org.junit.jupiter:junit-jupiter-engine:5.11.3") + implementation("org.junit.platform:junit-platform-launcher:1.11.3") + compileOnly("org.projectlombok:lombok:1.18.34") annotationProcessor("org.projectlombok:lombok:1.18.34") diff --git a/src/main/java/com/devoxx/genie/controller/ActionPanelController.java b/src/main/java/com/devoxx/genie/controller/ActionPanelController.java new file mode 100644 index 00000000..82e8e3be --- /dev/null +++ b/src/main/java/com/devoxx/genie/controller/ActionPanelController.java @@ -0,0 +1,180 @@ +package com.devoxx.genie.controller; + +import com.devoxx.genie.chatmodel.ChatModelProvider; +import com.devoxx.genie.model.LanguageModel; +import com.devoxx.genie.model.enumarations.ModelProvider; +import com.devoxx.genie.model.request.ChatMessageContext; +import com.devoxx.genie.service.*; +import com.devoxx.genie.ui.EditorFileButtonManager; +import com.devoxx.genie.ui.component.PromptInputArea; +import com.devoxx.genie.ui.panel.ActionButtonsPanel; +import com.devoxx.genie.ui.panel.PromptOutputPanel; +import com.devoxx.genie.ui.settings.DevoxxGenieStateService; +import com.devoxx.genie.ui.util.NotificationUtil; +import com.devoxx.genie.util.ChatMessageContextUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import com.intellij.openapi.ui.ComboBox; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ActionPanelController { + private final Project project; + private final ChatPromptExecutor chatPromptExecutor; + private final EditorFileButtonManager editorFileButtonManager; + private final PromptInputArea promptInputArea; + private final PromptOutputPanel promptOutputPanel; + private final ComboBox modelProviderComboBox; + private final ComboBox modelNameComboBox; + private final ChatModelProvider chatModelProvider = new ChatModelProvider(); + private final ActionButtonsPanel actionButtonsPanel; + private boolean isPromptRunning = false; + + private ChatMessageContext currentChatMessageContext; + + public ActionPanelController(Project project, + PromptInputArea promptInputArea, + PromptOutputPanel promptOutputPanel, + ComboBox modelProviderComboBox, + ComboBox modelNameComboBox, + ActionButtonsPanel actionButtonsPanel) { + + this.project = project; + this.promptInputArea = promptInputArea; + this.promptOutputPanel = promptOutputPanel; + this.chatPromptExecutor = new ChatPromptExecutor(promptInputArea); + this.editorFileButtonManager = new EditorFileButtonManager(project, null); + this.modelProviderComboBox = modelProviderComboBox; + this.modelNameComboBox = modelNameComboBox; + this.actionButtonsPanel = actionButtonsPanel; + } + + public boolean isPromptRunning() { + return isPromptRunning; + } + + public boolean executePrompt(String actionCommand, + boolean isProjectContextAdded, + String projectContext) { + if (isPromptRunning) { + stopPromptExecution(); + return true; + } + + if (!validateAndPreparePrompt(actionCommand, isProjectContextAdded, projectContext)) { + return false; + } + + isPromptRunning = true; + + AtomicBoolean response = new AtomicBoolean(true); + chatPromptExecutor.updatePromptWithCommandIfPresent(currentChatMessageContext, promptOutputPanel) + .ifPresentOrElse( + this::executePromptWithContext, + () -> response.set(false) + ); + + return response.get(); + } + + private void executePromptWithContext(String command) { + chatPromptExecutor.executePrompt(currentChatMessageContext, promptOutputPanel, () -> { + isPromptRunning = false; + actionButtonsPanel.enableButtons(); + ApplicationManager.getApplication().invokeLater(() -> { + promptInputArea.clear(); + promptInputArea.requestInputFocus(); + }); + }); + } + + public void stopPromptExecution() { + chatPromptExecutor.stopPromptExecution(project); + isPromptRunning = false; + actionButtonsPanel.enableButtons(); + } + + /** + * Validate and prepare the prompt. + * + * @param actionCommand the action event command + * @return true if the prompt is valid + */ + private boolean validateAndPreparePrompt(String actionCommand, + boolean isProjectContextAdded, + String projectContext) { + String userPromptText = getUserPromptText(); + if (userPromptText == null) { + return false; + } + + DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance(); + LanguageModel selectedLanguageModel = (LanguageModel) modelNameComboBox.getSelectedItem(); + + // If selectedLanguageModel is null, create a default one + if (selectedLanguageModel == null) { + selectedLanguageModel = createDefaultLanguageModel(stateService); + } + + currentChatMessageContext = ChatMessageContextUtil.createContext( + project, + userPromptText, + selectedLanguageModel, + chatModelProvider, + stateService, + actionCommand, + editorFileButtonManager, + projectContext, + isProjectContextAdded + ); + + return true; + } + + /** + * get the user prompt text. + */ + private @Nullable String getUserPromptText() { + String userPromptText = promptInputArea.getText(); + if (userPromptText.isEmpty()) { + NotificationUtil.sendNotification(project, "Please enter a prompt."); + return null; + } + return userPromptText; + } + + /** + * Create a default language model. + * + * @param stateService the state service + * @return the default language model + */ + private LanguageModel createDefaultLanguageModel(@NotNull DevoxxGenieSettingsService stateService) { + ModelProvider selectedProvider = (ModelProvider) modelProviderComboBox.getSelectedItem(); + if (selectedProvider != null && + (selectedProvider.equals(ModelProvider.LMStudio) || + selectedProvider.equals(ModelProvider.GPT4All) || + selectedProvider.equals(ModelProvider.Jlama) || + selectedProvider.equals(ModelProvider.LLaMA))) { + return LanguageModel.builder() + .provider(selectedProvider) + .apiKeyUsed(false) + .inputCost(0) + .outputCost(0) + .contextWindow(4096) + .build(); + } else { + String modelName = stateService.getSelectedLanguageModel(project.getLocationHash()); + return LanguageModel.builder() + .provider(selectedProvider != null ? selectedProvider : ModelProvider.OpenAI) + .modelName(modelName) + .apiKeyUsed(false) + .inputCost(0) + .outputCost(0) + .contextWindow(128_000) + .build(); + } + } +} diff --git a/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java b/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java index 0db67f8f..74124e30 100644 --- a/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java +++ b/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java @@ -9,6 +9,7 @@ import com.devoxx.genie.ui.panel.PromptOutputPanel; import com.devoxx.genie.ui.settings.DevoxxGenieStateService; import com.devoxx.genie.util.FileTypeUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.progress.ProgressIndicator; @@ -61,7 +62,7 @@ public void run(@NotNull ProgressIndicator progressIndicator) { new WebSearchExecutor().execute(chatMessageContext, promptOutputPanel, () -> { isRunningMap.put(project, false); enableButtons.run(); - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { promptInputArea.clear(); promptInputArea.requestInputFocus(); }); @@ -70,7 +71,7 @@ public void run(@NotNull ProgressIndicator progressIndicator) { streamingPromptExecutor.execute(chatMessageContext, promptOutputPanel, () -> { isRunningMap.put(project, false); enableButtons.run(); - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { promptInputArea.clear(); promptInputArea.requestInputFocus(); }); @@ -79,7 +80,7 @@ public void run(@NotNull ProgressIndicator progressIndicator) { nonStreamingPromptExecutor.execute(chatMessageContext, promptOutputPanel, () -> { isRunningMap.put(project, false); enableButtons.run(); - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { promptInputArea.clear(); promptInputArea.requestInputFocus(); }); diff --git a/src/main/java/com/devoxx/genie/service/MessageCreationService.java b/src/main/java/com/devoxx/genie/service/MessageCreationService.java index ca79ab99..750c7998 100644 --- a/src/main/java/com/devoxx/genie/service/MessageCreationService.java +++ b/src/main/java/com/devoxx/genie/service/MessageCreationService.java @@ -26,7 +26,6 @@ */ public class MessageCreationService { - public static final String CONTEXT_PROMPT = "Context: \n"; @NotNull diff --git a/src/main/java/com/devoxx/genie/service/streaming/StreamingResponseHandler.java b/src/main/java/com/devoxx/genie/service/streaming/StreamingResponseHandler.java index ea6f3297..e85ef5bf 100644 --- a/src/main/java/com/devoxx/genie/service/streaming/StreamingResponseHandler.java +++ b/src/main/java/com/devoxx/genie/service/streaming/StreamingResponseHandler.java @@ -67,7 +67,7 @@ private void finalizeResponse(@NotNull Response response) { private void addExpandablePanelIfNeeded() { if (chatMessageContext.hasFiles()) { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { ExpandablePanel fileListPanel = new ExpandablePanel(chatMessageContext, FileListManager.getInstance().getFiles()); fileListPanel.setName(chatMessageContext.getName()); diff --git a/src/main/java/com/devoxx/genie/ui/DevoxxGenieToolWindowContent.java b/src/main/java/com/devoxx/genie/ui/DevoxxGenieToolWindowContent.java index 5e458eae..4803d81d 100644 --- a/src/main/java/com/devoxx/genie/ui/DevoxxGenieToolWindowContent.java +++ b/src/main/java/com/devoxx/genie/ui/DevoxxGenieToolWindowContent.java @@ -21,6 +21,7 @@ import com.devoxx.genie.ui.settings.DevoxxGenieStateService; import com.devoxx.genie.ui.topic.AppTopics; import com.devoxx.genie.ui.util.NotificationUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; @@ -314,7 +315,7 @@ private void refreshModels() { } if (selectedProvider == ModelProvider.LMStudio || selectedProvider == ModelProvider.Ollama || selectedProvider == ModelProvider.Jan) { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { refreshButton.setEnabled(false); ChatModelFactory factory = ChatModelFactoryProvider.getFactoryByProvider(selectedProvider.name()) @@ -375,7 +376,7 @@ public void startNewConversation() { chatService.startNewConversation(""); - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { conversationPanel.updateNewConversationLabel(); promptInputArea.clear(); promptOutputPanel.clear(); @@ -487,7 +488,7 @@ public void settingsChanged() { @Override public void onCustomPromptsChanged() { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { // Update the help panel or any other UI components that display custom prompts if (promptOutputPanel != null) { promptOutputPanel.updateHelpText(); diff --git a/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java b/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java index 063a6cfc..b1855205 100644 --- a/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java +++ b/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java @@ -28,6 +28,9 @@ private void handleFileOpenClose() { FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { @Override public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { + if (addFileBtn == null) { + return; + } addFileBtn.setEnabled(true); addFileBtn.setToolTipText("Select file(s) for prompt context"); } diff --git a/src/main/java/com/devoxx/genie/ui/component/CommandAutoCompleteTextField.java b/src/main/java/com/devoxx/genie/ui/component/CommandAutoCompleteTextField.java index 38fe507e..e9adf807 100644 --- a/src/main/java/com/devoxx/genie/ui/component/CommandAutoCompleteTextField.java +++ b/src/main/java/com/devoxx/genie/ui/component/CommandAutoCompleteTextField.java @@ -113,7 +113,7 @@ public void insertString(int offs, String str, AttributeSet a) throws BadLocatio super.insertString(offs, str, a); - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { try { String text = getText(0, getLength()); String[] lines = text.split("\n"); diff --git a/src/main/java/com/devoxx/genie/ui/component/ContextPopupMenu.java b/src/main/java/com/devoxx/genie/ui/component/ContextPopupMenu.java index 6f51ba5d..6382c1c7 100644 --- a/src/main/java/com/devoxx/genie/ui/component/ContextPopupMenu.java +++ b/src/main/java/com/devoxx/genie/ui/component/ContextPopupMenu.java @@ -1,5 +1,6 @@ package com.devoxx.genie.ui.component; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupListener; import com.intellij.openapi.ui.popup.LightweightWindowEvent; @@ -33,6 +34,6 @@ public void beforeShown(@NotNull LightweightWindowEvent event) { }); // Ensure this is on the EDT - SwingUtilities.invokeLater(() -> popup.show(northWest)); + ApplicationManager.getApplication().invokeLater(() -> popup.show(northWest)); } } diff --git a/src/main/java/com/devoxx/genie/ui/component/PromptInputArea.java b/src/main/java/com/devoxx/genie/ui/component/PromptInputArea.java index 8c0cf614..5466b499 100644 --- a/src/main/java/com/devoxx/genie/ui/component/PromptInputArea.java +++ b/src/main/java/com/devoxx/genie/ui/component/PromptInputArea.java @@ -1,6 +1,7 @@ package com.devoxx.genie.ui.component; import com.devoxx.genie.ui.listener.PromptInputFocusListener; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.ui.JBColor; import org.jetbrains.annotations.NotNull; @@ -91,7 +92,7 @@ public boolean requestFocusInWindow() { } public void requestInputFocus() { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { inputField.requestFocusInWindow(); inputField.setCaretPosition(inputField.getText().length()); }); diff --git a/src/main/java/com/devoxx/genie/ui/panel/ActionButtonsPanel.java b/src/main/java/com/devoxx/genie/ui/panel/ActionButtonsPanel.java index bb1860cd..f8d83e50 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/ActionButtonsPanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/ActionButtonsPanel.java @@ -1,11 +1,11 @@ package com.devoxx.genie.ui.panel; -import com.devoxx.genie.chatmodel.ChatModelProvider; +import com.devoxx.genie.controller.ActionPanelController; import com.devoxx.genie.model.Constant; import com.devoxx.genie.model.LanguageModel; import com.devoxx.genie.model.enumarations.ModelProvider; -import com.devoxx.genie.model.request.ChatMessageContext; -import com.devoxx.genie.service.*; +import com.devoxx.genie.service.ProjectContentService; +import com.devoxx.genie.service.TokenCalculationService; import com.devoxx.genie.ui.DevoxxGenieToolWindowContent; import com.devoxx.genie.ui.EditorFileButtonManager; import com.devoxx.genie.ui.component.ContextPopupMenu; @@ -18,7 +18,6 @@ import com.devoxx.genie.ui.topic.AppTopics; import com.devoxx.genie.ui.util.NotificationUtil; import com.devoxx.genie.ui.util.WindowContextFormatterUtil; -import com.devoxx.genie.util.ChatMessageContextUtil; import com.devoxx.genie.util.DefaultLLMSettingsUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; @@ -30,7 +29,6 @@ import com.knuddels.jtokkit.Encodings; import com.knuddels.jtokkit.api.EncodingType; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -41,13 +39,11 @@ import static com.devoxx.genie.model.Constant.*; import static com.devoxx.genie.ui.util.DevoxxGenieIconsUtil.*; -import static javax.swing.SwingUtilities.invokeLater; public class ActionButtonsPanel extends JPanel implements SettingsChangeListener, PromptSubmissionListener { private final Project project; - private final ChatPromptExecutor chatPromptExecutor; private final EditorFileButtonManager editorFileButtonManager; private final JPanel calcProjectPanel = new JPanel(new GridLayout(1, 2)); @@ -59,22 +55,20 @@ public class ActionButtonsPanel extends JPanel implements SettingsChangeListener private final JButton calcTokenCostBtn = new JHoverButton("Calc tokens/cost", CalculateIcon, true); private final PromptInputArea promptInputArea; - private final PromptOutputPanel promptOutputPanel; private final ComboBox llmProvidersComboBox; private final ComboBox modelNameComboBox; private final TokenUsageBar tokenUsageBar = new TokenUsageBar(); private int tokenCount; private final DevoxxGenieToolWindowContent devoxxGenieToolWindowContent; - private final ChatModelProvider chatModelProvider = new ChatModelProvider(); - private boolean isPromptRunning = false; private boolean isProjectContextAdded = false; - private ChatMessageContext currentChatMessageContext; private String projectContext; private final TokenCalculationService tokenCalculationService; + private final ActionPanelController controller; + public ActionButtonsPanel(Project project, PromptInputArea promptInputArea, PromptOutputPanel promptOutputPanel, @@ -83,10 +77,17 @@ public ActionButtonsPanel(Project project, DevoxxGenieToolWindowContent devoxxGenieToolWindowContent) { setLayout(new BorderLayout()); + this.controller = new ActionPanelController( + project, + promptInputArea, + promptOutputPanel, + llmProvidersComboBox, + modelNameComboBox, + this + ); + this.project = project; this.promptInputArea = promptInputArea; - this.promptOutputPanel = promptOutputPanel; - this.chatPromptExecutor = new ChatPromptExecutor(promptInputArea); this.editorFileButtonManager = new EditorFileButtonManager(project, addFileBtn); this.llmProvidersComboBox = llmProvidersComboBox; this.modelNameComboBox = modelNameComboBox; @@ -94,11 +95,11 @@ public ActionButtonsPanel(Project project, this.llmProvidersComboBox.addActionListener(e -> updateAddProjectButtonVisibility()); this.tokenCalculationService = new TokenCalculationService(); + setupUI(); + MessageBusConnection messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); messageBusConnection.subscribe(AppTopics.SETTINGS_CHANGED_TOPIC, this); messageBusConnection.subscribe(AppTopics.PROMPT_SUBMISSION_TOPIC_TOPIC, this); - - setupUI(); } private void updateAddProjectButtonVisibility() { @@ -185,55 +186,46 @@ private void selectFilesForPromptContext(ActionEvent e) { * Submit the user prompt. */ private void onSubmitPrompt(ActionEvent actionEvent) { - if (isPromptRunning) { - stopPromptExecution(); + if (controller.isPromptRunning()) { + controller.stopPromptExecution(); return; } - if (!validateAndPreparePrompt(actionEvent)) { - return; - } + disableUIForPromptExecution(); - executePrompt(); + boolean response = controller.executePrompt(actionEvent.getActionCommand(), isProjectContextAdded, projectContext); + if (!response) { + enableButtons(); + } } - /** - * Execute the prompt. - */ - private void executePrompt() { - disableUIForPromptExecution(); + private void disableUIForPromptExecution() { + disableSubmitBtn(); + disableButtons(); + promptInputArea.startGlowing(); + } - chatPromptExecutor.updatePromptWithCommandIfPresent(currentChatMessageContext, promptOutputPanel) - .ifPresentOrElse( - command -> startPromptExecution(), - this::enableButtons - ); + public void enableButtons() { + ApplicationManager.getApplication().invokeLater(() -> { + submitBtn.setIcon(SubmitIcon); + submitBtn.setToolTipText(SUBMIT_THE_PROMPT); + promptInputArea.setEnabled(true); + promptInputArea.stopGlowing(); + }); } - /** - * Start the prompt execution. - */ - private void startPromptExecution() { - isPromptRunning = true; - promptInputArea.startGlowing(); - chatPromptExecutor.executePrompt(currentChatMessageContext, promptOutputPanel, this::enableButtons); + private void disableSubmitBtn() { + ApplicationManager.getApplication().invokeLater(() -> { + submitBtn.setIcon(StopIcon); + submitBtn.setToolTipText(PROMPT_IS_RUNNING_PLEASE_BE_PATIENT); + }); } - /** - * Stop the prompt execution. - */ - private void stopPromptExecution() { - chatPromptExecutor.stopPromptExecution(project); - isPromptRunning = false; - enableButtons(); + private void disableButtons() { + promptInputArea.setEnabled(false); } public void resetProjectContext() { - projectContext = null; - isProjectContextAdded = false; - if (currentChatMessageContext != null) { - currentChatMessageContext.setContext(null); - } updateAddProjectButton(); } @@ -260,125 +252,6 @@ private boolean isProjectContextSupportedProvider() { return selectedProvider != null && isSupportedProvider(selectedProvider); } - /** - * get the user prompt text. - */ - private @Nullable String getUserPromptText() { - String userPromptText = promptInputArea.getText(); - if (userPromptText.isEmpty()) { - NotificationUtil.sendNotification(project, "Please enter a prompt."); - return null; - } - return userPromptText; - } - - /** - * Disable the UI for prompt execution. - */ - private void disableUIForPromptExecution() { - disableSubmitBtn(); - disableButtons(); - promptInputArea.startGlowing(); - } - - /** - * Validate and prepare the prompt. - * - * @param actionEvent the action event - * @return true if the prompt is valid - */ - private boolean validateAndPreparePrompt(ActionEvent actionEvent) { - String userPromptText = getUserPromptText(); - if (userPromptText == null) { - return false; - } - - DevoxxGenieStateService stateService = DevoxxGenieStateService.getInstance(); - LanguageModel selectedLanguageModel = (LanguageModel) modelNameComboBox.getSelectedItem(); - - // If selectedLanguageModel is null, create a default one - if (selectedLanguageModel == null) { - selectedLanguageModel = createDefaultLanguageModel(stateService); - } - - currentChatMessageContext = ChatMessageContextUtil.createContext( - project, - userPromptText, - selectedLanguageModel, - chatModelProvider, - stateService, - actionEvent.getActionCommand(), - editorFileButtonManager, - projectContext, - isProjectContextAdded - ); - - return true; - } - - /** - * Create a default language model. - * - * @param stateService the state service - * @return the default language model - */ - private LanguageModel createDefaultLanguageModel(@NotNull DevoxxGenieSettingsService stateService) { - ModelProvider selectedProvider = (ModelProvider) llmProvidersComboBox.getSelectedItem(); - if (selectedProvider != null && - (selectedProvider.equals(ModelProvider.LMStudio) || - selectedProvider.equals(ModelProvider.GPT4All) || - selectedProvider.equals(ModelProvider.Jlama) || - selectedProvider.equals(ModelProvider.LLaMA))) { - return LanguageModel.builder() - .provider(selectedProvider) - .apiKeyUsed(false) - .inputCost(0) - .outputCost(0) - .contextWindow(4096) - .build(); - } else { - String modelName = stateService.getSelectedLanguageModel(project.getLocationHash()); - return LanguageModel.builder() - .provider(selectedProvider != null ? selectedProvider : ModelProvider.OpenAI) - .modelName(modelName) - .apiKeyUsed(false) - .inputCost(0) - .outputCost(0) - .contextWindow(128_000) - .build(); - } - } - - /** - * Enable the prompt input component and reset the Submit button icon. - */ - public void enableButtons() { - SwingUtilities.invokeLater(() -> { - submitBtn.setIcon(SubmitIcon); - submitBtn.setToolTipText(SUBMIT_THE_PROMPT); - promptInputArea.setEnabled(true); - isPromptRunning = false; - promptInputArea.stopGlowing(); - }); - } - - /** - * Disable the Submit button. - */ - private void disableSubmitBtn() { - invokeLater(() -> { - submitBtn.setIcon(StopIcon); - submitBtn.setToolTipText(PROMPT_IS_RUNNING_PLEASE_BE_PATIENT); - }); - } - - /** - * Disable the prompt input component. - */ - private void disableButtons() { - promptInputArea.setEnabled(false); - } - /** * Set the search buttons visibility based on settings. */ @@ -470,7 +343,7 @@ private void addProjectToContext() { .thenAccept(projectContent -> { projectContext = "Project Context:\n" + projectContent.getContent(); isProjectContextAdded = true; - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { addProjectBtn.setIcon(DeleteIcon); tokenCount = Encodings.newDefaultEncodingRegistry().getEncoding(EncodingType.CL100K_BASE).countTokens(projectContent.getContent()); addProjectBtn.setText("Full Project (" + WindowContextFormatterUtil.format(tokenCount, "tokens") + ")"); @@ -481,7 +354,7 @@ private void addProjectToContext() { }); }) .exceptionally(ex -> { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { addProjectBtn.setEnabled(true); tokenUsageBar.setVisible(false); NotificationUtil.sendNotification(project, "Error adding project content: " + ex.getMessage()); @@ -535,11 +408,11 @@ private void calculateTokensAndCost() { } public void updateTokenUsage(int maxTokens) { - SwingUtilities.invokeLater(() -> tokenUsageBar.setMaxTokens(maxTokens)); + ApplicationManager.getApplication().invokeLater(() -> tokenUsageBar.setMaxTokens(maxTokens)); } public void resetTokenUsageBar() { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { tokenUsageBar.reset(); tokenCount = 0; }); @@ -550,7 +423,7 @@ public void onPromptSubmitted(@NotNull Project projectPrompt, String prompt) { if (!this.project.getName().equals(projectPrompt.getName())) { return; } - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { promptInputArea.setText(prompt); onSubmitPrompt(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, Constant.SUBMIT_ACTION)); }); diff --git a/src/main/java/com/devoxx/genie/ui/panel/ChatStreamingResponsePanel.java b/src/main/java/com/devoxx/genie/ui/panel/ChatStreamingResponsePanel.java index 2a20a4fe..3aa3f9c3 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/ChatStreamingResponsePanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/ChatStreamingResponsePanel.java @@ -74,7 +74,7 @@ private void setMaxWidth() { * @param token the LLM string token */ public void insertToken(String token) { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { markdownContent.append(token); String fullHtmlContent = renderer.render(parser.parse(markdownContent.toString())); editorPane.setText(fullHtmlContent); diff --git a/src/main/java/com/devoxx/genie/ui/panel/ConversationPanel.java b/src/main/java/com/devoxx/genie/ui/panel/ConversationPanel.java index f92f0612..d1954e65 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/ConversationPanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/ConversationPanel.java @@ -37,6 +37,7 @@ public class ConversationPanel extends JPanel implements ConversationSelectionLi private final ConversationHistoryPanel historyPanel; private final PromptOutputPanel promptOutputPanel; private final JPanel conversationButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0)); + private JBPopup historyPopup; /** * The conversation panel constructor. @@ -88,6 +89,9 @@ private void updateFontSize() { } public void onConversationSelected(Conversation conversation) { + if (historyPopup != null && historyPopup.isVisible()) { + historyPopup.closeOk(null); + } promptOutputPanel.displayConversation(project, conversation); } @@ -132,7 +136,7 @@ public void updateNewConversationLabel() { } private void showConversationHistory() { - JBPopup popup = JBPopupFactory.getInstance() + historyPopup = JBPopupFactory.getInstance() .createComponentPopupBuilder(historyPanel, null) .setTitle("Conversation History") .setMovable(true) @@ -149,6 +153,6 @@ private void showConversationHistory() { SwingUtilities.convertPointToScreen(screenPoint, settingsBtn.getParent()); // Show the popup at the calculated position - popup.show(new RelativePoint(screenPoint)); + historyPopup.show(new RelativePoint(screenPoint)); } } diff --git a/src/main/java/com/devoxx/genie/ui/panel/HelpPanel.java b/src/main/java/com/devoxx/genie/ui/panel/HelpPanel.java index 5413c845..234c833a 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/HelpPanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/HelpPanel.java @@ -1,5 +1,6 @@ package com.devoxx.genie.ui.panel; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.ui.components.JBScrollPane; import javax.swing.*; @@ -32,7 +33,7 @@ public void updateHelpText(String newHelpMsg) { } private void updatePanelSize() { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { int preferredHeight = calculatePreferredHeight(); setPreferredSize(new Dimension(getWidth(), preferredHeight)); revalidate(); diff --git a/src/main/java/com/devoxx/genie/ui/panel/PromptOutputPanel.java b/src/main/java/com/devoxx/genie/ui/panel/PromptOutputPanel.java index 5881185b..c03ef5d7 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/PromptOutputPanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/PromptOutputPanel.java @@ -8,6 +8,7 @@ import com.devoxx.genie.ui.component.ExpandablePanel; import com.devoxx.genie.ui.settings.DevoxxGenieStateService; import com.devoxx.genie.ui.util.HelpUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.ui.components.JBPanel; import com.intellij.ui.components.JBScrollPane; @@ -118,7 +119,7 @@ public void removeLastUserPrompt(ChatMessageContext chatMessageContext) { } private void scrollToBottom() { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { Timer timer = new Timer(100, e -> { JScrollBar vertical = scrollPane.getVerticalScrollBar(); vertical.setValue(vertical.getMaximum()); @@ -133,7 +134,7 @@ public void updateHelpText() { } public void displayConversation(Project project, Conversation conversation) { - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { String conversationId = UUID.randomUUID().toString(); for (ChatMessage message : conversation.getMessages()) { conversation.setId(conversationId); diff --git a/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java b/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java index ec8a2cd1..d60e8057 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java @@ -83,7 +83,7 @@ public void showMsg() { scrollPane.setVisible(true); // Ensure the scroll pane is at the top when shown - SwingUtilities.invokeLater(() -> { + ApplicationManager.getApplication().invokeLater(() -> { JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); verticalScrollBar.setValue(verticalScrollBar.getMinimum()); }); diff --git a/src/main/java/com/devoxx/genie/ui/settings/costsettings/LanguageModelCostSettingsComponent.java b/src/main/java/com/devoxx/genie/ui/settings/costsettings/LanguageModelCostSettingsComponent.java index 94631d4d..f14f6dbe 100644 --- a/src/main/java/com/devoxx/genie/ui/settings/costsettings/LanguageModelCostSettingsComponent.java +++ b/src/main/java/com/devoxx/genie/ui/settings/costsettings/LanguageModelCostSettingsComponent.java @@ -5,6 +5,7 @@ import com.devoxx.genie.service.LLMModelRegistryService; import com.devoxx.genie.ui.settings.AbstractSettingsComponent; import com.devoxx.genie.util.LLMProviderUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.table.JBTable; @@ -185,7 +186,7 @@ private void addNewRow() { newRow.add(0.0); newRow.add(8000); tableModel.addRow(newRow); - SwingUtilities.invokeLater(this::scrollToBottom); + ApplicationManager.getApplication().invokeLater(this::scrollToBottom); } private void scrollToBottom() { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f8668dc0..6eb36bea 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -35,6 +35,10 @@ ]]> v0.2.27 +
    +
  • Feature #327: Test Driven Generation integration
  • +

v0.2.26

  • Feature #325: Exclude a single file for 'Scan & Copy Project'
  • @@ -470,5 +474,6 @@ description="Calculate the tokens for selected directory"> + From fd3695f633db246a804ef77fe82eb99baa4b0cd4 Mon Sep 17 00:00:00 2001 From: Stephan Janssen Date: Fri, 8 Nov 2024 14:38:11 +0100 Subject: [PATCH 2/2] Updated change notes --- src/main/resources/META-INF/plugin.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 6eb36bea..e616dd3f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -38,6 +38,8 @@

    v0.2.27

    • Feature #327: Test Driven Generation integration
    • +
    • Fix #324: Replace invocations of SwingUtilities.invokeLater()
    • +
    • Fix #236: Refactoring large class ActionButtonsPanel

    v0.2.26