@agents24/chat-react 0.1.3 → 0.1.5
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 +18 -1
- package/dist/index.cjs +238 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.js +229 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -349,6 +349,30 @@ declare const renderChatPart: (part: ChatPart, message: ChatMessage, renderOptio
|
|
|
349
349
|
declare const parseSseBlock: (block: string) => ChatRuntimeEvent | null;
|
|
350
350
|
declare const consumeSseResponse: (response: Response, onEvent: (event: ChatRuntimeEvent) => void | Promise<void>) => Promise<ChatStreamResult>;
|
|
351
351
|
|
|
352
|
+
type StreamingTextMode = "streaming" | "static";
|
|
353
|
+
type StreamingTextCache = {
|
|
354
|
+
get: (id: string) => string | undefined;
|
|
355
|
+
set: (id: string, text: string) => void;
|
|
356
|
+
};
|
|
357
|
+
type UseStreamingTextOptions = {
|
|
358
|
+
id: string;
|
|
359
|
+
isStreaming: boolean;
|
|
360
|
+
text: string;
|
|
361
|
+
cache?: StreamingTextCache | false;
|
|
362
|
+
charsPerSecond?: number;
|
|
363
|
+
catchupThreshold?: number;
|
|
364
|
+
maxCatchupChars?: number;
|
|
365
|
+
};
|
|
366
|
+
type UseStreamingTextResult = {
|
|
367
|
+
displayedText: string;
|
|
368
|
+
isAnimating: boolean;
|
|
369
|
+
mode: StreamingTextMode;
|
|
370
|
+
parseIncompleteMarkdown: boolean;
|
|
371
|
+
};
|
|
372
|
+
declare const getActiveStreamingTextPartId: (message: ChatMessage, streamingMessageId: string | null) => string | null;
|
|
373
|
+
declare const isActiveStreamingTextPart: (message: ChatMessage, part: ChatPart, streamingMessageId: string | null) => boolean;
|
|
374
|
+
declare function useStreamingText({ id, isStreaming, text, cache, charsPerSecond, catchupThreshold, maxCatchupChars, }: UseStreamingTextOptions): UseStreamingTextResult;
|
|
375
|
+
|
|
352
376
|
type MaybePromise<T> = T | Promise<T>;
|
|
353
377
|
type FetchChatTransportRoutes = {
|
|
354
378
|
listThreads: () => string;
|
|
@@ -381,6 +405,7 @@ type FetchChatTransportOptions = {
|
|
|
381
405
|
};
|
|
382
406
|
declare const createFetchChatTransport: ({ routes, fetchImpl, headers, streamBody, }: FetchChatTransportOptions) => ChatTransport;
|
|
383
407
|
|
|
408
|
+
type LatestFollowState = "following" | "detached";
|
|
384
409
|
declare const isScrollable: (element: Pick<HTMLDivElement, "scrollHeight" | "clientHeight">) => boolean;
|
|
385
410
|
declare const isAtTimelineLatestEdge: (element: Pick<HTMLDivElement, "scrollHeight" | "clientHeight" | "scrollTop">, isTopOrigin: boolean) => boolean;
|
|
386
411
|
declare const shouldPrefetchOlder: (element: Pick<HTMLDivElement, "scrollHeight" | "clientHeight" | "scrollTop">) => boolean;
|
|
@@ -390,6 +415,11 @@ declare const shouldUseTopOriginTimeline: ({ hasOlder, itemCount, topOriginMaxIt
|
|
|
390
415
|
itemCount: number;
|
|
391
416
|
topOriginMaxItems?: number;
|
|
392
417
|
}) => boolean;
|
|
418
|
+
declare const nextLatestFollowStateOnScroll: ({ atLatest, current, isProgrammatic, }: {
|
|
419
|
+
atLatest: boolean;
|
|
420
|
+
current: LatestFollowState;
|
|
421
|
+
isProgrammatic: boolean;
|
|
422
|
+
}) => LatestFollowState;
|
|
393
423
|
type UseLatestThreadViewportOptions = {
|
|
394
424
|
itemCount: number;
|
|
395
425
|
hasOlder: boolean;
|
|
@@ -402,15 +432,22 @@ type UseLatestThreadViewportOptions = {
|
|
|
402
432
|
type LatestThreadViewportState = {
|
|
403
433
|
scrollContainerRef: RefObject<HTMLDivElement | null>;
|
|
404
434
|
isAtLatest: boolean;
|
|
435
|
+
isFollowingLatest: boolean;
|
|
405
436
|
isTopOrigin: boolean;
|
|
406
437
|
shouldShowLatestButton: boolean;
|
|
407
438
|
handleScroll: (event: UIEvent<HTMLDivElement>) => void;
|
|
408
|
-
|
|
439
|
+
handleUserScrollIntent: () => void;
|
|
440
|
+
scrollToLatest: (options?: {
|
|
441
|
+
reattach?: boolean;
|
|
442
|
+
}) => void;
|
|
443
|
+
detachFromLatest: () => void;
|
|
444
|
+
reattachToLatest: () => void;
|
|
409
445
|
preserveIntrinsicResize: () => void;
|
|
410
446
|
};
|
|
411
447
|
declare function useLatestThreadViewport({ itemCount, hasOlder, isLoadingOlder, onLoadOlder, activeStreamKey, shouldAutoFollow, topOriginMaxItems, }: UseLatestThreadViewportOptions): LatestThreadViewportState;
|
|
412
448
|
type LatestThreadViewportRenderContext = {
|
|
413
449
|
preserveIntrinsicResize: () => void;
|
|
450
|
+
isFollowingLatest: boolean;
|
|
414
451
|
isTopOrigin: boolean;
|
|
415
452
|
};
|
|
416
453
|
type LatestThreadViewportProps<T> = {
|
|
@@ -426,4 +463,4 @@ type LatestThreadViewportProps<T> = {
|
|
|
426
463
|
};
|
|
427
464
|
declare function LatestThreadViewport<T>({ items, hasOlder, isLoadingOlder, onLoadOlder, activeStreamKey, className, latestSpacer, trailingSpacer, renderItem, }: LatestThreadViewportProps<T>): react.JSX.Element;
|
|
428
465
|
|
|
429
|
-
export { type Agents24ChatRenderOptions, type ChatActiveRun, type ChatApprovalPart, type ChatAttachment, type ChatCitation, type ChatContextWindow, type ChatController, type ChatDataPart, type ChatErrorPart, type ChatMessage, type ChatPart, type ChatReasoningPart, type ChatReasoningStep, type ChatRunStatus, type ChatRuntimeEvent, type ChatStorageAdapter, type ChatStreamResult, type ChatTextPart, type ChatThreadDetail, type ChatThreadSummary, type ChatThreadTurn, type ChatToolPart, type ChatToolState, type ChatTransport, type ChatUiBlocksPart, DEFAULT_THREAD_PAGE_SIZE, DefaultChatPart, DefaultToolPart, type FetchChatTransportOptions, type FetchChatTransportRoutes, LatestThreadViewport, type LatestThreadViewportProps, type LatestThreadViewportRenderContext, type LatestThreadViewportState, type StoredChatThread, type ToolPresentation, type ToolRenderer, type UseAgents24ChatControllerOptions, type UseLatestThreadViewportOptions, activeRunIdFromThread, activeRunIdFromThreadDetail, assistantTextFromParts, assistantTextFromResponseBlocks, consumeSseResponse, createChatId, createFetchChatTransport, getLatestScrollTop, hasStaleUnfinishedAssistantCache, hasUnfinishedAssistantMessage, isAtTimelineLatestEdge, isRunningThreadStatus, isScrollable, latestContextWindowFromThread, mergeReasoningSteps, parseSseBlock, partsFromResponseBlocks, reasoningStepsFromParts, renderChatPart, shouldPrefetchOlder, shouldUseTopOriginTimeline, textFromFinalOutput, threadActivityDate, threadDetailToMessages, threadPaging, titleFromMessage, toolStateFromStatus, useAgents24ChatController, useLatestThreadViewport };
|
|
466
|
+
export { type Agents24ChatRenderOptions, type ChatActiveRun, type ChatApprovalPart, type ChatAttachment, type ChatCitation, type ChatContextWindow, type ChatController, type ChatDataPart, type ChatErrorPart, type ChatMessage, type ChatPart, type ChatReasoningPart, type ChatReasoningStep, type ChatRunStatus, type ChatRuntimeEvent, type ChatStorageAdapter, type ChatStreamResult, type ChatTextPart, type ChatThreadDetail, type ChatThreadSummary, type ChatThreadTurn, type ChatToolPart, type ChatToolState, type ChatTransport, type ChatUiBlocksPart, DEFAULT_THREAD_PAGE_SIZE, DefaultChatPart, DefaultToolPart, type FetchChatTransportOptions, type FetchChatTransportRoutes, type LatestFollowState, LatestThreadViewport, type LatestThreadViewportProps, type LatestThreadViewportRenderContext, type LatestThreadViewportState, type StoredChatThread, type StreamingTextCache, type StreamingTextMode, type ToolPresentation, type ToolRenderer, type UseAgents24ChatControllerOptions, type UseLatestThreadViewportOptions, type UseStreamingTextOptions, type UseStreamingTextResult, activeRunIdFromThread, activeRunIdFromThreadDetail, assistantTextFromParts, assistantTextFromResponseBlocks, consumeSseResponse, createChatId, createFetchChatTransport, getActiveStreamingTextPartId, getLatestScrollTop, hasStaleUnfinishedAssistantCache, hasUnfinishedAssistantMessage, isActiveStreamingTextPart, isAtTimelineLatestEdge, isRunningThreadStatus, isScrollable, latestContextWindowFromThread, mergeReasoningSteps, nextLatestFollowStateOnScroll, parseSseBlock, partsFromResponseBlocks, reasoningStepsFromParts, renderChatPart, shouldPrefetchOlder, shouldUseTopOriginTimeline, textFromFinalOutput, threadActivityDate, threadDetailToMessages, threadPaging, titleFromMessage, toolStateFromStatus, useAgents24ChatController, useLatestThreadViewport, useStreamingText };
|
package/dist/index.js
CHANGED
|
@@ -1035,6 +1035,140 @@ var consumeSseResponse = async (response, onEvent) => {
|
|
|
1035
1035
|
return { threadId, runId };
|
|
1036
1036
|
};
|
|
1037
1037
|
|
|
1038
|
+
// src/streaming-text.ts
|
|
1039
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
1040
|
+
var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
|
|
1041
|
+
var defaultCompletedTextCache = /* @__PURE__ */ new Map();
|
|
1042
|
+
var defaultStreamingTextCache = {
|
|
1043
|
+
get: (id) => defaultCompletedTextCache.get(id),
|
|
1044
|
+
set: (id, text) => {
|
|
1045
|
+
defaultCompletedTextCache.set(id, text);
|
|
1046
|
+
if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
|
|
1047
|
+
const firstKey = defaultCompletedTextCache.keys().next().value;
|
|
1048
|
+
if (firstKey) defaultCompletedTextCache.delete(firstKey);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
var getActiveStreamingTextPartId = (message, streamingMessageId) => {
|
|
1053
|
+
if (message.role !== "assistant" || streamingMessageId !== message.id) return null;
|
|
1054
|
+
return message.parts.slice().reverse().find((part) => part.kind === "text")?.id || null;
|
|
1055
|
+
};
|
|
1056
|
+
var isActiveStreamingTextPart = (message, part, streamingMessageId) => part.kind === "text" && part.id === getActiveStreamingTextPartId(message, streamingMessageId);
|
|
1057
|
+
function useStreamingText({
|
|
1058
|
+
id,
|
|
1059
|
+
isStreaming,
|
|
1060
|
+
text,
|
|
1061
|
+
cache = defaultStreamingTextCache,
|
|
1062
|
+
charsPerSecond = 72,
|
|
1063
|
+
catchupThreshold = 40,
|
|
1064
|
+
maxCatchupChars = 20
|
|
1065
|
+
}) {
|
|
1066
|
+
const cacheAdapter = cache === false ? null : cache;
|
|
1067
|
+
const [displayedText, setDisplayedText] = useState2(() => {
|
|
1068
|
+
const cachedText = cacheAdapter?.get(id);
|
|
1069
|
+
return isStreaming && cachedText !== text ? "" : text;
|
|
1070
|
+
});
|
|
1071
|
+
const targetRef = useRef2(text);
|
|
1072
|
+
const displayedRef = useRef2(displayedText);
|
|
1073
|
+
const rafRef = useRef2(null);
|
|
1074
|
+
const lastFrameAtRef = useRef2(null);
|
|
1075
|
+
const idRef = useRef2(id);
|
|
1076
|
+
const shouldAnimateRef = useRef2(isStreaming);
|
|
1077
|
+
useEffect2(() => {
|
|
1078
|
+
if (isStreaming || !text) return;
|
|
1079
|
+
cacheAdapter?.set(id, text);
|
|
1080
|
+
}, [cacheAdapter, id, isStreaming, text]);
|
|
1081
|
+
useEffect2(() => {
|
|
1082
|
+
targetRef.current = text;
|
|
1083
|
+
}, [text]);
|
|
1084
|
+
useEffect2(() => {
|
|
1085
|
+
if (idRef.current === id) return;
|
|
1086
|
+
idRef.current = id;
|
|
1087
|
+
const cachedText = cacheAdapter?.get(id);
|
|
1088
|
+
const initial = isStreaming && cachedText !== text ? "" : text;
|
|
1089
|
+
shouldAnimateRef.current = isStreaming && initial !== text;
|
|
1090
|
+
displayedRef.current = initial;
|
|
1091
|
+
setDisplayedText(initial);
|
|
1092
|
+
if (rafRef.current !== null) {
|
|
1093
|
+
cancelAnimationFrame(rafRef.current);
|
|
1094
|
+
rafRef.current = null;
|
|
1095
|
+
}
|
|
1096
|
+
lastFrameAtRef.current = null;
|
|
1097
|
+
}, [cacheAdapter, id, isStreaming, text]);
|
|
1098
|
+
useEffect2(() => {
|
|
1099
|
+
if (typeof window === "undefined") {
|
|
1100
|
+
displayedRef.current = text;
|
|
1101
|
+
setDisplayedText(text);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1105
|
+
if (reduceMotion) {
|
|
1106
|
+
displayedRef.current = text;
|
|
1107
|
+
setDisplayedText(text);
|
|
1108
|
+
shouldAnimateRef.current = false;
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const cachedText = cacheAdapter?.get(id);
|
|
1112
|
+
if (cachedText === text) {
|
|
1113
|
+
displayedRef.current = text;
|
|
1114
|
+
setDisplayedText(text);
|
|
1115
|
+
shouldAnimateRef.current = false;
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (isStreaming) {
|
|
1119
|
+
shouldAnimateRef.current = true;
|
|
1120
|
+
}
|
|
1121
|
+
if (!shouldAnimateRef.current) {
|
|
1122
|
+
displayedRef.current = text;
|
|
1123
|
+
setDisplayedText(text);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (!text.startsWith(displayedRef.current)) {
|
|
1127
|
+
displayedRef.current = "";
|
|
1128
|
+
setDisplayedText("");
|
|
1129
|
+
}
|
|
1130
|
+
const tick = (timestamp) => {
|
|
1131
|
+
const previousTimestamp = lastFrameAtRef.current ?? timestamp;
|
|
1132
|
+
lastFrameAtRef.current = timestamp;
|
|
1133
|
+
const target = targetRef.current;
|
|
1134
|
+
const current = displayedRef.current;
|
|
1135
|
+
if (current.length >= target.length) {
|
|
1136
|
+
shouldAnimateRef.current = false;
|
|
1137
|
+
rafRef.current = null;
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const elapsedMs = Math.max(8, timestamp - previousTimestamp);
|
|
1141
|
+
const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
|
|
1142
|
+
const gap = target.length - current.length;
|
|
1143
|
+
const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
|
|
1144
|
+
const nextLength = Math.min(
|
|
1145
|
+
target.length,
|
|
1146
|
+
current.length + Math.max(charsFromTime, catchupStep)
|
|
1147
|
+
);
|
|
1148
|
+
const next = target.slice(0, nextLength);
|
|
1149
|
+
displayedRef.current = next;
|
|
1150
|
+
setDisplayedText(next);
|
|
1151
|
+
rafRef.current = window.requestAnimationFrame(tick);
|
|
1152
|
+
};
|
|
1153
|
+
if (rafRef.current === null && displayedRef.current.length < text.length) {
|
|
1154
|
+
rafRef.current = window.requestAnimationFrame(tick);
|
|
1155
|
+
}
|
|
1156
|
+
return () => {
|
|
1157
|
+
if (rafRef.current !== null) {
|
|
1158
|
+
cancelAnimationFrame(rafRef.current);
|
|
1159
|
+
rafRef.current = null;
|
|
1160
|
+
}
|
|
1161
|
+
lastFrameAtRef.current = null;
|
|
1162
|
+
};
|
|
1163
|
+
}, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
|
|
1164
|
+
return {
|
|
1165
|
+
displayedText,
|
|
1166
|
+
isAnimating: isStreaming,
|
|
1167
|
+
mode: isStreaming ? "streaming" : "static",
|
|
1168
|
+
parseIncompleteMarkdown: isStreaming
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1038
1172
|
// src/transport.ts
|
|
1039
1173
|
var jsonHeaders = (headers) => ({
|
|
1040
1174
|
...headers || {},
|
|
@@ -1118,11 +1252,11 @@ var createFetchChatTransport = ({
|
|
|
1118
1252
|
// src/viewport.tsx
|
|
1119
1253
|
import {
|
|
1120
1254
|
useCallback as useCallback2,
|
|
1121
|
-
useEffect as
|
|
1255
|
+
useEffect as useEffect3,
|
|
1122
1256
|
useLayoutEffect,
|
|
1123
1257
|
useMemo as useMemo2,
|
|
1124
|
-
useRef as
|
|
1125
|
-
useState as
|
|
1258
|
+
useRef as useRef3,
|
|
1259
|
+
useState as useState3
|
|
1126
1260
|
} from "react";
|
|
1127
1261
|
import { jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1128
1262
|
var LATEST_EDGE_THRESHOLD_PX = 2;
|
|
@@ -1140,6 +1274,14 @@ var shouldUseTopOriginTimeline = ({
|
|
|
1140
1274
|
itemCount,
|
|
1141
1275
|
topOriginMaxItems = 4
|
|
1142
1276
|
}) => !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
|
|
1277
|
+
var nextLatestFollowStateOnScroll = ({
|
|
1278
|
+
atLatest,
|
|
1279
|
+
current,
|
|
1280
|
+
isProgrammatic
|
|
1281
|
+
}) => {
|
|
1282
|
+
if (isProgrammatic) return current;
|
|
1283
|
+
return atLatest ? "following" : "detached";
|
|
1284
|
+
};
|
|
1143
1285
|
function useLatestThreadViewport({
|
|
1144
1286
|
itemCount,
|
|
1145
1287
|
hasOlder,
|
|
@@ -1149,16 +1291,23 @@ function useLatestThreadViewport({
|
|
|
1149
1291
|
shouldAutoFollow = true,
|
|
1150
1292
|
topOriginMaxItems = 4
|
|
1151
1293
|
}) {
|
|
1152
|
-
const scrollContainerRef =
|
|
1153
|
-
const olderPagePreserveRef =
|
|
1154
|
-
const olderPageRequestInFlightRef =
|
|
1155
|
-
const intrinsicResizePreserveRef =
|
|
1156
|
-
const
|
|
1157
|
-
const programmaticScrollRef =
|
|
1158
|
-
const programmaticScrollTimeoutRef =
|
|
1159
|
-
const activeStreamKeyRef =
|
|
1160
|
-
const
|
|
1294
|
+
const scrollContainerRef = useRef3(null);
|
|
1295
|
+
const olderPagePreserveRef = useRef3(null);
|
|
1296
|
+
const olderPageRequestInFlightRef = useRef3(false);
|
|
1297
|
+
const intrinsicResizePreserveRef = useRef3(null);
|
|
1298
|
+
const followStateRef = useRef3("following");
|
|
1299
|
+
const programmaticScrollRef = useRef3(false);
|
|
1300
|
+
const programmaticScrollTimeoutRef = useRef3(null);
|
|
1301
|
+
const activeStreamKeyRef = useRef3(null);
|
|
1302
|
+
const observedScrollHeightRef = useRef3(null);
|
|
1303
|
+
const [isAtLatest, setIsAtLatest] = useState3(true);
|
|
1304
|
+
const [followState, setFollowState] = useState3("following");
|
|
1161
1305
|
const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
|
|
1306
|
+
const setLatestFollowState = useCallback2((next) => {
|
|
1307
|
+
if (followStateRef.current === next) return;
|
|
1308
|
+
followStateRef.current = next;
|
|
1309
|
+
setFollowState(next);
|
|
1310
|
+
}, []);
|
|
1162
1311
|
const markProgrammaticScroll = useCallback2(() => {
|
|
1163
1312
|
programmaticScrollRef.current = true;
|
|
1164
1313
|
if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
|
|
@@ -1167,14 +1316,27 @@ function useLatestThreadViewport({
|
|
|
1167
1316
|
programmaticScrollTimeoutRef.current = null;
|
|
1168
1317
|
}, 80);
|
|
1169
1318
|
}, []);
|
|
1170
|
-
const scrollToLatest = useCallback2(() => {
|
|
1319
|
+
const scrollToLatest = useCallback2((options) => {
|
|
1171
1320
|
const element = scrollContainerRef.current;
|
|
1172
1321
|
if (!element) return;
|
|
1322
|
+
if (options?.reattach !== false) setLatestFollowState("following");
|
|
1173
1323
|
markProgrammaticScroll();
|
|
1174
1324
|
element.scrollTop = getLatestScrollTop(element, isTopOrigin);
|
|
1175
1325
|
setIsAtLatest(true);
|
|
1176
|
-
|
|
1177
|
-
|
|
1326
|
+
observedScrollHeightRef.current = element.scrollHeight;
|
|
1327
|
+
}, [isTopOrigin, markProgrammaticScroll, setLatestFollowState]);
|
|
1328
|
+
const detachFromLatest = useCallback2(() => {
|
|
1329
|
+
setLatestFollowState("detached");
|
|
1330
|
+
}, [setLatestFollowState]);
|
|
1331
|
+
const reattachToLatest = useCallback2(() => {
|
|
1332
|
+
scrollToLatest({ reattach: true });
|
|
1333
|
+
}, [scrollToLatest]);
|
|
1334
|
+
const handleUserScrollIntent = useCallback2(() => {
|
|
1335
|
+
const element = scrollContainerRef.current;
|
|
1336
|
+
if (!activeStreamKey || !element) return;
|
|
1337
|
+
if (!isAtTimelineLatestEdge(element, isTopOrigin)) detachFromLatest();
|
|
1338
|
+
}, [activeStreamKey, detachFromLatest, isTopOrigin]);
|
|
1339
|
+
useEffect3(() => {
|
|
1178
1340
|
return () => {
|
|
1179
1341
|
if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
|
|
1180
1342
|
const preserve = intrinsicResizePreserveRef.current;
|
|
@@ -1185,6 +1347,7 @@ function useLatestThreadViewport({
|
|
|
1185
1347
|
const element = scrollContainerRef.current;
|
|
1186
1348
|
const preserve = olderPagePreserveRef.current;
|
|
1187
1349
|
if (!element) return;
|
|
1350
|
+
observedScrollHeightRef.current = element.scrollHeight;
|
|
1188
1351
|
if (!preserve) {
|
|
1189
1352
|
setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
|
|
1190
1353
|
return;
|
|
@@ -1201,7 +1364,13 @@ function useLatestThreadViewport({
|
|
|
1201
1364
|
const element = event.currentTarget;
|
|
1202
1365
|
const atLatest = isAtTimelineLatestEdge(element, isTopOrigin);
|
|
1203
1366
|
setIsAtLatest(atLatest);
|
|
1204
|
-
|
|
1367
|
+
setLatestFollowState(
|
|
1368
|
+
nextLatestFollowStateOnScroll({
|
|
1369
|
+
atLatest,
|
|
1370
|
+
current: followStateRef.current,
|
|
1371
|
+
isProgrammatic: programmaticScrollRef.current
|
|
1372
|
+
})
|
|
1373
|
+
);
|
|
1205
1374
|
if (isTopOrigin || !hasOlder || isLoadingOlder || olderPageRequestInFlightRef.current || !shouldPrefetchOlder(element)) {
|
|
1206
1375
|
return;
|
|
1207
1376
|
}
|
|
@@ -1218,7 +1387,7 @@ function useLatestThreadViewport({
|
|
|
1218
1387
|
});
|
|
1219
1388
|
});
|
|
1220
1389
|
},
|
|
1221
|
-
[hasOlder, isLoadingOlder, isTopOrigin, onLoadOlder]
|
|
1390
|
+
[hasOlder, isLoadingOlder, isTopOrigin, onLoadOlder, setLatestFollowState]
|
|
1222
1391
|
);
|
|
1223
1392
|
const preserveIntrinsicResize = useCallback2(() => {
|
|
1224
1393
|
const element = scrollContainerRef.current;
|
|
@@ -1249,37 +1418,60 @@ function useLatestThreadViewport({
|
|
|
1249
1418
|
};
|
|
1250
1419
|
intrinsicResizePreserveRef.current.frame = requestAnimationFrame(preserveFrame);
|
|
1251
1420
|
}, [isTopOrigin, markProgrammaticScroll]);
|
|
1252
|
-
|
|
1421
|
+
useEffect3(() => {
|
|
1253
1422
|
const key = activeStreamKey || null;
|
|
1254
1423
|
if (!key || activeStreamKeyRef.current === key) {
|
|
1255
1424
|
activeStreamKeyRef.current = key;
|
|
1256
1425
|
return;
|
|
1257
1426
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
activeStreamKeyRef.current = key;
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
const frame = requestAnimationFrame(scrollToLatest);
|
|
1427
|
+
setLatestFollowState("following");
|
|
1428
|
+
const frame = requestAnimationFrame(() => scrollToLatest());
|
|
1264
1429
|
activeStreamKeyRef.current = key;
|
|
1265
1430
|
return () => cancelAnimationFrame(frame);
|
|
1266
|
-
}, [activeStreamKey,
|
|
1431
|
+
}, [activeStreamKey, scrollToLatest, setLatestFollowState]);
|
|
1267
1432
|
useLayoutEffect(() => {
|
|
1268
|
-
if (
|
|
1269
|
-
if (!activeStreamKey || !shouldAutoFollow || !autoFollowLatestRef.current) return;
|
|
1433
|
+
if (!activeStreamKey || !shouldAutoFollow || followStateRef.current !== "following") return;
|
|
1270
1434
|
if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
|
|
1271
1435
|
const frame = requestAnimationFrame(() => {
|
|
1272
|
-
if (
|
|
1436
|
+
if (followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
|
|
1437
|
+
scrollToLatest({ reattach: false });
|
|
1438
|
+
}
|
|
1273
1439
|
});
|
|
1274
1440
|
return () => cancelAnimationFrame(frame);
|
|
1275
|
-
}, [activeStreamKey, isLoadingOlder,
|
|
1441
|
+
}, [activeStreamKey, isLoadingOlder, itemCount, scrollToLatest, shouldAutoFollow]);
|
|
1442
|
+
useEffect3(() => {
|
|
1443
|
+
if (!activeStreamKey) {
|
|
1444
|
+
observedScrollHeightRef.current = scrollContainerRef.current?.scrollHeight ?? null;
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
let frame = null;
|
|
1448
|
+
const watchStreamResize = () => {
|
|
1449
|
+
const element = scrollContainerRef.current;
|
|
1450
|
+
if (!element) return;
|
|
1451
|
+
const previousHeight = observedScrollHeightRef.current;
|
|
1452
|
+
const nextHeight = element.scrollHeight;
|
|
1453
|
+
observedScrollHeightRef.current = nextHeight;
|
|
1454
|
+
if (previousHeight !== null && Math.abs(nextHeight - previousHeight) >= 1 && shouldAutoFollow && followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
|
|
1455
|
+
scrollToLatest({ reattach: false });
|
|
1456
|
+
}
|
|
1457
|
+
frame = requestAnimationFrame(watchStreamResize);
|
|
1458
|
+
};
|
|
1459
|
+
frame = requestAnimationFrame(watchStreamResize);
|
|
1460
|
+
return () => {
|
|
1461
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
1462
|
+
};
|
|
1463
|
+
}, [activeStreamKey, isLoadingOlder, scrollToLatest, shouldAutoFollow]);
|
|
1276
1464
|
return {
|
|
1277
1465
|
scrollContainerRef,
|
|
1278
1466
|
isAtLatest,
|
|
1467
|
+
isFollowingLatest: followState === "following",
|
|
1279
1468
|
isTopOrigin,
|
|
1280
1469
|
shouldShowLatestButton: !isAtLatest,
|
|
1281
1470
|
handleScroll,
|
|
1471
|
+
handleUserScrollIntent,
|
|
1282
1472
|
scrollToLatest,
|
|
1473
|
+
detachFromLatest,
|
|
1474
|
+
reattachToLatest,
|
|
1283
1475
|
preserveIntrinsicResize
|
|
1284
1476
|
};
|
|
1285
1477
|
}
|
|
@@ -1307,7 +1499,10 @@ function LatestThreadViewport({
|
|
|
1307
1499
|
{
|
|
1308
1500
|
ref: viewport.scrollContainerRef,
|
|
1309
1501
|
className,
|
|
1502
|
+
onKeyDown: viewport.handleUserScrollIntent,
|
|
1310
1503
|
onScroll: viewport.handleScroll,
|
|
1504
|
+
onTouchMove: viewport.handleUserScrollIntent,
|
|
1505
|
+
onWheel: viewport.handleUserScrollIntent,
|
|
1311
1506
|
role: "log",
|
|
1312
1507
|
style: { overflowAnchor: "none" },
|
|
1313
1508
|
children: [
|
|
@@ -1330,14 +1525,17 @@ export {
|
|
|
1330
1525
|
consumeSseResponse,
|
|
1331
1526
|
createChatId,
|
|
1332
1527
|
createFetchChatTransport,
|
|
1528
|
+
getActiveStreamingTextPartId,
|
|
1333
1529
|
getLatestScrollTop,
|
|
1334
1530
|
hasStaleUnfinishedAssistantCache,
|
|
1335
1531
|
hasUnfinishedAssistantMessage,
|
|
1532
|
+
isActiveStreamingTextPart,
|
|
1336
1533
|
isAtTimelineLatestEdge,
|
|
1337
1534
|
isRunningThreadStatus,
|
|
1338
1535
|
isScrollable,
|
|
1339
1536
|
latestContextWindowFromThread,
|
|
1340
1537
|
mergeReasoningSteps,
|
|
1538
|
+
nextLatestFollowStateOnScroll,
|
|
1341
1539
|
parseSseBlock,
|
|
1342
1540
|
partsFromResponseBlocks,
|
|
1343
1541
|
reasoningStepsFromParts,
|
|
@@ -1351,6 +1549,7 @@ export {
|
|
|
1351
1549
|
titleFromMessage,
|
|
1352
1550
|
toolStateFromStatus,
|
|
1353
1551
|
useAgents24ChatController,
|
|
1354
|
-
useLatestThreadViewport
|
|
1552
|
+
useLatestThreadViewport,
|
|
1553
|
+
useStreamingText
|
|
1355
1554
|
};
|
|
1356
1555
|
//# sourceMappingURL=index.js.map
|