fix: Rendering interrupts when there are no ai/tool messages

This commit is contained in:
bracesproul
2025-04-08 12:21:07 -07:00
parent 45c55b0a41
commit ec294c5dc9
2 changed files with 35 additions and 15 deletions

View File

@@ -200,6 +200,9 @@ export function Thread() {
};
const chatStarted = !!threadId || !!messages.length;
const hasNoAIOrToolMessages = !messages.find(
(m) => m.type === "ai" || m.type === "tool",
);
return (
<div className="flex w-full h-screen overflow-hidden">
@@ -350,6 +353,16 @@ export function Thread() {
/>
),
)}
{/* Special rendering case where there are no AI/tool messages, but there is an interrupt.
We need to render it outside of the messages list, since there are no messages to render */}
{hasNoAIOrToolMessages && !!stream.interrupt && (
<AssistantMessage
key="interrupt-msg"
message={undefined}
isLoading={isLoading}
handleRegenerate={handleRegenerate}
/>
)}
{isLoading && !firstTokenReceived && (
<AssistantMessageLoading />
)}

View File

@@ -70,11 +70,12 @@ export function AssistantMessage({
isLoading,
handleRegenerate,
}: {
message: Message;
message: Message | undefined;
isLoading: boolean;
handleRegenerate: (parentCheckpoint: Checkpoint | null | undefined) => void;
}) {
const contentString = getContentString(message.content);
const content = message?.content ?? [];
const contentString = getContentString(content);
const [hideToolCalls] = useQueryState(
"hideToolCalls",
parseAsBoolean.withDefault(false),
@@ -82,15 +83,20 @@ export function AssistantMessage({
const thread = useStreamContext();
const isLastMessage =
thread.messages[thread.messages.length - 1].id === message.id;
const meta = thread.getMessagesMetadata(message);
const interrupt = thread.interrupt;
thread.messages[thread.messages.length - 1].id === message?.id;
const hasNoAIOrToolMessages = !thread.messages.find(
(m) => m.type === "ai" || m.type === "tool",
);
const meta = message ? thread.getMessagesMetadata(message) : undefined;
const threadInterrupt = thread.interrupt;
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
const anthropicStreamedToolCalls = Array.isArray(message.content)
? parseAnthropicStreamedToolCalls(message.content)
const anthropicStreamedToolCalls = Array.isArray(content)
? parseAnthropicStreamedToolCalls(content)
: undefined;
const hasToolCalls =
message &&
"tool_calls" in message &&
message.tool_calls &&
message.tool_calls.length > 0;
@@ -100,7 +106,7 @@ export function AssistantMessage({
(tc) => tc.args && Object.keys(tc.args).length > 0,
);
const hasAnthropicToolCalls = !!anthropicStreamedToolCalls?.length;
const isToolResult = message.type === "tool";
const isToolResult = message?.type === "tool";
if (isToolResult && hideToolCalls) {
return null;
@@ -130,14 +136,15 @@ export function AssistantMessage({
</>
)}
<CustomComponent message={message} thread={thread} />
{isAgentInboxInterruptSchema(interrupt?.value) && isLastMessage && (
<ThreadView interrupt={interrupt.value} />
{message && <CustomComponent message={message} thread={thread} />}
{isAgentInboxInterruptSchema(threadInterrupt?.value) &&
(isLastMessage || hasNoAIOrToolMessages) && (
<ThreadView interrupt={threadInterrupt.value} />
)}
{interrupt?.value &&
!isAgentInboxInterruptSchema(interrupt.value) &&
{threadInterrupt?.value &&
!isAgentInboxInterruptSchema(threadInterrupt.value) &&
isLastMessage ? (
<GenericInterruptView interrupt={interrupt.value} />
<GenericInterruptView interrupt={threadInterrupt.value} />
) : null}
<div
className={cn(