@agents24/chat-react 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/{chunk-E3TJ3I6G.js → chunk-FFO6KEB4.js} +2 -1
- package/dist/chunk-FFO6KEB4.js.map +1 -0
- package/dist/index.cjs +40 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +40 -26
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +0 -1
- package/dist/templates/index.cjs +1 -0
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.js +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
- package/dist/chunk-E3TJ3I6G.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agents24 Chat React
|
|
2
2
|
|
|
3
|
-
Last Updated: 2026-07-
|
|
3
|
+
Last Updated: 2026-07-13
|
|
4
4
|
|
|
5
5
|
Headless React primitives for Agents24 chat surfaces.
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ The package owns thread hydration, older-page pagination, stream/reattach state,
|
|
|
18
18
|
- `getActiveStreamingTextPartId(message, streamingMessageId)`
|
|
19
19
|
- `isActiveStreamingTextPart(message, part, streamingMessageId)`
|
|
20
20
|
- `createFetchChatTransport(options)`
|
|
21
|
-
- `consumeSseResponse(response, onEvent)`
|
|
21
|
+
- `consumeSseResponse(response, onEvent, { signal })`
|
|
22
22
|
- `appendStableStreamingTurn(messages, userMessage, assistantMessage)`
|
|
23
23
|
- `upsertStableAssistantMessage(messages, input)`
|
|
24
24
|
- normalized chat message, part, transport, and storage types
|
|
@@ -37,10 +37,14 @@ Hosts can pass `onRuntimeEvent(event, context)` and `onThreadDetailLoaded(detail
|
|
|
37
37
|
|
|
38
38
|
Network access is always injected by the host. This browser package never requires platform API keys.
|
|
39
39
|
|
|
40
|
+
`streamMessage`, `attachRun`, and `subscribeThreadEvents` require owner-provided abort signals. `createFetchChatTransport` forwards each signal through connection setup and SSE consumption, and `useAgents24ChatController` aborts owned requests when it unmounts. Local abort detaches the client stream; use `cancelRun` when the durable backend run must also stop.
|
|
41
|
+
|
|
40
42
|
## Message Lifecycle
|
|
41
43
|
|
|
42
44
|
Chat surfaces should keep active assistant turns as stable rows in `messages`: append the user row and assistant placeholder together, stream updates into that assistant `messageId`, finalize that same row in place, then clear the streaming marker. `appendStableStreamingTurn` and `upsertStableAssistantMessage` centralize that invariant for custom hosts. Avoid host-side synthetic assistant rows because removing and re-adding the same response can flicker and can make MessageScroller treat the turn as new content.
|
|
43
45
|
|
|
46
|
+
The server owns assistant projection through `response_blocks` and `assistant_output_text`. Package version `0.2.0` removes the former public `textFromFinalOutput` helper; clients must not infer chat text from workflow `final_output`.
|
|
47
|
+
|
|
44
48
|
## MessageScroller Behavior
|
|
45
49
|
|
|
46
50
|
The package re-exports shadcn `@shadcn/react/message-scroller` and provides `LatestThreadScroller`, a thin native preset around the official primitives. Host chat shells should prefer:
|
|
@@ -216,6 +216,7 @@ function LatestThreadScrollerOutline({
|
|
|
216
216
|
);
|
|
217
217
|
};
|
|
218
218
|
updateSideRoom();
|
|
219
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
219
220
|
const resizeObserver = new ResizeObserver(updateSideRoom);
|
|
220
221
|
resizeObserver.observe(root);
|
|
221
222
|
const content = layout?.contentElement;
|
|
@@ -355,4 +356,4 @@ export {
|
|
|
355
356
|
LatestThreadScrollerOutline,
|
|
356
357
|
LatestThreadScroller
|
|
357
358
|
};
|
|
358
|
-
//# sourceMappingURL=chunk-
|
|
359
|
+
//# sourceMappingURL=chunk-FFO6KEB4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/latest-thread-scroller.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n MessageScroller,\n useMessageScroller,\n useMessageScrollerVisibility,\n} from \"@shadcn/react/message-scroller\";\n\nconst cx = (...values: Array<string | false | null | undefined>) =>\n values.filter(Boolean).join(\" \");\n\ntype MessageScrollerProviderProps = React.ComponentProps<\n typeof MessageScroller.Provider\n>;\ntype MessageScrollerRootProps = React.ComponentProps<typeof MessageScroller.Root>;\ntype MessageScrollerViewportProps = React.ComponentProps<\n typeof MessageScroller.Viewport\n>;\ntype MessageScrollerContentProps = React.ComponentProps<\n typeof MessageScroller.Content\n>;\ntype MessageScrollerItemProps = React.ComponentProps<typeof MessageScroller.Item>;\ntype MessageScrollerButtonProps = React.ComponentProps<typeof MessageScroller.Button>;\n\ntype LatestThreadScrollerLayoutContextValue = {\n contentElement: HTMLElement | null;\n rootElement: HTMLElement | null;\n setContentElement: (node: HTMLElement | null) => void;\n setRootElement: (node: HTMLElement | null) => void;\n};\n\nconst LatestThreadScrollerLayoutContext =\n React.createContext<LatestThreadScrollerLayoutContextValue | null>(null);\n\nconst useLatestThreadScrollerLayout = () =>\n React.useContext(LatestThreadScrollerLayoutContext);\n\nexport type LatestThreadScrollerProviderProps = MessageScrollerProviderProps;\n\nexport function LatestThreadScrollerProvider({\n autoScroll = true,\n defaultScrollPosition = \"last-anchor\",\n scrollPreviousItemPeek = 64,\n ...props\n}: LatestThreadScrollerProviderProps) {\n return (\n <MessageScroller.Provider\n autoScroll={autoScroll}\n defaultScrollPosition={defaultScrollPosition}\n scrollPreviousItemPeek={scrollPreviousItemPeek}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerRootProps = MessageScrollerRootProps;\n\nexport function LatestThreadScrollerRoot({\n className,\n ref,\n ...props\n}: LatestThreadScrollerRootProps) {\n const [rootElement, setRootElement] = React.useState<HTMLElement | null>(null);\n const [contentElement, setContentElement] =\n React.useState<HTMLElement | null>(null);\n const layoutContext = React.useMemo<LatestThreadScrollerLayoutContextValue>(\n () => ({\n contentElement,\n rootElement,\n setContentElement,\n setRootElement,\n }),\n [contentElement, rootElement]\n );\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n setRootElement(node);\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n return (\n <LatestThreadScrollerLayoutContext.Provider value={layoutContext}>\n <MessageScroller.Root\n ref={setRef}\n className={cx(\n \"relative flex size-full min-h-0 flex-1 flex-col overflow-hidden\",\n className\n )}\n {...props}\n />\n </LatestThreadScrollerLayoutContext.Provider>\n );\n}\n\nexport type LatestThreadScrollerViewportProps =\n MessageScrollerViewportProps & {\n onViewportChange?: (node: HTMLElement | null) => void;\n };\n\nexport function LatestThreadScrollerViewport({\n className,\n onViewportChange,\n preserveScrollOnPrepend = true,\n ref,\n ...props\n}: LatestThreadScrollerViewportProps) {\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n onViewportChange?.(node);\n },\n [onViewportChange, ref]\n );\n\n return (\n <MessageScroller.Viewport\n ref={setRef}\n className={cx(\n \"size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain\",\n className\n )}\n preserveScrollOnPrepend={preserveScrollOnPrepend}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerContentProps = MessageScrollerContentProps;\n\nexport function LatestThreadScrollerContent({\n className,\n ref,\n ...props\n}: LatestThreadScrollerContentProps) {\n const layout = useLatestThreadScrollerLayout();\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n layout?.setContentElement(node);\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [layout, ref]\n );\n\n return (\n <MessageScroller.Content\n ref={setRef}\n data-agents24-latest-thread-content=\"\"\n className={cx(\"flex h-max min-h-full flex-col gap-8 p-4\", className)}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerItemProps = Omit<\n MessageScrollerItemProps,\n \"messageId\"\n> & {\n messageId: string;\n};\n\nexport function LatestThreadScrollerItem({\n className,\n messageId,\n ...props\n}: LatestThreadScrollerItemProps) {\n return (\n <MessageScroller.Item\n className={cx(\n \"min-w-0 shrink-0 overflow-visible\",\n className\n )}\n messageId={messageId}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerButtonProps = Omit<\n MessageScrollerButtonProps,\n \"render\"\n> & {\n className?: string;\n render?: MessageScrollerButtonProps[\"render\"];\n};\n\nexport function LatestThreadScrollerButton({\n children,\n className,\n direction = \"end\",\n render,\n ...props\n}: LatestThreadScrollerButtonProps) {\n if (render) {\n return (\n <MessageScroller.Button\n direction={direction}\n render={render}\n {...props}\n />\n );\n }\n\n return (\n <MessageScroller.Button\n direction={direction}\n render={(buttonProps) => (\n <button\n {...(buttonProps as Record<string, unknown>)}\n aria-label={\n direction === \"end\"\n ? \"Scroll to latest message\"\n : \"Scroll to first message\"\n }\n className={cx(\n \"absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-all duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100\",\n className\n )}\n type=\"button\"\n >\n {children ?? (direction === \"end\" ? \"↓\" : \"↑\")}\n </button>\n )}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerOutlineItem = {\n id: string;\n label: string;\n messageIds?: string[];\n responsePreview?: string;\n};\n\nexport type LatestThreadScrollerOutlineProps = {\n cardClassName?: string;\n className?: string;\n contentMaxWidth?: number;\n dir?: \"ltr\" | \"rtl\";\n items: LatestThreadScrollerOutlineItem[];\n minContainerWidthForRail?: number;\n minSideGutterForRail?: number;\n side?: \"left\" | \"right\";\n};\n\nexport function LatestThreadScrollerOutline({\n cardClassName,\n className,\n contentMaxWidth = 768,\n dir = \"ltr\",\n items,\n minContainerWidthForRail = 0,\n minSideGutterForRail = 48,\n side,\n}: LatestThreadScrollerOutlineProps) {\n const navRef = React.useRef<HTMLElement | null>(null);\n const layout = useLatestThreadScrollerLayout();\n const [hasSideRoom, setHasSideRoom] = React.useState(false);\n const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null);\n const { scrollToMessage } = useMessageScroller();\n const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility();\n const visibleMessageIdSet = React.useMemo(\n () => new Set(visibleMessageIds),\n [visibleMessageIds]\n );\n const focusIndex = hoveredIndex ?? -1;\n const railSide = side ?? (dir === \"rtl\" ? \"right\" : \"left\");\n\n React.useEffect(() => {\n const root = layout?.rootElement ?? navRef.current?.parentElement;\n if (!root) return;\n\n const updateSideRoom = () => {\n const rootRect = root.getBoundingClientRect();\n const rootWidth = rootRect.width;\n const content = layout?.contentElement;\n const sideGutter = content\n ? Math.max(\n 0,\n railSide === \"left\"\n ? content.getBoundingClientRect().left - rootRect.left\n : rootRect.right - content.getBoundingClientRect().right\n )\n : Math.max(0, (rootWidth - contentMaxWidth) / 2);\n const nextHasSideRoom =\n rootWidth >= minContainerWidthForRail &&\n sideGutter >= minSideGutterForRail;\n setHasSideRoom((current) =>\n current === nextHasSideRoom ? current : nextHasSideRoom\n );\n };\n\n updateSideRoom();\n if (typeof ResizeObserver === \"undefined\") return;\n const resizeObserver = new ResizeObserver(updateSideRoom);\n resizeObserver.observe(root);\n const content = layout?.contentElement;\n if (content) {\n resizeObserver.observe(content);\n }\n return () => resizeObserver.disconnect();\n }, [\n contentMaxWidth,\n layout?.contentElement,\n layout?.rootElement,\n minContainerWidthForRail,\n minSideGutterForRail,\n railSide,\n ]);\n\n if (items.length < 2) {\n return null;\n }\n\n return (\n <nav\n ref={navRef}\n aria-label=\"Transcript outline\"\n className={cx(\n \"pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible\",\n hasSideRoom ? \"hidden md:block\" : \"hidden\",\n railSide === \"left\" ? \"left-1\" : \"right-1\",\n className\n )}\n dir={dir}\n >\n <div className=\"pointer-events-auto flex flex-col gap-0 py-1\">\n {items.map((item, index) => {\n const itemMessageIds =\n item.messageIds && item.messageIds.length > 0\n ? item.messageIds\n : [item.id];\n const active = itemMessageIds.some(\n (messageId) =>\n visibleMessageIdSet.has(messageId) || messageId === currentAnchorId\n );\n const distance =\n focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY;\n const isFocused = distance === 0;\n const isNear = distance === 1;\n const isNextNear = distance === 2;\n const barWidth = isFocused\n ? 50\n : isNear\n ? 36\n : isNextNear\n ? 23\n : 10;\n const barOpacity = isFocused ? 1 : isNear ? 0.78 : isNextNear ? 0.58 : 0.36;\n const fallbackLabel = `Turn ${index + 1}`;\n const previewLabel =\n item.label && item.label !== fallbackLabel ? item.label : \"\";\n const responsePreview = (item.responsePreview || \"\").trim();\n\n return (\n <button\n key={item.id}\n type=\"button\"\n data-active={active}\n dir=\"ltr\"\n className={cx(\n \"group relative flex h-2 w-16 items-center rounded-sm outline-none\",\n railSide === \"left\" ? \"justify-start\" : \"justify-end\"\n )}\n aria-label={`Jump to ${item.label || fallbackLabel}`}\n onBlur={() => setHoveredIndex(null)}\n onFocus={() => setHoveredIndex(index)}\n onMouseEnter={() => setHoveredIndex(index)}\n onMouseLeave={() => setHoveredIndex(null)}\n onClick={() =>\n scrollToMessage(item.id, {\n align: \"start\",\n behavior: \"smooth\",\n })\n }\n >\n <span\n className=\"h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground\"\n style={{\n opacity: barOpacity,\n width: barWidth,\n }}\n />\n {previewLabel ? (\n <span\n dir={dir}\n className={cx(\n \"pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100\",\n railSide === \"left\"\n ? \"left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0\"\n : \"right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0\",\n cardClassName\n )}\n >\n <span\n className=\"block overflow-hidden text-sm leading-5 text-foreground\"\n style={{\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: responsePreview ? 2 : 4,\n }}\n >\n {previewLabel}\n </span>\n {responsePreview ? (\n <span\n className=\"mt-1 block overflow-hidden text-sm leading-5 text-muted-foreground\"\n style={{\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: 2,\n }}\n >\n {responsePreview}\n </span>\n ) : null}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n </nav>\n );\n}\n\nexport const LatestThreadScroller = {\n Provider: LatestThreadScrollerProvider,\n Root: LatestThreadScrollerRoot,\n Viewport: LatestThreadScrollerViewport,\n Content: LatestThreadScrollerContent,\n Item: LatestThreadScrollerItem,\n Button: LatestThreadScrollerButton,\n Outline: LatestThreadScrollerOutline,\n};\n"],"mappings":"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","names":["content"]}
|
package/dist/index.cjs
CHANGED
|
@@ -71,7 +71,6 @@ __export(index_exports, {
|
|
|
71
71
|
partsFromResponseBlocks: () => partsFromResponseBlocks,
|
|
72
72
|
reasoningStepsFromParts: () => reasoningStepsFromParts,
|
|
73
73
|
renderChatPart: () => renderChatPart,
|
|
74
|
-
textFromFinalOutput: () => textFromFinalOutput,
|
|
75
74
|
threadActivityDate: () => threadActivityDate,
|
|
76
75
|
threadDetailToMessages: () => threadDetailToMessages,
|
|
77
76
|
threadPaging: () => threadPaging,
|
|
@@ -295,12 +294,6 @@ var titleFromMessage = (text, files = []) => {
|
|
|
295
294
|
var threadActivityDate = (thread) => String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString());
|
|
296
295
|
var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
|
|
297
296
|
var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
|
|
298
|
-
var textFromFinalOutput = (value) => {
|
|
299
|
-
if (typeof value === "string") return value;
|
|
300
|
-
if (!value || typeof value !== "object") return "";
|
|
301
|
-
const record = value;
|
|
302
|
-
return String(record.message || record.text || record.answer || "");
|
|
303
|
-
};
|
|
304
297
|
var displayTextWithoutInlineAttachments = (text, hasAttachments) => {
|
|
305
298
|
if (!hasAttachments) return text;
|
|
306
299
|
const marker = "Attached text file (";
|
|
@@ -347,7 +340,7 @@ var responseBlocksFromTurn = (turn) => {
|
|
|
347
340
|
var assistantTextFromEvents = (events) => {
|
|
348
341
|
const assistantText = latestEventPayloadValue(events, "assistant_output_text");
|
|
349
342
|
if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
|
|
350
|
-
return
|
|
343
|
+
return "";
|
|
351
344
|
};
|
|
352
345
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
353
346
|
var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -531,7 +524,7 @@ var turnToMessages = (turn, activeRunId) => {
|
|
|
531
524
|
attachments
|
|
532
525
|
});
|
|
533
526
|
}
|
|
534
|
-
const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events)
|
|
527
|
+
const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events);
|
|
535
528
|
if (assistantText || responseBlocks.length > 0 || isRunning) {
|
|
536
529
|
const parts = partsFromResponseBlocks(responseBlocks, assistantText);
|
|
537
530
|
messages.push({
|
|
@@ -779,6 +772,7 @@ function useAgents24ChatController({
|
|
|
779
772
|
const activeRunIdRef = (0, import_react2.useRef)(null);
|
|
780
773
|
const reattachedRunIdRef = (0, import_react2.useRef)(null);
|
|
781
774
|
const abortControllerRef = (0, import_react2.useRef)(null);
|
|
775
|
+
const [lifecycleAbortController] = (0, import_react2.useState)(() => new AbortController());
|
|
782
776
|
const streamingContentRef = (0, import_react2.useRef)("");
|
|
783
777
|
const streamingMessageIdRef = (0, import_react2.useRef)(null);
|
|
784
778
|
const reasoningRef = (0, import_react2.useRef)([]);
|
|
@@ -853,15 +847,16 @@ function useAgents24ChatController({
|
|
|
853
847
|
const seq = ++refreshSeqRef.current;
|
|
854
848
|
setIsRefreshingThreads(true);
|
|
855
849
|
try {
|
|
856
|
-
const data = await transport.listThreads();
|
|
850
|
+
const data = await transport.listThreads({ signal: lifecycleAbortController.signal });
|
|
851
|
+
if (lifecycleAbortController.signal.aborted) return;
|
|
857
852
|
if (seq !== refreshSeqRef.current) return;
|
|
858
853
|
const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
|
|
859
854
|
storage.setThreads(mergeStoredThreadsForRefresh(storage.listThreads(), nextThreads));
|
|
860
855
|
setThreads(storage.listThreads());
|
|
861
856
|
} finally {
|
|
862
|
-
if (seq === refreshSeqRef.current) setIsRefreshingThreads(false);
|
|
857
|
+
if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
|
|
863
858
|
}
|
|
864
|
-
}, [storage, transport]);
|
|
859
|
+
}, [lifecycleAbortController, storage, transport]);
|
|
865
860
|
const applyThreadId = (0, import_react2.useCallback)(
|
|
866
861
|
(threadId, baseMessages) => {
|
|
867
862
|
if (!threadId || activeThreadIdRef.current === threadId) return;
|
|
@@ -1016,7 +1011,8 @@ function useAgents24ChatController({
|
|
|
1016
1011
|
const detail = await transport.getThread({
|
|
1017
1012
|
threadId,
|
|
1018
1013
|
limit: pageSize,
|
|
1019
|
-
includeRunEvents: false
|
|
1014
|
+
includeRunEvents: false,
|
|
1015
|
+
signal: lifecycleAbortController.signal
|
|
1020
1016
|
});
|
|
1021
1017
|
if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
|
|
1022
1018
|
void onThreadDetailLoaded?.(detail);
|
|
@@ -1045,10 +1041,10 @@ function useAgents24ChatController({
|
|
|
1045
1041
|
}
|
|
1046
1042
|
throw error;
|
|
1047
1043
|
} finally {
|
|
1048
|
-
if (activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
|
|
1044
|
+
if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
|
|
1049
1045
|
}
|
|
1050
1046
|
},
|
|
1051
|
-
[clearMissingThread, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
1047
|
+
[clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
1052
1048
|
);
|
|
1053
1049
|
const loadOlderTurns = (0, import_react2.useCallback)(async () => {
|
|
1054
1050
|
const threadId = activeThreadIdRef.current;
|
|
@@ -1061,7 +1057,8 @@ function useAgents24ChatController({
|
|
|
1061
1057
|
threadId,
|
|
1062
1058
|
limit: pageSize,
|
|
1063
1059
|
beforeTurnIndex,
|
|
1064
|
-
includeRunEvents: false
|
|
1060
|
+
includeRunEvents: false,
|
|
1061
|
+
signal: lifecycleAbortController.signal
|
|
1065
1062
|
});
|
|
1066
1063
|
if (activeThreadIdRef.current !== threadId) return;
|
|
1067
1064
|
const older = threadDetailToMessages(detail);
|
|
@@ -1081,10 +1078,10 @@ function useAgents24ChatController({
|
|
|
1081
1078
|
}
|
|
1082
1079
|
throw error;
|
|
1083
1080
|
} finally {
|
|
1084
|
-
if (activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
|
|
1081
|
+
if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
|
|
1085
1082
|
isLoadingOlderRef.current = false;
|
|
1086
1083
|
}
|
|
1087
|
-
}, [clearMissingThread, pageSize, persistThread, transport]);
|
|
1084
|
+
}, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
|
|
1088
1085
|
const handleStreamEvent = (0, import_react2.useCallback)(
|
|
1089
1086
|
(input) => {
|
|
1090
1087
|
const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
|
|
@@ -1131,7 +1128,7 @@ function useAgents24ChatController({
|
|
|
1131
1128
|
streamThreadIdRef.current = terminalThreadId;
|
|
1132
1129
|
applyThreadId(terminalThreadId, baseMessages);
|
|
1133
1130
|
}
|
|
1134
|
-
const finalText = String(payload.assistant_output_text ||
|
|
1131
|
+
const finalText = String(payload.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks || []) || streamingContentRef.current || "");
|
|
1135
1132
|
finalizeAssistantMessage({
|
|
1136
1133
|
threadId: streamThreadIdRef.current || activeThreadIdRef.current,
|
|
1137
1134
|
baseMessages: messagesRef.current,
|
|
@@ -1305,8 +1302,19 @@ function useAgents24ChatController({
|
|
|
1305
1302
|
}
|
|
1306
1303
|
}, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
|
|
1307
1304
|
(0, import_react2.useEffect)(() => {
|
|
1308
|
-
refresh().catch(() =>
|
|
1309
|
-
|
|
1305
|
+
refresh().catch((error) => {
|
|
1306
|
+
if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
|
|
1307
|
+
setThreads(storage.listThreads());
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
}, [lifecycleAbortController, refresh, storage]);
|
|
1311
|
+
(0, import_react2.useEffect)(() => {
|
|
1312
|
+
return () => {
|
|
1313
|
+
lifecycleAbortController.abort();
|
|
1314
|
+
abortDetachedStream(abortControllerRef.current);
|
|
1315
|
+
abortControllerRef.current = null;
|
|
1316
|
+
};
|
|
1317
|
+
}, [lifecycleAbortController]);
|
|
1310
1318
|
(0, import_react2.useEffect)(() => {
|
|
1311
1319
|
if (!transport.subscribeThreadEvents) return;
|
|
1312
1320
|
let cancelled = false;
|
|
@@ -1737,6 +1745,7 @@ function LatestThreadScrollerOutline({
|
|
|
1737
1745
|
);
|
|
1738
1746
|
};
|
|
1739
1747
|
updateSideRoom();
|
|
1748
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1740
1749
|
const resizeObserver = new ResizeObserver(updateSideRoom);
|
|
1741
1750
|
resizeObserver.observe(root);
|
|
1742
1751
|
const content = layout?.contentElement;
|
|
@@ -1946,7 +1955,9 @@ var consumeSseResponse = async (response, onEvent, options = {}) => {
|
|
|
1946
1955
|
let closed = false;
|
|
1947
1956
|
const closeReader = () => {
|
|
1948
1957
|
closed = true;
|
|
1949
|
-
|
|
1958
|
+
queueMicrotask(() => {
|
|
1959
|
+
void reader.cancel().catch(() => {
|
|
1960
|
+
});
|
|
1950
1961
|
});
|
|
1951
1962
|
};
|
|
1952
1963
|
if (options.signal?.aborted) {
|
|
@@ -2170,7 +2181,7 @@ var createFetchChatTransport = ({
|
|
|
2170
2181
|
}
|
|
2171
2182
|
)
|
|
2172
2183
|
});
|
|
2173
|
-
return consumeSseResponse(response, onEvent);
|
|
2184
|
+
return consumeSseResponse(response, onEvent, { signal: input.signal });
|
|
2174
2185
|
},
|
|
2175
2186
|
async attachRun(input, onEvent) {
|
|
2176
2187
|
const response = await fetchImpl(routes.attachRun(input), {
|
|
@@ -2179,7 +2190,7 @@ var createFetchChatTransport = ({
|
|
|
2179
2190
|
headers: streamHeaders(await loadHeaders()),
|
|
2180
2191
|
body: JSON.stringify({})
|
|
2181
2192
|
});
|
|
2182
|
-
return consumeSseResponse(response, onEvent);
|
|
2193
|
+
return consumeSseResponse(response, onEvent, { signal: input.signal });
|
|
2183
2194
|
},
|
|
2184
2195
|
async cancelRun(input) {
|
|
2185
2196
|
const response = await fetchImpl(routes.cancelRun(input), {
|
|
@@ -2206,7 +2217,11 @@ var createFetchChatTransport = ({
|
|
|
2206
2217
|
signal: input.signal,
|
|
2207
2218
|
headers: streamHeaders(await loadHeaders())
|
|
2208
2219
|
});
|
|
2209
|
-
await consumeSseResponse(
|
|
2220
|
+
await consumeSseResponse(
|
|
2221
|
+
response,
|
|
2222
|
+
(event) => onEvent(event),
|
|
2223
|
+
{ signal: input.signal }
|
|
2224
|
+
);
|
|
2210
2225
|
},
|
|
2211
2226
|
async resumeHitl(input) {
|
|
2212
2227
|
if (!routes.resumeRun) throw new ChatTransportError("HITL resume is not configured.", 501);
|
|
@@ -2286,7 +2301,6 @@ var import_message_scroller2 = require("@shadcn/react/message-scroller");
|
|
|
2286
2301
|
partsFromResponseBlocks,
|
|
2287
2302
|
reasoningStepsFromParts,
|
|
2288
2303
|
renderChatPart,
|
|
2289
|
-
textFromFinalOutput,
|
|
2290
2304
|
threadActivityDate,
|
|
2291
2305
|
threadDetailToMessages,
|
|
2292
2306
|
threadPaging,
|