diff --git a/app/components/assistant/message-body.tsx b/app/components/assistant/message-body.tsx index 8e9540c..5003978 100644 --- a/app/components/assistant/message-body.tsx +++ b/app/components/assistant/message-body.tsx @@ -1,30 +1,53 @@ -// Renders an assistant message: markdown for prose, and a small "Ran N -// actions" pill in place of any ```action``` fenced blocks (which are the -// machine-readable instructions the bus has already executed). +// Renders an assistant message: markdown for prose, plus pills for any +// command-bus action blocks or native tool calls attached to the message. +// Tool-role messages (results from a function call) render as a JSON card. import { useMemo } from "react" import ReactMarkdown from "react-markdown" -import { Sparkles } from "lucide-react" +import { Sparkles, Wrench } from "lucide-react" import { extractActionBlocks } from "@crema/action-bus" +import { stripToolCallTags, type ToolCall } from "@crema/llm-ui" const ACTION_BLOCK_RE = /```action\s*\n[\s\S]*?```/g -export function MessageBody({ content }: { content: string }) { +export type MessageBodyProps = { + content: string + /** When set, render as a tool-result card. */ + isToolResult?: boolean + /** Native tool calls attached to this assistant message, if any. */ + toolCalls?: ToolCall[] +} + +export function MessageBody({ content, isToolResult, toolCalls }: MessageBodyProps) { const { prose, actionCount } = useMemo(() => { const blocks = extractActionBlocks(content) + const cleaned = stripToolCallTags(content).replace(ACTION_BLOCK_RE, "").trim() return { - prose: content.replace(ACTION_BLOCK_RE, "").trim(), + prose: cleaned, actionCount: blocks.length, } }, [content]) + if (isToolResult) { + return ( +
+ {content}
+
+ {children}
, code: ({ children, className }) => { const isBlock = className?.startsWith("language-") @@ -63,6 +86,15 @@ export function MessageBody({ content }: { content: string }) { Ran {actionCount} action{actionCount > 1 ? "s" : ""} )} + {toolCalls && toolCalls.length > 0 && ( + +