@wix/legends-platform-sdk 1.8.0 → 1.10.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/dist/cjs/hooks/useTextChat/useTextChatStream.js +68 -54
- package/dist/cjs/hooks/useTextChat/useTextChatStream.js.map +1 -1
- package/dist/cjs/services/streamChatMessage.js +3 -1
- package/dist/cjs/services/streamChatMessage.js.map +1 -1
- package/dist/esm/hooks/useTextChat/useTextChatStream.js +69 -56
- package/dist/esm/hooks/useTextChat/useTextChatStream.js.map +1 -1
- package/dist/esm/services/streamChatMessage.js +3 -1
- package/dist/esm/services/streamChatMessage.js.map +1 -1
- package/dist/types/hooks/useTextChat/useTextChatStream.d.ts +11 -5
- package/dist/types/hooks/useTextChat/useTextChatStream.d.ts.map +1 -1
- package/dist/types/services/streamChatMessage.d.ts +1 -0
- package/dist/types/services/streamChatMessage.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -10,14 +10,6 @@ exports.streamChatMessage = _streamChatMessage.streamChatMessage;
|
|
|
10
10
|
// Re-exported here so consumers can reach the framework-agnostic transport
|
|
11
11
|
// alongside the hook.
|
|
12
12
|
|
|
13
|
-
// Progressive-reveal drip. We uncover the buffered reply a few chars at a time
|
|
14
|
-
// on a timer rather than dumping each network chunk in. The effective rate
|
|
15
|
-
// (~100 chars/s) is kept just under a typical chat-UI typewriter's own rate so
|
|
16
|
-
// the typer always catches up between drips, yielding a smooth type-out whether
|
|
17
|
-
// tokens trickle in or arrive in one burst.
|
|
18
|
-
const REVEAL_INTERVAL_MS = 30;
|
|
19
|
-
const REVEAL_CHARS_PER_TICK = 3;
|
|
20
|
-
|
|
21
13
|
/**
|
|
22
14
|
* A `ChatMessage` augmented with streaming metadata. The host renderer uses
|
|
23
15
|
* `streamStatus`/`streamingText` to drive its typewriter; confirmed
|
|
@@ -39,8 +31,14 @@ const messageKey = m => `${m.role}::${(m.content ?? '').trim()}`;
|
|
|
39
31
|
|
|
40
32
|
/**
|
|
41
33
|
* Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
|
|
42
|
-
* and
|
|
43
|
-
*
|
|
34
|
+
* and drips the accumulated reply into `streamingText` at a steady, time-paced
|
|
35
|
+
* rate (see `revealMsPerChar`) via requestAnimationFrame, merging the in-flight
|
|
36
|
+
* and completed streamed exchanges with the confirmed history (oldest-first).
|
|
37
|
+
* The host renderer (e.g. dolly) animates the dripped prefix with its own
|
|
38
|
+
* typewriter; pacing the drip below the typer's per-char rate keeps the typer
|
|
39
|
+
* from building a backlog it would otherwise skip through (jumpy reveal). The
|
|
40
|
+
* exchange settles only once the drip has revealed everything AND the network
|
|
41
|
+
* stream has ended, which avoids an end-of-reply snap.
|
|
44
42
|
*
|
|
45
43
|
* KNOWN LIMITATION: the streaming path does not run a function-call resolution
|
|
46
44
|
* loop — parity with the current unary behavior.
|
|
@@ -49,10 +47,9 @@ function useTextChatStream(options) {
|
|
|
49
47
|
const {
|
|
50
48
|
streamUrl,
|
|
51
49
|
limit = 50,
|
|
52
|
-
revealIntervalMs = REVEAL_INTERVAL_MS,
|
|
53
|
-
revealCharsPerTick = REVEAL_CHARS_PER_TICK,
|
|
54
50
|
headers,
|
|
55
|
-
onError
|
|
51
|
+
onError,
|
|
52
|
+
revealMsPerChar = 16
|
|
56
53
|
} = options;
|
|
57
54
|
const {
|
|
58
55
|
chatId,
|
|
@@ -96,29 +93,31 @@ function useTextChatStream(options) {
|
|
|
96
93
|
// confirmed copy, with a different id, arrives.
|
|
97
94
|
const [completedMessages, setCompletedMessages] = (0, _react.useState)([]);
|
|
98
95
|
const abortRef = (0, _react.useRef)(null);
|
|
99
|
-
//
|
|
100
|
-
const
|
|
96
|
+
// Handle of the pending reveal-drip animation frame, if any.
|
|
97
|
+
const rafRef = (0, _react.useRef)(null);
|
|
101
98
|
// Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;
|
|
102
99
|
// falling back to this keeps the history from disappearing and reappearing.
|
|
103
100
|
const lastConfirmedRef = (0, _react.useRef)([]);
|
|
104
101
|
// Monotonic per-hook counter so two sends within the same millisecond still
|
|
105
102
|
// produce distinct message ids (used as React keys downstream).
|
|
106
103
|
const sendSeqRef = (0, _react.useRef)(0);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
|
|
105
|
+
// Cancel any pending reveal-drip frame.
|
|
106
|
+
const stopRaf = (0, _react.useCallback)(() => {
|
|
107
|
+
if (rafRef.current !== null) {
|
|
108
|
+
cancelAnimationFrame(rafRef.current);
|
|
109
|
+
rafRef.current = null;
|
|
111
110
|
}
|
|
112
111
|
}, []);
|
|
113
112
|
|
|
114
|
-
// Stop any in-flight stream
|
|
113
|
+
// Stop any in-flight stream (and reveal drip) on unmount.
|
|
115
114
|
(0, _react.useEffect)(() => {
|
|
116
115
|
return () => {
|
|
117
116
|
var _abortRef$current;
|
|
118
117
|
(_abortRef$current = abortRef.current) == null || _abortRef$current.abort();
|
|
119
|
-
|
|
118
|
+
stopRaf();
|
|
120
119
|
};
|
|
121
|
-
}, [
|
|
120
|
+
}, [stopRaf]);
|
|
122
121
|
const messages = (0, _react.useMemo)(() => {
|
|
123
122
|
const converted = [...sdkMessages].reverse();
|
|
124
123
|
const confirmedMessages = converted.length > 0 ? converted : lastConfirmedRef.current;
|
|
@@ -143,12 +142,12 @@ function useTextChatStream(options) {
|
|
|
143
142
|
const send = (0, _react.useCallback)(async ({
|
|
144
143
|
message,
|
|
145
144
|
chatConfig,
|
|
146
|
-
attachments
|
|
145
|
+
attachments,
|
|
146
|
+
metadata
|
|
147
147
|
}) => {
|
|
148
148
|
var _abortRef$current2;
|
|
149
|
-
// Abort any in-flight stream
|
|
149
|
+
// Abort any in-flight stream before starting a new one.
|
|
150
150
|
(_abortRef$current2 = abortRef.current) == null || _abortRef$current2.abort();
|
|
151
|
-
stopRevealTimer();
|
|
152
151
|
const controller = new AbortController();
|
|
153
152
|
abortRef.current = controller;
|
|
154
153
|
const seq = sendSeqRef.current += 1;
|
|
@@ -169,18 +168,23 @@ function useTextChatStream(options) {
|
|
|
169
168
|
};
|
|
170
169
|
setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);
|
|
171
170
|
|
|
172
|
-
// Tokens
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let
|
|
171
|
+
// Tokens accumulate as they arrive, but we reveal them into
|
|
172
|
+
// `streamingText` at a steady time-paced rate via requestAnimationFrame.
|
|
173
|
+
// Pacing the reveal below the host typer's per-char rate stops it from
|
|
174
|
+
// building a backlog it would skip through (the jumpy/"super fast" look).
|
|
175
|
+
let accumulated = ''; // full text received so far
|
|
176
|
+
let revealedLen = 0; // how many chars we've dripped into streamingText
|
|
177
|
+
let streamDone = false; // network stream ended
|
|
178
|
+
let carry = 0; // fractional chars carried between frames
|
|
179
|
+
let lastFrameTime = 0; // 0 = uninitialized
|
|
180
|
+
|
|
177
181
|
const settle = () => {
|
|
178
|
-
|
|
179
|
-
// Mirror the
|
|
180
|
-
// host
|
|
181
|
-
// settled message shows in full with no re-type or flicker. Kept in
|
|
182
|
+
// Settle only after the drip has revealed everything AND the stream
|
|
183
|
+
// ended. Mirror the text into both `content` and `streamingText` so a
|
|
184
|
+
// host typer hands off with no re-type or flicker. Kept in
|
|
182
185
|
// `completedMessages` (stable id) so the confirmed copy from refetch is
|
|
183
186
|
// hidden and never remounts.
|
|
187
|
+
stopRaf();
|
|
184
188
|
const finalAssistantMsg = {
|
|
185
189
|
...streamingAssistantMsg,
|
|
186
190
|
content: accumulated,
|
|
@@ -189,28 +193,38 @@ function useTextChatStream(options) {
|
|
|
189
193
|
};
|
|
190
194
|
setCompletedMessages(prev => [...prev, optimisticUserMsg, finalAssistantMsg]);
|
|
191
195
|
setStreamingMessages([]);
|
|
192
|
-
// Sync the SDK's confirmed list only now — doing it mid-reveal would
|
|
193
|
-
// surface a confirmed duplicate next to the still-revealing copy.
|
|
194
196
|
void refetch();
|
|
195
197
|
};
|
|
196
|
-
const
|
|
198
|
+
const frame = now => {
|
|
199
|
+
if (lastFrameTime === 0) {
|
|
200
|
+
lastFrameTime = now;
|
|
201
|
+
}
|
|
202
|
+
const elapsedMs = now - lastFrameTime;
|
|
203
|
+
lastFrameTime = now;
|
|
197
204
|
if (revealedLen < accumulated.length) {
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
carry += elapsedMs / revealMsPerChar;
|
|
206
|
+
const step = Math.floor(carry);
|
|
207
|
+
if (step > 0) {
|
|
208
|
+
carry -= step;
|
|
209
|
+
revealedLen = Math.min(accumulated.length, revealedLen + step);
|
|
210
|
+
setStreamingMessages([optimisticUserMsg, {
|
|
211
|
+
...streamingAssistantMsg,
|
|
212
|
+
streamStatus: 'streaming',
|
|
213
|
+
streamingText: accumulated.slice(0, revealedLen)
|
|
214
|
+
}]);
|
|
215
|
+
}
|
|
216
|
+
rafRef.current = requestAnimationFrame(frame);
|
|
206
217
|
} else if (streamDone) {
|
|
207
|
-
// Network finished and the reveal has caught up — settle.
|
|
208
218
|
settle();
|
|
219
|
+
} else {
|
|
220
|
+
// Caught up, waiting for more tokens; onChunk will restart the loop.
|
|
221
|
+
stopRaf();
|
|
209
222
|
}
|
|
210
223
|
};
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
|
|
224
|
+
const startRaf = () => {
|
|
225
|
+
if (rafRef.current === null) {
|
|
226
|
+
lastFrameTime = 0;
|
|
227
|
+
rafRef.current = requestAnimationFrame(frame);
|
|
214
228
|
}
|
|
215
229
|
};
|
|
216
230
|
|
|
@@ -225,24 +239,24 @@ function useTextChatStream(options) {
|
|
|
225
239
|
namespace,
|
|
226
240
|
chatConfig,
|
|
227
241
|
attachments,
|
|
242
|
+
metadata,
|
|
228
243
|
headers
|
|
229
244
|
}, {
|
|
230
245
|
onChunk: token => {
|
|
231
246
|
accumulated += token;
|
|
232
|
-
|
|
247
|
+
startRaf();
|
|
233
248
|
},
|
|
234
249
|
onDone: () => {
|
|
235
|
-
// Let the drip finish revealing, then settle (see `tick`).
|
|
236
250
|
streamDone = true;
|
|
237
|
-
|
|
251
|
+
startRaf();
|
|
238
252
|
},
|
|
239
253
|
onError: err => {
|
|
240
|
-
|
|
254
|
+
stopRaf();
|
|
241
255
|
onError == null || onError(err);
|
|
242
256
|
setStreamingMessages([]);
|
|
243
257
|
}
|
|
244
258
|
}, controller.signal);
|
|
245
|
-
}, [streamUrl, chatId, namespace,
|
|
259
|
+
}, [streamUrl, chatId, namespace, headers, onError, refetch, revealMsPerChar, stopRaf]);
|
|
246
260
|
return {
|
|
247
261
|
status,
|
|
248
262
|
// Present for symmetry with `useTextChat`; the streaming path tracks its own
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_reactQuery","_sdkConsumer","_streamChatMessage","exports","streamChatMessage","REVEAL_INTERVAL_MS","REVEAL_CHARS_PER_TICK","messageKey","m","role","content","trim","useTextChatStream","options","streamUrl","limit","revealIntervalMs","revealCharsPerTick","headers","onError","chatId","namespace","personaChatService","useSdkConsumer","data","sdkMessages","status","fetchNextPage","refetch","useInfiniteQuery","queryKey","queryFn","pageParam","messages","nextCursor","undefined","queryChatMessages","cursor","getNextPageParam","lastPage","initialPageParam","select","pages","flatMap","page","streamingMessages","setStreamingMessages","useState","completedMessages","setCompletedMessages","abortRef","useRef","revealTimerRef","lastConfirmedRef","sendSeqRef","stopRevealTimer","useCallback","current","clearInterval","useEffect","_abortRef$current","abort","useMemo","converted","reverse","confirmedMessages","length","completedKeys","Set","map","confirmedShown","filter","has","send","message","chatConfig","attachments","_abortRef$current2","controller","AbortController","seq","userMsgId","Date","now","assistantMsgId","optimisticUserMsg","id","streamingAssistantMsg","streamStatus","streamingText","accumulated","revealedLen","streamDone","settle","finalAssistantMsg","prev","tick","Math","min","revealedText","slice","startReveal","setInterval","url","onChunk","token","onDone","err","signal","pending","fetchNext"],"sources":["../../../../src/hooks/useTextChat/useTextChatStream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { ChatMessage, ChatMessageAttachment } from '../../types';\nimport { useSdkConsumer } from '../../sdk-consumer';\nimport { streamChatMessage } from '../../services/streamChatMessage';\n\n// Re-exported here so consumers can reach the framework-agnostic transport\n// alongside the hook.\nexport {\n streamChatMessage,\n type StreamCallbacks,\n type StreamChatMessageParams,\n} from '../../services/streamChatMessage';\n\n// Progressive-reveal drip. We uncover the buffered reply a few chars at a time\n// on a timer rather than dumping each network chunk in. The effective rate\n// (~100 chars/s) is kept just under a typical chat-UI typewriter's own rate so\n// the typer always catches up between drips, yielding a smooth type-out whether\n// tokens trickle in or arrive in one burst.\nconst REVEAL_INTERVAL_MS = 30;\nconst REVEAL_CHARS_PER_TICK = 3;\n\n/**\n * A `ChatMessage` augmented with streaming metadata. The host renderer uses\n * `streamStatus`/`streamingText` to drive its typewriter; confirmed\n * (non-streamed) messages carry neither.\n */\nexport type StreamingChatMessage = ChatMessage & {\n streamingText?: string;\n streamStatus?: 'streaming' | 'settled';\n};\n\nexport interface UseTextChatStreamOptions {\n streamUrl: string;\n limit?: number;\n revealIntervalMs?: number;\n revealCharsPerTick?: number;\n // Extra headers forwarded on every stream request — e.g. an `x-xsrf-token`\n // when `streamUrl` sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n // Stream failures surfaced to the host (e.g. to report to Sentry + toast).\n onError?: (err: Error) => void;\n}\n\nexport interface SendStreamMessageParams {\n message: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n}\n\n/**\n * Identity for deduping optimistic streamed messages against confirmed ones.\n *\n * This is a *content* join: the streamed text must equal the server-persisted\n * copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-\n * whitespace differences, but heavier server-side post-processing (markdown /\n * entity normalization, an appended citation) would make the keys diverge and\n * briefly surface both copies. A shared message id would be more robust, but\n * the streamed frames carry only token deltas — no id — so content equality is\n * the best join available without backend support.\n */\nconst messageKey = (m: StreamingChatMessage) =>\n `${m.role}::${(m.content ?? '').trim()}`;\n\n/**\n * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport\n * and progressively reveals the reply, merging the in-flight and completed\n * streamed exchanges with the confirmed history (oldest-first).\n *\n * KNOWN LIMITATION: the streaming path does not run a function-call resolution\n * loop — parity with the current unary behavior.\n */\nexport function useTextChatStream(options: UseTextChatStreamOptions) {\n const {\n streamUrl,\n limit = 50,\n revealIntervalMs = REVEAL_INTERVAL_MS,\n revealCharsPerTick = REVEAL_CHARS_PER_TICK,\n headers,\n onError,\n } = options;\n\n const { chatId, namespace, personaChatService } = useSdkConsumer();\n\n const {\n data: sdkMessages = [],\n status,\n fetchNextPage,\n refetch,\n } = useInfiniteQuery({\n queryKey: ['chat-messages', chatId, namespace],\n queryFn: async ({ pageParam }: { pageParam?: string }) => {\n if (!personaChatService) {\n return { messages: [], nextCursor: undefined };\n }\n return personaChatService.queryChatMessages({\n chatId,\n namespace,\n limit,\n cursor: pageParam,\n });\n },\n getNextPageParam: (lastPage) => lastPage.nextCursor,\n initialPageParam: undefined as string | undefined,\n select: (data) =>\n data.pages.flatMap((page) => page.messages as ChatMessage[]),\n });\n\n // Local state for the in-progress streaming exchange (user + assistant messages).\n const [streamingMessages, setStreamingMessages] = useState<\n StreamingChatMessage[]\n >([]);\n // Exchanges that finished streaming, kept for the lifetime of this chat. They\n // keep their stable optimistic ids and stand in for the SDK's confirmed copies\n // (which are hidden as duplicates) — this both bridges the refetch lag and\n // avoids remounting the message (and restarting its typewriter) when the\n // confirmed copy, with a different id, arrives.\n const [completedMessages, setCompletedMessages] = useState<\n StreamingChatMessage[]\n >([]);\n const abortRef = useRef<AbortController | null>(null);\n // Interval that drips the buffered reply into the streaming message.\n const revealTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n // Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;\n // falling back to this keeps the history from disappearing and reappearing.\n const lastConfirmedRef = useRef<StreamingChatMessage[]>([]);\n // Monotonic per-hook counter so two sends within the same millisecond still\n // produce distinct message ids (used as React keys downstream).\n const sendSeqRef = useRef(0);\n\n const stopRevealTimer = useCallback(() => {\n if (revealTimerRef.current) {\n clearInterval(revealTimerRef.current);\n revealTimerRef.current = null;\n }\n }, []);\n\n // Stop any in-flight stream + reveal on unmount.\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n stopRevealTimer();\n };\n }, [stopRevealTimer]);\n\n const messages = useMemo<StreamingChatMessage[]>(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n const confirmedMessages =\n converted.length > 0 ? converted : lastConfirmedRef.current;\n\n // Show our locally-tracked streamed exchanges (stable ids) and hide the\n // SDK's confirmed duplicates. Swapping to a confirmed copy would remount the\n // message (different id) and restart its typewriter — that's the flicker.\n // The in-progress streaming messages are always shown as-is.\n const completedKeys = new Set(completedMessages.map(messageKey));\n const confirmedShown = confirmedMessages.filter(\n (m) => !completedKeys.has(messageKey(m)),\n );\n return [...confirmedShown, ...completedMessages, ...streamingMessages];\n }, [sdkMessages, streamingMessages, completedMessages]);\n\n // Remember the latest non-empty confirmed list so a transient empty during a\n // refetch doesn't blank the history.\n useEffect(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n if (converted.length > 0) {\n lastConfirmedRef.current = converted;\n }\n }, [sdkMessages]);\n\n const send = useCallback(\n async ({ message, chatConfig, attachments }: SendStreamMessageParams) => {\n // Abort any in-flight stream + reveal before starting a new one.\n abortRef.current?.abort();\n stopRevealTimer();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const seq = (sendSeqRef.current += 1);\n const userMsgId = `stream-user-${Date.now()}-${seq}`;\n const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;\n\n const optimisticUserMsg: StreamingChatMessage = {\n id: userMsgId,\n role: 'USER',\n content: message,\n attachments,\n };\n\n const streamingAssistantMsg: StreamingChatMessage = {\n id: assistantMsgId,\n role: 'ASSISTANT',\n content: '',\n streamStatus: 'streaming',\n streamingText: '',\n };\n\n setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);\n\n // Tokens are buffered into `accumulated` as they arrive; `revealedLen`\n // tracks how much the drip timer has uncovered so far.\n let accumulated = '';\n let revealedLen = 0;\n let streamDone = false;\n\n const settle = () => {\n stopRevealTimer();\n // Mirror the streamed text into both `content` and `streamingText`. A\n // host renderer can diff the two to place its typer at the end, so the\n // settled message shows in full with no re-type or flicker. Kept in\n // `completedMessages` (stable id) so the confirmed copy from refetch is\n // hidden and never remounts.\n const finalAssistantMsg: StreamingChatMessage = {\n ...streamingAssistantMsg,\n content: accumulated,\n streamStatus: 'settled',\n streamingText: accumulated,\n };\n setCompletedMessages((prev) => [\n ...prev,\n optimisticUserMsg,\n finalAssistantMsg,\n ]);\n setStreamingMessages([]);\n // Sync the SDK's confirmed list only now — doing it mid-reveal would\n // surface a confirmed duplicate next to the still-revealing copy.\n void refetch();\n };\n\n const tick = () => {\n if (revealedLen < accumulated.length) {\n revealedLen = Math.min(\n accumulated.length,\n revealedLen + revealCharsPerTick,\n );\n const revealedText = accumulated.slice(0, revealedLen);\n setStreamingMessages([\n optimisticUserMsg,\n {\n ...streamingAssistantMsg,\n content: '',\n streamStatus: 'streaming',\n streamingText: revealedText,\n },\n ]);\n } else if (streamDone) {\n // Network finished and the reveal has caught up — settle.\n settle();\n }\n };\n\n const startReveal = () => {\n if (!revealTimerRef.current) {\n revealTimerRef.current = setInterval(tick, revealIntervalMs);\n }\n };\n\n // Streaming deliberately bypasses the injected `personaChatService` (the\n // DI seam every other hook uses): SSE needs a raw fetch with cookie auth\n // to a caller-supplied url, which the Api-Key `IPersonaChatService` can't\n // model. History reads still go through the service (see the query above).\n await streamChatMessage(\n {\n url: streamUrl,\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n headers,\n },\n {\n onChunk: (token) => {\n accumulated += token;\n startReveal();\n },\n onDone: () => {\n // Let the drip finish revealing, then settle (see `tick`).\n streamDone = true;\n startReveal();\n },\n onError: (err) => {\n stopRevealTimer();\n onError?.(err);\n setStreamingMessages([]);\n },\n },\n controller.signal,\n );\n },\n [\n streamUrl,\n chatId,\n namespace,\n revealIntervalMs,\n revealCharsPerTick,\n headers,\n onError,\n refetch,\n stopRevealTimer,\n ],\n );\n\n return {\n status,\n // Present for symmetry with `useTextChat`; the streaming path tracks its own\n // optimistic state, so this is always empty.\n pending: [],\n messages,\n fetchNext,\n refetch,\n send,\n };\n\n function fetchNext() {\n void fetchNextPage();\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["_react","require","_reactQuery","_sdkConsumer","_streamChatMessage","exports","streamChatMessage","messageKey","m","role","content","trim","useTextChatStream","options","streamUrl","limit","headers","onError","revealMsPerChar","chatId","namespace","personaChatService","useSdkConsumer","data","sdkMessages","status","fetchNextPage","refetch","useInfiniteQuery","queryKey","queryFn","pageParam","messages","nextCursor","undefined","queryChatMessages","cursor","getNextPageParam","lastPage","initialPageParam","select","pages","flatMap","page","streamingMessages","setStreamingMessages","useState","completedMessages","setCompletedMessages","abortRef","useRef","rafRef","lastConfirmedRef","sendSeqRef","stopRaf","useCallback","current","cancelAnimationFrame","useEffect","_abortRef$current","abort","useMemo","converted","reverse","confirmedMessages","length","completedKeys","Set","map","confirmedShown","filter","has","send","message","chatConfig","attachments","metadata","_abortRef$current2","controller","AbortController","seq","userMsgId","Date","now","assistantMsgId","optimisticUserMsg","id","streamingAssistantMsg","streamStatus","streamingText","accumulated","revealedLen","streamDone","carry","lastFrameTime","settle","finalAssistantMsg","prev","frame","elapsedMs","step","Math","floor","min","slice","requestAnimationFrame","startRaf","url","onChunk","token","onDone","err","signal","pending","fetchNext"],"sources":["../../../../src/hooks/useTextChat/useTextChatStream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { ChatMessage, ChatMessageAttachment } from '../../types';\nimport { useSdkConsumer } from '../../sdk-consumer';\nimport { streamChatMessage } from '../../services/streamChatMessage';\n\n// Re-exported here so consumers can reach the framework-agnostic transport\n// alongside the hook.\nexport {\n streamChatMessage,\n type StreamCallbacks,\n type StreamChatMessageParams,\n} from '../../services/streamChatMessage';\n\n/**\n * A `ChatMessage` augmented with streaming metadata. The host renderer uses\n * `streamStatus`/`streamingText` to drive its typewriter; confirmed\n * (non-streamed) messages carry neither.\n */\nexport type StreamingChatMessage = ChatMessage & {\n streamingText?: string;\n streamStatus?: 'streaming' | 'settled';\n};\n\nexport interface UseTextChatStreamOptions {\n streamUrl: string;\n limit?: number;\n // Extra headers forwarded on every stream request — e.g. an `x-xsrf-token`\n // when `streamUrl` sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n // Stream failures surfaced to the host (e.g. to report to Sentry + toast).\n onError?: (err: Error) => void;\n // Milliseconds per character for the requestAnimationFrame reveal drip.\n // Default 16 (~62 chars/s) — comfortably slower than the chat UI typer's\n // ~8ms/char, so the typer never builds a backlog it would skip through.\n revealMsPerChar?: number;\n}\n\nexport interface SendStreamMessageParams {\n message: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n // Request metadata (e.g. `additionalMetadata`) forwarded to the stream\n // transport, at parity with the unary `sendChatMessage` request body.\n metadata?: object;\n}\n\n/**\n * Identity for deduping optimistic streamed messages against confirmed ones.\n *\n * This is a *content* join: the streamed text must equal the server-persisted\n * copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-\n * whitespace differences, but heavier server-side post-processing (markdown /\n * entity normalization, an appended citation) would make the keys diverge and\n * briefly surface both copies. A shared message id would be more robust, but\n * the streamed frames carry only token deltas — no id — so content equality is\n * the best join available without backend support.\n */\nconst messageKey = (m: StreamingChatMessage) =>\n `${m.role}::${(m.content ?? '').trim()}`;\n\n/**\n * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport\n * and drips the accumulated reply into `streamingText` at a steady, time-paced\n * rate (see `revealMsPerChar`) via requestAnimationFrame, merging the in-flight\n * and completed streamed exchanges with the confirmed history (oldest-first).\n * The host renderer (e.g. dolly) animates the dripped prefix with its own\n * typewriter; pacing the drip below the typer's per-char rate keeps the typer\n * from building a backlog it would otherwise skip through (jumpy reveal). The\n * exchange settles only once the drip has revealed everything AND the network\n * stream has ended, which avoids an end-of-reply snap.\n *\n * KNOWN LIMITATION: the streaming path does not run a function-call resolution\n * loop — parity with the current unary behavior.\n */\nexport function useTextChatStream(options: UseTextChatStreamOptions) {\n const {\n streamUrl,\n limit = 50,\n headers,\n onError,\n revealMsPerChar = 16,\n } = options;\n\n const { chatId, namespace, personaChatService } = useSdkConsumer();\n\n const {\n data: sdkMessages = [],\n status,\n fetchNextPage,\n refetch,\n } = useInfiniteQuery({\n queryKey: ['chat-messages', chatId, namespace],\n queryFn: async ({ pageParam }: { pageParam?: string }) => {\n if (!personaChatService) {\n return { messages: [], nextCursor: undefined };\n }\n return personaChatService.queryChatMessages({\n chatId,\n namespace,\n limit,\n cursor: pageParam,\n });\n },\n getNextPageParam: (lastPage) => lastPage.nextCursor,\n initialPageParam: undefined as string | undefined,\n select: (data) =>\n data.pages.flatMap((page) => page.messages as ChatMessage[]),\n });\n\n // Local state for the in-progress streaming exchange (user + assistant messages).\n const [streamingMessages, setStreamingMessages] = useState<\n StreamingChatMessage[]\n >([]);\n // Exchanges that finished streaming, kept for the lifetime of this chat. They\n // keep their stable optimistic ids and stand in for the SDK's confirmed copies\n // (which are hidden as duplicates) — this both bridges the refetch lag and\n // avoids remounting the message (and restarting its typewriter) when the\n // confirmed copy, with a different id, arrives.\n const [completedMessages, setCompletedMessages] = useState<\n StreamingChatMessage[]\n >([]);\n const abortRef = useRef<AbortController | null>(null);\n // Handle of the pending reveal-drip animation frame, if any.\n const rafRef = useRef<number | null>(null);\n // Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;\n // falling back to this keeps the history from disappearing and reappearing.\n const lastConfirmedRef = useRef<StreamingChatMessage[]>([]);\n // Monotonic per-hook counter so two sends within the same millisecond still\n // produce distinct message ids (used as React keys downstream).\n const sendSeqRef = useRef(0);\n\n // Cancel any pending reveal-drip frame.\n const stopRaf = useCallback(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }, []);\n\n // Stop any in-flight stream (and reveal drip) on unmount.\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n stopRaf();\n };\n }, [stopRaf]);\n\n const messages = useMemo<StreamingChatMessage[]>(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n const confirmedMessages =\n converted.length > 0 ? converted : lastConfirmedRef.current;\n\n // Show our locally-tracked streamed exchanges (stable ids) and hide the\n // SDK's confirmed duplicates. Swapping to a confirmed copy would remount the\n // message (different id) and restart its typewriter — that's the flicker.\n // The in-progress streaming messages are always shown as-is.\n const completedKeys = new Set(completedMessages.map(messageKey));\n const confirmedShown = confirmedMessages.filter(\n (m) => !completedKeys.has(messageKey(m)),\n );\n return [...confirmedShown, ...completedMessages, ...streamingMessages];\n }, [sdkMessages, streamingMessages, completedMessages]);\n\n // Remember the latest non-empty confirmed list so a transient empty during a\n // refetch doesn't blank the history.\n useEffect(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n if (converted.length > 0) {\n lastConfirmedRef.current = converted;\n }\n }, [sdkMessages]);\n\n const send = useCallback(\n async ({\n message,\n chatConfig,\n attachments,\n metadata,\n }: SendStreamMessageParams) => {\n // Abort any in-flight stream before starting a new one.\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const seq = (sendSeqRef.current += 1);\n const userMsgId = `stream-user-${Date.now()}-${seq}`;\n const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;\n\n const optimisticUserMsg: StreamingChatMessage = {\n id: userMsgId,\n role: 'USER',\n content: message,\n attachments,\n };\n\n const streamingAssistantMsg: StreamingChatMessage = {\n id: assistantMsgId,\n role: 'ASSISTANT',\n content: '',\n streamStatus: 'streaming',\n streamingText: '',\n };\n\n setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);\n\n // Tokens accumulate as they arrive, but we reveal them into\n // `streamingText` at a steady time-paced rate via requestAnimationFrame.\n // Pacing the reveal below the host typer's per-char rate stops it from\n // building a backlog it would skip through (the jumpy/\"super fast\" look).\n let accumulated = ''; // full text received so far\n let revealedLen = 0; // how many chars we've dripped into streamingText\n let streamDone = false; // network stream ended\n let carry = 0; // fractional chars carried between frames\n let lastFrameTime = 0; // 0 = uninitialized\n\n const settle = () => {\n // Settle only after the drip has revealed everything AND the stream\n // ended. Mirror the text into both `content` and `streamingText` so a\n // host typer hands off with no re-type or flicker. Kept in\n // `completedMessages` (stable id) so the confirmed copy from refetch is\n // hidden and never remounts.\n stopRaf();\n const finalAssistantMsg: StreamingChatMessage = {\n ...streamingAssistantMsg,\n content: accumulated,\n streamStatus: 'settled',\n streamingText: accumulated,\n };\n setCompletedMessages((prev) => [\n ...prev,\n optimisticUserMsg,\n finalAssistantMsg,\n ]);\n setStreamingMessages([]);\n void refetch();\n };\n\n const frame = (now: number) => {\n if (lastFrameTime === 0) {\n lastFrameTime = now;\n }\n const elapsedMs = now - lastFrameTime;\n lastFrameTime = now;\n\n if (revealedLen < accumulated.length) {\n carry += elapsedMs / revealMsPerChar;\n const step = Math.floor(carry);\n if (step > 0) {\n carry -= step;\n revealedLen = Math.min(accumulated.length, revealedLen + step);\n setStreamingMessages([\n optimisticUserMsg,\n {\n ...streamingAssistantMsg,\n streamStatus: 'streaming',\n streamingText: accumulated.slice(0, revealedLen),\n },\n ]);\n }\n rafRef.current = requestAnimationFrame(frame);\n } else if (streamDone) {\n settle();\n } else {\n // Caught up, waiting for more tokens; onChunk will restart the loop.\n stopRaf();\n }\n };\n\n const startRaf = () => {\n if (rafRef.current === null) {\n lastFrameTime = 0;\n rafRef.current = requestAnimationFrame(frame);\n }\n };\n\n // Streaming deliberately bypasses the injected `personaChatService` (the\n // DI seam every other hook uses): SSE needs a raw fetch with cookie auth\n // to a caller-supplied url, which the Api-Key `IPersonaChatService` can't\n // model. History reads still go through the service (see the query above).\n await streamChatMessage(\n {\n url: streamUrl,\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n metadata,\n headers,\n },\n {\n onChunk: (token) => {\n accumulated += token;\n startRaf();\n },\n onDone: () => {\n streamDone = true;\n startRaf();\n },\n onError: (err) => {\n stopRaf();\n onError?.(err);\n setStreamingMessages([]);\n },\n },\n controller.signal,\n );\n },\n [\n streamUrl,\n chatId,\n namespace,\n headers,\n onError,\n refetch,\n revealMsPerChar,\n stopRaf,\n ],\n );\n\n return {\n status,\n // Present for symmetry with `useTextChat`; the streaming path tracks its own\n // optimistic state, so this is always empty.\n pending: [],\n messages,\n fetchNext,\n refetch,\n send,\n };\n\n function fetchNext() {\n void fetchNextPage();\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -81,6 +81,7 @@ async function streamChatMessage(params, callbacks, signal) {
|
|
|
81
81
|
namespace,
|
|
82
82
|
chatConfig,
|
|
83
83
|
attachments,
|
|
84
|
+
metadata,
|
|
84
85
|
headers
|
|
85
86
|
} = params;
|
|
86
87
|
const {
|
|
@@ -101,7 +102,8 @@ async function streamChatMessage(params, callbacks, signal) {
|
|
|
101
102
|
message,
|
|
102
103
|
namespace,
|
|
103
104
|
chatConfig,
|
|
104
|
-
attachments
|
|
105
|
+
attachments,
|
|
106
|
+
metadata
|
|
105
107
|
}),
|
|
106
108
|
signal
|
|
107
109
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["extractContent","frame","parsed","JSON","parse","content","body","decoded","TextDecoder","decode","Uint8Array","from","atob","char","charCodeAt","inner","createObjectScanner","onObject","buffer","cursor","braceDepth","objectStart","inString","escaped","feed","text","length","slice","streamChatMessage","params","callbacks","signal","url","chatId","message","namespace","chatConfig","attachments","headers","onChunk","onDone","onError","response","fetch","method","credentials","stringify","ok","Error","status","scanForObjects","reader","getReader","decoder","done","value","read","stream","err","name"],"sources":["../../../src/services/streamChatMessage.ts"],"sourcesContent":["import type { ChatMessageAttachment } from '../types';\n\nexport type StreamCallbacks = {\n onChunk: (token: string) => void;\n onDone: () => void;\n onError: (err: Error) => void;\n};\n\nexport type StreamChatMessageParams = {\n // The caller supplies the endpoint (e.g. a proxied SSE route URL). The SDK\n // stays agnostic about base paths and gateway passthroughs.\n //\n // SECURITY: the request is sent with `credentials: 'include'`, so the session\n // cookie is delivered to whatever origin this resolves to. It must come from\n // trusted host/config — never from user input.\n url: string;\n chatId: string;\n message: string;\n namespace: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n // Extra request headers merged onto the POST. Lets the caller satisfy the\n // host's transport requirements the SDK can't know about — e.g. an\n // `x-xsrf-token` when the endpoint sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n};\n\n// Pull the assistant text out of one streamed frame. Two framings reach us: a\n// proxy route yields `{ content }` objects, while the gateway wraps each frame\n// as `{ body: base64(json) }`. Returns null for frames without content.\nexport function extractContent(frame: string): string | null {\n try {\n const parsed = JSON.parse(frame);\n if (typeof parsed.content === 'string') {\n return parsed.content;\n }\n if (typeof parsed.body === 'string') {\n const decoded = new TextDecoder().decode(\n Uint8Array.from(atob(parsed.body), (char) => char.charCodeAt(0)),\n );\n const inner = JSON.parse(decoded);\n return typeof inner.content === 'string' ? inner.content : null;\n }\n } catch {\n // Incomplete or non-object frame — skip it.\n }\n return null;\n}\n\n// Feeds decoded text through a running scanner that emits each complete\n// top-level `{…}` object as its closing brace arrives. This keeps a message\n// that's split across reads intact, and parses a newline-free JSON array (the\n// gateway framing) incrementally. Anything outside a top-level object is ignored.\nexport function createObjectScanner(onObject: (frame: string) => void) {\n let buffer = '';\n let cursor = 0;\n let braceDepth = 0;\n let objectStart = -1;\n let inString = false;\n let escaped = false;\n\n return function feed(text: string) {\n buffer += text;\n for (; cursor < buffer.length; cursor++) {\n const char = buffer[cursor];\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (char === '\\\\') {\n escaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n } else if (char === '{') {\n if (braceDepth === 0) {\n objectStart = cursor;\n }\n braceDepth++;\n } else if (char === '}' && braceDepth > 0) {\n braceDepth--;\n if (braceDepth === 0 && objectStart >= 0) {\n onObject(buffer.slice(objectStart, cursor + 1));\n objectStart = -1;\n }\n }\n }\n };\n}\n\n/**\n * Framework-agnostic streaming transport. POSTs the message to the\n * caller-supplied SSE endpoint, reads the response body incrementally, and\n * fires `onChunk` per assistant token, `onDone` on close, `onError` on failure.\n * A caller-supplied `AbortSignal` cancels the request; `AbortError` is swallowed.\n */\nexport async function streamChatMessage(\n params: StreamChatMessageParams,\n callbacks: StreamCallbacks,\n signal?: AbortSignal,\n): Promise<void> {\n const {
|
|
1
|
+
{"version":3,"names":["extractContent","frame","parsed","JSON","parse","content","body","decoded","TextDecoder","decode","Uint8Array","from","atob","char","charCodeAt","inner","createObjectScanner","onObject","buffer","cursor","braceDepth","objectStart","inString","escaped","feed","text","length","slice","streamChatMessage","params","callbacks","signal","url","chatId","message","namespace","chatConfig","attachments","metadata","headers","onChunk","onDone","onError","response","fetch","method","credentials","stringify","ok","Error","status","scanForObjects","reader","getReader","decoder","done","value","read","stream","err","name"],"sources":["../../../src/services/streamChatMessage.ts"],"sourcesContent":["import type { ChatMessageAttachment } from '../types';\n\nexport type StreamCallbacks = {\n onChunk: (token: string) => void;\n onDone: () => void;\n onError: (err: Error) => void;\n};\n\nexport type StreamChatMessageParams = {\n // The caller supplies the endpoint (e.g. a proxied SSE route URL). The SDK\n // stays agnostic about base paths and gateway passthroughs.\n //\n // SECURITY: the request is sent with `credentials: 'include'`, so the session\n // cookie is delivered to whatever origin this resolves to. It must come from\n // trusted host/config — never from user input.\n url: string;\n chatId: string;\n message: string;\n namespace: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n // Request metadata forwarded to the RPC (e.g. `additionalMetadata` carrying\n // opening-message context). Mirrors the top-level `metadata` object the unary\n // `sendChatMessage` request accepts, so a streamed send carries it identically.\n metadata?: object;\n // Extra request headers merged onto the POST. Lets the caller satisfy the\n // host's transport requirements the SDK can't know about — e.g. an\n // `x-xsrf-token` when the endpoint sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n};\n\n// Pull the assistant text out of one streamed frame. Two framings reach us: a\n// proxy route yields `{ content }` objects, while the gateway wraps each frame\n// as `{ body: base64(json) }`. Returns null for frames without content.\nexport function extractContent(frame: string): string | null {\n try {\n const parsed = JSON.parse(frame);\n if (typeof parsed.content === 'string') {\n return parsed.content;\n }\n if (typeof parsed.body === 'string') {\n const decoded = new TextDecoder().decode(\n Uint8Array.from(atob(parsed.body), (char) => char.charCodeAt(0)),\n );\n const inner = JSON.parse(decoded);\n return typeof inner.content === 'string' ? inner.content : null;\n }\n } catch {\n // Incomplete or non-object frame — skip it.\n }\n return null;\n}\n\n// Feeds decoded text through a running scanner that emits each complete\n// top-level `{…}` object as its closing brace arrives. This keeps a message\n// that's split across reads intact, and parses a newline-free JSON array (the\n// gateway framing) incrementally. Anything outside a top-level object is ignored.\nexport function createObjectScanner(onObject: (frame: string) => void) {\n let buffer = '';\n let cursor = 0;\n let braceDepth = 0;\n let objectStart = -1;\n let inString = false;\n let escaped = false;\n\n return function feed(text: string) {\n buffer += text;\n for (; cursor < buffer.length; cursor++) {\n const char = buffer[cursor];\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (char === '\\\\') {\n escaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n } else if (char === '{') {\n if (braceDepth === 0) {\n objectStart = cursor;\n }\n braceDepth++;\n } else if (char === '}' && braceDepth > 0) {\n braceDepth--;\n if (braceDepth === 0 && objectStart >= 0) {\n onObject(buffer.slice(objectStart, cursor + 1));\n objectStart = -1;\n }\n }\n }\n };\n}\n\n/**\n * Framework-agnostic streaming transport. POSTs the message to the\n * caller-supplied SSE endpoint, reads the response body incrementally, and\n * fires `onChunk` per assistant token, `onDone` on close, `onError` on failure.\n * A caller-supplied `AbortSignal` cancels the request; `AbortError` is swallowed.\n */\nexport async function streamChatMessage(\n params: StreamChatMessageParams,\n callbacks: StreamCallbacks,\n signal?: AbortSignal,\n): Promise<void> {\n const {\n url,\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n metadata,\n headers,\n } = params;\n const { onChunk, onDone, onError } = callbacks;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify({\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n metadata,\n }),\n signal,\n });\n\n if (!response.ok || !response.body) {\n onError(new Error(`Stream request failed: ${response.status}`));\n return;\n }\n\n const scanForObjects = createObjectScanner((frame) => {\n const content = extractContent(frame);\n if (content !== null) {\n onChunk(content);\n }\n });\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n scanForObjects(decoder.decode(value, { stream: true }));\n }\n scanForObjects(decoder.decode());\n onDone();\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n onError(err as Error);\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -7,14 +7,6 @@ import { streamChatMessage } from '../../services/streamChatMessage';
|
|
|
7
7
|
// alongside the hook.
|
|
8
8
|
export { streamChatMessage } from '../../services/streamChatMessage';
|
|
9
9
|
|
|
10
|
-
// Progressive-reveal drip. We uncover the buffered reply a few chars at a time
|
|
11
|
-
// on a timer rather than dumping each network chunk in. The effective rate
|
|
12
|
-
// (~100 chars/s) is kept just under a typical chat-UI typewriter's own rate so
|
|
13
|
-
// the typer always catches up between drips, yielding a smooth type-out whether
|
|
14
|
-
// tokens trickle in or arrive in one burst.
|
|
15
|
-
const REVEAL_INTERVAL_MS = 30;
|
|
16
|
-
const REVEAL_CHARS_PER_TICK = 3;
|
|
17
|
-
|
|
18
10
|
/**
|
|
19
11
|
* A `ChatMessage` augmented with streaming metadata. The host renderer uses
|
|
20
12
|
* `streamStatus`/`streamingText` to drive its typewriter; confirmed
|
|
@@ -36,8 +28,14 @@ const messageKey = m => `${m.role}::${(m.content ?? '').trim()}`;
|
|
|
36
28
|
|
|
37
29
|
/**
|
|
38
30
|
* Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
|
|
39
|
-
* and
|
|
40
|
-
*
|
|
31
|
+
* and drips the accumulated reply into `streamingText` at a steady, time-paced
|
|
32
|
+
* rate (see `revealMsPerChar`) via requestAnimationFrame, merging the in-flight
|
|
33
|
+
* and completed streamed exchanges with the confirmed history (oldest-first).
|
|
34
|
+
* The host renderer (e.g. dolly) animates the dripped prefix with its own
|
|
35
|
+
* typewriter; pacing the drip below the typer's per-char rate keeps the typer
|
|
36
|
+
* from building a backlog it would otherwise skip through (jumpy reveal). The
|
|
37
|
+
* exchange settles only once the drip has revealed everything AND the network
|
|
38
|
+
* stream has ended, which avoids an end-of-reply snap.
|
|
41
39
|
*
|
|
42
40
|
* KNOWN LIMITATION: the streaming path does not run a function-call resolution
|
|
43
41
|
* loop — parity with the current unary behavior.
|
|
@@ -46,12 +44,10 @@ export function useTextChatStream(options) {
|
|
|
46
44
|
const streamUrl = options.streamUrl,
|
|
47
45
|
_options$limit = options.limit,
|
|
48
46
|
limit = _options$limit === void 0 ? 50 : _options$limit,
|
|
49
|
-
_options$revealInterv = options.revealIntervalMs,
|
|
50
|
-
revealIntervalMs = _options$revealInterv === void 0 ? REVEAL_INTERVAL_MS : _options$revealInterv,
|
|
51
|
-
_options$revealCharsP = options.revealCharsPerTick,
|
|
52
|
-
revealCharsPerTick = _options$revealCharsP === void 0 ? REVEAL_CHARS_PER_TICK : _options$revealCharsP,
|
|
53
47
|
headers = options.headers,
|
|
54
|
-
onError = options.onError
|
|
48
|
+
onError = options.onError,
|
|
49
|
+
_options$revealMsPerC = options.revealMsPerChar,
|
|
50
|
+
revealMsPerChar = _options$revealMsPerC === void 0 ? 16 : _options$revealMsPerC;
|
|
55
51
|
const _useSdkConsumer = useSdkConsumer(),
|
|
56
52
|
chatId = _useSdkConsumer.chatId,
|
|
57
53
|
namespace = _useSdkConsumer.namespace,
|
|
@@ -96,29 +92,31 @@ export function useTextChatStream(options) {
|
|
|
96
92
|
completedMessages = _useState2[0],
|
|
97
93
|
setCompletedMessages = _useState2[1];
|
|
98
94
|
const abortRef = useRef(null);
|
|
99
|
-
//
|
|
100
|
-
const
|
|
95
|
+
// Handle of the pending reveal-drip animation frame, if any.
|
|
96
|
+
const rafRef = useRef(null);
|
|
101
97
|
// Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;
|
|
102
98
|
// falling back to this keeps the history from disappearing and reappearing.
|
|
103
99
|
const lastConfirmedRef = useRef([]);
|
|
104
100
|
// Monotonic per-hook counter so two sends within the same millisecond still
|
|
105
101
|
// produce distinct message ids (used as React keys downstream).
|
|
106
102
|
const sendSeqRef = useRef(0);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
|
|
104
|
+
// Cancel any pending reveal-drip frame.
|
|
105
|
+
const stopRaf = useCallback(() => {
|
|
106
|
+
if (rafRef.current !== null) {
|
|
107
|
+
cancelAnimationFrame(rafRef.current);
|
|
108
|
+
rafRef.current = null;
|
|
111
109
|
}
|
|
112
110
|
}, []);
|
|
113
111
|
|
|
114
|
-
// Stop any in-flight stream
|
|
112
|
+
// Stop any in-flight stream (and reveal drip) on unmount.
|
|
115
113
|
useEffect(() => {
|
|
116
114
|
return () => {
|
|
117
115
|
var _abortRef$current;
|
|
118
116
|
(_abortRef$current = abortRef.current) == null || _abortRef$current.abort();
|
|
119
|
-
|
|
117
|
+
stopRaf();
|
|
120
118
|
};
|
|
121
|
-
}, [
|
|
119
|
+
}, [stopRaf]);
|
|
122
120
|
const messages = useMemo(() => {
|
|
123
121
|
const converted = [...sdkMessages].reverse();
|
|
124
122
|
const confirmedMessages = converted.length > 0 ? converted : lastConfirmedRef.current;
|
|
@@ -144,10 +142,10 @@ export function useTextChatStream(options) {
|
|
|
144
142
|
var _abortRef$current2;
|
|
145
143
|
let message = _ref2.message,
|
|
146
144
|
chatConfig = _ref2.chatConfig,
|
|
147
|
-
attachments = _ref2.attachments
|
|
148
|
-
|
|
145
|
+
attachments = _ref2.attachments,
|
|
146
|
+
metadata = _ref2.metadata;
|
|
147
|
+
// Abort any in-flight stream before starting a new one.
|
|
149
148
|
(_abortRef$current2 = abortRef.current) == null || _abortRef$current2.abort();
|
|
150
|
-
stopRevealTimer();
|
|
151
149
|
const controller = new AbortController();
|
|
152
150
|
abortRef.current = controller;
|
|
153
151
|
const seq = sendSeqRef.current += 1;
|
|
@@ -168,18 +166,23 @@ export function useTextChatStream(options) {
|
|
|
168
166
|
};
|
|
169
167
|
setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);
|
|
170
168
|
|
|
171
|
-
// Tokens
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
let
|
|
169
|
+
// Tokens accumulate as they arrive, but we reveal them into
|
|
170
|
+
// `streamingText` at a steady time-paced rate via requestAnimationFrame.
|
|
171
|
+
// Pacing the reveal below the host typer's per-char rate stops it from
|
|
172
|
+
// building a backlog it would skip through (the jumpy/"super fast" look).
|
|
173
|
+
let accumulated = ''; // full text received so far
|
|
174
|
+
let revealedLen = 0; // how many chars we've dripped into streamingText
|
|
175
|
+
let streamDone = false; // network stream ended
|
|
176
|
+
let carry = 0; // fractional chars carried between frames
|
|
177
|
+
let lastFrameTime = 0; // 0 = uninitialized
|
|
178
|
+
|
|
176
179
|
const settle = () => {
|
|
177
|
-
|
|
178
|
-
// Mirror the
|
|
179
|
-
// host
|
|
180
|
-
// settled message shows in full with no re-type or flicker. Kept in
|
|
180
|
+
// Settle only after the drip has revealed everything AND the stream
|
|
181
|
+
// ended. Mirror the text into both `content` and `streamingText` so a
|
|
182
|
+
// host typer hands off with no re-type or flicker. Kept in
|
|
181
183
|
// `completedMessages` (stable id) so the confirmed copy from refetch is
|
|
182
184
|
// hidden and never remounts.
|
|
185
|
+
stopRaf();
|
|
183
186
|
const finalAssistantMsg = {
|
|
184
187
|
...streamingAssistantMsg,
|
|
185
188
|
content: accumulated,
|
|
@@ -188,28 +191,38 @@ export function useTextChatStream(options) {
|
|
|
188
191
|
};
|
|
189
192
|
setCompletedMessages(prev => [...prev, optimisticUserMsg, finalAssistantMsg]);
|
|
190
193
|
setStreamingMessages([]);
|
|
191
|
-
// Sync the SDK's confirmed list only now — doing it mid-reveal would
|
|
192
|
-
// surface a confirmed duplicate next to the still-revealing copy.
|
|
193
194
|
void refetch();
|
|
194
195
|
};
|
|
195
|
-
const
|
|
196
|
+
const frame = now => {
|
|
197
|
+
if (lastFrameTime === 0) {
|
|
198
|
+
lastFrameTime = now;
|
|
199
|
+
}
|
|
200
|
+
const elapsedMs = now - lastFrameTime;
|
|
201
|
+
lastFrameTime = now;
|
|
196
202
|
if (revealedLen < accumulated.length) {
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
carry += elapsedMs / revealMsPerChar;
|
|
204
|
+
const step = Math.floor(carry);
|
|
205
|
+
if (step > 0) {
|
|
206
|
+
carry -= step;
|
|
207
|
+
revealedLen = Math.min(accumulated.length, revealedLen + step);
|
|
208
|
+
setStreamingMessages([optimisticUserMsg, {
|
|
209
|
+
...streamingAssistantMsg,
|
|
210
|
+
streamStatus: 'streaming',
|
|
211
|
+
streamingText: accumulated.slice(0, revealedLen)
|
|
212
|
+
}]);
|
|
213
|
+
}
|
|
214
|
+
rafRef.current = requestAnimationFrame(frame);
|
|
205
215
|
} else if (streamDone) {
|
|
206
|
-
// Network finished and the reveal has caught up — settle.
|
|
207
216
|
settle();
|
|
217
|
+
} else {
|
|
218
|
+
// Caught up, waiting for more tokens; onChunk will restart the loop.
|
|
219
|
+
stopRaf();
|
|
208
220
|
}
|
|
209
221
|
};
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
212
|
-
|
|
222
|
+
const startRaf = () => {
|
|
223
|
+
if (rafRef.current === null) {
|
|
224
|
+
lastFrameTime = 0;
|
|
225
|
+
rafRef.current = requestAnimationFrame(frame);
|
|
213
226
|
}
|
|
214
227
|
};
|
|
215
228
|
|
|
@@ -224,24 +237,24 @@ export function useTextChatStream(options) {
|
|
|
224
237
|
namespace,
|
|
225
238
|
chatConfig,
|
|
226
239
|
attachments,
|
|
240
|
+
metadata,
|
|
227
241
|
headers
|
|
228
242
|
}, {
|
|
229
243
|
onChunk: token => {
|
|
230
244
|
accumulated += token;
|
|
231
|
-
|
|
245
|
+
startRaf();
|
|
232
246
|
},
|
|
233
247
|
onDone: () => {
|
|
234
|
-
// Let the drip finish revealing, then settle (see `tick`).
|
|
235
248
|
streamDone = true;
|
|
236
|
-
|
|
249
|
+
startRaf();
|
|
237
250
|
},
|
|
238
251
|
onError: err => {
|
|
239
|
-
|
|
252
|
+
stopRaf();
|
|
240
253
|
onError == null || onError(err);
|
|
241
254
|
setStreamingMessages([]);
|
|
242
255
|
}
|
|
243
256
|
}, controller.signal);
|
|
244
|
-
}, [streamUrl, chatId, namespace,
|
|
257
|
+
}, [streamUrl, chatId, namespace, headers, onError, refetch, revealMsPerChar, stopRaf]);
|
|
245
258
|
return {
|
|
246
259
|
status,
|
|
247
260
|
// Present for symmetry with `useTextChat`; the streaming path tracks its own
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","useInfiniteQuery","useSdkConsumer","streamChatMessage","REVEAL_INTERVAL_MS","REVEAL_CHARS_PER_TICK","messageKey","m","role","content","trim","useTextChatStream","options","streamUrl","_options$limit","limit","_options$revealInterv","revealIntervalMs","_options$revealCharsP","revealCharsPerTick","headers","onError","_useSdkConsumer","chatId","namespace","personaChatService","_useInfiniteQuery","queryKey","queryFn","_ref","pageParam","messages","nextCursor","undefined","queryChatMessages","cursor","getNextPageParam","lastPage","initialPageParam","select","data","pages","flatMap","page","_useInfiniteQuery$dat","sdkMessages","status","fetchNextPage","refetch","_useState","streamingMessages","setStreamingMessages","_useState2","completedMessages","setCompletedMessages","abortRef","revealTimerRef","lastConfirmedRef","sendSeqRef","stopRevealTimer","current","clearInterval","_abortRef$current","abort","converted","reverse","confirmedMessages","length","completedKeys","Set","map","confirmedShown","filter","has","send","_ref2","_abortRef$current2","message","chatConfig","attachments","controller","AbortController","seq","userMsgId","Date","now","assistantMsgId","optimisticUserMsg","id","streamingAssistantMsg","streamStatus","streamingText","accumulated","revealedLen","streamDone","settle","finalAssistantMsg","prev","tick","Math","min","revealedText","slice","startReveal","setInterval","url","onChunk","token","onDone","err","signal","pending","fetchNext"],"sources":["../../../../src/hooks/useTextChat/useTextChatStream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { ChatMessage, ChatMessageAttachment } from '../../types';\nimport { useSdkConsumer } from '../../sdk-consumer';\nimport { streamChatMessage } from '../../services/streamChatMessage';\n\n// Re-exported here so consumers can reach the framework-agnostic transport\n// alongside the hook.\nexport {\n streamChatMessage,\n type StreamCallbacks,\n type StreamChatMessageParams,\n} from '../../services/streamChatMessage';\n\n// Progressive-reveal drip. We uncover the buffered reply a few chars at a time\n// on a timer rather than dumping each network chunk in. The effective rate\n// (~100 chars/s) is kept just under a typical chat-UI typewriter's own rate so\n// the typer always catches up between drips, yielding a smooth type-out whether\n// tokens trickle in or arrive in one burst.\nconst REVEAL_INTERVAL_MS = 30;\nconst REVEAL_CHARS_PER_TICK = 3;\n\n/**\n * A `ChatMessage` augmented with streaming metadata. The host renderer uses\n * `streamStatus`/`streamingText` to drive its typewriter; confirmed\n * (non-streamed) messages carry neither.\n */\nexport type StreamingChatMessage = ChatMessage & {\n streamingText?: string;\n streamStatus?: 'streaming' | 'settled';\n};\n\nexport interface UseTextChatStreamOptions {\n streamUrl: string;\n limit?: number;\n revealIntervalMs?: number;\n revealCharsPerTick?: number;\n // Extra headers forwarded on every stream request — e.g. an `x-xsrf-token`\n // when `streamUrl` sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n // Stream failures surfaced to the host (e.g. to report to Sentry + toast).\n onError?: (err: Error) => void;\n}\n\nexport interface SendStreamMessageParams {\n message: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n}\n\n/**\n * Identity for deduping optimistic streamed messages against confirmed ones.\n *\n * This is a *content* join: the streamed text must equal the server-persisted\n * copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-\n * whitespace differences, but heavier server-side post-processing (markdown /\n * entity normalization, an appended citation) would make the keys diverge and\n * briefly surface both copies. A shared message id would be more robust, but\n * the streamed frames carry only token deltas — no id — so content equality is\n * the best join available without backend support.\n */\nconst messageKey = (m: StreamingChatMessage) =>\n `${m.role}::${(m.content ?? '').trim()}`;\n\n/**\n * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport\n * and progressively reveals the reply, merging the in-flight and completed\n * streamed exchanges with the confirmed history (oldest-first).\n *\n * KNOWN LIMITATION: the streaming path does not run a function-call resolution\n * loop — parity with the current unary behavior.\n */\nexport function useTextChatStream(options: UseTextChatStreamOptions) {\n const {\n streamUrl,\n limit = 50,\n revealIntervalMs = REVEAL_INTERVAL_MS,\n revealCharsPerTick = REVEAL_CHARS_PER_TICK,\n headers,\n onError,\n } = options;\n\n const { chatId, namespace, personaChatService } = useSdkConsumer();\n\n const {\n data: sdkMessages = [],\n status,\n fetchNextPage,\n refetch,\n } = useInfiniteQuery({\n queryKey: ['chat-messages', chatId, namespace],\n queryFn: async ({ pageParam }: { pageParam?: string }) => {\n if (!personaChatService) {\n return { messages: [], nextCursor: undefined };\n }\n return personaChatService.queryChatMessages({\n chatId,\n namespace,\n limit,\n cursor: pageParam,\n });\n },\n getNextPageParam: (lastPage) => lastPage.nextCursor,\n initialPageParam: undefined as string | undefined,\n select: (data) =>\n data.pages.flatMap((page) => page.messages as ChatMessage[]),\n });\n\n // Local state for the in-progress streaming exchange (user + assistant messages).\n const [streamingMessages, setStreamingMessages] = useState<\n StreamingChatMessage[]\n >([]);\n // Exchanges that finished streaming, kept for the lifetime of this chat. They\n // keep their stable optimistic ids and stand in for the SDK's confirmed copies\n // (which are hidden as duplicates) — this both bridges the refetch lag and\n // avoids remounting the message (and restarting its typewriter) when the\n // confirmed copy, with a different id, arrives.\n const [completedMessages, setCompletedMessages] = useState<\n StreamingChatMessage[]\n >([]);\n const abortRef = useRef<AbortController | null>(null);\n // Interval that drips the buffered reply into the streaming message.\n const revealTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n // Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;\n // falling back to this keeps the history from disappearing and reappearing.\n const lastConfirmedRef = useRef<StreamingChatMessage[]>([]);\n // Monotonic per-hook counter so two sends within the same millisecond still\n // produce distinct message ids (used as React keys downstream).\n const sendSeqRef = useRef(0);\n\n const stopRevealTimer = useCallback(() => {\n if (revealTimerRef.current) {\n clearInterval(revealTimerRef.current);\n revealTimerRef.current = null;\n }\n }, []);\n\n // Stop any in-flight stream + reveal on unmount.\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n stopRevealTimer();\n };\n }, [stopRevealTimer]);\n\n const messages = useMemo<StreamingChatMessage[]>(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n const confirmedMessages =\n converted.length > 0 ? converted : lastConfirmedRef.current;\n\n // Show our locally-tracked streamed exchanges (stable ids) and hide the\n // SDK's confirmed duplicates. Swapping to a confirmed copy would remount the\n // message (different id) and restart its typewriter — that's the flicker.\n // The in-progress streaming messages are always shown as-is.\n const completedKeys = new Set(completedMessages.map(messageKey));\n const confirmedShown = confirmedMessages.filter(\n (m) => !completedKeys.has(messageKey(m)),\n );\n return [...confirmedShown, ...completedMessages, ...streamingMessages];\n }, [sdkMessages, streamingMessages, completedMessages]);\n\n // Remember the latest non-empty confirmed list so a transient empty during a\n // refetch doesn't blank the history.\n useEffect(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n if (converted.length > 0) {\n lastConfirmedRef.current = converted;\n }\n }, [sdkMessages]);\n\n const send = useCallback(\n async ({ message, chatConfig, attachments }: SendStreamMessageParams) => {\n // Abort any in-flight stream + reveal before starting a new one.\n abortRef.current?.abort();\n stopRevealTimer();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const seq = (sendSeqRef.current += 1);\n const userMsgId = `stream-user-${Date.now()}-${seq}`;\n const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;\n\n const optimisticUserMsg: StreamingChatMessage = {\n id: userMsgId,\n role: 'USER',\n content: message,\n attachments,\n };\n\n const streamingAssistantMsg: StreamingChatMessage = {\n id: assistantMsgId,\n role: 'ASSISTANT',\n content: '',\n streamStatus: 'streaming',\n streamingText: '',\n };\n\n setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);\n\n // Tokens are buffered into `accumulated` as they arrive; `revealedLen`\n // tracks how much the drip timer has uncovered so far.\n let accumulated = '';\n let revealedLen = 0;\n let streamDone = false;\n\n const settle = () => {\n stopRevealTimer();\n // Mirror the streamed text into both `content` and `streamingText`. A\n // host renderer can diff the two to place its typer at the end, so the\n // settled message shows in full with no re-type or flicker. Kept in\n // `completedMessages` (stable id) so the confirmed copy from refetch is\n // hidden and never remounts.\n const finalAssistantMsg: StreamingChatMessage = {\n ...streamingAssistantMsg,\n content: accumulated,\n streamStatus: 'settled',\n streamingText: accumulated,\n };\n setCompletedMessages((prev) => [\n ...prev,\n optimisticUserMsg,\n finalAssistantMsg,\n ]);\n setStreamingMessages([]);\n // Sync the SDK's confirmed list only now — doing it mid-reveal would\n // surface a confirmed duplicate next to the still-revealing copy.\n void refetch();\n };\n\n const tick = () => {\n if (revealedLen < accumulated.length) {\n revealedLen = Math.min(\n accumulated.length,\n revealedLen + revealCharsPerTick,\n );\n const revealedText = accumulated.slice(0, revealedLen);\n setStreamingMessages([\n optimisticUserMsg,\n {\n ...streamingAssistantMsg,\n content: '',\n streamStatus: 'streaming',\n streamingText: revealedText,\n },\n ]);\n } else if (streamDone) {\n // Network finished and the reveal has caught up — settle.\n settle();\n }\n };\n\n const startReveal = () => {\n if (!revealTimerRef.current) {\n revealTimerRef.current = setInterval(tick, revealIntervalMs);\n }\n };\n\n // Streaming deliberately bypasses the injected `personaChatService` (the\n // DI seam every other hook uses): SSE needs a raw fetch with cookie auth\n // to a caller-supplied url, which the Api-Key `IPersonaChatService` can't\n // model. History reads still go through the service (see the query above).\n await streamChatMessage(\n {\n url: streamUrl,\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n headers,\n },\n {\n onChunk: (token) => {\n accumulated += token;\n startReveal();\n },\n onDone: () => {\n // Let the drip finish revealing, then settle (see `tick`).\n streamDone = true;\n startReveal();\n },\n onError: (err) => {\n stopRevealTimer();\n onError?.(err);\n setStreamingMessages([]);\n },\n },\n controller.signal,\n );\n },\n [\n streamUrl,\n chatId,\n namespace,\n revealIntervalMs,\n revealCharsPerTick,\n headers,\n onError,\n refetch,\n stopRevealTimer,\n ],\n );\n\n return {\n status,\n // Present for symmetry with `useTextChat`; the streaming path tracks its own\n // optimistic state, so this is always empty.\n pending: [],\n messages,\n fetchNext,\n refetch,\n send,\n };\n\n function fetchNext() {\n void fetchNextPage();\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","useInfiniteQuery","useSdkConsumer","streamChatMessage","messageKey","m","role","content","trim","useTextChatStream","options","streamUrl","_options$limit","limit","headers","onError","_options$revealMsPerC","revealMsPerChar","_useSdkConsumer","chatId","namespace","personaChatService","_useInfiniteQuery","queryKey","queryFn","_ref","pageParam","messages","nextCursor","undefined","queryChatMessages","cursor","getNextPageParam","lastPage","initialPageParam","select","data","pages","flatMap","page","_useInfiniteQuery$dat","sdkMessages","status","fetchNextPage","refetch","_useState","streamingMessages","setStreamingMessages","_useState2","completedMessages","setCompletedMessages","abortRef","rafRef","lastConfirmedRef","sendSeqRef","stopRaf","current","cancelAnimationFrame","_abortRef$current","abort","converted","reverse","confirmedMessages","length","completedKeys","Set","map","confirmedShown","filter","has","send","_ref2","_abortRef$current2","message","chatConfig","attachments","metadata","controller","AbortController","seq","userMsgId","Date","now","assistantMsgId","optimisticUserMsg","id","streamingAssistantMsg","streamStatus","streamingText","accumulated","revealedLen","streamDone","carry","lastFrameTime","settle","finalAssistantMsg","prev","frame","elapsedMs","step","Math","floor","min","slice","requestAnimationFrame","startRaf","url","onChunk","token","onDone","err","signal","pending","fetchNext"],"sources":["../../../../src/hooks/useTextChat/useTextChatStream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { ChatMessage, ChatMessageAttachment } from '../../types';\nimport { useSdkConsumer } from '../../sdk-consumer';\nimport { streamChatMessage } from '../../services/streamChatMessage';\n\n// Re-exported here so consumers can reach the framework-agnostic transport\n// alongside the hook.\nexport {\n streamChatMessage,\n type StreamCallbacks,\n type StreamChatMessageParams,\n} from '../../services/streamChatMessage';\n\n/**\n * A `ChatMessage` augmented with streaming metadata. The host renderer uses\n * `streamStatus`/`streamingText` to drive its typewriter; confirmed\n * (non-streamed) messages carry neither.\n */\nexport type StreamingChatMessage = ChatMessage & {\n streamingText?: string;\n streamStatus?: 'streaming' | 'settled';\n};\n\nexport interface UseTextChatStreamOptions {\n streamUrl: string;\n limit?: number;\n // Extra headers forwarded on every stream request — e.g. an `x-xsrf-token`\n // when `streamUrl` sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n // Stream failures surfaced to the host (e.g. to report to Sentry + toast).\n onError?: (err: Error) => void;\n // Milliseconds per character for the requestAnimationFrame reveal drip.\n // Default 16 (~62 chars/s) — comfortably slower than the chat UI typer's\n // ~8ms/char, so the typer never builds a backlog it would skip through.\n revealMsPerChar?: number;\n}\n\nexport interface SendStreamMessageParams {\n message: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n // Request metadata (e.g. `additionalMetadata`) forwarded to the stream\n // transport, at parity with the unary `sendChatMessage` request body.\n metadata?: object;\n}\n\n/**\n * Identity for deduping optimistic streamed messages against confirmed ones.\n *\n * This is a *content* join: the streamed text must equal the server-persisted\n * copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-\n * whitespace differences, but heavier server-side post-processing (markdown /\n * entity normalization, an appended citation) would make the keys diverge and\n * briefly surface both copies. A shared message id would be more robust, but\n * the streamed frames carry only token deltas — no id — so content equality is\n * the best join available without backend support.\n */\nconst messageKey = (m: StreamingChatMessage) =>\n `${m.role}::${(m.content ?? '').trim()}`;\n\n/**\n * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport\n * and drips the accumulated reply into `streamingText` at a steady, time-paced\n * rate (see `revealMsPerChar`) via requestAnimationFrame, merging the in-flight\n * and completed streamed exchanges with the confirmed history (oldest-first).\n * The host renderer (e.g. dolly) animates the dripped prefix with its own\n * typewriter; pacing the drip below the typer's per-char rate keeps the typer\n * from building a backlog it would otherwise skip through (jumpy reveal). The\n * exchange settles only once the drip has revealed everything AND the network\n * stream has ended, which avoids an end-of-reply snap.\n *\n * KNOWN LIMITATION: the streaming path does not run a function-call resolution\n * loop — parity with the current unary behavior.\n */\nexport function useTextChatStream(options: UseTextChatStreamOptions) {\n const {\n streamUrl,\n limit = 50,\n headers,\n onError,\n revealMsPerChar = 16,\n } = options;\n\n const { chatId, namespace, personaChatService } = useSdkConsumer();\n\n const {\n data: sdkMessages = [],\n status,\n fetchNextPage,\n refetch,\n } = useInfiniteQuery({\n queryKey: ['chat-messages', chatId, namespace],\n queryFn: async ({ pageParam }: { pageParam?: string }) => {\n if (!personaChatService) {\n return { messages: [], nextCursor: undefined };\n }\n return personaChatService.queryChatMessages({\n chatId,\n namespace,\n limit,\n cursor: pageParam,\n });\n },\n getNextPageParam: (lastPage) => lastPage.nextCursor,\n initialPageParam: undefined as string | undefined,\n select: (data) =>\n data.pages.flatMap((page) => page.messages as ChatMessage[]),\n });\n\n // Local state for the in-progress streaming exchange (user + assistant messages).\n const [streamingMessages, setStreamingMessages] = useState<\n StreamingChatMessage[]\n >([]);\n // Exchanges that finished streaming, kept for the lifetime of this chat. They\n // keep their stable optimistic ids and stand in for the SDK's confirmed copies\n // (which are hidden as duplicates) — this both bridges the refetch lag and\n // avoids remounting the message (and restarting its typewriter) when the\n // confirmed copy, with a different id, arrives.\n const [completedMessages, setCompletedMessages] = useState<\n StreamingChatMessage[]\n >([]);\n const abortRef = useRef<AbortController | null>(null);\n // Handle of the pending reveal-drip animation frame, if any.\n const rafRef = useRef<number | null>(null);\n // Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;\n // falling back to this keeps the history from disappearing and reappearing.\n const lastConfirmedRef = useRef<StreamingChatMessage[]>([]);\n // Monotonic per-hook counter so two sends within the same millisecond still\n // produce distinct message ids (used as React keys downstream).\n const sendSeqRef = useRef(0);\n\n // Cancel any pending reveal-drip frame.\n const stopRaf = useCallback(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }, []);\n\n // Stop any in-flight stream (and reveal drip) on unmount.\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n stopRaf();\n };\n }, [stopRaf]);\n\n const messages = useMemo<StreamingChatMessage[]>(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n const confirmedMessages =\n converted.length > 0 ? converted : lastConfirmedRef.current;\n\n // Show our locally-tracked streamed exchanges (stable ids) and hide the\n // SDK's confirmed duplicates. Swapping to a confirmed copy would remount the\n // message (different id) and restart its typewriter — that's the flicker.\n // The in-progress streaming messages are always shown as-is.\n const completedKeys = new Set(completedMessages.map(messageKey));\n const confirmedShown = confirmedMessages.filter(\n (m) => !completedKeys.has(messageKey(m)),\n );\n return [...confirmedShown, ...completedMessages, ...streamingMessages];\n }, [sdkMessages, streamingMessages, completedMessages]);\n\n // Remember the latest non-empty confirmed list so a transient empty during a\n // refetch doesn't blank the history.\n useEffect(() => {\n const converted = [...sdkMessages].reverse() as StreamingChatMessage[];\n if (converted.length > 0) {\n lastConfirmedRef.current = converted;\n }\n }, [sdkMessages]);\n\n const send = useCallback(\n async ({\n message,\n chatConfig,\n attachments,\n metadata,\n }: SendStreamMessageParams) => {\n // Abort any in-flight stream before starting a new one.\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const seq = (sendSeqRef.current += 1);\n const userMsgId = `stream-user-${Date.now()}-${seq}`;\n const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;\n\n const optimisticUserMsg: StreamingChatMessage = {\n id: userMsgId,\n role: 'USER',\n content: message,\n attachments,\n };\n\n const streamingAssistantMsg: StreamingChatMessage = {\n id: assistantMsgId,\n role: 'ASSISTANT',\n content: '',\n streamStatus: 'streaming',\n streamingText: '',\n };\n\n setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);\n\n // Tokens accumulate as they arrive, but we reveal them into\n // `streamingText` at a steady time-paced rate via requestAnimationFrame.\n // Pacing the reveal below the host typer's per-char rate stops it from\n // building a backlog it would skip through (the jumpy/\"super fast\" look).\n let accumulated = ''; // full text received so far\n let revealedLen = 0; // how many chars we've dripped into streamingText\n let streamDone = false; // network stream ended\n let carry = 0; // fractional chars carried between frames\n let lastFrameTime = 0; // 0 = uninitialized\n\n const settle = () => {\n // Settle only after the drip has revealed everything AND the stream\n // ended. Mirror the text into both `content` and `streamingText` so a\n // host typer hands off with no re-type or flicker. Kept in\n // `completedMessages` (stable id) so the confirmed copy from refetch is\n // hidden and never remounts.\n stopRaf();\n const finalAssistantMsg: StreamingChatMessage = {\n ...streamingAssistantMsg,\n content: accumulated,\n streamStatus: 'settled',\n streamingText: accumulated,\n };\n setCompletedMessages((prev) => [\n ...prev,\n optimisticUserMsg,\n finalAssistantMsg,\n ]);\n setStreamingMessages([]);\n void refetch();\n };\n\n const frame = (now: number) => {\n if (lastFrameTime === 0) {\n lastFrameTime = now;\n }\n const elapsedMs = now - lastFrameTime;\n lastFrameTime = now;\n\n if (revealedLen < accumulated.length) {\n carry += elapsedMs / revealMsPerChar;\n const step = Math.floor(carry);\n if (step > 0) {\n carry -= step;\n revealedLen = Math.min(accumulated.length, revealedLen + step);\n setStreamingMessages([\n optimisticUserMsg,\n {\n ...streamingAssistantMsg,\n streamStatus: 'streaming',\n streamingText: accumulated.slice(0, revealedLen),\n },\n ]);\n }\n rafRef.current = requestAnimationFrame(frame);\n } else if (streamDone) {\n settle();\n } else {\n // Caught up, waiting for more tokens; onChunk will restart the loop.\n stopRaf();\n }\n };\n\n const startRaf = () => {\n if (rafRef.current === null) {\n lastFrameTime = 0;\n rafRef.current = requestAnimationFrame(frame);\n }\n };\n\n // Streaming deliberately bypasses the injected `personaChatService` (the\n // DI seam every other hook uses): SSE needs a raw fetch with cookie auth\n // to a caller-supplied url, which the Api-Key `IPersonaChatService` can't\n // model. History reads still go through the service (see the query above).\n await streamChatMessage(\n {\n url: streamUrl,\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n metadata,\n headers,\n },\n {\n onChunk: (token) => {\n accumulated += token;\n startRaf();\n },\n onDone: () => {\n streamDone = true;\n startRaf();\n },\n onError: (err) => {\n stopRaf();\n onError?.(err);\n setStreamingMessages([]);\n },\n },\n controller.signal,\n );\n },\n [\n streamUrl,\n chatId,\n namespace,\n headers,\n onError,\n refetch,\n revealMsPerChar,\n stopRaf,\n ],\n );\n\n return {\n status,\n // Present for symmetry with `useTextChat`; the streaming path tracks its own\n // optimistic state, so this is always empty.\n pending: [],\n messages,\n fetchNext,\n refetch,\n send,\n };\n\n function fetchNext() {\n void fetchNextPage();\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -74,6 +74,7 @@ export async function streamChatMessage(params, callbacks, signal) {
|
|
|
74
74
|
namespace = params.namespace,
|
|
75
75
|
chatConfig = params.chatConfig,
|
|
76
76
|
attachments = params.attachments,
|
|
77
|
+
metadata = params.metadata,
|
|
77
78
|
headers = params.headers;
|
|
78
79
|
const onChunk = callbacks.onChunk,
|
|
79
80
|
onDone = callbacks.onDone,
|
|
@@ -91,7 +92,8 @@ export async function streamChatMessage(params, callbacks, signal) {
|
|
|
91
92
|
message,
|
|
92
93
|
namespace,
|
|
93
94
|
chatConfig,
|
|
94
|
-
attachments
|
|
95
|
+
attachments,
|
|
96
|
+
metadata
|
|
95
97
|
}),
|
|
96
98
|
signal
|
|
97
99
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["extractContent","frame","parsed","JSON","parse","content","body","decoded","TextDecoder","decode","Uint8Array","from","atob","char","charCodeAt","inner","createObjectScanner","onObject","buffer","cursor","braceDepth","objectStart","inString","escaped","feed","text","length","slice","streamChatMessage","params","callbacks","signal","url","chatId","message","namespace","chatConfig","attachments","headers","onChunk","onDone","onError","response","fetch","method","credentials","stringify","ok","Error","status","scanForObjects","reader","getReader","decoder","_await$reader$read","read","done","value","stream","err","name"],"sources":["../../../src/services/streamChatMessage.ts"],"sourcesContent":["import type { ChatMessageAttachment } from '../types';\n\nexport type StreamCallbacks = {\n onChunk: (token: string) => void;\n onDone: () => void;\n onError: (err: Error) => void;\n};\n\nexport type StreamChatMessageParams = {\n // The caller supplies the endpoint (e.g. a proxied SSE route URL). The SDK\n // stays agnostic about base paths and gateway passthroughs.\n //\n // SECURITY: the request is sent with `credentials: 'include'`, so the session\n // cookie is delivered to whatever origin this resolves to. It must come from\n // trusted host/config — never from user input.\n url: string;\n chatId: string;\n message: string;\n namespace: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n // Extra request headers merged onto the POST. Lets the caller satisfy the\n // host's transport requirements the SDK can't know about — e.g. an\n // `x-xsrf-token` when the endpoint sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n};\n\n// Pull the assistant text out of one streamed frame. Two framings reach us: a\n// proxy route yields `{ content }` objects, while the gateway wraps each frame\n// as `{ body: base64(json) }`. Returns null for frames without content.\nexport function extractContent(frame: string): string | null {\n try {\n const parsed = JSON.parse(frame);\n if (typeof parsed.content === 'string') {\n return parsed.content;\n }\n if (typeof parsed.body === 'string') {\n const decoded = new TextDecoder().decode(\n Uint8Array.from(atob(parsed.body), (char) => char.charCodeAt(0)),\n );\n const inner = JSON.parse(decoded);\n return typeof inner.content === 'string' ? inner.content : null;\n }\n } catch {\n // Incomplete or non-object frame — skip it.\n }\n return null;\n}\n\n// Feeds decoded text through a running scanner that emits each complete\n// top-level `{…}` object as its closing brace arrives. This keeps a message\n// that's split across reads intact, and parses a newline-free JSON array (the\n// gateway framing) incrementally. Anything outside a top-level object is ignored.\nexport function createObjectScanner(onObject: (frame: string) => void) {\n let buffer = '';\n let cursor = 0;\n let braceDepth = 0;\n let objectStart = -1;\n let inString = false;\n let escaped = false;\n\n return function feed(text: string) {\n buffer += text;\n for (; cursor < buffer.length; cursor++) {\n const char = buffer[cursor];\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (char === '\\\\') {\n escaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n } else if (char === '{') {\n if (braceDepth === 0) {\n objectStart = cursor;\n }\n braceDepth++;\n } else if (char === '}' && braceDepth > 0) {\n braceDepth--;\n if (braceDepth === 0 && objectStart >= 0) {\n onObject(buffer.slice(objectStart, cursor + 1));\n objectStart = -1;\n }\n }\n }\n };\n}\n\n/**\n * Framework-agnostic streaming transport. POSTs the message to the\n * caller-supplied SSE endpoint, reads the response body incrementally, and\n * fires `onChunk` per assistant token, `onDone` on close, `onError` on failure.\n * A caller-supplied `AbortSignal` cancels the request; `AbortError` is swallowed.\n */\nexport async function streamChatMessage(\n params: StreamChatMessageParams,\n callbacks: StreamCallbacks,\n signal?: AbortSignal,\n): Promise<void> {\n const {
|
|
1
|
+
{"version":3,"names":["extractContent","frame","parsed","JSON","parse","content","body","decoded","TextDecoder","decode","Uint8Array","from","atob","char","charCodeAt","inner","createObjectScanner","onObject","buffer","cursor","braceDepth","objectStart","inString","escaped","feed","text","length","slice","streamChatMessage","params","callbacks","signal","url","chatId","message","namespace","chatConfig","attachments","metadata","headers","onChunk","onDone","onError","response","fetch","method","credentials","stringify","ok","Error","status","scanForObjects","reader","getReader","decoder","_await$reader$read","read","done","value","stream","err","name"],"sources":["../../../src/services/streamChatMessage.ts"],"sourcesContent":["import type { ChatMessageAttachment } from '../types';\n\nexport type StreamCallbacks = {\n onChunk: (token: string) => void;\n onDone: () => void;\n onError: (err: Error) => void;\n};\n\nexport type StreamChatMessageParams = {\n // The caller supplies the endpoint (e.g. a proxied SSE route URL). The SDK\n // stays agnostic about base paths and gateway passthroughs.\n //\n // SECURITY: the request is sent with `credentials: 'include'`, so the session\n // cookie is delivered to whatever origin this resolves to. It must come from\n // trusted host/config — never from user input.\n url: string;\n chatId: string;\n message: string;\n namespace: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n // Request metadata forwarded to the RPC (e.g. `additionalMetadata` carrying\n // opening-message context). Mirrors the top-level `metadata` object the unary\n // `sendChatMessage` request accepts, so a streamed send carries it identically.\n metadata?: object;\n // Extra request headers merged onto the POST. Lets the caller satisfy the\n // host's transport requirements the SDK can't know about — e.g. an\n // `x-xsrf-token` when the endpoint sits behind a CSRF-protected gateway.\n headers?: Record<string, string>;\n};\n\n// Pull the assistant text out of one streamed frame. Two framings reach us: a\n// proxy route yields `{ content }` objects, while the gateway wraps each frame\n// as `{ body: base64(json) }`. Returns null for frames without content.\nexport function extractContent(frame: string): string | null {\n try {\n const parsed = JSON.parse(frame);\n if (typeof parsed.content === 'string') {\n return parsed.content;\n }\n if (typeof parsed.body === 'string') {\n const decoded = new TextDecoder().decode(\n Uint8Array.from(atob(parsed.body), (char) => char.charCodeAt(0)),\n );\n const inner = JSON.parse(decoded);\n return typeof inner.content === 'string' ? inner.content : null;\n }\n } catch {\n // Incomplete or non-object frame — skip it.\n }\n return null;\n}\n\n// Feeds decoded text through a running scanner that emits each complete\n// top-level `{…}` object as its closing brace arrives. This keeps a message\n// that's split across reads intact, and parses a newline-free JSON array (the\n// gateway framing) incrementally. Anything outside a top-level object is ignored.\nexport function createObjectScanner(onObject: (frame: string) => void) {\n let buffer = '';\n let cursor = 0;\n let braceDepth = 0;\n let objectStart = -1;\n let inString = false;\n let escaped = false;\n\n return function feed(text: string) {\n buffer += text;\n for (; cursor < buffer.length; cursor++) {\n const char = buffer[cursor];\n if (inString) {\n if (escaped) {\n escaped = false;\n } else if (char === '\\\\') {\n escaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n } else if (char === '{') {\n if (braceDepth === 0) {\n objectStart = cursor;\n }\n braceDepth++;\n } else if (char === '}' && braceDepth > 0) {\n braceDepth--;\n if (braceDepth === 0 && objectStart >= 0) {\n onObject(buffer.slice(objectStart, cursor + 1));\n objectStart = -1;\n }\n }\n }\n };\n}\n\n/**\n * Framework-agnostic streaming transport. POSTs the message to the\n * caller-supplied SSE endpoint, reads the response body incrementally, and\n * fires `onChunk` per assistant token, `onDone` on close, `onError` on failure.\n * A caller-supplied `AbortSignal` cancels the request; `AbortError` is swallowed.\n */\nexport async function streamChatMessage(\n params: StreamChatMessageParams,\n callbacks: StreamCallbacks,\n signal?: AbortSignal,\n): Promise<void> {\n const {\n url,\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n metadata,\n headers,\n } = params;\n const { onChunk, onDone, onError } = callbacks;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify({\n chatId,\n message,\n namespace,\n chatConfig,\n attachments,\n metadata,\n }),\n signal,\n });\n\n if (!response.ok || !response.body) {\n onError(new Error(`Stream request failed: ${response.status}`));\n return;\n }\n\n const scanForObjects = createObjectScanner((frame) => {\n const content = extractContent(frame);\n if (content !== null) {\n onChunk(content);\n }\n });\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n scanForObjects(decoder.decode(value, { stream: true }));\n }\n scanForObjects(decoder.decode());\n onDone();\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n onError(err as Error);\n }\n }\n}\n"],"mappings":"AA+BA;AACA;AACA;AACA,OAAO,SAASA,cAAcA,CAACC,KAAa,EAAiB;EAC3D,IAAI;IACF,MAAMC,MAAM,GAAGC,IAAI,CAACC,KAAK,CAACH,KAAK,CAAC;IAChC,IAAI,OAAOC,MAAM,CAACG,OAAO,KAAK,QAAQ,EAAE;MACtC,OAAOH,MAAM,CAACG,OAAO;IACvB;IACA,IAAI,OAAOH,MAAM,CAACI,IAAI,KAAK,QAAQ,EAAE;MACnC,MAAMC,OAAO,GAAG,IAAIC,WAAW,CAAC,CAAC,CAACC,MAAM,CACtCC,UAAU,CAACC,IAAI,CAACC,IAAI,CAACV,MAAM,CAACI,IAAI,CAAC,EAAGO,IAAI,IAAKA,IAAI,CAACC,UAAU,CAAC,CAAC,CAAC,CACjE,CAAC;MACD,MAAMC,KAAK,GAAGZ,IAAI,CAACC,KAAK,CAACG,OAAO,CAAC;MACjC,OAAO,OAAOQ,KAAK,CAACV,OAAO,KAAK,QAAQ,GAAGU,KAAK,CAACV,OAAO,GAAG,IAAI;IACjE;EACF,CAAC,CAAC,MAAM;IACN;EAAA;EAEF,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASW,mBAAmBA,CAACC,QAAiC,EAAE;EACrE,IAAIC,MAAM,GAAG,EAAE;EACf,IAAIC,MAAM,GAAG,CAAC;EACd,IAAIC,UAAU,GAAG,CAAC;EAClB,IAAIC,WAAW,GAAG,CAAC,CAAC;EACpB,IAAIC,QAAQ,GAAG,KAAK;EACpB,IAAIC,OAAO,GAAG,KAAK;EAEnB,OAAO,SAASC,IAAIA,CAACC,IAAY,EAAE;IACjCP,MAAM,IAAIO,IAAI;IACd,OAAON,MAAM,GAAGD,MAAM,CAACQ,MAAM,EAAEP,MAAM,EAAE,EAAE;MACvC,MAAMN,IAAI,GAAGK,MAAM,CAACC,MAAM,CAAC;MAC3B,IAAIG,QAAQ,EAAE;QACZ,IAAIC,OAAO,EAAE;UACXA,OAAO,GAAG,KAAK;QACjB,CAAC,MAAM,IAAIV,IAAI,KAAK,IAAI,EAAE;UACxBU,OAAO,GAAG,IAAI;QAChB,CAAC,MAAM,IAAIV,IAAI,KAAK,GAAG,EAAE;UACvBS,QAAQ,GAAG,KAAK;QAClB;QACA;MACF;MACA,IAAIT,IAAI,KAAK,GAAG,EAAE;QAChBS,QAAQ,GAAG,IAAI;MACjB,CAAC,MAAM,IAAIT,IAAI,KAAK,GAAG,EAAE;QACvB,IAAIO,UAAU,KAAK,CAAC,EAAE;UACpBC,WAAW,GAAGF,MAAM;QACtB;QACAC,UAAU,EAAE;MACd,CAAC,MAAM,IAAIP,IAAI,KAAK,GAAG,IAAIO,UAAU,GAAG,CAAC,EAAE;QACzCA,UAAU,EAAE;QACZ,IAAIA,UAAU,KAAK,CAAC,IAAIC,WAAW,IAAI,CAAC,EAAE;UACxCJ,QAAQ,CAACC,MAAM,CAACS,KAAK,CAACN,WAAW,EAAEF,MAAM,GAAG,CAAC,CAAC,CAAC;UAC/CE,WAAW,GAAG,CAAC,CAAC;QAClB;MACF;IACF;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeO,iBAAiBA,CACrCC,MAA+B,EAC/BC,SAA0B,EAC1BC,MAAoB,EACL;EACf,MACEC,GAAG,GAQDH,MAAM,CARRG,GAAG;IACHC,MAAM,GAOJJ,MAAM,CAPRI,MAAM;IACNC,OAAO,GAMLL,MAAM,CANRK,OAAO;IACPC,SAAS,GAKPN,MAAM,CALRM,SAAS;IACTC,UAAU,GAIRP,MAAM,CAJRO,UAAU;IACVC,WAAW,GAGTR,MAAM,CAHRQ,WAAW;IACXC,QAAQ,GAENT,MAAM,CAFRS,QAAQ;IACRC,OAAO,GACLV,MAAM,CADRU,OAAO;EAET,MAAQC,OAAO,GAAsBV,SAAS,CAAtCU,OAAO;IAAEC,MAAM,GAAcX,SAAS,CAA7BW,MAAM;IAAEC,OAAO,GAAKZ,SAAS,CAArBY,OAAO;EAEhC,IAAI;IACF,MAAMC,QAAQ,GAAG,MAAMC,KAAK,CAACZ,GAAG,EAAE;MAChCa,MAAM,EAAE,MAAM;MACdC,WAAW,EAAE,SAAS;MACtBP,OAAO,EAAE;QAAE,cAAc,EAAE,kBAAkB;QAAE,GAAGA;MAAQ,CAAC;MAC3DjC,IAAI,EAAEH,IAAI,CAAC4C,SAAS,CAAC;QACnBd,MAAM;QACNC,OAAO;QACPC,SAAS;QACTC,UAAU;QACVC,WAAW;QACXC;MACF,CAAC,CAAC;MACFP;IACF,CAAC,CAAC;IAEF,IAAI,CAACY,QAAQ,CAACK,EAAE,IAAI,CAACL,QAAQ,CAACrC,IAAI,EAAE;MAClCoC,OAAO,CAAC,IAAIO,KAAK,CAAC,0BAA0BN,QAAQ,CAACO,MAAM,EAAE,CAAC,CAAC;MAC/D;IACF;IAEA,MAAMC,cAAc,GAAGnC,mBAAmB,CAAEf,KAAK,IAAK;MACpD,MAAMI,OAAO,GAAGL,cAAc,CAACC,KAAK,CAAC;MACrC,IAAII,OAAO,KAAK,IAAI,EAAE;QACpBmC,OAAO,CAACnC,OAAO,CAAC;MAClB;IACF,CAAC,CAAC;IAEF,MAAM+C,MAAM,GAAGT,QAAQ,CAACrC,IAAI,CAAC+C,SAAS,CAAC,CAAC;IACxC,MAAMC,OAAO,GAAG,IAAI9C,WAAW,CAAC,CAAC;IACjC,SAAS;MACP,MAAA+C,kBAAA,GAAwB,MAAMH,MAAM,CAACI,IAAI,CAAC,CAAC;QAAnCC,IAAI,GAAAF,kBAAA,CAAJE,IAAI;QAAEC,KAAK,GAAAH,kBAAA,CAALG,KAAK;MACnB,IAAID,IAAI,EAAE;QACR;MACF;MACAN,cAAc,CAACG,OAAO,CAAC7C,MAAM,CAACiD,KAAK,EAAE;QAAEC,MAAM,EAAE;MAAK,CAAC,CAAC,CAAC;IACzD;IACAR,cAAc,CAACG,OAAO,CAAC7C,MAAM,CAAC,CAAC,CAAC;IAChCgC,MAAM,CAAC,CAAC;EACV,CAAC,CAAC,OAAOmB,GAAG,EAAE;IACZ,IAAKA,GAAG,CAAWC,IAAI,KAAK,YAAY,EAAE;MACxCnB,OAAO,CAACkB,GAAY,CAAC;IACvB;EACF;AACF","ignoreList":[]}
|
|
@@ -12,20 +12,26 @@ export type StreamingChatMessage = ChatMessage & {
|
|
|
12
12
|
export interface UseTextChatStreamOptions {
|
|
13
13
|
streamUrl: string;
|
|
14
14
|
limit?: number;
|
|
15
|
-
revealIntervalMs?: number;
|
|
16
|
-
revealCharsPerTick?: number;
|
|
17
15
|
headers?: Record<string, string>;
|
|
18
16
|
onError?: (err: Error) => void;
|
|
17
|
+
revealMsPerChar?: number;
|
|
19
18
|
}
|
|
20
19
|
export interface SendStreamMessageParams {
|
|
21
20
|
message: string;
|
|
22
21
|
chatConfig?: object;
|
|
23
22
|
attachments?: ChatMessageAttachment[];
|
|
23
|
+
metadata?: object;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
|
|
27
|
-
* and
|
|
28
|
-
*
|
|
27
|
+
* and drips the accumulated reply into `streamingText` at a steady, time-paced
|
|
28
|
+
* rate (see `revealMsPerChar`) via requestAnimationFrame, merging the in-flight
|
|
29
|
+
* and completed streamed exchanges with the confirmed history (oldest-first).
|
|
30
|
+
* The host renderer (e.g. dolly) animates the dripped prefix with its own
|
|
31
|
+
* typewriter; pacing the drip below the typer's per-char rate keeps the typer
|
|
32
|
+
* from building a backlog it would otherwise skip through (jumpy reveal). The
|
|
33
|
+
* exchange settles only once the drip has revealed everything AND the network
|
|
34
|
+
* stream has ended, which avoids an end-of-reply snap.
|
|
29
35
|
*
|
|
30
36
|
* KNOWN LIMITATION: the streaming path does not run a function-call resolution
|
|
31
37
|
* loop — parity with the current unary behavior.
|
|
@@ -36,6 +42,6 @@ export declare function useTextChatStream(options: UseTextChatStreamOptions): {
|
|
|
36
42
|
messages: StreamingChatMessage[];
|
|
37
43
|
fetchNext: () => void;
|
|
38
44
|
refetch: (options?: import("@tanstack/query-core/build/legacy/hydration-BlEVG2Lp").aq | undefined) => Promise<import("@tanstack/query-core/build/legacy/hydration-BlEVG2Lp").aH<ChatMessage[], Error>>;
|
|
39
|
-
send: ({ message, chatConfig, attachments }: SendStreamMessageParams) => Promise<void>;
|
|
45
|
+
send: ({ message, chatConfig, attachments, metadata, }: SendStreamMessageParams) => Promise<void>;
|
|
40
46
|
};
|
|
41
47
|
//# sourceMappingURL=useTextChatStream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTextChatStream.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextChat/useTextChatStream.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAMtE,OAAO,EACL,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,uBAAuB,GAC7B,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"useTextChatStream.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextChat/useTextChatStream.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAMtE,OAAO,EACL,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,uBAAuB,GAC7B,MAAM,kCAAkC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACxC,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAI/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAGtC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB;;;;;;4DAwG5D,uBAAuB;EA4J7B"}
|
|
@@ -11,6 +11,7 @@ export type StreamChatMessageParams = {
|
|
|
11
11
|
namespace: string;
|
|
12
12
|
chatConfig?: object;
|
|
13
13
|
attachments?: ChatMessageAttachment[];
|
|
14
|
+
metadata?: object;
|
|
14
15
|
headers?: Record<string, string>;
|
|
15
16
|
};
|
|
16
17
|
export declare function extractContent(frame: string): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamChatMessage.d.ts","sourceRoot":"","sources":["../../../src/services/streamChatMessage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IAOpC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAItC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAKF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB3D;AAMD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,UAQxC,MAAM,UA8BlC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,EAC/B,SAAS,EAAE,eAAe,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"streamChatMessage.d.ts","sourceRoot":"","sources":["../../../src/services/streamChatMessage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IAOpC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAItC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAIlB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAKF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB3D;AAMD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,UAQxC,MAAM,UA8BlC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,EAC/B,SAAS,EAAE,eAAe,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAyDf"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.10.0",
|
|
3
3
|
"name": "@wix/legends-platform-sdk",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": {
|
|
@@ -103,5 +103,5 @@
|
|
|
103
103
|
"wallaby": {
|
|
104
104
|
"autoDetect": true
|
|
105
105
|
},
|
|
106
|
-
"falconPackageHash": "
|
|
106
|
+
"falconPackageHash": "18c2eb4ee97d69ddad821c7e45766958577391a3fa4bebca529fc4e5"
|
|
107
107
|
}
|