@agents24/chat-react 0.1.3 → 0.1.4

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/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 useEffect2,
1255
+ useEffect as useEffect3,
1122
1256
  useLayoutEffect,
1123
1257
  useMemo as useMemo2,
1124
- useRef as useRef2,
1125
- useState as useState2
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;
@@ -1149,15 +1283,15 @@ function useLatestThreadViewport({
1149
1283
  shouldAutoFollow = true,
1150
1284
  topOriginMaxItems = 4
1151
1285
  }) {
1152
- const scrollContainerRef = useRef2(null);
1153
- const olderPagePreserveRef = useRef2(null);
1154
- const olderPageRequestInFlightRef = useRef2(false);
1155
- const intrinsicResizePreserveRef = useRef2(null);
1156
- const autoFollowLatestRef = useRef2(true);
1157
- const programmaticScrollRef = useRef2(false);
1158
- const programmaticScrollTimeoutRef = useRef2(null);
1159
- const activeStreamKeyRef = useRef2(null);
1160
- const [isAtLatest, setIsAtLatest] = useState2(true);
1286
+ const scrollContainerRef = useRef3(null);
1287
+ const olderPagePreserveRef = useRef3(null);
1288
+ const olderPageRequestInFlightRef = useRef3(false);
1289
+ const intrinsicResizePreserveRef = useRef3(null);
1290
+ const autoFollowLatestRef = useRef3(true);
1291
+ const programmaticScrollRef = useRef3(false);
1292
+ const programmaticScrollTimeoutRef = useRef3(null);
1293
+ const activeStreamKeyRef = useRef3(null);
1294
+ const [isAtLatest, setIsAtLatest] = useState3(true);
1161
1295
  const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
1162
1296
  const markProgrammaticScroll = useCallback2(() => {
1163
1297
  programmaticScrollRef.current = true;
@@ -1174,7 +1308,7 @@ function useLatestThreadViewport({
1174
1308
  element.scrollTop = getLatestScrollTop(element, isTopOrigin);
1175
1309
  setIsAtLatest(true);
1176
1310
  }, [isTopOrigin, markProgrammaticScroll]);
1177
- useEffect2(() => {
1311
+ useEffect3(() => {
1178
1312
  return () => {
1179
1313
  if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
1180
1314
  const preserve = intrinsicResizePreserveRef.current;
@@ -1249,7 +1383,7 @@ function useLatestThreadViewport({
1249
1383
  };
1250
1384
  intrinsicResizePreserveRef.current.frame = requestAnimationFrame(preserveFrame);
1251
1385
  }, [isTopOrigin, markProgrammaticScroll]);
1252
- useEffect2(() => {
1386
+ useEffect3(() => {
1253
1387
  const key = activeStreamKey || null;
1254
1388
  if (!key || activeStreamKeyRef.current === key) {
1255
1389
  activeStreamKeyRef.current = key;
@@ -1330,9 +1464,11 @@ export {
1330
1464
  consumeSseResponse,
1331
1465
  createChatId,
1332
1466
  createFetchChatTransport,
1467
+ getActiveStreamingTextPartId,
1333
1468
  getLatestScrollTop,
1334
1469
  hasStaleUnfinishedAssistantCache,
1335
1470
  hasUnfinishedAssistantMessage,
1471
+ isActiveStreamingTextPart,
1336
1472
  isAtTimelineLatestEdge,
1337
1473
  isRunningThreadStatus,
1338
1474
  isScrollable,
@@ -1351,6 +1487,7 @@ export {
1351
1487
  titleFromMessage,
1352
1488
  toolStateFromStatus,
1353
1489
  useAgents24ChatController,
1354
- useLatestThreadViewport
1490
+ useLatestThreadViewport,
1491
+ useStreamingText
1355
1492
  };
1356
1493
  //# sourceMappingURL=index.js.map