Skip to content

Commit 24b6c5a

Browse files
Revert "Improve display of agent/tool cards in chat session" (kagent-dev#1729)
Reverts kagent-dev#1360 This seems to have messed up tool output formatting. prev: <img width="70%" alt="image" src="https://github.com/user-attachments/assets/5b11d5d6-f180-4a57-9b52-0d5cc207c484" /> new: <img width="70%" alt="image" src="https://github.com/user-attachments/assets/7b9fb2e9-ba49-4773-9402-d3982618113a" /> Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
1 parent 027db2b commit 24b6c5a

7 files changed

Lines changed: 106 additions & 582 deletions

File tree

ui/src/components/ToolDisplay.stories.tsx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,6 @@ export const VeryLongUrl: Story = {
7373
},
7474
};
7575

76-
export const VeryLongToolId: Story = {
77-
args: {
78-
call: {
79-
id: "call_super_duper_long_tool_identifier_that_goes_on_and_on_and_on_and_will_surely_overflow_if_not_truncated_properly_abc123_def456_ghi789",
80-
name: "some_function",
81-
args: {
82-
path: "/src/components/App.tsx",
83-
},
84-
},
85-
result: {
86-
content: "Result text here",
87-
},
88-
status: "completed",
89-
},
90-
};
91-
9276
export const LongUnbreakableString: Story = {
9377
args: {
9478
call: {
@@ -150,13 +134,6 @@ export const InChatLayoutLongUrl: Story = {
150134
},
151135
};
152136

153-
export const InChatLayoutLongToolId: Story = {
154-
decorators: [ChatLayoutDecorator],
155-
args: {
156-
...VeryLongToolId.args,
157-
},
158-
};
159-
160137
export const InChatLayoutUnbreakable: Story = {
161138
decorators: [ChatLayoutDecorator],
162139
args: {

ui/src/components/ToolDisplay.tsx

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { useState } from "react";
22
import { FunctionCall, TokenStats } from "@/types";
3-
import { FunctionSquare, CheckCircle, Clock, Code, Loader2, Text, AlertCircle, ShieldAlert } from "lucide-react";
3+
import { ScrollArea } from "@radix-ui/react-scroll-area";
4+
import { FunctionSquare, CheckCircle, Clock, Code, ChevronUp, ChevronDown, Loader2, Text, Check, Copy, AlertCircle, ShieldAlert } from "lucide-react";
45
import { Button } from "@/components/ui/button";
56
import { Textarea } from "@/components/ui/textarea";
67
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
78
import TokenStatsTooltip from "@/components/chat/TokenStatsTooltip";
89
import { convertToUserFriendlyName } from "@/lib/utils";
9-
import { SmartContent, parseJsonOrString } from "@/components/chat/SmartContent";
10-
import { CollapsibleSection } from "@/components/chat/CollapsibleSection";
1110

1211
export type ToolCallStatus = "requested" | "executing" | "completed" | "pending_approval" | "approved" | "rejected";
1312

@@ -28,17 +27,25 @@ interface ToolDisplayProps {
2827
tokenStats?: TokenStats;
2928
}
3029

31-
32-
// ── Main component ─────────────────────────────────────────────────────────
3330
const ToolDisplay = ({ call, result, status = "requested", isError = false, isDecided = false, subagentName, onApprove, onReject, tokenStats }: ToolDisplayProps) => {
3431
const [areArgumentsExpanded, setAreArgumentsExpanded] = useState(status === "pending_approval");
3532
const [areResultsExpanded, setAreResultsExpanded] = useState(false);
33+
const [isCopied, setIsCopied] = useState(false);
3634
const [isSubmitting, setIsSubmitting] = useState(false);
3735
const [showRejectForm, setShowRejectForm] = useState(false);
3836
const [rejectionReason, setRejectionReason] = useState("");
3937

4038
const hasResult = result !== undefined;
41-
const parsedResult = hasResult ? parseJsonOrString(result.content) : null;
39+
40+
const handleCopy = async () => {
41+
try {
42+
await navigator.clipboard.writeText(result?.content || "");
43+
setIsCopied(true);
44+
setTimeout(() => setIsCopied(false), 2000);
45+
} catch (err) {
46+
console.error("Failed to copy text:", err);
47+
}
48+
};
4249

4350
const handleApprove = async () => {
4451
if (!onApprove) {
@@ -136,49 +143,53 @@ const ToolDisplay = ({ call, result, status = "requested", isError = false, isDe
136143
}
137144
};
138145

139-
const argsContent = <SmartContent data={call.args} />;
140-
const resultContent = parsedResult !== null
141-
? <SmartContent data={parsedResult} className={isError ? "text-red-600 dark:text-red-400" : ""} />
142-
: null;
143-
144146
const borderClass = status === "pending_approval"
145-
? 'border-amber-300 dark:border-amber-700'
146-
: status === "rejected"
147-
? 'border-red-300 dark:border-red-700'
148-
: status === "approved"
149-
? 'border-green-300 dark:border-green-700'
150-
: isError
151-
? 'border-red-300'
152-
: '';
147+
? 'border-amber-300 dark:border-amber-700'
148+
: status === "rejected"
149+
? 'border-red-300 dark:border-red-700'
150+
: status === "approved"
151+
? 'border-green-300 dark:border-green-700'
152+
: isError
153+
? 'border-red-300'
154+
: '';
153155

154156
return (
155157
<Card className={`w-full mx-auto my-1 min-w-full ${borderClass}`}>
156158
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
157-
<CardTitle className="text-xs flex space-x-5 min-w-0">
158-
<div className="flex items-center font-medium shrink-0">
159+
<CardTitle className="text-xs flex space-x-5">
160+
<div className="flex items-center font-medium">
159161
<FunctionSquare className="w-4 h-4 mr-2" />
160162
{call.name}
161163
</div>
162164
{subagentName && (
163-
<div className="flex items-center text-muted-foreground font-normal shrink-0">
165+
<div className="flex items-center text-muted-foreground font-normal">
164166
via {convertToUserFriendlyName(subagentName)} subagent
165167
</div>
166168
)}
167-
<div className="font-light truncate min-w-0">{call.id}</div>
169+
<div className="font-light">{call.id}</div>
168170
</CardTitle>
169-
<div className="flex items-center gap-2 text-xs shrink-0 pl-2">
171+
<div className="flex items-center gap-2 text-xs">
170172
{tokenStats && <TokenStatsTooltip stats={tokenStats} />}
171173
{getStatusDisplay()}
172174
</div>
173175
</CardHeader>
174176
<CardContent>
175-
<CollapsibleSection
176-
icon={Code}
177-
expanded={areArgumentsExpanded}
178-
onToggle={() => setAreArgumentsExpanded(!areArgumentsExpanded)}
179-
previewContent={argsContent}
180-
expandedContent={argsContent}
181-
/>
177+
<div className="space-y-2 mt-4">
178+
<Button variant="ghost" size="sm" className="p-0 h-auto justify-start" onClick={() => setAreArgumentsExpanded(!areArgumentsExpanded)}>
179+
<Code className="w-4 h-4 mr-2" />
180+
<span className="mr-2">Arguments</span>
181+
{areArgumentsExpanded ? <ChevronUp className="w-4 h-4 ml-auto" /> : <ChevronDown className="w-4 h-4 ml-auto" />}
182+
</Button>
183+
{areArgumentsExpanded && (
184+
<div className="relative">
185+
<ScrollArea className="max-h-96 overflow-y-auto p-4 w-full mt-2 bg-muted/50">
186+
<pre className="text-sm whitespace-pre-wrap break-words">
187+
{JSON.stringify(call.args, null, 2)}
188+
</pre>
189+
</ScrollArea>
190+
</div>
191+
)}
192+
</div>
182193

183194
{/* Approval buttons — hidden when decided (batch) or submitting */}
184195
{status === "pending_approval" && !isSubmitting && !isDecided && !showRejectForm && (
@@ -242,20 +253,32 @@ const ToolDisplay = ({ call, result, status = "requested", isError = false, isDe
242253

243254
<div className="mt-4 w-full">
244255
{status === "executing" && !hasResult && (
245-
<div className="flex items-center gap-2 py-1">
256+
<div className="flex items-center gap-2 py-2">
246257
<Loader2 className="h-4 w-4 animate-spin" />
247258
<span className="text-sm">Executing...</span>
248259
</div>
249260
)}
250-
{hasResult && resultContent && (
251-
<CollapsibleSection
252-
icon={Text}
253-
expanded={areResultsExpanded}
254-
onToggle={() => setAreResultsExpanded(!areResultsExpanded)}
255-
previewContent={resultContent}
256-
expandedContent={resultContent}
257-
errorStyle={isError}
258-
/>
261+
{hasResult && (
262+
<>
263+
<Button variant="ghost" size="sm" className="p-0 h-auto justify-start" onClick={() => setAreResultsExpanded(!areResultsExpanded)}>
264+
<Text className="w-4 h-4 mr-2" />
265+
<span className="mr-2">{isError ? "Error" : "Results"}</span>
266+
{areResultsExpanded ? <ChevronUp className="w-4 h-4 ml-auto" /> : <ChevronDown className="w-4 h-4 ml-auto" />}
267+
</Button>
268+
{areResultsExpanded && (
269+
<div className="relative">
270+
<ScrollArea className={`max-h-96 overflow-y-auto p-4 w-full mt-2 ${isError ? 'bg-red-50 dark:bg-red-950/10' : ''}`}>
271+
<pre className={`text-sm whitespace-pre-wrap break-words ${isError ? 'text-red-600 dark:text-red-400' : ''}`}>
272+
{result.content}
273+
</pre>
274+
</ScrollArea>
275+
276+
<Button variant="ghost" size="sm" className="absolute top-2 right-2 p-2" onClick={handleCopy}>
277+
{isCopied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
278+
</Button>
279+
</div>
280+
)}
281+
</>
259282
)}
260283
</div>
261284
</CardContent>

ui/src/components/chat/AgentCallDisplay.tsx

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import { createContext, useContext, useMemo, useState, useEffect } from "react";
22
import { FunctionCall, TokenStats } from "@/types";
33
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
4-
import { convertToUserFriendlyName, isAgentToolName } from "@/lib/utils";
4+
import { convertToUserFriendlyName } from "@/lib/utils";
55
import { ChevronDown, ChevronUp, MessageSquare, Loader2, AlertCircle, CheckCircle, Activity } from "lucide-react";
66
import KagentLogo from "../kagent-logo";
77
import TokenStatsTooltip from "@/components/chat/TokenStatsTooltip";
88
import { getSubagentSessionWithEvents } from "@/app/actions/sessions";
99
import { Message, Task } from "@a2a-js/sdk";
1010
import { extractMessagesFromTasks } from "@/lib/messageHandlers";
1111
import ChatMessage from "@/components/chat/ChatMessage";
12-
import { SmartContent, parseJsonOrString } from "./SmartContent";
13-
import { CollapsibleSection } from "./CollapsibleSection";
1412

1513
// Track and avoid too deep nested agent viewing to avoid UI issues
1614
// In theory this works for infinite depth
@@ -134,9 +132,7 @@ const AgentCallDisplay = ({ call, result, status = "requested", isError = false,
134132
const activityDepth = useContext(ActivityDepthContext);
135133
const agentDisplay = useMemo(() => convertToUserFriendlyName(call.name), [call.name]);
136134
const hasResult = result !== undefined;
137-
const showActivitySection = !!subagentSessionId && !isError && activityDepth < MAX_ACTIVITY_DEPTH;
138-
139-
const isAgent = isAgentToolName(call.name);
135+
const showActivitySection = !!subagentSessionId && !isError && activityDepth < MAX_ACTIVITY_DEPTH;
140136

141137
const getStatusDisplay = () => {
142138
if (isError && status === "executing") {
@@ -182,12 +178,6 @@ const showActivitySection = !!subagentSessionId && !isError && activityDepth < M
182178
}
183179
};
184180

185-
const parsedResult = hasResult && result?.content ? parseJsonOrString(result.content) : null;
186-
const argsContent = <SmartContent data={call.args} />;
187-
const resultContent = parsedResult !== null
188-
? <SmartContent data={parsedResult} className={isError ? "text-red-600 dark:text-red-400" : ""} />
189-
: null;
190-
191181
return (
192182
<Card className={`w-full mx-auto my-1 min-w-full ${isError ? 'border-red-300' : ''}`}>
193183
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -196,39 +186,51 @@ const showActivitySection = !!subagentSessionId && !isError && activityDepth < M
196186
<KagentLogo className="w-4 h-4 mr-2" />
197187
{agentDisplay}
198188
</div>
199-
<div className="font-light">
200-
{call.id}
201-
</div>
189+
<div className="font-light">{call.id}</div>
202190
</CardTitle>
203191
<div className="flex items-center gap-2 text-xs">
204192
{tokenStats && <TokenStatsTooltip stats={tokenStats} />}
205193
{getStatusDisplay()}
206194
</div>
207195
</CardHeader>
208-
<CardContent className="space-y-1 pt-0">
209-
<CollapsibleSection
210-
icon={MessageSquare}
211-
expanded={areInputsExpanded}
212-
onToggle={() => setAreInputsExpanded(!areInputsExpanded)}
213-
previewContent={argsContent}
214-
expandedContent={argsContent}
215-
/>
216-
{status === "executing" && !hasResult && (
217-
<div className="flex items-center gap-2 py-1">
218-
<Loader2 className="h-4 w-4 animate-spin" />
219-
<span className="text-sm">{agentDisplay} is responding...</span>
220-
</div>
221-
)}
222-
{hasResult && resultContent && (
223-
<CollapsibleSection
224-
icon={MessageSquare}
225-
expanded={areResultsExpanded}
226-
onToggle={() => setAreResultsExpanded(!areResultsExpanded)}
227-
previewContent={resultContent}
228-
expandedContent={resultContent}
229-
errorStyle={isError}
230-
/>
231-
)}
196+
<CardContent>
197+
<div className="space-y-2 mt-2">
198+
<button className="text-xs flex items-center gap-2" onClick={() => setAreInputsExpanded(!areInputsExpanded)}>
199+
<MessageSquare className="w-4 h-4" />
200+
<span>Input</span>
201+
{areInputsExpanded ? <ChevronUp className="w-4 h-4 ml-1" /> : <ChevronDown className="w-4 h-4 ml-1" />}
202+
</button>
203+
{areInputsExpanded && (
204+
<div className="mt-2 bg-muted/50 p-3 rounded">
205+
<pre className="text-sm whitespace-pre-wrap break-words">{JSON.stringify(call.args, null, 2)}</pre>
206+
</div>
207+
)}
208+
</div>
209+
210+
<div className="mt-4 w-full">
211+
{status === "executing" && !hasResult && (
212+
<div className="flex items-center gap-2 py-2">
213+
<Loader2 className="h-4 w-4 animate-spin" />
214+
<span className="text-sm">{agentDisplay} is responding...</span>
215+
</div>
216+
)}
217+
{hasResult && result?.content && (
218+
<div className="space-y-2">
219+
<button className="text-xs flex items-center gap-2" onClick={() => setAreResultsExpanded(!areResultsExpanded)}>
220+
<MessageSquare className="w-4 h-4" />
221+
<span>Output</span>
222+
{areResultsExpanded ? <ChevronUp className="w-4 h-4 ml-1" /> : <ChevronDown className="w-4 h-4 ml-1" />}
223+
</button>
224+
{areResultsExpanded && (
225+
<div className={`mt-2 ${isError ? 'bg-red-50 dark:bg-red-950/10' : 'bg-muted/50'} p-3 rounded`}>
226+
<pre className={`text-sm whitespace-pre-wrap break-words ${isError ? 'text-red-600 dark:text-red-400' : ''}`}>
227+
{result?.content}
228+
</pre>
229+
</div>
230+
)}
231+
</div>
232+
)}
233+
</div>
232234

233235
{showActivitySection && (
234236
<div className="mt-4 border-t pt-3">

ui/src/components/chat/ChatMessage.tsx

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { TruncatableText } from "@/components/chat/TruncatableText";
33
import ToolCallDisplay from "@/components/chat/ToolCallDisplay";
44
import AskUserDisplay, { AskUserQuestion } from "@/components/chat/AskUserDisplay";
55
import KagentLogo from "../kagent-logo";
6-
import { ThumbsUp, ThumbsDown, Copy, Check } from "lucide-react";
6+
import { ThumbsUp, ThumbsDown } from "lucide-react";
77
import TokenStatsTooltip from "@/components/chat/TokenStatsTooltip";
88
import type { TokenStats } from "@/types";
99
import { useState } from "react";
@@ -29,7 +29,6 @@ interface ChatMessageProps {
2929
export default function ChatMessage({ message, allMessages, agentContext, onApprove, onReject, onAskUserSubmit, pendingDecisions }: ChatMessageProps) {
3030
const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false);
3131
const [isPositiveFeedback, setIsPositiveFeedback] = useState(true);
32-
const [copied, setCopied] = useState(false);
3332

3433
if (!message) return null;
3534

@@ -151,16 +150,6 @@ export default function ChatMessage({ message, allMessages, agentContext, onAppr
151150
}
152151

153152

154-
const handleCopy = async () => {
155-
try {
156-
await navigator.clipboard.writeText(String(content));
157-
setCopied(true);
158-
setTimeout(() => setCopied(false), 2000);
159-
} catch {
160-
/* clipboard unavailable */
161-
}
162-
};
163-
164153
const handleFeedback = (isPositive: boolean) => {
165154
if (!messageId) {
166155
console.error("Message ID is undefined, cannot submit feedback.");
@@ -183,13 +172,6 @@ export default function ChatMessage({ message, allMessages, agentContext, onAppr
183172
{source !== "user" && (
184173
<div className="flex mt-2 justify-end items-center gap-2">
185174
{tokenStats && <TokenStatsTooltip stats={tokenStats} />}
186-
<button
187-
onClick={handleCopy}
188-
className="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
189-
aria-label="Copy to clipboard"
190-
>
191-
{copied ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
192-
</button>
193175
{messageId !== undefined && (
194176
<>
195177
<button

0 commit comments

Comments
 (0)