fix: improve thinking panel lifecycle and response rendering

This commit is contained in:
Carl-Robert Linnupuu 2026-05-26 15:27:50 +01:00
parent 6ae3fff7cf
commit 3a6e62c4f4
4 changed files with 56 additions and 29 deletions

View file

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

View file

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

View file

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

View file

@ -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...