Skip to content

Commit a3f4a2b

Browse files
committed
fix(ai-chat): defer multi-tab broadcasts, disable streamdown word animation
1 parent 955e727 commit a3f4a2b

2 files changed

Lines changed: 41 additions & 20 deletions

File tree

packages/trigger-sdk/src/v3/chat-react.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,44 @@ export function useMultiTabChat<T = unknown>(
148148

149149
// Active tab: broadcast messages to other tabs on change.
150150
// Only broadcast when THIS tab holds the claim (is the current sender).
151-
// Using !isReadOnly alone causes a feedback loop when both tabs are idle.
151+
// Deferred via requestIdleCallback so the structured clone in
152+
// BroadcastChannel.postMessage never blocks rendering during streaming.
153+
const idleRef = useRef<number | ReturnType<typeof setTimeout> | null>(null);
154+
const latestMessagesRef = useRef(messages);
155+
latestMessagesRef.current = messages;
156+
152157
useEffect(() => {
153-
if (transport.hasClaim(chatId) && messages.length > 0) {
154-
transport.broadcastMessages(chatId, messages as unknown[]);
155-
}
158+
if (!transport.hasClaim(chatId) || messages.length === 0) return;
159+
if (idleRef.current !== null) return; // Already scheduled
160+
161+
const schedule =
162+
typeof requestIdleCallback === "function"
163+
? requestIdleCallback
164+
: (fn: () => void) => setTimeout(fn, 50);
165+
166+
idleRef.current = schedule(() => {
167+
idleRef.current = null;
168+
if (transport.hasClaim(chatId)) {
169+
transport.broadcastMessages(chatId, latestMessagesRef.current as unknown[]);
170+
}
171+
});
156172
}, [transport, chatId, messages]);
157173

174+
// Flush final state when claim is released (turn complete)
175+
useEffect(() => {
176+
if (!transport.hasClaim(chatId) && latestMessagesRef.current.length > 0) {
177+
if (idleRef.current !== null) {
178+
const cancel =
179+
typeof cancelIdleCallback === "function"
180+
? cancelIdleCallback
181+
: clearTimeout;
182+
cancel(idleRef.current as any);
183+
idleRef.current = null;
184+
}
185+
transport.broadcastMessages(chatId, latestMessagesRef.current as unknown[]);
186+
}
187+
}, [transport, chatId, isReadOnly]);
188+
158189
// Read-only tab: receive messages from the active tab
159190
useEffect(() => {
160191
const listener = (id: string, msgs: unknown[]) => {

references/ai-chat/src/components/chat.tsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function ToolInvocation({
2222
part: any;
2323
onApprove?: (approvalId: string) => void;
2424
onDeny?: (approvalId: string) => void;
25-
onToolOutput?: (toolCallId: string, output: unknown) => void;
25+
onToolOutput?: (tool: string, toolCallId: string, output: unknown) => void;
2626
}) {
2727
const [expanded, setExpanded] = useState(false);
2828
const toolName = part.type.startsWith("tool-") ? part.type.slice(5) : "tool";
@@ -87,7 +87,7 @@ function ToolInvocation({
8787
key={opt.id}
8888
type="button"
8989
onClick={() =>
90-
onToolOutput?.(part.toolCallId, {
90+
onToolOutput?.(toolName, part.toolCallId, {
9191
skipped: false,
9292
answers: [{ questionId: args.question, optionId: opt.id, text: opt.label }],
9393
})
@@ -566,7 +566,7 @@ export function Chat({
566566
</p>
567567
)}
568568

569-
{messages.map((message, messageIndex) => (
569+
{messages.map((message) => (
570570
<div
571571
key={message.id}
572572
className={`flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
@@ -580,17 +580,7 @@ export function Chat({
580580
{message.parts.map((part, i) => {
581581
if (part.type === "text") {
582582
if (message.role === "assistant") {
583-
return (
584-
<Streamdown
585-
key={i}
586-
animated
587-
isAnimating={
588-
status === "streaming" && messageIndex === messages.length - 1
589-
}
590-
>
591-
{part.text}
592-
</Streamdown>
593-
);
583+
return <Streamdown key={i}>{part.text}</Streamdown>;
594584
}
595585
return <span key={i}>{part.text}</span>;
596586
}
@@ -652,8 +642,8 @@ export function Chat({
652642
part={part}
653643
onApprove={handleApprove}
654644
onDeny={handleDeny}
655-
onToolOutput={(toolCallId, output) =>
656-
addToolOutput({ toolCallId, output })
645+
onToolOutput={(tool, toolCallId, output) =>
646+
addToolOutput({ tool, toolCallId, output })
657647
}
658648
/>
659649
);

0 commit comments

Comments
 (0)