fix: show tool outputs in embedded tool calls and improve light mode styling
- Build toolCallId -> output map from TOOL messages - Pass outputs to embedded tool calls in ASSISTANT messages - Skip rendering separate TOOL messages (now shown with their calls) - Improve light mode contrast for tool cards: - Use slate-50 background instead of gray - Better status badge colors (green-700, blue-700 in light mode) - Better error colors (red-600 in light mode) - Better output text colors (emerald-700, red-600 in light mode) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
49af625254
commit
5c0b9c5ae9
1 changed files with 85 additions and 77 deletions
|
|
@ -86,10 +86,14 @@ function ToolCallCard({
|
||||||
const effectiveStatus = hasError ? 'error' : toolCall.status;
|
const effectiveStatus = hasError ? 'error' : toolCall.status;
|
||||||
|
|
||||||
const statusColors = {
|
const statusColors = {
|
||||||
pending: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
pending:
|
||||||
running: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
|
'bg-yellow-100 dark:bg-yellow-500/20 text-yellow-700 dark:text-yellow-400 border-yellow-300 dark:border-yellow-500/30',
|
||||||
success: 'bg-green-500/20 text-green-400 border-green-500/30',
|
running:
|
||||||
error: 'bg-red-500/20 text-red-400 border-red-500/30',
|
'bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-400 border-blue-300 dark:border-blue-500/30',
|
||||||
|
success:
|
||||||
|
'bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400 border-green-300 dark:border-green-500/30',
|
||||||
|
error:
|
||||||
|
'bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400 border-red-300 dark:border-red-500/30',
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusIcons: Record<ToolCall['status'], 'loader' | 'check' | 'alertCircle' | 'info'> = {
|
const statusIcons: Record<ToolCall['status'], 'loader' | 'check' | 'alertCircle' | 'info'> = {
|
||||||
|
|
@ -109,13 +113,13 @@ function ToolCallCard({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg border overflow-hidden font-mono text-xs ${hasError ? 'border-red-500/50 bg-red-500/5' : 'border-border bg-surface-secondary/50'}`}
|
className={`rounded-lg border overflow-hidden font-mono text-xs ${hasError ? 'border-red-500/50 bg-red-50 dark:bg-red-500/5' : 'border-border bg-slate-50 dark:bg-surface-secondary/50'}`}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
className="w-full flex items-center gap-3 p-3 hover:bg-surface-secondary/80 transition-colors"
|
className="w-full flex items-center gap-3 p-3 hover:bg-slate-100 dark:hover:bg-surface-secondary/80 transition-colors"
|
||||||
>
|
>
|
||||||
<div className={`p-1.5 rounded ${statusColors[effectiveStatus]}`}>
|
<div className={`p-1.5 rounded ${statusColors[effectiveStatus]}`}>
|
||||||
<Icon
|
<Icon
|
||||||
|
|
@ -131,14 +135,14 @@ function ToolCallCard({
|
||||||
{toolCall.toolCallId.slice(0, 8)}...
|
{toolCall.toolCallId.slice(0, 8)}...
|
||||||
</span>
|
</span>
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-red-500/20 text-red-400">
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-red-100 dark:bg-red-500/20 text-red-600 dark:text-red-400">
|
||||||
ERROR
|
ERROR
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Show error message preview in header */}
|
{/* Show error message preview in header */}
|
||||||
{hasError && errorMessage && !isExpanded && (
|
{hasError && errorMessage && !isExpanded && (
|
||||||
<div className="text-red-400 text-[10px] mt-1 truncate max-w-[300px]">
|
<div className="text-red-600 dark:text-red-400 text-[10px] mt-1 truncate max-w-[300px]">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -170,14 +174,14 @@ function ToolCallCard({
|
||||||
|
|
||||||
{/* Error Message Section */}
|
{/* Error Message Section */}
|
||||||
{hasError && errorMessage && (
|
{hasError && errorMessage && (
|
||||||
<div className="p-3 bg-red-500/10 border-b border-red-500/20">
|
<div className="p-3 bg-red-100 dark:bg-red-500/10 border-b border-red-200 dark:border-red-500/20">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Icon icon="alertCircle" size="xs" className="text-red-400" />
|
<Icon icon="alertCircle" size="xs" className="text-red-600 dark:text-red-400" />
|
||||||
<span className="text-[10px] uppercase tracking-wider text-red-400 font-semibold">
|
<span className="text-[10px] uppercase tracking-wider text-red-600 dark:text-red-400 font-semibold">
|
||||||
Error
|
Error
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[11px] text-red-400">{errorMessage}</p>
|
<p className="text-[11px] text-red-600 dark:text-red-400">{errorMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -191,7 +195,7 @@ function ToolCallCard({
|
||||||
<div className="flex-1 h-px bg-border/50" />
|
<div className="flex-1 h-px bg-border/50" />
|
||||||
</div>
|
</div>
|
||||||
<pre
|
<pre
|
||||||
className={`text-[11px] overflow-x-auto whitespace-pre-wrap break-all max-h-48 overflow-y-auto ${hasError ? 'text-red-300' : 'text-green-400'}`}
|
className={`text-[11px] overflow-x-auto whitespace-pre-wrap break-all max-h-48 overflow-y-auto ${hasError ? 'text-red-600 dark:text-red-300' : 'text-emerald-700 dark:text-green-400'}`}
|
||||||
>
|
>
|
||||||
{formatJson(toolCall.output)}
|
{formatJson(toolCall.output)}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
@ -604,80 +608,84 @@ export default function AgentChatPage(): React.ReactElement {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{messages.map((message) => {
|
{(() => {
|
||||||
// Parse tool output safely
|
// Build a map of toolCallId -> tool result from TOOL messages
|
||||||
const getToolOutput = () => {
|
const toolResultsMap = new Map<string, unknown>();
|
||||||
if (message.toolResult) return message.toolResult;
|
for (const msg of messages) {
|
||||||
try {
|
if (msg.role === 'TOOL' && msg.toolCallId) {
|
||||||
return JSON.parse(message.content || '{}');
|
let output: unknown;
|
||||||
} catch {
|
if (msg.toolResult) {
|
||||||
return { result: message.content };
|
output = msg.toolResult;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
output = JSON.parse(msg.content || '{}');
|
||||||
|
} catch {
|
||||||
|
output = { result: msg.content };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toolResultsMap.set(msg.toolCallId, output);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Check if this ASSISTANT message has embedded tool calls
|
return messages.map((message) => {
|
||||||
const hasEmbeddedToolCalls =
|
// Check if this ASSISTANT message has embedded tool calls
|
||||||
message.role === 'ASSISTANT' &&
|
const hasEmbeddedToolCalls =
|
||||||
message.toolCalls &&
|
message.role === 'ASSISTANT' &&
|
||||||
Array.isArray(message.toolCalls) &&
|
message.toolCalls &&
|
||||||
message.toolCalls.length > 0;
|
Array.isArray(message.toolCalls) &&
|
||||||
|
message.toolCalls.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={message.id} className="space-y-2">
|
<div key={message.id} className="space-y-2">
|
||||||
{/* Render embedded tool calls from ASSISTANT messages */}
|
{/* Render embedded tool calls from ASSISTANT messages */}
|
||||||
{hasEmbeddedToolCalls && (
|
{hasEmbeddedToolCalls && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{message.toolCalls!.map((tc) => (
|
{message.toolCalls?.map((tc) => {
|
||||||
<div key={tc.toolCallId} className="flex justify-start">
|
// Look up the output for this tool call
|
||||||
<div className="max-w-[80%]">
|
const toolOutput = toolResultsMap.get(tc.toolCallId);
|
||||||
<ToolCallCard
|
const hasOutput = toolOutput !== undefined;
|
||||||
toolCall={{
|
|
||||||
toolCallId: tc.toolCallId,
|
|
||||||
toolName: tc.toolName,
|
|
||||||
input: tc.args,
|
|
||||||
status: 'success',
|
|
||||||
}}
|
|
||||||
isExpanded={expandedToolCalls.has(tc.toolCallId)}
|
|
||||||
onToggle={() => toggleToolCall(tc.toolCallId)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Render the message content */}
|
return (
|
||||||
<div
|
<div key={tc.toolCallId} className="flex justify-start">
|
||||||
className={`flex ${message.role === 'USER' ? 'justify-end' : 'justify-start'}`}
|
<div className="max-w-[80%]">
|
||||||
>
|
<ToolCallCard
|
||||||
{message.role === 'TOOL' ? (
|
toolCall={{
|
||||||
<div className="max-w-[80%]">
|
toolCallId: tc.toolCallId,
|
||||||
<ToolCallCard
|
toolName: tc.toolName,
|
||||||
toolCall={{
|
input: tc.args,
|
||||||
toolCallId: message.toolCallId || message.id,
|
output: toolOutput,
|
||||||
toolName: message.toolName || 'Unknown Tool',
|
status: hasOutput ? 'success' : 'pending',
|
||||||
output: getToolOutput(),
|
}}
|
||||||
status: 'success',
|
isExpanded={expandedToolCalls.has(tc.toolCallId)}
|
||||||
}}
|
onToggle={() => toggleToolCall(tc.toolCallId)}
|
||||||
isExpanded={expandedToolCalls.has(message.toolCallId || message.id)}
|
/>
|
||||||
onToggle={() => toggleToolCall(message.toolCallId || message.id)}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{/* Render the message content - skip TOOL messages as they're shown with their calls */}
|
||||||
|
{message.role !== 'TOOL' && (
|
||||||
<div
|
<div
|
||||||
className={`max-w-[80%] rounded-lg p-4 ${
|
className={`flex ${message.role === 'USER' ? 'justify-end' : 'justify-start'}`}
|
||||||
message.role === 'USER'
|
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'bg-surface-secondary'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
|
<div
|
||||||
|
className={`max-w-[80%] rounded-lg p-4 ${
|
||||||
|
message.role === 'USER'
|
||||||
|
? 'bg-primary text-primary-foreground'
|
||||||
|
: 'bg-surface-secondary'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
});
|
||||||
})}
|
})()}
|
||||||
|
|
||||||
{/* Live tool calls during streaming */}
|
{/* Live tool calls during streaming */}
|
||||||
{toolCalls.length > 0 && (
|
{toolCalls.length > 0 && (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue