11'use client'
22
3- import { memo , useEffect , useRef , useState } from 'react'
3+ import { memo , useEffect , useMemo , useRef , useState } from 'react'
44import clsx from 'clsx'
55import { ChevronUp } from 'lucide-react'
66import CopilotMarkdownRenderer from './markdown-renderer'
77
8+ /**
9+ * Removes thinking tags (raw or escaped) from streamed content.
10+ */
11+ function stripThinkingTags ( text : string ) : string {
12+ return text
13+ . replace ( / < \/ ? t h i n k i n g [ ^ > ] * > / gi, '' )
14+ . replace ( / & l t ; \/ ? t h i n k i n g [ ^ & ] * & g t ; / gi, '' )
15+ . trim ( )
16+ }
17+
818/**
919 * Max height for thinking content before internal scrolling kicks in
1020 */
@@ -187,6 +197,9 @@ export function ThinkingBlock({
187197 label = 'Thought' ,
188198 hasSpecialTags = false ,
189199} : ThinkingBlockProps ) {
200+ // Strip thinking tags from content on render to handle persisted messages
201+ const cleanContent = useMemo ( ( ) => stripThinkingTags ( content || '' ) , [ content ] )
202+
190203 const [ isExpanded , setIsExpanded ] = useState ( false )
191204 const [ duration , setDuration ] = useState ( 0 )
192205 const [ userHasScrolledAway , setUserHasScrolledAway ] = useState ( false )
@@ -209,10 +222,10 @@ export function ThinkingBlock({
209222 return
210223 }
211224
212- if ( ! userCollapsedRef . current && content && content . trim ( ) . length > 0 ) {
225+ if ( ! userCollapsedRef . current && cleanContent && cleanContent . length > 0 ) {
213226 setIsExpanded ( true )
214227 }
215- } , [ isStreaming , content , hasFollowingContent , hasSpecialTags ] )
228+ } , [ isStreaming , cleanContent , hasFollowingContent , hasSpecialTags ] )
216229
217230 // Reset start time when streaming begins
218231 useEffect ( ( ) => {
@@ -298,7 +311,7 @@ export function ThinkingBlock({
298311 return `${ seconds } s`
299312 }
300313
301- const hasContent = content && content . trim ( ) . length > 0
314+ const hasContent = cleanContent . length > 0
302315 // Thinking is "done" when streaming ends OR when there's following content (like a tool call) OR when special tags appear
303316 const isThinkingDone = ! isStreaming || hasFollowingContent || hasSpecialTags
304317 const durationText = `${ label } for ${ formatDuration ( duration ) } `
@@ -374,7 +387,7 @@ export function ThinkingBlock({
374387 isExpanded ? 'mt-1.5 max-h-[150px] opacity-100' : 'max-h-0 opacity-0'
375388 ) }
376389 >
377- < SmoothThinkingText content = { content } isStreaming = { isStreaming && ! hasFollowingContent } />
390+ < SmoothThinkingText content = { cleanContent } isStreaming = { isStreaming && ! hasFollowingContent } />
378391 </ div >
379392 </ div >
380393 )
@@ -412,7 +425,7 @@ export function ThinkingBlock({
412425 >
413426 { /* Completed thinking text - dimmed with markdown */ }
414427 < div className = '[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.4] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-6 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 font-season text-[12px] text-[var(--text-muted)]' >
415- < CopilotMarkdownRenderer content = { content } />
428+ < CopilotMarkdownRenderer content = { cleanContent } />
416429 </ div >
417430 </ div >
418431 </ div >
0 commit comments