fix(ai-chat): defer multi-tab broadcasts, disable streamdown word animation
This commit is contained in:
@@ -148,13 +148,44 @@ export function useMultiTabChat<T = unknown>(
|
||||
|
||||
// Active tab: broadcast messages to other tabs on change.
|
||||
// Only broadcast when THIS tab holds the claim (is the current sender).
|
||||
// Using !isReadOnly alone causes a feedback loop when both tabs are idle.
|
||||
// Deferred via requestIdleCallback so the structured clone in
|
||||
// BroadcastChannel.postMessage never blocks rendering during streaming.
|
||||
const idleRef = useRef<number | ReturnType<typeof setTimeout> | null>(null);
|
||||
const latestMessagesRef = useRef(messages);
|
||||
latestMessagesRef.current = messages;
|
||||
|
||||
useEffect(() => {
|
||||
if (transport.hasClaim(chatId) && messages.length > 0) {
|
||||
transport.broadcastMessages(chatId, messages as unknown[]);
|
||||
}
|
||||
if (!transport.hasClaim(chatId) || messages.length === 0) return;
|
||||
if (idleRef.current !== null) return; // Already scheduled
|
||||
|
||||
const schedule =
|
||||
typeof requestIdleCallback === "function"
|
||||
? requestIdleCallback
|
||||
: (fn: () => void) => setTimeout(fn, 50);
|
||||
|
||||
idleRef.current = schedule(() => {
|
||||
idleRef.current = null;
|
||||
if (transport.hasClaim(chatId)) {
|
||||
transport.broadcastMessages(chatId, latestMessagesRef.current as unknown[]);
|
||||
}
|
||||
});
|
||||
}, [transport, chatId, messages]);
|
||||
|
||||
// Flush final state when claim is released (turn complete)
|
||||
useEffect(() => {
|
||||
if (!transport.hasClaim(chatId) && latestMessagesRef.current.length > 0) {
|
||||
if (idleRef.current !== null) {
|
||||
const cancel =
|
||||
typeof cancelIdleCallback === "function"
|
||||
? cancelIdleCallback
|
||||
: clearTimeout;
|
||||
cancel(idleRef.current as any);
|
||||
idleRef.current = null;
|
||||
}
|
||||
transport.broadcastMessages(chatId, latestMessagesRef.current as unknown[]);
|
||||
}
|
||||
}, [transport, chatId, isReadOnly]);
|
||||
|
||||
// Read-only tab: receive messages from the active tab
|
||||
useEffect(() => {
|
||||
const listener = (id: string, msgs: unknown[]) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ function ToolInvocation({
|
||||
part: any;
|
||||
onApprove?: (approvalId: string) => void;
|
||||
onDeny?: (approvalId: string) => void;
|
||||
onToolOutput?: (toolCallId: string, output: unknown) => void;
|
||||
onToolOutput?: (tool: string, toolCallId: string, output: unknown) => void;
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const toolName = part.type.startsWith("tool-") ? part.type.slice(5) : "tool";
|
||||
@@ -87,7 +87,7 @@ function ToolInvocation({
|
||||
key={opt.id}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onToolOutput?.(part.toolCallId, {
|
||||
onToolOutput?.(toolName, part.toolCallId, {
|
||||
skipped: false,
|
||||
answers: [{ questionId: args.question, optionId: opt.id, text: opt.label }],
|
||||
})
|
||||
@@ -566,7 +566,7 @@ export function Chat({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{messages.map((message, messageIndex) => (
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
|
||||
@@ -580,17 +580,7 @@ export function Chat({
|
||||
{message.parts.map((part, i) => {
|
||||
if (part.type === "text") {
|
||||
if (message.role === "assistant") {
|
||||
return (
|
||||
<Streamdown
|
||||
key={i}
|
||||
animated
|
||||
isAnimating={
|
||||
status === "streaming" && messageIndex === messages.length - 1
|
||||
}
|
||||
>
|
||||
{part.text}
|
||||
</Streamdown>
|
||||
);
|
||||
return <Streamdown key={i}>{part.text}</Streamdown>;
|
||||
}
|
||||
return <span key={i}>{part.text}</span>;
|
||||
}
|
||||
@@ -652,8 +642,8 @@ export function Chat({
|
||||
part={part}
|
||||
onApprove={handleApprove}
|
||||
onDeny={handleDeny}
|
||||
onToolOutput={(toolCallId, output) =>
|
||||
addToolOutput({ toolCallId, output })
|
||||
onToolOutput={(tool, toolCallId, output) =>
|
||||
addToolOutput({ tool, toolCallId, output })
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user