UNPKG

@assistant-ui/react

Version:

Open-source TypeScript/React library for building production-grade AI chat experiences

73 lines (72 loc) • 2.48 kB
"use client"; import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim"; import { useAuiState } from "@assistant-ui/store"; import { useShallowSelector } from "@assistant-ui/store/internal"; // src/unstable/useMessageStallDetection.ts /** * Detects mid-run output stalls on the current message: while the message is * running, watches its text, reasoning, and tool-argument values plus tool-result * availability and reports a stall once they stop changing for `thresholdMs`. * Useful for re-surfacing a "still working" indicator during tool think-time or * provider stalls, after the first tokens have already streamed. * * Must be used inside a message scope. * * @deprecated Experimental since 2026-06-13. Not scheduled for removal; the API may change in any release. */ function unstable_useMessageStallDetection(options) { const thresholdMs = options?.thresholdMs ?? 2e3; const activity = useAuiState(useShallowSelector((s) => { if (!(s.message.status?.type === "running")) return [false]; const values = [true, s.message.content.length]; for (const part of s.message.content) if (part.type === "text" || part.type === "reasoning") values.push(part.type, part.text); else if (part.type === "tool-call") values.push(part.type, part.argsText, part.result !== void 0); else values.push(part.type); return values; })); const running = activity[0] === true; const lastActivityRef = useRef(0); const [stalled, setStalled] = useState(false); const [now, setNow] = useState(0); useEffect(() => { if (!running) return void 0; lastActivityRef.current = Date.now(); }, [running, activity]); useEffect(() => { if (!running) { setStalled(false); return; } const stall = () => { setNow(Date.now()); setStalled(true); }; const sinceActivity = Date.now() - lastActivityRef.current; if (sinceActivity >= thresholdMs) { stall(); return; } setStalled(false); const id = setTimeout(stall, thresholdMs - sinceActivity); return () => clearTimeout(id); }, [ running, activity, thresholdMs ]); useEffect(() => { if (!stalled) return void 0; const id = setInterval(() => setNow(Date.now()), 1e3); return () => clearInterval(id); }, [stalled]); if (!stalled) return { stalled: false, stalledForMs: 0 }; return { stalled: true, stalledForMs: Math.max(0, now - lastActivityRef.current) }; } export { unstable_useMessageStallDetection }; //# sourceMappingURL=useMessageStallDetection.js.map