@@ -7,6 +7,7 @@ import CodeDisplayBlock from "../code-display-block";
77import Markdown from "react-markdown" ;
88import remarkGfm from "remark-gfm" ;
99import { Message } from "ai" ;
10+ import ThinkBlock from "../think-block" ;
1011
1112interface ChatListProps {
1213 messages : Message [ ] ;
@@ -22,6 +23,47 @@ const MessageToolbar = () => (
2223 </ div >
2324) ;
2425
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+
2567export default function ChatList ( { messages, isLoading } : ChatListProps ) {
2668 const bottomRef = useRef < HTMLDivElement > ( null ) ;
2769
@@ -102,10 +144,17 @@ export default function ChatList({ messages, isLoading }: ChatListProps) {
102144 { /* Check if the message content contains a code block */ }
103145 { message . content . split ( "```" ) . map ( ( part , index ) => {
104146 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+ )
109158 ) ;
110159 } else {
111160 return (
0 commit comments