mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 22:17:36 +00:00
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.
This commit is contained in:
1 parent
612d9fa965
commit
8efa34fff7
2 files changed
+66
-4
No files matched your search
@@ -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 (
|
||||
<div className="space-y-1">
|
||||
{message.toolCalls!.map((tc) => (
|
||||
<ToolCallItem key={tc.id} toolCall={tc} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex gap-3 ${isUser ? "flex-row-reverse" : ""}`}>
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border bg-background">
|
||||
@@ -31,6 +52,13 @@ export function MessageBubble({ message }: MessageBubbleProps) {
|
||||
: "bg-card text-card-foreground border border-border shadow-sm"
|
||||
}`}
|
||||
>
|
||||
{hasToolCalls && (
|
||||
<div className="mb-2 space-y-1">
|
||||
{message.toolCalls!.map((tc) => (
|
||||
<ToolCallItem key={tc.id} toolCall={tc} compact />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<MessageContent content={message.content} role={message.role} />
|
||||
{message.timestamp && (
|
||||
<div className={`mt-1 text-[10px] ${isUser ? "text-primary-foreground/60" : "text-muted-foreground"}`}>
|
||||
@@ -44,3 +72,39 @@ export function MessageBubble({ message }: MessageBubbleProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolCallItem({ toolCall, compact }: { toolCall: ToolCall; compact?: boolean }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const hasArgs = toolCall.arguments && Object.keys(toolCall.arguments).length > 0;
|
||||
|
||||
return (
|
||||
<div className={compact ? "" : "rounded-md border bg-muted/50 overflow-hidden"}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => hasArgs && setExpanded(!expanded)}
|
||||
className={`flex items-center gap-1.5 w-full text-left ${
|
||||
compact
|
||||
? "text-xs text-muted-foreground py-0.5"
|
||||
: "px-3 py-1.5 text-sm hover:bg-muted/80 transition-colors"
|
||||
} ${hasArgs ? "cursor-pointer" : "cursor-default"}`}
|
||||
>
|
||||
{compact ? (
|
||||
<Wrench className="h-3 w-3 shrink-0" />
|
||||
) : (
|
||||
<Wrench className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
)}
|
||||
<span className={`font-mono truncate ${compact ? "" : "text-xs"}`}>{toolCall.name}</span>
|
||||
{hasArgs && (
|
||||
<ChevronRight className={`h-3 w-3 shrink-0 ml-auto text-muted-foreground transition-transform ${expanded ? "rotate-90" : ""}`} />
|
||||
)}
|
||||
</button>
|
||||
{expanded && hasArgs && (
|
||||
<pre className={`text-[11px] text-muted-foreground overflow-x-auto ${
|
||||
compact ? "pl-4.5 pb-1" : "px-3 pb-2 border-t bg-muted/30"
|
||||
}`}>
|
||||
{JSON.stringify(toolCall.arguments, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -68,9 +68,7 @@ export function ChatThread({
|
||||
|
||||
{/* Thinking block (extended thinking / reasoning) */}
|
||||
{isRunning && thinkingText && (
|
||||
<div className="mx-auto max-w-[80%]">
|
||||
<ThinkingBlock text={thinkingText} isStreaming={streamText === null} />
|
||||
</div>
|
||||
<ThinkingBlock text={thinkingText} isStreaming={streamText === null} />
|
||||
)}
|
||||
|
||||
{/* Streaming text */}
|
||||
|
||||
Reference in new issue
Block a user