@@ -7,6 +7,7 @@ import CodeDisplayBlock from "../code-display-block";
7
7
import Markdown from "react-markdown" ;
8
8
import remarkGfm from "remark-gfm" ;
9
9
import { Message } from "ai" ;
10
+ import ThinkBlock from "../think-block" ;
10
11
11
12
interface ChatListProps {
12
13
messages : Message [ ] ;
@@ -22,6 +23,47 @@ const MessageToolbar = () => (
22
23
</ div >
23
24
) ;
24
25
26
+ // Utility to process <think> tags
27
+ function processThinkTags ( content : string , isLoading : boolean , message : Message | undefined , prevUserMessage : Message | undefined ) {
28
+ const thinkOpen = content . indexOf ( "<think>" ) ;
29
+ const thinkClose = content . indexOf ( "</think>" ) ;
30
+
31
+ // If <think> is present and </think> is not, show live thinking content
32
+ if ( thinkOpen !== - 1 && thinkClose === - 1 ) {
33
+ // Show everything before <think>, then the ThinkBlock component with live mode
34
+ const before = content . slice ( 0 , thinkOpen ) ;
35
+ const thinkContent = content . slice ( thinkOpen + 7 ) ; // everything after <think>
36
+ return [
37
+ before ,
38
+ < ThinkBlock key = "live-think" content = { thinkContent . trim ( ) } live = { true } /> ,
39
+ ] ;
40
+ }
41
+
42
+ // If both <think> and </think> are present, show collapsible block
43
+ if ( thinkOpen !== - 1 && thinkClose !== - 1 ) {
44
+ const before = content . slice ( 0 , thinkOpen ) ;
45
+ const thinkContent = content . slice ( thinkOpen + 7 , thinkClose ) ;
46
+ const after = content . slice ( thinkClose + 8 ) ;
47
+ // Calculate duration if timestamps are available
48
+ let duration ;
49
+ if ( message ?. createdAt && prevUserMessage ?. createdAt ) {
50
+ const start = new Date ( prevUserMessage . createdAt ) . getTime ( ) ;
51
+ const end = new Date ( message . createdAt ) . getTime ( ) ;
52
+ if ( ! isNaN ( start ) && ! isNaN ( end ) && end > start ) {
53
+ duration = ( ( end - start ) / 1000 ) . toFixed ( 2 ) + " seconds" ;
54
+ }
55
+ }
56
+ return [
57
+ before ,
58
+ < ThinkBlock key = "collapsible-think" content = { thinkContent . trim ( ) } duration = { duration } /> ,
59
+ after ,
60
+ ] ;
61
+ }
62
+
63
+ // No <think> tag, return as is
64
+ return [ content ] ;
65
+ }
66
+
25
67
export default function ChatList ( { messages, isLoading } : ChatListProps ) {
26
68
const bottomRef = useRef < HTMLDivElement > ( null ) ;
27
69
@@ -102,10 +144,17 @@ export default function ChatList({ messages, isLoading }: ChatListProps) {
102
144
{ /* Check if the message content contains a code block */ }
103
145
{ message . content . split ( "```" ) . map ( ( part , index ) => {
104
146
if ( index % 2 === 0 ) {
105
- return (
106
- < Markdown key = { index } remarkPlugins = { [ remarkGfm ] } >
107
- { part }
108
- </ Markdown >
147
+ // Find previous user message
148
+ const prevUserMessage = messages . slice ( 0 , messages . indexOf ( message ) ) . reverse ( ) . find ( m => m . role === "user" ) ;
149
+ // Process <think> tags before rendering Markdown
150
+ return processThinkTags ( part , isLoading , message , prevUserMessage ) . map ( ( segment , segIdx ) =>
151
+ typeof segment === "string" ? (
152
+ < Markdown key = { index + "-" + segIdx } remarkPlugins = { [ remarkGfm ] } >
153
+ { segment }
154
+ </ Markdown >
155
+ ) : (
156
+ segment // This is the <Thinking /> component
157
+ )
109
158
) ;
110
159
} else {
111
160
return (
0 commit comments