From 3e97f9e985d018af84f2affa023c3e1521205ab6 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 13 Feb 2026 06:26:24 -0700 Subject: [PATCH] Show tool calls separately from message --- web/public/locales/en/views/chat.json | 4 +- web/src/components/chat/AssistantMessage.tsx | 61 +--------------- web/src/components/chat/ToolCallBubble.tsx | 77 ++++++++++++++++++++ web/src/pages/Chat.tsx | 61 +++++++++------- web/src/types/chat.ts | 11 +++ 5 files changed, 129 insertions(+), 85 deletions(-) create mode 100644 web/src/components/chat/ToolCallBubble.tsx create mode 100644 web/src/types/chat.ts diff --git a/web/public/locales/en/views/chat.json b/web/public/locales/en/views/chat.json index 1fe688fdd..152a8210a 100644 --- a/web/public/locales/en/views/chat.json +++ b/web/public/locales/en/views/chat.json @@ -4,5 +4,7 @@ "processing": "Processing...", "toolsUsed": "Used: {{tools}}", "showTools": "Show tools ({{count}})", - "hideTools": "Hide tools" + "hideTools": "Hide tools", + "call": "Call", + "result": "Result" } diff --git a/web/src/components/chat/AssistantMessage.tsx b/web/src/components/chat/AssistantMessage.tsx index 73008527f..17d697b65 100644 --- a/web/src/components/chat/AssistantMessage.tsx +++ b/web/src/components/chat/AssistantMessage.tsx @@ -1,66 +1,9 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; import ReactMarkdown from "react-markdown"; -import { Button } from "@/components/ui/button"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; - -export type ToolCall = { - name: string; - arguments?: Record; - response?: string; -}; type AssistantMessageProps = { content: string; - toolCalls?: ToolCall[]; }; -export function AssistantMessage({ - content, - toolCalls, -}: AssistantMessageProps) { - const { t } = useTranslation(["views/chat"]); - const [open, setOpen] = useState(false); - const hasToolCalls = toolCalls && toolCalls.length > 0; - - return ( -
- {content} - {hasToolCalls && ( - - - - - -
    - {toolCalls.map((tc, idx) => ( -
  • - - {tc.name} - - {tc.response != null && tc.response !== "" && ( -
    -                      {tc.response}
    -                    
    - )} -
  • - ))} -
-
-
- )} -
- ); +export function AssistantMessage({ content }: AssistantMessageProps) { + return {content}; } diff --git a/web/src/components/chat/ToolCallBubble.tsx b/web/src/components/chat/ToolCallBubble.tsx new file mode 100644 index 000000000..05c39ae2a --- /dev/null +++ b/web/src/components/chat/ToolCallBubble.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { Button } from "@/components/ui/button"; +import { ChevronDown, ChevronRight } from "lucide-react"; + +type ToolCallBubbleProps = { + name: string; + arguments?: Record; + response?: string; + side: "left" | "right"; +}; + +export function ToolCallBubble({ + name, + arguments: args, + response, + side, +}: ToolCallBubbleProps) { + const { t } = useTranslation(["views/chat"]); + const [open, setOpen] = useState(false); + const isLeft = side === "left"; + const normalizedName = name + .replace(/_/g, " ") + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); + + return ( +
+ + + + + +
+ {isLeft && args && Object.keys(args).length > 0 && ( +
+
Arguments:
+
+                  {JSON.stringify(args, null, 2)}
+                
+
+ )} + {!isLeft && response && response !== "" && ( +
+
Response:
+
+                  {response}
+                
+
+ )} +
+
+
+
+ ); +} diff --git a/web/src/pages/Chat.tsx b/web/src/pages/Chat.tsx index 8c49119c1..7a0dde930 100644 --- a/web/src/pages/Chat.tsx +++ b/web/src/pages/Chat.tsx @@ -4,16 +4,9 @@ import { FaArrowUpLong } from "react-icons/fa6"; import { useTranslation } from "react-i18next"; import { useState, useCallback } from "react"; import axios from "axios"; -import { - AssistantMessage, - type ToolCall, -} from "@/components/chat/AssistantMessage"; - -type ChatMessage = { - role: "user" | "assistant"; - content: string; - toolCalls?: ToolCall[]; -}; +import { AssistantMessage } from "@/components/chat/AssistantMessage"; +import { ToolCallBubble } from "@/components/chat/ToolCallBubble"; +import type { ChatMessage, ToolCall } from "@/types/chat"; export default function ChatPage() { const { t } = useTranslation(["views/chat"]); @@ -62,22 +55,40 @@ export default function ChatPage() {
{messages.map((msg, i) => ( -
- {msg.role === "assistant" ? ( - - ) : ( - msg.content +
+ {msg.role === "assistant" && msg.toolCalls && ( + <> + {msg.toolCalls.map((tc, tcIdx) => ( +
+ + {tc.response && ( + + )} +
+ ))} + )} +
+ {msg.role === "assistant" ? ( + + ) : ( + msg.content + )} +
))} {isLoading && ( diff --git a/web/src/types/chat.ts b/web/src/types/chat.ts new file mode 100644 index 000000000..b9217e1c2 --- /dev/null +++ b/web/src/types/chat.ts @@ -0,0 +1,11 @@ +export type ToolCall = { + name: string; + arguments?: Record; + response?: string; +}; + +export type ChatMessage = { + role: "user" | "assistant"; + content: string; + toolCalls?: ToolCall[]; +};