From 3a6e62c4f4bc0a2993ef66258e299df404bc1401 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Tue, 26 May 2026 15:27:50 +0100 Subject: [PATCH] fix: improve thinking panel lifecycle and response rendering --- .../chat/ui/ChatMessageResponseBody.java | 50 +++++++++---------- ...houghtProcessPanel.kt => ThinkingPanel.kt} | 29 +++++++++-- .../resources/messages/codegpt.properties | 2 + .../resources/messages/codegpt_zh.properties | 4 ++ 4 files changed, 56 insertions(+), 29 deletions(-) rename src/main/kotlin/ee/carlrobert/codegpt/ui/{ThoughtProcessPanel.kt => ThinkingPanel.kt} (72%) diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java index 130bb96f..2436c1ba 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java @@ -54,14 +54,13 @@ import ee.carlrobert.codegpt.toolwindow.chat.parser.Text; import ee.carlrobert.codegpt.toolwindow.chat.parser.Thinking; import ee.carlrobert.codegpt.toolwindow.ui.ResponseBodyProgressPanel; import ee.carlrobert.codegpt.toolwindow.ui.WebpageList; -import ee.carlrobert.codegpt.ui.ThoughtProcessPanel; +import ee.carlrobert.codegpt.ui.ThinkingPanel; import ee.carlrobert.codegpt.ui.UIUtil; import ee.carlrobert.codegpt.ui.hover.PsiLinkHoverPreview; import ee.carlrobert.codegpt.util.EditorUtil; import java.awt.BorderLayout; import java.util.Locale; import java.util.Objects; -import java.util.stream.Stream; import javax.swing.DefaultListModel; import javax.swing.JComponent; import javax.swing.JEditorPane; @@ -87,6 +86,7 @@ public class ChatMessageResponseBody extends JPanel { new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 4, true, false)); private final StringBuilder accumulatedThinking = new StringBuilder(); + private ThinkingPanel currentThinkingPanel; private ResponseEditorPanel currentlyProcessedEditorPanel; private MermaidResponsePanel currentlyProcessedMermaidPanel; private JEditorPane currentlyProcessedTextPane; @@ -142,10 +142,7 @@ public class ChatMessageResponseBody extends JPanel { currentlyProcessedEditorPanel = null; currentlyProcessedMermaidPanel = null; } - var thoughtProcessPanel = getExistingThoughtProcessPanel(); - if (thoughtProcessPanel != null && !thoughtProcessPanel.isFinished()) { - thoughtProcessPanel.setFinished(); - } + finishThinkingSection(false); } catch (Exception e) { LOG.error("Something went wrong while processing input", e); } @@ -153,6 +150,7 @@ public class ChatMessageResponseBody extends JPanel { } public void addToolStatusPanel(JComponent component) { + finishThinkingSection(false); finishCurrentStreamingSection(); streamOutputParser.startNewVisualSection(); contentPanel.add(component); @@ -259,10 +257,7 @@ public class ChatMessageResponseBody extends JPanel { public void finishThinking() { ApplicationManager.getApplication().invokeLater(() -> { - var thoughtProcessPanel = getExistingThoughtProcessPanel(); - if (thoughtProcessPanel != null && !thoughtProcessPanel.isFinished()) { - thoughtProcessPanel.setFinished(); - } + finishThinkingSection(false); }); } @@ -270,6 +265,7 @@ public class ChatMessageResponseBody extends JPanel { contentPanel.removeAll(); streamOutputParser.clear(); accumulatedThinking.setLength(0); + currentThinkingPanel = null; // Reset for the next incoming message prepareProcessingText(true); @@ -314,21 +310,26 @@ public class ChatMessageResponseBody extends JPanel { private void processThinkingOutput(String thoughtProcess) { progressPanel.setVisible(false); - var thoughtProcessPanel = getExistingThoughtProcessPanel(); - if (thoughtProcessPanel == null) { - thoughtProcessPanel = new ThoughtProcessPanel(); - thoughtProcessPanel.updateText(thoughtProcess); - contentPanel.add(thoughtProcessPanel); - } else { - thoughtProcessPanel.updateText(thoughtProcess); + if (currentThinkingPanel == null || currentThinkingPanel.isFinished()) { + currentThinkingPanel = new ThinkingPanel(); + contentPanel.add(currentThinkingPanel); } + currentThinkingPanel.updateText(thoughtProcess); + contentPanel.revalidate(); + contentPanel.repaint(); } - private ThoughtProcessPanel getExistingThoughtProcessPanel() { - return (ThoughtProcessPanel) Stream.of(contentPanel.getComponents()) - .filter(it -> it instanceof ThoughtProcessPanel) - .findFirst() - .orElse(null); + private void finishThinkingSection(boolean resetStreamingSection) { + if (currentThinkingPanel != null && !currentThinkingPanel.isFinished()) { + currentThinkingPanel.setFinished(); + } + currentThinkingPanel = null; + accumulatedThinking.setLength(0); + if (resetStreamingSection) { + finishCurrentStreamingSection(); + } + contentPanel.revalidate(); + contentPanel.repaint(); } private void processResponse(Segment item, boolean caretVisible) { @@ -337,9 +338,8 @@ public class ChatMessageResponseBody extends JPanel { return; } - var thoughtProcessPanel = getExistingThoughtProcessPanel(); - if (thoughtProcessPanel != null && !thoughtProcessPanel.isFinished()) { - thoughtProcessPanel.setFinished(); + if (currentThinkingPanel != null && !currentThinkingPanel.isFinished()) { + finishThinkingSection(true); } if (item instanceof CodeEnd) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/ThinkingPanel.kt similarity index 72% rename from src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt rename to src/main/kotlin/ee/carlrobert/codegpt/ui/ThinkingPanel.kt index 8694e118..9b3dbb4a 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/ThinkingPanel.kt @@ -4,15 +4,16 @@ import com.intellij.icons.AllIcons import com.intellij.ui.JBColor import com.intellij.util.ui.JBUI import com.intellij.util.ui.components.BorderLayoutPanel -import ee.carlrobert.codegpt.util.MarkdownUtil import ee.carlrobert.codegpt.CodeGPTBundle +import ee.carlrobert.codegpt.util.MarkdownUtil import java.awt.BorderLayout import java.awt.event.ItemEvent import javax.swing.* -class ThoughtProcessPanel : JPanel(BorderLayout()) { +class ThinkingPanel : BorderLayoutPanel() { private var finished: Boolean = false + private val startedAtMillis = System.currentTimeMillis() private val responseBodyContent = UIUtil.createTextPane("", false).apply { foreground = JBUI.CurrentTheme.Label.disabledForeground() } @@ -31,7 +32,7 @@ class ThoughtProcessPanel : JPanel(BorderLayout()) { fun setFinished() { if (finished) return - toggleButton.text = CodeGPTBundle.get("thoughtProcess.title") + toggleButton.text = formatFinishedTitle() toggleButton.isSelected = false finished = true @@ -44,12 +45,18 @@ class ThoughtProcessPanel : JPanel(BorderLayout()) { ) ) ) + revalidate() + repaint() } fun updateText(text: String) { responseBodyContent.text = MarkdownUtil.convertMdToHtml(text) } + fun getTitleText(): String = toggleButton.text + + fun getRenderedText(): String = responseBodyContent.text + private fun createContentPanel(): JPanel { val panel = JPanel().apply { isOpaque = false @@ -64,7 +71,11 @@ class ThoughtProcessPanel : JPanel(BorderLayout()) { } private fun createToggleButton() = - JToggleButton(CodeGPTBundle.get("thoughtProcess.thinking"), AllIcons.General.ArrowUp, true).apply { + JToggleButton( + CodeGPTBundle.get("thoughtProcess.thinking"), + AllIcons.General.ArrowRight, + true + ).apply { isFocusPainted = false isContentAreaFilled = false background = background @@ -77,4 +88,14 @@ class ThoughtProcessPanel : JPanel(BorderLayout()) { contentPanel.isVisible = e.stateChange == ItemEvent.SELECTED } } + + private fun formatFinishedTitle(): String { + val elapsedSeconds = ((System.currentTimeMillis() - startedAtMillis) / 1000).toInt() + .coerceAtLeast(1) + return if (elapsedSeconds < 60) { + CodeGPTBundle.get("thoughtProcess.duration.seconds", elapsedSeconds) + } else { + CodeGPTBundle.get("thoughtProcess.duration.minutes", elapsedSeconds / 60) + } + } } diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index 173270a6..3312b4e9 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -478,6 +478,8 @@ userInput.addContextTooltip=Add Context # Thought process panel thoughtProcess.thinking=Thinking... thoughtProcess.title=Thought Process +thoughtProcess.duration.seconds=Thought for {0} sec +thoughtProcess.duration.minutes=Thought for {0} min agent.credits.label=ProxyAI credits agent.credits.remainingValue={0} left agent.credits.tooltip.remaining=Remaining: {0} diff --git a/src/main/resources/messages/codegpt_zh.properties b/src/main/resources/messages/codegpt_zh.properties index 666277ea..6d9f14b0 100644 --- a/src/main/resources/messages/codegpt_zh.properties +++ b/src/main/resources/messages/codegpt_zh.properties @@ -323,6 +323,10 @@ smartTextPane.stopButton.title=\u505C\u6B62 smartTextPane.stopButton.description=\u505C\u6B62\u8865\u5168 smartTextPane.promptEnhancer.title=\u589E\u5F3A\u63D0\u793A smartTextPane.promptEnhancer.description=\u589E\u5F3A Agent \u6A21\u5F0F\u7684\u63D0\u793A +thoughtProcess.thinking=Thinking... +thoughtProcess.title=Thought Process +thoughtProcess.duration.seconds=Thought for {0} sec +thoughtProcess.duration.minutes=Thought for {0} min promptEnhancer.thinking=\u6B63\u5728\u589E\u5F3A\u63D0\u793A... chatMessageResponseBody.webPages.title=\u7F51\u9875 chatMessageResponseBody.webDocs.startProgress.label=\u6B63\u5728\u5206\u6790\u7F51\u9875\u5185\u5BB9...