@@ -6,6 +6,9 @@ import { Tooltip } from "./Tooltip";
66export const MessageBubble = ( { message } : { message : Message } ) => {
77 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
88 const [ copySuccess , setCopySuccess ] = useState ( false ) ;
9+ const [ toolCallCopySuccess , setToolCallCopySuccess ] = useState <
10+ Record < number , boolean >
11+ > ( { } ) ;
912 const isUser = message . role === "user" ;
1013 const isSystem = message . role === "system" ;
1114 const isTool = message . role === "tool" ;
@@ -19,7 +22,7 @@ export const MessageBubble = ({ message }: { message: Message }) => {
1922 return message . content ;
2023 } else if ( Array . isArray ( message . content ) ) {
2124 return message . content
22- . map ( ( part , i ) =>
25+ . map ( ( part ) =>
2326 part . type === "text" ? part . text : JSON . stringify ( part )
2427 )
2528 . join ( "" ) ;
@@ -45,6 +48,22 @@ export const MessageBubble = ({ message }: { message: Message }) => {
4548 }
4649 } ;
4750
51+ const handleToolCallCopy = async ( index : number , argumentsText : string ) => {
52+ try {
53+ await navigator . clipboard . writeText ( argumentsText ) ;
54+ setToolCallCopySuccess ( ( prev ) => ( { ...prev , [ index ] : true } ) ) ;
55+ setTimeout ( ( ) => {
56+ setToolCallCopySuccess ( ( prev ) => {
57+ const newState = { ...prev } ;
58+ delete newState [ index ] ;
59+ return newState ;
60+ } ) ;
61+ } , 2000 ) ;
62+ } catch ( err ) {
63+ console . error ( "Failed to copy tool call:" , err ) ;
64+ }
65+ } ;
66+
4867 return (
4968 < div className = { `flex ${ isUser ? "justify-end" : "justify-start" } mb-1` } >
5069 < div
@@ -106,13 +125,35 @@ export const MessageBubble = ({ message }: { message: Message }) => {
106125 </ button >
107126 ) }
108127 { reasoning && reasoning . trim ( ) . length > 0 && (
109- < div className = { `mt-2 pt-1 border-t ${ isTool ? "border-green-200" : "border-yellow-200" } ` } >
110- < div className = { `font-semibold text-xs mb-0.5 ${ isTool ? "text-green-700" : "text-yellow-700" } ` } >
128+ < div
129+ className = { `mt-2 pt-1 border-t ${
130+ isTool ? "border-green-200" : "border-yellow-200"
131+ } `}
132+ >
133+ < div
134+ className = { `font-semibold text-xs mb-0.5 ${
135+ isTool ? "text-green-700" : "text-yellow-700"
136+ } `}
137+ >
111138 Thinking:
112139 </ div >
113140 < details className = "mb-1" >
114- < summary className = { `cursor-pointer text-xs ${ isTool ? "text-green-700" : "text-yellow-700" } ` } > Show reasoning</ summary >
115- < pre className = { `mt-1 p-1 border rounded text-xs whitespace-pre-wrap break-words ${ isTool ? "bg-green-100 border-green-200 text-green-800" : "bg-yellow-100 border-yellow-200 text-yellow-800" } ` } > { reasoning } </ pre >
141+ < summary
142+ className = { `cursor-pointer text-xs ${
143+ isTool ? "text-green-700" : "text-yellow-700"
144+ } `}
145+ >
146+ Show reasoning
147+ </ summary >
148+ < pre
149+ className = { `mt-1 p-1 border rounded text-xs whitespace-pre-wrap break-words ${
150+ isTool
151+ ? "bg-green-100 border-green-200 text-green-800"
152+ : "bg-yellow-100 border-yellow-200 text-yellow-800"
153+ } `}
154+ >
155+ { reasoning }
156+ </ pre >
116157 </ details >
117158 </ div >
118159 ) }
@@ -132,14 +173,40 @@ export const MessageBubble = ({ message }: { message: Message }) => {
132173 { message . tool_calls . map ( ( call , i ) => (
133174 < div
134175 key = { i }
135- className = { `mb-1 p-1 border rounded text-xs ${
176+ className = { `mb-1 p-1 border rounded text-xs relative ${
136177 isTool
137178 ? "bg-green-100 border-green-200"
138179 : "bg-yellow-100 border-yellow-200"
139180 } `}
140181 >
182+ { /* Copy button for tool call arguments */ }
183+ < div className = "absolute top-1 right-1" >
184+ < Tooltip
185+ content = {
186+ toolCallCopySuccess [ i ]
187+ ? "Copied!"
188+ : "Copy tool call arguments"
189+ }
190+ position = "top"
191+ >
192+ < Button
193+ onClick = { ( ) =>
194+ handleToolCallCopy ( i , call . function . arguments )
195+ }
196+ size = "sm"
197+ variant = "secondary"
198+ className = { `p-0.5 h-5 text-[10px] opacity-60 hover:opacity-100 transition-opacity cursor-pointer ${
199+ isTool
200+ ? "text-green-600 hover:bg-green-200"
201+ : "text-yellow-600 hover:bg-yellow-200"
202+ } `}
203+ >
204+ Copy
205+ </ Button >
206+ </ Tooltip >
207+ </ div >
141208 < div
142- className = { `font-semibold mb-0.5 text-xs ${
209+ className = { `font-semibold mb-0.5 text-xs pr-8 ${
143210 isTool ? "text-green-800" : "text-yellow-800"
144211 } `}
145212 >
@@ -170,14 +237,40 @@ export const MessageBubble = ({ message }: { message: Message }) => {
170237 Function Call:
171238 </ div >
172239 < div
173- className = { `p-1 border rounded text-xs ${
240+ className = { `p-1 border rounded text-xs relative ${
174241 isTool
175242 ? "bg-green-100 border-green-200"
176243 : "bg-yellow-100 border-yellow-200"
177244 } `}
178245 >
246+ { /* Copy button for function call arguments */ }
247+ < div className = "absolute top-1 right-1" >
248+ < Tooltip
249+ content = {
250+ toolCallCopySuccess [ - 1 ]
251+ ? "Copied!"
252+ : "Copy function call arguments"
253+ }
254+ position = "top"
255+ >
256+ < Button
257+ onClick = { ( ) =>
258+ handleToolCallCopy ( - 1 , message . function_call ! . arguments )
259+ }
260+ size = "sm"
261+ variant = "secondary"
262+ className = { `p-0.5 h-5 text-[10px] opacity-60 hover:opacity-100 transition-opacity cursor-pointer ${
263+ isTool
264+ ? "text-green-600 hover:bg-green-200"
265+ : "text-yellow-600 hover:bg-yellow-200"
266+ } `}
267+ >
268+ Copy
269+ </ Button >
270+ </ Tooltip >
271+ </ div >
179272 < div
180- className = { `font-semibold mb-0.5 text-xs ${
273+ className = { `font-semibold mb-0.5 text-xs pr-8 ${
181274 isTool ? "text-green-800" : "text-yellow-800"
182275 } `}
183276 >
0 commit comments