From 6be3b3229b70435cfe4b1b77d6cf84b3eebec379 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Mon, 17 Mar 2025 14:42:58 -0700 Subject: [PATCH] feat: Add generic interrupt renderer --- src/components/thread/messages/ai.tsx | 6 + .../thread/messages/generic-interrupt.tsx | 126 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/components/thread/messages/generic-interrupt.tsx diff --git a/src/components/thread/messages/ai.tsx b/src/components/thread/messages/ai.tsx index 6799d9e..75a0b16 100644 --- a/src/components/thread/messages/ai.tsx +++ b/src/components/thread/messages/ai.tsx @@ -12,6 +12,7 @@ import { Fragment } from "react/jsx-runtime"; import { isAgentInboxInterruptSchema } from "@/lib/agent-inbox-interrupt"; import { ThreadView } from "../agent-inbox"; import { useQueryState, parseAsBoolean } from "nuqs"; +import { GenericInterruptView } from "./generic-interrupt"; function CustomComponent({ message, @@ -133,6 +134,11 @@ export function AssistantMessage({ {isAgentInboxInterruptSchema(interrupt?.value) && isLastMessage && ( )} + {interrupt?.value && + !isAgentInboxInterruptSchema(interrupt.value) && + isLastMessage ? ( + + ) : null}
| Record[]; +}) { + const [isExpanded, setIsExpanded] = useState(false); + + const contentStr = JSON.stringify(interrupt, null, 2); + const contentLines = contentStr.split("\n"); + const shouldTruncate = contentLines.length > 4 || contentStr.length > 500; + + // Function to truncate long string values + const truncateValue = (value: any): any => { + if (typeof value === "string" && value.length > 100) { + return value.substring(0, 100) + "..."; + } + + if (Array.isArray(value) && !isExpanded) { + return value.slice(0, 2).map(truncateValue); + } + + if (isComplexValue(value) && !isExpanded) { + const strValue = JSON.stringify(value, null, 2); + if (strValue.length > 100) { + // Return plain text for truncated content instead of a JSON object + return `Truncated ${strValue.length} characters...`; + } + } + + return value; + }; + + // Process entries based on expanded state + const processEntries = () => { + if (Array.isArray(interrupt)) { + return isExpanded ? interrupt : interrupt.slice(0, 5); + } else { + const entries = Object.entries(interrupt); + if (!isExpanded && shouldTruncate) { + // When collapsed, process each value to potentially truncate it + return entries.map(([key, value]) => [key, truncateValue(value)]); + } + return entries; + } + }; + + const displayEntries = processEntries(); + + return ( +
+
+
+

Human Interrupt

+
+
+ +
+ + + + + {displayEntries.map((item, argIdx) => { + const [key, value] = Array.isArray(interrupt) + ? [argIdx.toString(), item] + : (item as [string, any]); + return ( + + + + + ); + })} + +
+ {key} + + {isComplexValue(value) ? ( + + {JSON.stringify(value, null, 2)} + + ) : ( + String(value) + )} +
+
+
+
+ {(shouldTruncate || + (Array.isArray(interrupt) && interrupt.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" + initial={{ scale: 1 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {isExpanded ? : } + + )} +
+
+ ); +}