From fe75c8aca1e4dd868d9d88e735c349df6781d5a2 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 4 Mar 2025 14:59:16 -0800 Subject: [PATCH 1/2] feat: Render tool result messages --- src/components/thread/messages/ai.tsx | 59 ++++++++------- src/components/thread/messages/tool-calls.tsx | 72 ++++++++++++++++++- 2 files changed, 102 insertions(+), 29 deletions(-) diff --git a/src/components/thread/messages/ai.tsx b/src/components/thread/messages/ai.tsx index e269a63..91fa365 100644 --- a/src/components/thread/messages/ai.tsx +++ b/src/components/thread/messages/ai.tsx @@ -6,7 +6,7 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { MarkdownText } from "../markdown-text"; import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui/client"; import { cn } from "@/lib/utils"; -import { ToolCalls } from "./tool-calls"; +import { ToolCalls, ToolResult } from "./tool-calls"; function CustomComponent({ message, @@ -61,40 +61,45 @@ export function AssistantMessage({ "tool_calls" in message && message.tool_calls && message.tool_calls.length > 0; + const isToolResult = message.type === "tool"; return (
A -
- {hasToolCalls && } - - {contentString.length > 0 && ( -
- {contentString} -
- )} -
+ ) : ( +
+ {hasToolCalls && } + + {contentString.length > 0 && ( +
+ {contentString} +
)} - > - thread.setBranch(branch)} - isLoading={isLoading} - /> - handleRegenerate(parentCheckpoint)} - /> +
+ thread.setBranch(branch)} + isLoading={isLoading} + /> + handleRegenerate(parentCheckpoint)} + /> +
-
+ )}
); } diff --git a/src/components/thread/messages/tool-calls.tsx b/src/components/thread/messages/tool-calls.tsx index 741b297..ebf39f6 100644 --- a/src/components/thread/messages/tool-calls.tsx +++ b/src/components/thread/messages/tool-calls.tsx @@ -1,4 +1,7 @@ -import { AIMessage } from "@langchain/langgraph-sdk"; +import { AIMessage, ToolMessage } from "@langchain/langgraph-sdk"; +import { useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { ChevronDown, ChevronUp } from "lucide-react"; function isComplexValue(value: any): boolean { return Array.isArray(value) || (typeof value === "object" && value !== null); @@ -23,7 +26,14 @@ export function ToolCalls({ className="border border-gray-200 rounded-lg overflow-hidden" >
-

{tc.name}

+

+ {tc.name} + {tc.id && ( + + {tc.id} + + )} +

@@ -51,3 +61,61 @@ export function ToolCalls({ ); } + +export function ToolResult({ message }: { message: ToolMessage }) { + const [isExpanded, setIsExpanded] = useState(false); + const contentStr = JSON.stringify(message.content, null, 2); + const contentLines = contentStr.split('\n'); + const shouldTruncate = contentLines.length > 4 || contentStr.length > 500; + const displayedContent = shouldTruncate && !isExpanded + ? (contentStr.length > 500 + ? contentStr.slice(0, 500) + '...' + : contentLines.slice(0, 4).join('\n') + '\n...') + : contentStr; + + return ( +
+
+
+ {message.name ?

Tool Result: {message.name}

:

Tool Result

} + {message.tool_call_id && ( + + {message.tool_call_id} + + )} +
+
+ +
+ + + {displayedContent} + + +
+ {shouldTruncate && ( + setIsExpanded(!isExpanded)} + className="w-full py-2 flex items-center justify-center border-t-[1px] border-gray-200 text-gray-500 hover:text-gray-600 hover:bg-gray-50 transition-all ease-in-out duration-200 cursor-pointer" + initial={{ scale: 1 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {isExpanded ? : } + + )} +
+
+ ); +} From af659d598e05539b914315a9c4b867843876c867 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 4 Mar 2025 15:22:46 -0800 Subject: [PATCH 2/2] fix --- src/components/thread/messages/tool-calls.tsx | 84 ++++++++++++++++--- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/src/components/thread/messages/tool-calls.tsx b/src/components/thread/messages/tool-calls.tsx index ebf39f6..bd768c3 100644 --- a/src/components/thread/messages/tool-calls.tsx +++ b/src/components/thread/messages/tool-calls.tsx @@ -64,20 +64,46 @@ export function ToolCalls({ export function ToolResult({ message }: { message: ToolMessage }) { const [isExpanded, setIsExpanded] = useState(false); - const contentStr = JSON.stringify(message.content, null, 2); - const contentLines = contentStr.split('\n'); + + let parsedContent: any; + let isJsonContent = false; + + try { + if (typeof message.content === "string") { + parsedContent = JSON.parse(message.content); + isJsonContent = true; + } + } catch { + // Content is not JSON, use as is + parsedContent = message.content; + } + + const contentStr = isJsonContent + ? JSON.stringify(parsedContent, null, 2) + : String(message.content); + const contentLines = contentStr.split("\n"); const shouldTruncate = contentLines.length > 4 || contentStr.length > 500; - const displayedContent = shouldTruncate && !isExpanded - ? (contentStr.length > 500 - ? contentStr.slice(0, 500) + '...' - : contentLines.slice(0, 4).join('\n') + '\n...') - : contentStr; + const displayedContent = + shouldTruncate && !isExpanded + ? contentStr.length > 500 + ? contentStr.slice(0, 500) + "..." + : contentLines.slice(0, 4).join("\n") + "\n..." + : contentStr; return (
- {message.name ?

Tool Result: {message.name}

:

Tool Result

} + {message.name ? ( +

+ Tool Result:{" "} + + {message.name} + +

+ ) : ( +

Tool Result

+ )} {message.tool_call_id && ( {message.tool_call_id} @@ -85,7 +111,7 @@ export function ToolResult({ message }: { message: ToolMessage }) { )}
- - {displayedContent} + {isJsonContent ? ( +
+ + {(Array.isArray(parsedContent) + ? isExpanded + ? parsedContent + : parsedContent.slice(0, 5) + : Object.entries(parsedContent) + ).map((item, argIdx) => { + const [key, value] = Array.isArray(parsedContent) + ? [argIdx, item] + : [item[0], item[1]]; + return ( + + + + + ); + })} + +
+ {key} + + {isComplexValue(value) ? ( + + {JSON.stringify(value, null, 2)} + + ) : ( + String(value) + )} +
+ ) : ( + {displayedContent} + )}
- {shouldTruncate && ( + {((shouldTruncate && !isJsonContent) || + (isJsonContent && + Array.isArray(parsedContent) && + parsedContent.length > 5)) && ( setIsExpanded(!isExpanded)} className="w-full py-2 flex items-center justify-center border-t-[1px] border-gray-200 text-gray-500 hover:text-gray-600 hover:bg-gray-50 transition-all ease-in-out duration-200 cursor-pointer"