@agents24/chat-react 0.1.6 → 0.1.8
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 +29 -12
- package/dist/chunk-E3TJ3I6G.js +358 -0
- package/dist/chunk-E3TJ3I6G.js.map +1 -0
- package/dist/chunk-EWZO4QJI.js +140 -0
- package/dist/chunk-EWZO4QJI.js.map +1 -0
- package/dist/chunk-UU7OXHL3.js +11 -0
- package/dist/chunk-UU7OXHL3.js.map +1 -0
- package/dist/context-window.d.ts +38 -0
- package/dist/controller.d.ts +22 -0
- package/dist/index.cjs +653 -336
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -466
- package/dist/index.js +290 -455
- package/dist/index.js.map +1 -1
- package/dist/latest-thread-scroller.d.ts +54 -0
- package/dist/model.d.ts +29 -0
- package/dist/renderers.d.ts +10 -0
- package/dist/sse.d.ts +3 -0
- package/dist/streaming-text.d.ts +24 -0
- package/dist/templates/agent-chat-shell.d.ts +25 -0
- package/dist/templates/index.cjs +523 -0
- package/dist/templates/index.cjs.map +1 -0
- package/dist/templates/index.d.ts +1 -0
- package/dist/templates/index.js +140 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/transport.d.ts +29 -0
- package/dist/types.d.ts +312 -0
- package/dist/ui/adapters.d.ts +36 -0
- package/dist/ui/attachment.d.ts +23 -0
- package/dist/ui/bubble.d.ts +17 -0
- package/dist/ui/index.cjs +943 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +734 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/marker.d.ts +11 -0
- package/dist/ui/message-response.d.ts +7 -0
- package/dist/ui/message.d.ts +10 -0
- package/dist/ui/utils.d.ts +2 -0
- package/dist/viewport.d.ts +2 -0
- package/package.json +22 -4
- package/dist/index.d.cts +0 -466
package/README.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# Agents24 Chat React
|
|
2
2
|
|
|
3
|
-
Last Updated: 2026-06-
|
|
3
|
+
Last Updated: 2026-06-29
|
|
4
4
|
|
|
5
5
|
Headless React primitives for Agents24 chat surfaces.
|
|
6
6
|
|
|
7
|
-
The package owns thread hydration, older-page pagination, stream/reattach state, active run cancellation, normalized message parts, and latest-thread
|
|
7
|
+
The package owns thread hydration, older-page pagination, stream/reattach state, active run cancellation, normalized message parts, and shadcn MessageScroller primitives for latest-thread chat surfaces. Hosts provide transport routes, cache storage, product rendering, source handling, pagination triggers, and styling.
|
|
8
8
|
|
|
9
9
|
## Public API
|
|
10
10
|
|
|
11
11
|
- `useAgents24ChatController(options)`
|
|
12
|
-
- `
|
|
13
|
-
- `
|
|
12
|
+
- `MessageScroller`
|
|
13
|
+
- `LatestThreadScroller`
|
|
14
|
+
- `useMessageScroller()`
|
|
15
|
+
- `useMessageScrollerScrollable()`
|
|
16
|
+
- `useMessageScrollerVisibility()`
|
|
14
17
|
- `useStreamingText(options)`
|
|
15
18
|
- `getActiveStreamingTextPartId(message, streamingMessageId)`
|
|
16
19
|
- `isActiveStreamingTextPart(message, part, streamingMessageId)`
|
|
@@ -19,6 +22,12 @@ The package owns thread hydration, older-page pagination, stream/reattach state,
|
|
|
19
22
|
- normalized chat message, part, transport, and storage types
|
|
20
23
|
- `partsFromResponseBlocks(blocks, fallbackText)`
|
|
21
24
|
- `renderChatPart(part, message, renderOptions)`
|
|
25
|
+
- `@agents24/chat-react/ui` for shared shadcn-derived `Message`, `Bubble`, `Attachment`, `Marker`, `MessageResponse`, and Agents24 message/attachment adapters
|
|
26
|
+
- `@agents24/chat-react/templates` for the slot-based `AgentChatShell` starter shell
|
|
27
|
+
|
|
28
|
+
`useAgents24ChatController` exposes reactive `threads`, `activeThreadId`, `activeThread`, `activeRunId`, `isRefreshingThreads`, `loadThreadById(threadId)`, and `startNewThread()` in addition to message and stream state. It hydrates selected threads, detects active runs from `active_run`/`last_run_status`, attaches through the host transport, detaches local SSE streams on navigation, and refreshes history after terminal events.
|
|
29
|
+
|
|
30
|
+
Hosts can pass `onRuntimeEvent(event, context)` and `onThreadDetailLoaded(detail)` to layer product-specific runtime UI, trace panels, analytics, or metadata over the shared chat lifecycle without forking stream handling. That surface is enough for running-history badges and read-only live thread inspectors without a separate host-owned reattach state machine.
|
|
22
31
|
|
|
23
32
|
`ChatMessage.parts` is the only render model. Backend `response_blocks` are normalized into ordered parts such as `text`, `tool-get_meteo`, `ui-blocks`, `reasoning`, `approval`, `error`, and `data`. Tool UI is client-owned through `renderPart`, `toolRenderers`, and `fallbackToolRenderer`; the package does not infer English tool labels or group tool rows.
|
|
24
33
|
|
|
@@ -26,14 +35,22 @@ The package owns thread hydration, older-page pagination, stream/reattach state,
|
|
|
26
35
|
|
|
27
36
|
Network access is always injected by the host. This browser package never requires platform API keys.
|
|
28
37
|
|
|
29
|
-
##
|
|
38
|
+
## MessageScroller Behavior
|
|
39
|
+
|
|
40
|
+
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:
|
|
41
|
+
|
|
42
|
+
- `LatestThreadScroller.Provider`
|
|
43
|
+
- `LatestThreadScroller.Root`
|
|
44
|
+
- `LatestThreadScroller.Viewport`
|
|
45
|
+
- `LatestThreadScroller.Content`
|
|
46
|
+
- `LatestThreadScroller.Item`
|
|
47
|
+
- `LatestThreadScroller.Button`
|
|
48
|
+
- `LatestThreadScroller.Outline`
|
|
49
|
+
|
|
50
|
+
The preset defaults to `autoScroll`, `defaultScrollPosition="last-anchor"`, `scrollPreviousItemPeek={64}`, and `preserveScrollOnPrepend`. Each item requires a stable `messageId`; callers set `scrollAnchor` on turn-start rows. MessageScroller owns anchoring, stream follow, detach/reattach, resize handling, visibility, outline jumping, and the latest button state. Hosts keep only product pagination triggers for loading older data and product message rendering.
|
|
30
51
|
|
|
31
|
-
|
|
52
|
+
## Shared UI Subpaths
|
|
32
53
|
|
|
33
|
-
-
|
|
34
|
-
- streaming text growth stays pinned to latest while the user remains there
|
|
35
|
-
- user scrolling away detaches latest-follow mode
|
|
36
|
-
- manually reaching latest again, or clicking the latest button, reattaches follow mode
|
|
37
|
-
- older-page prepends preserve the visible viewport instead of snapping to latest
|
|
54
|
+
`@agents24/chat-react/ui` contains package-owned source adapted from the shadcn CLI `message`, `bubble`, `attachment`, and `marker` components. Base primitive names stay close to shadcn, while Agents24-specific compatibility adapters sit beside them as `Message`, `MessageContent`, `MessageActions`, `MessageAction`, `MessageAttachments`, `MessageAttachment`, and `MessageResponse`.
|
|
38
55
|
|
|
39
|
-
|
|
56
|
+
`@agents24/chat-react/templates` exposes `AgentChatShell`, a convenience shell that composes `LatestThreadScroller`, the outline rail, latest button, composer overlay spacing, older-history trigger, and message slots. Product apps still own transport, citations, TTS, source panes, artifact actions, and row-specific rendering.
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// src/latest-thread-scroller.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
MessageScroller,
|
|
5
|
+
useMessageScroller,
|
|
6
|
+
useMessageScrollerVisibility
|
|
7
|
+
} from "@shadcn/react/message-scroller";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var cx = (...values) => values.filter(Boolean).join(" ");
|
|
10
|
+
var LatestThreadScrollerLayoutContext = React.createContext(null);
|
|
11
|
+
var useLatestThreadScrollerLayout = () => React.useContext(LatestThreadScrollerLayoutContext);
|
|
12
|
+
function LatestThreadScrollerProvider({
|
|
13
|
+
autoScroll = true,
|
|
14
|
+
defaultScrollPosition = "last-anchor",
|
|
15
|
+
scrollPreviousItemPeek = 64,
|
|
16
|
+
...props
|
|
17
|
+
}) {
|
|
18
|
+
return /* @__PURE__ */ jsx(
|
|
19
|
+
MessageScroller.Provider,
|
|
20
|
+
{
|
|
21
|
+
autoScroll,
|
|
22
|
+
defaultScrollPosition,
|
|
23
|
+
scrollPreviousItemPeek,
|
|
24
|
+
...props
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function LatestThreadScrollerRoot({
|
|
29
|
+
className,
|
|
30
|
+
ref,
|
|
31
|
+
...props
|
|
32
|
+
}) {
|
|
33
|
+
const [rootElement, setRootElement] = React.useState(null);
|
|
34
|
+
const [contentElement, setContentElement] = React.useState(null);
|
|
35
|
+
const layoutContext = React.useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
contentElement,
|
|
38
|
+
rootElement,
|
|
39
|
+
setContentElement,
|
|
40
|
+
setRootElement
|
|
41
|
+
}),
|
|
42
|
+
[contentElement, rootElement]
|
|
43
|
+
);
|
|
44
|
+
const setRef = React.useCallback(
|
|
45
|
+
(node) => {
|
|
46
|
+
setRootElement(node);
|
|
47
|
+
if (typeof ref === "function") {
|
|
48
|
+
ref(node);
|
|
49
|
+
} else if (ref) {
|
|
50
|
+
ref.current = node;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[ref]
|
|
54
|
+
);
|
|
55
|
+
return /* @__PURE__ */ jsx(LatestThreadScrollerLayoutContext.Provider, { value: layoutContext, children: /* @__PURE__ */ jsx(
|
|
56
|
+
MessageScroller.Root,
|
|
57
|
+
{
|
|
58
|
+
ref: setRef,
|
|
59
|
+
className: cx(
|
|
60
|
+
"relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
...props
|
|
64
|
+
}
|
|
65
|
+
) });
|
|
66
|
+
}
|
|
67
|
+
function LatestThreadScrollerViewport({
|
|
68
|
+
className,
|
|
69
|
+
onViewportChange,
|
|
70
|
+
preserveScrollOnPrepend = true,
|
|
71
|
+
ref,
|
|
72
|
+
...props
|
|
73
|
+
}) {
|
|
74
|
+
const setRef = React.useCallback(
|
|
75
|
+
(node) => {
|
|
76
|
+
if (typeof ref === "function") {
|
|
77
|
+
ref(node);
|
|
78
|
+
} else if (ref) {
|
|
79
|
+
ref.current = node;
|
|
80
|
+
}
|
|
81
|
+
onViewportChange?.(node);
|
|
82
|
+
},
|
|
83
|
+
[onViewportChange, ref]
|
|
84
|
+
);
|
|
85
|
+
return /* @__PURE__ */ jsx(
|
|
86
|
+
MessageScroller.Viewport,
|
|
87
|
+
{
|
|
88
|
+
ref: setRef,
|
|
89
|
+
className: cx(
|
|
90
|
+
"size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
|
|
91
|
+
className
|
|
92
|
+
),
|
|
93
|
+
preserveScrollOnPrepend,
|
|
94
|
+
...props
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function LatestThreadScrollerContent({
|
|
99
|
+
className,
|
|
100
|
+
ref,
|
|
101
|
+
...props
|
|
102
|
+
}) {
|
|
103
|
+
const layout = useLatestThreadScrollerLayout();
|
|
104
|
+
const setRef = React.useCallback(
|
|
105
|
+
(node) => {
|
|
106
|
+
layout?.setContentElement(node);
|
|
107
|
+
if (typeof ref === "function") {
|
|
108
|
+
ref(node);
|
|
109
|
+
} else if (ref) {
|
|
110
|
+
ref.current = node;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[layout, ref]
|
|
114
|
+
);
|
|
115
|
+
return /* @__PURE__ */ jsx(
|
|
116
|
+
MessageScroller.Content,
|
|
117
|
+
{
|
|
118
|
+
ref: setRef,
|
|
119
|
+
"data-agents24-latest-thread-content": "",
|
|
120
|
+
className: cx("flex h-max min-h-full flex-col gap-8 p-4", className),
|
|
121
|
+
...props
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
function LatestThreadScrollerItem({
|
|
126
|
+
className,
|
|
127
|
+
messageId,
|
|
128
|
+
...props
|
|
129
|
+
}) {
|
|
130
|
+
return /* @__PURE__ */ jsx(
|
|
131
|
+
MessageScroller.Item,
|
|
132
|
+
{
|
|
133
|
+
className: cx(
|
|
134
|
+
"min-w-0 shrink-0 overflow-visible",
|
|
135
|
+
className
|
|
136
|
+
),
|
|
137
|
+
messageId,
|
|
138
|
+
...props
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function LatestThreadScrollerButton({
|
|
143
|
+
children,
|
|
144
|
+
className,
|
|
145
|
+
direction = "end",
|
|
146
|
+
render,
|
|
147
|
+
...props
|
|
148
|
+
}) {
|
|
149
|
+
if (render) {
|
|
150
|
+
return /* @__PURE__ */ jsx(
|
|
151
|
+
MessageScroller.Button,
|
|
152
|
+
{
|
|
153
|
+
direction,
|
|
154
|
+
render,
|
|
155
|
+
...props
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return /* @__PURE__ */ jsx(
|
|
160
|
+
MessageScroller.Button,
|
|
161
|
+
{
|
|
162
|
+
direction,
|
|
163
|
+
render: (buttonProps) => /* @__PURE__ */ jsx(
|
|
164
|
+
"button",
|
|
165
|
+
{
|
|
166
|
+
...buttonProps,
|
|
167
|
+
"aria-label": direction === "end" ? "Scroll to latest message" : "Scroll to first message",
|
|
168
|
+
className: cx(
|
|
169
|
+
"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",
|
|
170
|
+
className
|
|
171
|
+
),
|
|
172
|
+
type: "button",
|
|
173
|
+
children: children ?? (direction === "end" ? "\u2193" : "\u2191")
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
...props
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
function LatestThreadScrollerOutline({
|
|
181
|
+
cardClassName,
|
|
182
|
+
className,
|
|
183
|
+
contentMaxWidth = 768,
|
|
184
|
+
dir = "ltr",
|
|
185
|
+
items,
|
|
186
|
+
minContainerWidthForRail = 0,
|
|
187
|
+
minSideGutterForRail = 48,
|
|
188
|
+
side
|
|
189
|
+
}) {
|
|
190
|
+
const navRef = React.useRef(null);
|
|
191
|
+
const layout = useLatestThreadScrollerLayout();
|
|
192
|
+
const [hasSideRoom, setHasSideRoom] = React.useState(false);
|
|
193
|
+
const [hoveredIndex, setHoveredIndex] = React.useState(null);
|
|
194
|
+
const { scrollToMessage } = useMessageScroller();
|
|
195
|
+
const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility();
|
|
196
|
+
const visibleMessageIdSet = React.useMemo(
|
|
197
|
+
() => new Set(visibleMessageIds),
|
|
198
|
+
[visibleMessageIds]
|
|
199
|
+
);
|
|
200
|
+
const focusIndex = hoveredIndex ?? -1;
|
|
201
|
+
const railSide = side ?? (dir === "rtl" ? "right" : "left");
|
|
202
|
+
React.useEffect(() => {
|
|
203
|
+
const root = layout?.rootElement ?? navRef.current?.parentElement;
|
|
204
|
+
if (!root) return;
|
|
205
|
+
const updateSideRoom = () => {
|
|
206
|
+
const rootRect = root.getBoundingClientRect();
|
|
207
|
+
const rootWidth = rootRect.width;
|
|
208
|
+
const content2 = layout?.contentElement;
|
|
209
|
+
const sideGutter = content2 ? Math.max(
|
|
210
|
+
0,
|
|
211
|
+
railSide === "left" ? content2.getBoundingClientRect().left - rootRect.left : rootRect.right - content2.getBoundingClientRect().right
|
|
212
|
+
) : Math.max(0, (rootWidth - contentMaxWidth) / 2);
|
|
213
|
+
const nextHasSideRoom = rootWidth >= minContainerWidthForRail && sideGutter >= minSideGutterForRail;
|
|
214
|
+
setHasSideRoom(
|
|
215
|
+
(current) => current === nextHasSideRoom ? current : nextHasSideRoom
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
updateSideRoom();
|
|
219
|
+
const resizeObserver = new ResizeObserver(updateSideRoom);
|
|
220
|
+
resizeObserver.observe(root);
|
|
221
|
+
const content = layout?.contentElement;
|
|
222
|
+
if (content) {
|
|
223
|
+
resizeObserver.observe(content);
|
|
224
|
+
}
|
|
225
|
+
return () => resizeObserver.disconnect();
|
|
226
|
+
}, [
|
|
227
|
+
contentMaxWidth,
|
|
228
|
+
layout?.contentElement,
|
|
229
|
+
layout?.rootElement,
|
|
230
|
+
minContainerWidthForRail,
|
|
231
|
+
minSideGutterForRail,
|
|
232
|
+
railSide
|
|
233
|
+
]);
|
|
234
|
+
if (items.length < 2) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return /* @__PURE__ */ jsx(
|
|
238
|
+
"nav",
|
|
239
|
+
{
|
|
240
|
+
ref: navRef,
|
|
241
|
+
"aria-label": "Transcript outline",
|
|
242
|
+
className: cx(
|
|
243
|
+
"pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
|
|
244
|
+
hasSideRoom ? "hidden md:block" : "hidden",
|
|
245
|
+
railSide === "left" ? "left-1" : "right-1",
|
|
246
|
+
className
|
|
247
|
+
),
|
|
248
|
+
dir,
|
|
249
|
+
children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
|
|
250
|
+
const itemMessageIds = item.messageIds && item.messageIds.length > 0 ? item.messageIds : [item.id];
|
|
251
|
+
const active = itemMessageIds.some(
|
|
252
|
+
(messageId) => visibleMessageIdSet.has(messageId) || messageId === currentAnchorId
|
|
253
|
+
);
|
|
254
|
+
const distance = focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY;
|
|
255
|
+
const isFocused = distance === 0;
|
|
256
|
+
const isNear = distance === 1;
|
|
257
|
+
const isNextNear = distance === 2;
|
|
258
|
+
const barWidth = isFocused ? 50 : isNear ? 36 : isNextNear ? 23 : 10;
|
|
259
|
+
const barOpacity = isFocused ? 1 : isNear ? 0.78 : isNextNear ? 0.58 : 0.36;
|
|
260
|
+
const fallbackLabel = `Turn ${index + 1}`;
|
|
261
|
+
const previewLabel = item.label && item.label !== fallbackLabel ? item.label : "";
|
|
262
|
+
const responsePreview = (item.responsePreview || "").trim();
|
|
263
|
+
return /* @__PURE__ */ jsxs(
|
|
264
|
+
"button",
|
|
265
|
+
{
|
|
266
|
+
type: "button",
|
|
267
|
+
"data-active": active,
|
|
268
|
+
dir: "ltr",
|
|
269
|
+
className: cx(
|
|
270
|
+
"group relative flex h-2 w-16 items-center rounded-sm outline-none",
|
|
271
|
+
railSide === "left" ? "justify-start" : "justify-end"
|
|
272
|
+
),
|
|
273
|
+
"aria-label": `Jump to ${item.label || fallbackLabel}`,
|
|
274
|
+
onBlur: () => setHoveredIndex(null),
|
|
275
|
+
onFocus: () => setHoveredIndex(index),
|
|
276
|
+
onMouseEnter: () => setHoveredIndex(index),
|
|
277
|
+
onMouseLeave: () => setHoveredIndex(null),
|
|
278
|
+
onClick: () => scrollToMessage(item.id, {
|
|
279
|
+
align: "start",
|
|
280
|
+
behavior: "smooth"
|
|
281
|
+
}),
|
|
282
|
+
children: [
|
|
283
|
+
/* @__PURE__ */ jsx(
|
|
284
|
+
"span",
|
|
285
|
+
{
|
|
286
|
+
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",
|
|
287
|
+
style: {
|
|
288
|
+
opacity: barOpacity,
|
|
289
|
+
width: barWidth
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
),
|
|
293
|
+
previewLabel ? /* @__PURE__ */ jsxs(
|
|
294
|
+
"span",
|
|
295
|
+
{
|
|
296
|
+
dir,
|
|
297
|
+
className: cx(
|
|
298
|
+
"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",
|
|
299
|
+
railSide === "left" ? "left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0" : "right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0",
|
|
300
|
+
cardClassName
|
|
301
|
+
),
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ jsx(
|
|
304
|
+
"span",
|
|
305
|
+
{
|
|
306
|
+
className: "block overflow-hidden text-sm leading-5 text-foreground",
|
|
307
|
+
style: {
|
|
308
|
+
display: "-webkit-box",
|
|
309
|
+
WebkitBoxOrient: "vertical",
|
|
310
|
+
WebkitLineClamp: responsePreview ? 2 : 4
|
|
311
|
+
},
|
|
312
|
+
children: previewLabel
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
responsePreview ? /* @__PURE__ */ jsx(
|
|
316
|
+
"span",
|
|
317
|
+
{
|
|
318
|
+
className: "mt-1 block overflow-hidden text-sm leading-5 text-muted-foreground",
|
|
319
|
+
style: {
|
|
320
|
+
display: "-webkit-box",
|
|
321
|
+
WebkitBoxOrient: "vertical",
|
|
322
|
+
WebkitLineClamp: 2
|
|
323
|
+
},
|
|
324
|
+
children: responsePreview
|
|
325
|
+
}
|
|
326
|
+
) : null
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
) : null
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
item.id
|
|
333
|
+
);
|
|
334
|
+
}) })
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
var LatestThreadScroller = {
|
|
339
|
+
Provider: LatestThreadScrollerProvider,
|
|
340
|
+
Root: LatestThreadScrollerRoot,
|
|
341
|
+
Viewport: LatestThreadScrollerViewport,
|
|
342
|
+
Content: LatestThreadScrollerContent,
|
|
343
|
+
Item: LatestThreadScrollerItem,
|
|
344
|
+
Button: LatestThreadScrollerButton,
|
|
345
|
+
Outline: LatestThreadScrollerOutline
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
export {
|
|
349
|
+
LatestThreadScrollerProvider,
|
|
350
|
+
LatestThreadScrollerRoot,
|
|
351
|
+
LatestThreadScrollerViewport,
|
|
352
|
+
LatestThreadScrollerContent,
|
|
353
|
+
LatestThreadScrollerItem,
|
|
354
|
+
LatestThreadScrollerButton,
|
|
355
|
+
LatestThreadScrollerOutline,
|
|
356
|
+
LatestThreadScroller
|
|
357
|
+
};
|
|
358
|
+
//# sourceMappingURL=chunk-E3TJ3I6G.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 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"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/streaming-text.ts
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
|
|
4
|
+
var defaultCompletedTextCache = /* @__PURE__ */ new Map();
|
|
5
|
+
var defaultStreamingTextCache = {
|
|
6
|
+
get: (id) => defaultCompletedTextCache.get(id),
|
|
7
|
+
set: (id, text) => {
|
|
8
|
+
defaultCompletedTextCache.set(id, text);
|
|
9
|
+
if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
|
|
10
|
+
const firstKey = defaultCompletedTextCache.keys().next().value;
|
|
11
|
+
if (firstKey) defaultCompletedTextCache.delete(firstKey);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var getActiveStreamingTextPartId = (message, streamingMessageId) => {
|
|
16
|
+
if (message.role !== "assistant" || streamingMessageId !== message.id) return null;
|
|
17
|
+
return message.parts.slice().reverse().find((part) => part.kind === "text")?.id || null;
|
|
18
|
+
};
|
|
19
|
+
var isActiveStreamingTextPart = (message, part, streamingMessageId) => part.kind === "text" && part.id === getActiveStreamingTextPartId(message, streamingMessageId);
|
|
20
|
+
function useStreamingText({
|
|
21
|
+
id,
|
|
22
|
+
isStreaming,
|
|
23
|
+
text,
|
|
24
|
+
cache = defaultStreamingTextCache,
|
|
25
|
+
charsPerSecond = 72,
|
|
26
|
+
catchupThreshold = 40,
|
|
27
|
+
maxCatchupChars = 20
|
|
28
|
+
}) {
|
|
29
|
+
const cacheAdapter = cache === false ? null : cache;
|
|
30
|
+
const [displayedText, setDisplayedText] = useState(() => {
|
|
31
|
+
const cachedText = cacheAdapter?.get(id);
|
|
32
|
+
return isStreaming && cachedText !== text ? "" : text;
|
|
33
|
+
});
|
|
34
|
+
const targetRef = useRef(text);
|
|
35
|
+
const displayedRef = useRef(displayedText);
|
|
36
|
+
const rafRef = useRef(null);
|
|
37
|
+
const lastFrameAtRef = useRef(null);
|
|
38
|
+
const idRef = useRef(id);
|
|
39
|
+
const shouldAnimateRef = useRef(isStreaming);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (isStreaming || !text) return;
|
|
42
|
+
cacheAdapter?.set(id, text);
|
|
43
|
+
}, [cacheAdapter, id, isStreaming, text]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
targetRef.current = text;
|
|
46
|
+
}, [text]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (idRef.current === id) return;
|
|
49
|
+
idRef.current = id;
|
|
50
|
+
const cachedText = cacheAdapter?.get(id);
|
|
51
|
+
const initial = isStreaming && cachedText !== text ? "" : text;
|
|
52
|
+
shouldAnimateRef.current = isStreaming && initial !== text;
|
|
53
|
+
displayedRef.current = initial;
|
|
54
|
+
setDisplayedText(initial);
|
|
55
|
+
if (rafRef.current !== null) {
|
|
56
|
+
cancelAnimationFrame(rafRef.current);
|
|
57
|
+
rafRef.current = null;
|
|
58
|
+
}
|
|
59
|
+
lastFrameAtRef.current = null;
|
|
60
|
+
}, [cacheAdapter, id, isStreaming, text]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (typeof window === "undefined") {
|
|
63
|
+
displayedRef.current = text;
|
|
64
|
+
setDisplayedText(text);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
68
|
+
if (reduceMotion) {
|
|
69
|
+
displayedRef.current = text;
|
|
70
|
+
setDisplayedText(text);
|
|
71
|
+
shouldAnimateRef.current = false;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const cachedText = cacheAdapter?.get(id);
|
|
75
|
+
if (cachedText === text) {
|
|
76
|
+
displayedRef.current = text;
|
|
77
|
+
setDisplayedText(text);
|
|
78
|
+
shouldAnimateRef.current = false;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (isStreaming) {
|
|
82
|
+
shouldAnimateRef.current = true;
|
|
83
|
+
}
|
|
84
|
+
if (!shouldAnimateRef.current) {
|
|
85
|
+
displayedRef.current = text;
|
|
86
|
+
setDisplayedText(text);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!text.startsWith(displayedRef.current)) {
|
|
90
|
+
displayedRef.current = "";
|
|
91
|
+
setDisplayedText("");
|
|
92
|
+
}
|
|
93
|
+
const tick = (timestamp) => {
|
|
94
|
+
const previousTimestamp = lastFrameAtRef.current ?? timestamp;
|
|
95
|
+
lastFrameAtRef.current = timestamp;
|
|
96
|
+
const target = targetRef.current;
|
|
97
|
+
const current = displayedRef.current;
|
|
98
|
+
if (current.length >= target.length) {
|
|
99
|
+
shouldAnimateRef.current = false;
|
|
100
|
+
rafRef.current = null;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const elapsedMs = Math.max(8, timestamp - previousTimestamp);
|
|
104
|
+
const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
|
|
105
|
+
const gap = target.length - current.length;
|
|
106
|
+
const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
|
|
107
|
+
const nextLength = Math.min(
|
|
108
|
+
target.length,
|
|
109
|
+
current.length + Math.max(charsFromTime, catchupStep)
|
|
110
|
+
);
|
|
111
|
+
const next = target.slice(0, nextLength);
|
|
112
|
+
displayedRef.current = next;
|
|
113
|
+
setDisplayedText(next);
|
|
114
|
+
rafRef.current = window.requestAnimationFrame(tick);
|
|
115
|
+
};
|
|
116
|
+
if (rafRef.current === null && displayedRef.current.length < text.length) {
|
|
117
|
+
rafRef.current = window.requestAnimationFrame(tick);
|
|
118
|
+
}
|
|
119
|
+
return () => {
|
|
120
|
+
if (rafRef.current !== null) {
|
|
121
|
+
cancelAnimationFrame(rafRef.current);
|
|
122
|
+
rafRef.current = null;
|
|
123
|
+
}
|
|
124
|
+
lastFrameAtRef.current = null;
|
|
125
|
+
};
|
|
126
|
+
}, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
|
|
127
|
+
return {
|
|
128
|
+
displayedText,
|
|
129
|
+
isAnimating: isStreaming,
|
|
130
|
+
mode: isStreaming ? "streaming" : "static",
|
|
131
|
+
parseIncompleteMarkdown: isStreaming
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
getActiveStreamingTextPartId,
|
|
137
|
+
isActiveStreamingTextPart,
|
|
138
|
+
useStreamingText
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=chunk-EWZO4QJI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/streaming-text.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nimport type { ChatMessage, ChatPart } from \"./types\";\n\nexport type StreamingTextMode = \"streaming\" | \"static\";\n\nexport type StreamingTextCache = {\n get: (id: string) => string | undefined;\n set: (id: string, text: string) => void;\n};\n\nexport type UseStreamingTextOptions = {\n id: string;\n isStreaming: boolean;\n text: string;\n cache?: StreamingTextCache | false;\n charsPerSecond?: number;\n catchupThreshold?: number;\n maxCatchupChars?: number;\n};\n\nexport type UseStreamingTextResult = {\n displayedText: string;\n isAnimating: boolean;\n mode: StreamingTextMode;\n parseIncompleteMarkdown: boolean;\n};\n\nconst DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;\nconst defaultCompletedTextCache = new Map<string, string>();\n\nconst defaultStreamingTextCache: StreamingTextCache = {\n get: (id) => defaultCompletedTextCache.get(id),\n set: (id, text) => {\n defaultCompletedTextCache.set(id, text);\n if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {\n const firstKey = defaultCompletedTextCache.keys().next().value;\n if (firstKey) defaultCompletedTextCache.delete(firstKey);\n }\n },\n};\n\nexport const getActiveStreamingTextPartId = (\n message: ChatMessage,\n streamingMessageId: string | null\n) => {\n if (message.role !== \"assistant\" || streamingMessageId !== message.id) return null;\n return (\n message.parts\n .slice()\n .reverse()\n .find((part) => part.kind === \"text\")?.id || null\n );\n};\n\nexport const isActiveStreamingTextPart = (\n message: ChatMessage,\n part: ChatPart,\n streamingMessageId: string | null\n) => part.kind === \"text\" && part.id === getActiveStreamingTextPartId(message, streamingMessageId);\n\nexport function useStreamingText({\n id,\n isStreaming,\n text,\n cache = defaultStreamingTextCache,\n charsPerSecond = 72,\n catchupThreshold = 40,\n maxCatchupChars = 20,\n}: UseStreamingTextOptions): UseStreamingTextResult {\n const cacheAdapter = cache === false ? null : cache;\n const [displayedText, setDisplayedText] = useState(() => {\n const cachedText = cacheAdapter?.get(id);\n return isStreaming && cachedText !== text ? \"\" : text;\n });\n const targetRef = useRef(text);\n const displayedRef = useRef(displayedText);\n const rafRef = useRef<number | null>(null);\n const lastFrameAtRef = useRef<number | null>(null);\n const idRef = useRef(id);\n const shouldAnimateRef = useRef(isStreaming);\n\n useEffect(() => {\n if (isStreaming || !text) return;\n cacheAdapter?.set(id, text);\n }, [cacheAdapter, id, isStreaming, text]);\n\n useEffect(() => {\n targetRef.current = text;\n }, [text]);\n\n useEffect(() => {\n if (idRef.current === id) return;\n\n idRef.current = id;\n const cachedText = cacheAdapter?.get(id);\n const initial = isStreaming && cachedText !== text ? \"\" : text;\n shouldAnimateRef.current = isStreaming && initial !== text;\n displayedRef.current = initial;\n setDisplayedText(initial);\n\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n lastFrameAtRef.current = null;\n }, [cacheAdapter, id, isStreaming, text]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") {\n displayedRef.current = text;\n setDisplayedText(text);\n return;\n }\n\n const reduceMotion =\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n if (reduceMotion) {\n displayedRef.current = text;\n setDisplayedText(text);\n shouldAnimateRef.current = false;\n return;\n }\n\n const cachedText = cacheAdapter?.get(id);\n if (cachedText === text) {\n displayedRef.current = text;\n setDisplayedText(text);\n shouldAnimateRef.current = false;\n return;\n }\n\n if (isStreaming) {\n shouldAnimateRef.current = true;\n }\n\n if (!shouldAnimateRef.current) {\n displayedRef.current = text;\n setDisplayedText(text);\n return;\n }\n\n if (!text.startsWith(displayedRef.current)) {\n displayedRef.current = \"\";\n setDisplayedText(\"\");\n }\n\n const tick = (timestamp: number) => {\n const previousTimestamp = lastFrameAtRef.current ?? timestamp;\n lastFrameAtRef.current = timestamp;\n\n const target = targetRef.current;\n const current = displayedRef.current;\n\n if (current.length >= target.length) {\n shouldAnimateRef.current = false;\n rafRef.current = null;\n return;\n }\n\n const elapsedMs = Math.max(8, timestamp - previousTimestamp);\n const charsFromTime = Math.max(1, Math.floor((elapsedMs / 1000) * charsPerSecond));\n const gap = target.length - current.length;\n const catchupStep =\n gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;\n const nextLength = Math.min(\n target.length,\n current.length + Math.max(charsFromTime, catchupStep)\n );\n\n const next = target.slice(0, nextLength);\n displayedRef.current = next;\n setDisplayedText(next);\n rafRef.current = window.requestAnimationFrame(tick);\n };\n\n if (rafRef.current === null && displayedRef.current.length < text.length) {\n rafRef.current = window.requestAnimationFrame(tick);\n }\n\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n lastFrameAtRef.current = null;\n };\n }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);\n\n return {\n displayedText,\n isAnimating: isStreaming,\n mode: isStreaming ? \"streaming\" : \"static\",\n parseIncompleteMarkdown: isStreaming,\n };\n}\n"],"mappings":";AAAA,SAAS,WAAW,QAAQ,gBAAgB;AA4B5C,IAAM,oCAAoC;AAC1C,IAAM,4BAA4B,oBAAI,IAAoB;AAE1D,IAAM,4BAAgD;AAAA,EACpD,KAAK,CAAC,OAAO,0BAA0B,IAAI,EAAE;AAAA,EAC7C,KAAK,CAAC,IAAI,SAAS;AACjB,8BAA0B,IAAI,IAAI,IAAI;AACtC,QAAI,0BAA0B,OAAO,mCAAmC;AACtE,YAAM,WAAW,0BAA0B,KAAK,EAAE,KAAK,EAAE;AACzD,UAAI,SAAU,2BAA0B,OAAO,QAAQ;AAAA,IACzD;AAAA,EACF;AACF;AAEO,IAAM,+BAA+B,CAC1C,SACA,uBACG;AACH,MAAI,QAAQ,SAAS,eAAe,uBAAuB,QAAQ,GAAI,QAAO;AAC9E,SACE,QAAQ,MACL,MAAM,EACN,QAAQ,EACR,KAAK,CAAC,SAAS,KAAK,SAAS,MAAM,GAAG,MAAM;AAEnD;AAEO,IAAM,4BAA4B,CACvC,SACA,MACA,uBACG,KAAK,SAAS,UAAU,KAAK,OAAO,6BAA6B,SAAS,kBAAkB;AAE1F,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,kBAAkB;AACpB,GAAoD;AAClD,QAAM,eAAe,UAAU,QAAQ,OAAO;AAC9C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,MAAM;AACvD,UAAM,aAAa,cAAc,IAAI,EAAE;AACvC,WAAO,eAAe,eAAe,OAAO,KAAK;AAAA,EACnD,CAAC;AACD,QAAM,YAAY,OAAO,IAAI;AAC7B,QAAM,eAAe,OAAO,aAAa;AACzC,QAAM,SAAS,OAAsB,IAAI;AACzC,QAAM,iBAAiB,OAAsB,IAAI;AACjD,QAAM,QAAQ,OAAO,EAAE;AACvB,QAAM,mBAAmB,OAAO,WAAW;AAE3C,YAAU,MAAM;AACd,QAAI,eAAe,CAAC,KAAM;AAC1B,kBAAc,IAAI,IAAI,IAAI;AAAA,EAC5B,GAAG,CAAC,cAAc,IAAI,aAAa,IAAI,CAAC;AAExC,YAAU,MAAM;AACd,cAAU,UAAU;AAAA,EACtB,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,QAAI,MAAM,YAAY,GAAI;AAE1B,UAAM,UAAU;AAChB,UAAM,aAAa,cAAc,IAAI,EAAE;AACvC,UAAM,UAAU,eAAe,eAAe,OAAO,KAAK;AAC1D,qBAAiB,UAAU,eAAe,YAAY;AACtD,iBAAa,UAAU;AACvB,qBAAiB,OAAO;AAExB,QAAI,OAAO,YAAY,MAAM;AAC3B,2BAAqB,OAAO,OAAO;AACnC,aAAO,UAAU;AAAA,IACnB;AACA,mBAAe,UAAU;AAAA,EAC3B,GAAG,CAAC,cAAc,IAAI,aAAa,IAAI,CAAC;AAExC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,aAAa;AACjC,mBAAa,UAAU;AACvB,uBAAiB,IAAI;AACrB;AAAA,IACF;AAEA,UAAM,eACJ,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,EAAE;AAExD,QAAI,cAAc;AAChB,mBAAa,UAAU;AACvB,uBAAiB,IAAI;AACrB,uBAAiB,UAAU;AAC3B;AAAA,IACF;AAEA,UAAM,aAAa,cAAc,IAAI,EAAE;AACvC,QAAI,eAAe,MAAM;AACvB,mBAAa,UAAU;AACvB,uBAAiB,IAAI;AACrB,uBAAiB,UAAU;AAC3B;AAAA,IACF;AAEA,QAAI,aAAa;AACf,uBAAiB,UAAU;AAAA,IAC7B;AAEA,QAAI,CAAC,iBAAiB,SAAS;AAC7B,mBAAa,UAAU;AACvB,uBAAiB,IAAI;AACrB;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,WAAW,aAAa,OAAO,GAAG;AAC1C,mBAAa,UAAU;AACvB,uBAAiB,EAAE;AAAA,IACrB;AAEA,UAAM,OAAO,CAAC,cAAsB;AAClC,YAAM,oBAAoB,eAAe,WAAW;AACpD,qBAAe,UAAU;AAEzB,YAAM,SAAS,UAAU;AACzB,YAAM,UAAU,aAAa;AAE7B,UAAI,QAAQ,UAAU,OAAO,QAAQ;AACnC,yBAAiB,UAAU;AAC3B,eAAO,UAAU;AACjB;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,IAAI,GAAG,YAAY,iBAAiB;AAC3D,YAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAO,YAAY,MAAQ,cAAc,CAAC;AACjF,YAAM,MAAM,OAAO,SAAS,QAAQ;AACpC,YAAM,cACJ,MAAM,mBAAmB,KAAK,IAAI,iBAAiB,KAAK,KAAK,MAAM,EAAE,CAAC,IAAI;AAC5E,YAAM,aAAa,KAAK;AAAA,QACtB,OAAO;AAAA,QACP,QAAQ,SAAS,KAAK,IAAI,eAAe,WAAW;AAAA,MACtD;AAEA,YAAM,OAAO,OAAO,MAAM,GAAG,UAAU;AACvC,mBAAa,UAAU;AACvB,uBAAiB,IAAI;AACrB,aAAO,UAAU,OAAO,sBAAsB,IAAI;AAAA,IACpD;AAEA,QAAI,OAAO,YAAY,QAAQ,aAAa,QAAQ,SAAS,KAAK,QAAQ;AACxE,aAAO,UAAU,OAAO,sBAAsB,IAAI;AAAA,IACpD;AAEA,WAAO,MAAM;AACX,UAAI,OAAO,YAAY,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AACnC,eAAO,UAAU;AAAA,MACnB;AACA,qBAAe,UAAU;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,cAAc,kBAAkB,gBAAgB,IAAI,aAAa,iBAAiB,IAAI,CAAC;AAE3F,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,IACb,MAAM,cAAc,cAAc;AAAA,IAClC,yBAAyB;AAAA,EAC3B;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/utils.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...values: ClassValue[]) {\n return twMerge(clsx(values));\n}\n"],"mappings":";AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
|