11"use client"
22
3- import React from "react"
3+ import React , { useState } from "react"
4+ import { motion , AnimatePresence } from "framer-motion"
45import {
56 IconTool ,
67 IconBrain ,
78 IconArrowRight ,
89 IconMessageCircle ,
910 IconInfoCircle ,
10- IconAlertTriangle
11+ IconAlertTriangle ,
12+ IconChevronDown
1113} from "@tabler/icons-react"
1214import ReactMarkdown from "react-markdown"
1315import { cn } from "@utils/cn"
1416
17+ const CollapsibleSection = ( {
18+ title,
19+ icon,
20+ colorClass,
21+ children,
22+ defaultOpen = false
23+ } ) => {
24+ const [ isExpanded , setIsExpanded ] = useState ( defaultOpen )
25+
26+ return (
27+ < div
28+ className = { cn (
29+ "border-l-2 pl-3 transition-colors" ,
30+ isExpanded ? colorClass : "border-neutral-700"
31+ ) }
32+ >
33+ < button
34+ onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
35+ className = "flex items-center gap-2 w-full text-left text-sm font-semibold"
36+ >
37+ { icon }
38+ < span className = "flex-grow" > { title } </ span >
39+ < IconChevronDown
40+ size = { 16 }
41+ className = { cn (
42+ "transform transition-transform duration-200" ,
43+ isExpanded ? "rotate-180" : "rotate-0"
44+ ) }
45+ />
46+ </ button >
47+ < AnimatePresence >
48+ { isExpanded && (
49+ < motion . div
50+ initial = { { height : 0 , opacity : 0 , marginTop : 0 } }
51+ animate = { { height : "auto" , opacity : 1 , marginTop : 8 } }
52+ exit = { { height : 0 , opacity : 0 , marginTop : 0 } }
53+ className = "overflow-hidden"
54+ >
55+ { children }
56+ </ motion . div >
57+ ) }
58+ </ AnimatePresence >
59+ </ div >
60+ )
61+ }
62+
1563const ExecutionUpdate = ( { update } ) => {
1664 const { message, timestamp } = update
1765
@@ -21,18 +69,6 @@ const ExecutionUpdate = ({ update }) => {
2169 second : "2-digit"
2270 } )
2371
24- if ( typeof message === "string" ) {
25- // Handle old string-based updates for backward compatibility
26- return (
27- < div className = "flex gap-3 text-sm" >
28- < span className = "text-neutral-500 font-mono text-xs flex-shrink-0 pt-0.5" >
29- [{ formattedTimestamp } ]
30- </ span >
31- < p className = "text-neutral-300" > { message } </ p >
32- </ div >
33- )
34- }
35-
3672 const { type, content, tool_name, parameters, result, is_error } = message
3773
3874 const renderContent = ( ) => {
@@ -42,7 +78,7 @@ const ExecutionUpdate = ({ update }) => {
4278 < div className = "flex items-start gap-2 text-neutral-400" >
4379 < IconInfoCircle
4480 size = { 16 }
45- className = "flex-shrink-0 mt-1 text-neutral-500"
81+ className = "flex-shrink-0 mt-0.5 text-neutral-500"
4682 />
4783 < p > { content } </ p >
4884 </ div >
@@ -52,74 +88,70 @@ const ExecutionUpdate = ({ update }) => {
5288 < div className = "flex items-start gap-2 text-red-400" >
5389 < IconAlertTriangle
5490 size = { 16 }
55- className = "flex-shrink-0 mt-1 "
91+ className = "flex-shrink-0 mt-0.5 "
5692 />
5793 < p > { content } </ p >
5894 </ div >
5995 )
6096 case "thought" :
6197 return (
62- < div className = "flex items-start gap-2 text-neutral-400" >
63- < IconBrain
64- size = { 16 }
65- className = "flex-shrink-0 mt-1 text-yellow-400/80"
66- />
67- < ReactMarkdown className = "prose prose-sm prose-invert text-neutral-400" >
68- { content }
69- </ ReactMarkdown >
70- </ div >
98+ < CollapsibleSection
99+ title = "Thought Process"
100+ icon = {
101+ < IconBrain
102+ size = { 16 }
103+ className = "text-yellow-400/80"
104+ />
105+ }
106+ colorClass = "border-yellow-500/50"
107+ >
108+ < div className = "p-3 bg-neutral-800/50 rounded-md" >
109+ < ReactMarkdown className = "prose prose-sm prose-invert text-neutral-300 whitespace-pre-wrap" >
110+ { content }
111+ </ ReactMarkdown >
112+ </ div >
113+ </ CollapsibleSection >
71114 )
72115 case "tool_call" :
73116 return (
74- < div className = "flex items-start gap-2" >
75- < IconTool
76- size = { 16 }
77- className = "flex-shrink-0 mt-1 text-blue-400"
78- />
79- < div className = "flex-1" >
80- < p className = "font-medium text-neutral-200" >
81- < span className = "text-blue-400" >
82- Tool Call:
83- </ span > { " " }
84- < span className = "font-mono text-sm" >
85- { tool_name }
86- </ span >
87- </ p >
88- < pre className = "text-xs bg-neutral-900/50 p-2 rounded-md mt-1 whitespace-pre-wrap font-mono border border-neutral-700" >
117+ < CollapsibleSection
118+ title = { `Tool Call: ${ tool_name } ` }
119+ icon = { < IconTool size = { 16 } className = "text-blue-400" /> }
120+ colorClass = "border-blue-500/50"
121+ >
122+ < div className = "p-3 bg-neutral-800/50 rounded-md" >
123+ < pre className = "text-xs text-neutral-300 whitespace-pre-wrap font-mono" >
89124 { JSON . stringify ( parameters , null , 2 ) }
90125 </ pre >
91126 </ div >
92- </ div >
127+ </ CollapsibleSection >
93128 )
94129 case "tool_result" :
95130 return (
96- < div className = "flex items-start gap-2" >
97- < IconArrowRight
98- size = { 16 }
99- className = { cn (
100- "flex-shrink-0 mt-1" ,
101- is_error ? "text-red-400" : "text-green-400"
102- ) }
103- />
104- < div className = "flex-1" >
105- < p
106- className = { cn (
107- "font-medium" ,
131+ < CollapsibleSection
132+ title = { `Tool Result: ${ tool_name } ` }
133+ icon = {
134+ < IconArrowRight
135+ size = { 16 }
136+ className = {
108137 is_error ? "text-red-400" : "text-green-400"
109- ) }
110- >
111- Tool Result:{ " " }
112- < span className = "font-mono text-sm" >
113- { tool_name }
114- </ span >
115- </ p >
116- < pre className = "text-xs bg-neutral-900/50 p-2 rounded-md mt-1 whitespace-pre-wrap font-mono border border-neutral-700" >
138+ }
139+ />
140+ }
141+ colorClass = {
142+ is_error
143+ ? "border-red-500/50"
144+ : "border-green-500/50"
145+ }
146+ >
147+ < div className = "p-3 bg-neutral-800/50 rounded-md" >
148+ < pre className = "text-xs text-neutral-300 whitespace-pre-wrap font-mono" >
117149 { typeof result === "object"
118150 ? JSON . stringify ( result , null , 2 )
119151 : String ( result ) }
120152 </ pre >
121153 </ div >
122- </ div >
154+ </ CollapsibleSection >
123155 )
124156 case "final_answer" :
125157 return (
@@ -143,7 +175,7 @@ const ExecutionUpdate = ({ update }) => {
143175 }
144176
145177 return (
146- < div className = "flex gap-3" >
178+ < div className = "flex gap-3 text-sm " >
147179 < span className = "text-neutral-500 font-mono text-xs flex-shrink-0 pt-1" >
148180 [{ formattedTimestamp } ]
149181 </ span >
@@ -152,4 +184,4 @@ const ExecutionUpdate = ({ update }) => {
152184 )
153185}
154186
155- export default ExecutionUpdate
187+ export default ExecutionUpdate
0 commit comments