From 8efa34fff79f0197291e8547f6c67260f9d4ff90 Mon Sep 17 00:00:00 2001 From: viettranx Date: Sat, 7 Mar 2026 14:55:19 +0700 Subject: [PATCH] feat(chat): show tool calls with expandable arguments in message history Render tool-call-only assistant messages as compact cards with wrench icon instead of empty bubbles. Click to expand and view tool arguments as formatted JSON. Make thinking block full-width in chat thread. --- ui/web/src/components/chat/message-bubble.tsx | 66 ++++++++++++++++++- ui/web/src/pages/chat/chat-thread.tsx | 4 +- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/ui/web/src/components/chat/message-bubble.tsx b/ui/web/src/components/chat/message-bubble.tsx index be4b51ff..749b877a 100644 --- a/ui/web/src/components/chat/message-bubble.tsx +++ b/ui/web/src/components/chat/message-bubble.tsx @@ -1,6 +1,8 @@ -import { Bot, User } from "lucide-react"; +import { useState } from "react"; +import { Bot, User, Wrench, ChevronRight } from "lucide-react"; import { MessageContent } from "./message-content"; import type { ChatMessage } from "@/types/chat"; +import type { ToolCall } from "@/types/session"; interface MessageBubbleProps { message: ChatMessage; @@ -14,6 +16,25 @@ export function MessageBubble({ message }: MessageBubbleProps) { return null; // Tool messages are shown inline with assistant messages } + const hasContent = !!message.content?.trim(); + const hasToolCalls = message.toolCalls && message.toolCalls.length > 0; + + // Skip assistant messages with neither text nor tool calls + if (!isUser && !hasContent && !hasToolCalls) { + return null; + } + + // Tool-call-only assistant message: render compact tool call list + if (!isUser && !hasContent && hasToolCalls) { + return ( +
+ {message.toolCalls!.map((tc) => ( + + ))} +
+ ); + } + return (
@@ -31,6 +52,13 @@ export function MessageBubble({ message }: MessageBubbleProps) { : "bg-card text-card-foreground border border-border shadow-sm" }`} > + {hasToolCalls && ( +
+ {message.toolCalls!.map((tc) => ( + + ))} +
+ )} {message.timestamp && (
@@ -44,3 +72,39 @@ export function MessageBubble({ message }: MessageBubbleProps) {
); } + +function ToolCallItem({ toolCall, compact }: { toolCall: ToolCall; compact?: boolean }) { + const [expanded, setExpanded] = useState(false); + const hasArgs = toolCall.arguments && Object.keys(toolCall.arguments).length > 0; + + return ( +
+ + {expanded && hasArgs && ( +
+          {JSON.stringify(toolCall.arguments, null, 2)}
+        
+ )} +
+ ); +} diff --git a/ui/web/src/pages/chat/chat-thread.tsx b/ui/web/src/pages/chat/chat-thread.tsx index 71204bb7..d0c58cfe 100644 --- a/ui/web/src/pages/chat/chat-thread.tsx +++ b/ui/web/src/pages/chat/chat-thread.tsx @@ -68,9 +68,7 @@ export function ChatThread({ {/* Thinking block (extended thinking / reasoning) */} {isRunning && thinkingText && ( -
- -
+ )} {/* Streaming text */}