From 26a4c549d7e630819d2c652dfc7328445b78b183 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 8 Apr 2025 13:37:47 -0700 Subject: [PATCH 1/2] fix: Handle unknown interrupt objects --- src/components/thread/messages/ai.tsx | 2 +- .../thread/messages/generic-interrupt.tsx | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/thread/messages/ai.tsx b/src/components/thread/messages/ai.tsx index 785978b..c761a6b 100644 --- a/src/components/thread/messages/ai.tsx +++ b/src/components/thread/messages/ai.tsx @@ -143,7 +143,7 @@ export function AssistantMessage({ )} {threadInterrupt?.value && !isAgentInboxInterruptSchema(threadInterrupt.value) && - isLastMessage ? ( + (isLastMessage || hasNoAIOrToolMessages) ? ( ) : null}
| Record[]; + interrupt: unknown; }) { const [isExpanded, setIsExpanded] = useState(false); @@ -39,23 +39,25 @@ export function GenericInterruptView({ }; // Process entries based on expanded state - const processEntries = () => { + const processEntries = (): unknown[] | string => { if (Array.isArray(interrupt)) { return isExpanded ? interrupt : interrupt.slice(0, 5); - } else { + } else if (typeof interrupt === "object" && interrupt !== null) { 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; + } else { + return interrupt?.toString() ?? ""; } }; const displayEntries = processEntries(); return ( -
+

Human Interrupt

@@ -82,7 +84,7 @@ export function GenericInterruptView({ > - {displayEntries.map((item, argIdx) => { + {Array.isArray(displayEntries) ? displayEntries.map((item, argIdx) => { const [key, value] = Array.isArray(interrupt) ? [argIdx.toString(), item] : (item as [string, any]); @@ -102,7 +104,13 @@ export function GenericInterruptView({ ); - })} + }) : ( + + + + )}
+ {displayEntries} +
From 3f25899bebaba8727949e9803a96012b0847bfbd Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 8 Apr 2025 14:12:09 -0700 Subject: [PATCH 2/2] cr --- src/components/thread/messages/ai.tsx | 10 +- .../thread/messages/generic-interrupt.tsx | 127 ++++++++++++++---- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/src/components/thread/messages/ai.tsx b/src/components/thread/messages/ai.tsx index c761a6b..71402b1 100644 --- a/src/components/thread/messages/ai.tsx +++ b/src/components/thread/messages/ai.tsx @@ -12,7 +12,10 @@ 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"; +import { + GenericInterruptView, + ResumeGenericInterrupt, +} from "./generic-interrupt"; function CustomComponent({ message, @@ -144,7 +147,10 @@ export function AssistantMessage({ {threadInterrupt?.value && !isAgentInboxInterruptSchema(threadInterrupt.value) && (isLastMessage || hasNoAIOrToolMessages) ? ( - +
+ + +
) : null}
- {Array.isArray(displayEntries) ? displayEntries.map((item, argIdx) => { - const [key, value] = Array.isArray(interrupt) - ? [argIdx.toString(), item] - : (item as [string, any]); - return ( - - - - - ); - }) : ( + {Array.isArray(displayEntries) ? ( + 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) - )} -
+ {key} + + {isComplexValue(value) ? ( + + {JSON.stringify(value, null, 2)} + + ) : ( + String(value) + )} +
{displayEntries} @@ -132,3 +139,69 @@ export function GenericInterruptView({ ); } + +const QuestionCircle = () => ( +
+

?

+
+); + +const Code = ({ children }: { children: React.ReactNode }) => ( + {children} +); + +export function ResumeGenericInterrupt() { + const [resumeValue, setResumeValue] = useState(""); + const stream = useStreamContext(); + + const handleResume = ( + e: + | React.MouseEvent + | React.FormEvent, + ) => { + e.preventDefault(); + stream.submit(null, { + command: { + resume: resumeValue, + }, + }); + }; + + return ( +
+ + + +

What's this

+ +
+ +

+ This input offers a way to resume your graph from the point of the + interrupt. +

+

+ The graph will be resumed using the Command API, with + the input you provide passed as a string value to the{" "} + resume key. +

+
+
+
+
+ setResumeValue(e.target.value)} + className="bg-inherit border-none shadow-none" + /> + +
+
+ ); +}