fix: improve thought process parsing and displaying

This commit is contained in:
Carl-Robert Linnupuu 2025-02-03 14:56:20 +00:00
parent e348e53242
commit 33760001d7
9 changed files with 130 additions and 54 deletions

View file

@ -123,11 +123,12 @@ public class ChatMessageResponseBody extends JPanel {
return;
}
if (handleThinking(partialMessage)) {
var processedPartialMessage = processThinkingOutput(partialMessage);
if (processedPartialMessage.isEmpty()) {
return;
}
for (var item : streamParser.parse(partialMessage)) {
for (var item : streamParser.parse(processedPartialMessage)) {
processResponse(item.response(), CODE.equals(item.type()), true);
}
}
@ -240,9 +241,8 @@ public class ChatMessageResponseBody extends JPanel {
revalidate();
}
private boolean handleThinking(String partialMessage) {
thinkingOutputParser.processChunk(partialMessage);
private String processThinkingOutput(String partialMessage) {
var processedChunk = thinkingOutputParser.processChunk(partialMessage);
var thoughtProcessPanel = getExistingThoughtProcessPanel();
if (thinkingOutputParser.isThinking()) {
@ -254,14 +254,13 @@ public class ChatMessageResponseBody extends JPanel {
} else {
thoughtProcessPanel.updateText(thinkingOutputParser.getThoughtProcess());
}
return true;
}
if (thoughtProcessPanel != null && !thoughtProcessPanel.getFinished()) {
if (thoughtProcessPanel != null && thinkingOutputParser.isFinished()) {
thoughtProcessPanel.setFinished();
}
return false;
return processedChunk;
}
private ThoughtProcessPanel getExistingThoughtProcessPanel() {

View file

@ -1,39 +1,54 @@
package ee.carlrobert.codegpt.toolwindow.chat
import java.util.regex.Pattern
class ThinkingOutputParser {
private val buffer = StringBuilder()
var isThinking: Boolean = false
private set
var isFinished: Boolean = false
private set
companion object {
private const val OPEN_TAG = "<think>"
private const val CLOSE_TAG = "</think>"
}
var thoughtProcess: String = ""
private set
var isThinking: Boolean = false
private set
var isFinished: Boolean = false
private set
fun processChunk(chunk: String) {
private val buffer = StringBuilder()
fun processChunk(chunk: String): String {
if (isFinished) {
return
return chunk
}
if (buffer.isEmpty() && chunk.isNotEmpty() && !OPEN_TAG.contains(chunk.take(OPEN_TAG.length))) {
isFinished = true
return chunk
}
buffer.append(chunk)
val current = buffer.toString()
val thinkPattern = Pattern.compile("<think>(.*?)</think>", Pattern.DOTALL)
val matcher = thinkPattern.matcher(buffer.toString())
if (matcher.find()) {
val indexOpen = current.indexOf(OPEN_TAG)
if (indexOpen == -1) {
return ""
}
isThinking = true
val startContent = indexOpen + OPEN_TAG.length
val indexClose = current.indexOf(CLOSE_TAG, startContent)
if (indexClose != -1) {
thoughtProcess = current.substring(startContent, indexClose).trim()
isFinished = true
isThinking = false
thoughtProcess = matcher.group(1).trim { it <= ' ' }
} else if (buffer.isNotBlank() && "<think>".contains(buffer)) {
thoughtProcess = ""
isThinking = true
} else if (buffer.toString().startsWith("<think>")) {
thoughtProcess = buffer.toString().replaceFirst("<think>".toRegex(), "")
isThinking = true
val responseStart = indexClose + CLOSE_TAG.length
return current.substring(responseStart)
} else {
thoughtProcess = current.substring(startContent)
return ""
}
}
}
}

View file

@ -23,11 +23,14 @@ class ResponseBodyProgressPanel : JPanel() {
init {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
border = JBUI.Borders.empty(4, 0, 8, 0)
border = JBUI.Borders.empty(4, 0)
isVisible = false
}
fun updateProgressContainer(text: String, icon: Icon?) {
runInEdt {
isVisible = true
removeAll()
val wrapper = if (icon != null) {
JBLabel(

View file

@ -16,5 +16,8 @@ open class ResponseMessagePanel : BaseMessagePanel() {
)
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold())
.apply {
iconTextGap = 6
}
}
}

View file

@ -51,7 +51,7 @@ class UserMessagePanel(
Icons.User
} else {
val originalIcon = ImageIcon(Base64.getDecoder().decode(avatarBase64))
val resizedImage = originalIcon.image.getScaledInstance(20, 20, Image.SCALE_SMOOTH)
val resizedImage = originalIcon.image.getScaledInstance(24, 24, Image.SCALE_SMOOTH)
RoundedIcon(resizedImage, 1.0)
}
} catch (ex: Exception) {
@ -67,6 +67,9 @@ class UserMessagePanel(
)
.setAllowAutoWrapping(true)
.withFont(JBFont.label().asBold())
.apply {
iconTextGap = 6
}
}
fun addReloadAction(onReload: Runnable) {

View file

@ -11,10 +11,10 @@ import javax.swing.*
class ThoughtProcessPanel : JPanel(BorderLayout()) {
var finished: Boolean = false
private set
private val responseBodyContent = UIUtil.createTextPane("", false)
private var finished: Boolean = false
private val responseBodyContent = UIUtil.createTextPane("", false).apply {
foreground = JBUI.CurrentTheme.Label.disabledForeground()
}
private val contentPanel = createContentPanel()
private val toggleButton: JToggleButton = createToggleButton()
@ -26,6 +26,8 @@ class ThoughtProcessPanel : JPanel(BorderLayout()) {
}
fun setFinished() {
if (finished) return
toggleButton.text = "Thought Process"
toggleButton.isSelected = false
finished = true
@ -58,21 +60,18 @@ class ThoughtProcessPanel : JPanel(BorderLayout()) {
return panel
}
private fun createToggleButton(): JToggleButton {
return JToggleButton("Thinking...", AllIcons.General.ArrowUp)
.apply {
isFocusPainted = false
isContentAreaFilled = false
background = background
selectedIcon = AllIcons.General.ArrowDown
border = null
isSelected = true
horizontalAlignment = SwingConstants.LEFT
horizontalTextPosition = SwingConstants.RIGHT
iconTextGap = 4
addItemListener { e: ItemEvent ->
contentPanel.isVisible = e.stateChange == ItemEvent.SELECTED
}
private fun createToggleButton() =
JToggleButton("Thinking...", AllIcons.General.ArrowUp, true).apply {
isFocusPainted = false
isContentAreaFilled = false
background = background
selectedIcon = AllIcons.General.ArrowDown
border = null
horizontalAlignment = SwingConstants.LEFT
horizontalTextPosition = SwingConstants.RIGHT
iconTextGap = 4
addItemListener { e: ItemEvent ->
contentPanel.isVisible = e.stateChange == ItemEvent.SELECTED
}
}
}
}

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="20px" height="20px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#000000"></stop><stop offset="0.699" stop-color="#000000"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#ffffff"></stop><stop offset="1" stop-color="#ffffff"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="24px" height="24px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#000000"></stop><stop offset="0.699" stop-color="#000000"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#ffffff"></stop><stop offset="1" stop-color="#ffffff"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="20px" height="20px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#ffffff"></stop><stop offset="0.699" stop-color="#ffffff"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#000000"></stop><stop offset="1" stop-color="#000000"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#ffffff" fill-rule="nonzero" stroke="#ffffff" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="24px" height="24px" fill-rule="nonzero"><defs><linearGradient x1="32" y1="7" x2="32" y2="58" gradientUnits="userSpaceOnUse" id="color-1"><stop offset="0" stop-color="#ffffff"></stop><stop offset="0.699" stop-color="#ffffff"></stop></linearGradient><linearGradient x1="32" y1="0.872" x2="32" y2="62.679" gradientUnits="userSpaceOnUse" id="color-2"><stop offset="0" stop-color="#000000"></stop><stop offset="1" stop-color="#000000"></stop></linearGradient></defs><g transform="translate(-16,-16) scale(1.125,1.125)"><g fill="#ffffff" fill-rule="nonzero" stroke="#ffffff" stroke-width="4" stroke-linecap="butt" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path transform="scale(4,4)" d="M56.96,35.77c0,3.26677 -1.24798,6.23506 -3.30276,8.44932c-1.99429,2.15164 -4.75072,3.59119 -7.86757,3.90222c-0.88208,4.1799 -3.85802,7.55012 -7.70667,9.04414c-1.38917,0.53944 -2.8918,0.83432 -4.45001,0.83432c-0.0005,0 -0.00101,0 -0.00151,0c-0.0005,0 -0.00099,0 -0.00149,0c-3.7,0 -7.13,-1.61 -9.51,-4.44c-0.17355,0.02083 -0.33988,0.03683 -0.50148,0.04886c-0.30513,0.02465 -0.59103,0.03614 -0.87352,0.03614c-3.67218,0 -6.97773,-1.60053 -9.2559,-4.14039c-1.97547,-2.20076 -3.1791,-5.10856 -3.1791,-8.29461c0,-1.35992 0.21997,-2.69984 0.65992,-3.99977c-0.14403,-0.1355 -0.28449,-0.27402 -0.4213,-0.41542c-2.23865,-2.3122 -3.50863,-5.40357 -3.50863,-8.65481c0,-6.09 4.39,-11.24 10.32,-12.25c1.22799,-4.14839 4.4698,-7.27438 8.50115,-8.41707c1.08917,-0.30894 2.23614,-0.47293 3.41685,-0.47293c0.00033,0 0.00067,0 0.001,0c0.00033,0 0.00066,0 0.001,0c0.29833,0 0.59479,0.0104 0.88899,0.031c2.29405,0.16031 4.44707,0.93829 6.27413,2.23974c0.91063,0.64757 1.74044,1.42439 2.4662,2.31841c0.05659,-0.01106 0.11314,-0.02173 0.16963,-0.03201c0.24688,-0.04603 0.49363,-0.08341 0.73963,-0.11274c0.48506,-0.05945 0.96473,-0.08941 1.43643,-0.08941c0.11876,0 0.23715,0.00167 0.35512,0.005c3.5298,0.09951 6.69629,1.67767 8.90078,4.13539c1.97547,2.20076 3.1791,5.10856 3.1791,8.29461c0,0.67861 -0.05738,1.35723 -0.17214,2.03322c-0.06371,0.37884 -0.14508,0.7564 -0.24386,1.13178c1.12918,1.12101 2.02385,2.43767 2.6483,3.87003c0.6764,1.54524 1.0377,3.22623 1.0377,4.94497z" id="strokeMainSVG"></path></g><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.27,26.96c0.28,-1.05 0.42,-2.11 0.42,-3.17c0,-6.86 -5.58,-12.43 -12.43,-12.43c-0.77,0 -1.56,0.07 -2.35,0.23c-2.37,-2.92 -5.85,-4.59 -9.63,-4.59c-5.55,0 -10.36,3.62 -11.92,8.89c-5.93,1.01 -10.32,6.16 -10.32,12.25c0,3.45 1.43,6.72 3.93,9.07c-0.44,1.3 -0.66,2.64 -0.66,4c0,6.86 5.58,12.43 12.43,12.43c0.44,0 0.88,-0.02 1.38,-0.08c2.38,2.83 5.81,4.44 9.51,4.44c5.88,0 10.96,-4.19 12.16,-9.88c6.32,-0.63 11.17,-5.91 11.17,-12.35c0,-3.31 -1.34,-6.48 -3.69,-8.81zM38.25,35.88l-6.63,4l-6.5,-4v-7.26l6.63,-3.87l6.63,3.75z" fill="url(#color-1)"></path><path d="M53.274,26.955c0.275,-1.045 0.415,-2.107 0.415,-3.166c0,-6.855 -5.578,-12.434 -12.434,-12.434c-0.766,0 -1.553,0.079 -2.35,0.235c-2.369,-2.925 -5.843,-4.59 -9.627,-4.59c-5.549,0 -10.353,3.622 -11.913,8.891c-5.93,1.012 -10.32,6.163 -10.32,12.254c0,3.448 1.424,6.715 3.93,9.07c-0.44,1.299 -0.664,2.64 -0.664,3.996c0,6.855 5.578,12.434 12.434,12.434c0.433,0 0.874,-0.027 1.38,-0.087c2.376,2.831 5.809,4.442 9.508,4.442c5.875,0 10.96,-4.192 12.152,-9.878c6.327,-0.629 11.17,-5.908 11.17,-12.355c0.001,-3.304 -1.333,-6.481 -3.681,-8.812zM51.689,23.789c0,0.646 -0.07,1.293 -0.193,1.937l-12.293,-7.185l-13.146,7.991v-4.91l12.814,-7.972c0.813,-0.191 1.615,-0.295 2.383,-0.295c5.754,0 10.435,4.681 10.435,10.434zM37.397,35.171l-5.563,3.316l-5.776,-3.303v-6.311l5.465,-3.322l-0.031,0.052l5.905,3.48zM29.278,9c2.997,0 5.755,1.251 7.728,3.457l-12.948,8.054v13.529l-4.898,-2.801v-14.317c1.159,-4.668 5.302,-7.922 10.118,-7.922zM9.044,28.145c0,-4.923 3.419,-9.109 8.116,-10.169v14.424l12.701,7.264l-5.227,3.115l-11.897,-6.674c-2.345,-1.998 -3.693,-4.894 -3.693,-7.96zM12.311,41.211c0,-0.955 0.138,-1.902 0.4,-2.828l11.954,6.706l12.732,-7.588v6.27l-13.172,7.754c-0.57,0.078 -1.043,0.12 -1.48,0.12c-5.753,0 -10.434,-4.681 -10.434,-10.434zM33.633,56c-2.886,0 -5.578,-1.175 -7.546,-3.252l13.31,-7.835v-14.652l4.539,2.675v14.154c-0.744,5.083 -5.163,8.91 -10.303,8.91zM45.936,46.091v-14.298l-6.539,-3.853v-0.068h-0.115l-5.879,-3.465l5.821,-3.538l12.309,7.195c2.174,1.981 3.422,4.782 3.422,7.703c0.001,5.288 -3.885,9.639 -9.019,10.324z" fill="url(#color-2)"></path></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

View file

@ -0,0 +1,54 @@
package ee.carlrobert.codegpt.toolwindow.chat
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class ThinkingOutputParserTest {
@Test
fun `when not processing then return empty string`() {
val parser = ThinkingOutputParser()
assertThat(parser.processChunk("Some text")).isEmpty()
assertThat(parser.thoughtProcess).isEmpty()
}
@Test
fun `when processing chunk but thinking not finished then return empty string`() {
val parser = ThinkingOutputParser()
assertThat(parser.processChunk("<think>starting")).isEmpty()
assertThat(parser.thoughtProcess).isEqualTo("starting")
val finalOutput = parser.processChunk(" some processing...")
assertThat(finalOutput).isEmpty()
assertThat(parser.thoughtProcess).isEqualTo("starting some processing...")
}
@Test
fun `when thinking finished then return everything after the last closing think tag`() {
val parser = ThinkingOutputParser()
parser.processChunk("<think>the internal thought</think>")
assertThat(parser.thoughtProcess).isEqualTo("the internal thought")
val finalOutput = parser.processChunk("Here is the user response.")
assertThat(finalOutput).isEqualTo("Here is the user response.")
assertThat(parser.thoughtProcess).isEqualTo("the internal thought")
}
@Test
fun `accumulate chunks and return response only after final chunk with closing tag`() {
val parser = ThinkingOutputParser()
assertThat(parser.processChunk("<think>")).isEmpty()
assertThat(parser.thoughtProcess).isEqualTo("")
assertThat(parser.processChunk("some internal processing")).isEmpty()
assertThat(parser.thoughtProcess).isEqualTo("some internal processing")
assertThat(parser.processChunk(" with even more details... ")).isEmpty()
assertThat(parser.thoughtProcess).isEqualTo("some internal processing with even more details... ")
val finalOutput = parser.processChunk("</think>The final answer.")
assertThat(finalOutput).isEqualTo("The final answer.")
assertThat(parser.thoughtProcess).isEqualTo("some internal processing with even more details...")
}
}