Skip to content

Commit 8a53d2d

Browse files
author
Dylan Huang
committed
add tool call copy
1 parent d301d0e commit 8a53d2d

File tree

1 file changed

+102
-9
lines changed

1 file changed

+102
-9
lines changed

vite-app/src/components/MessageBubble.tsx

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { Tooltip } from "./Tooltip";
66
export 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

Comments
 (0)