@wix/legends-platform-sdk 1.12.0 → 1.13.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.
@@ -7,10 +7,4 @@ Object.keys(_useTextChat).forEach(function (key) {
7
7
  if (key in exports && exports[key] === _useTextChat[key]) return;
8
8
  exports[key] = _useTextChat[key];
9
9
  });
10
- var _useTextChatStream = require("./useTextChatStream");
11
- Object.keys(_useTextChatStream).forEach(function (key) {
12
- if (key === "default" || key === "__esModule") return;
13
- if (key in exports && exports[key] === _useTextChatStream[key]) return;
14
- exports[key] = _useTextChatStream[key];
15
- });
16
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_useTextChat","require","Object","keys","forEach","key","exports","_useTextChatStream"],"sources":["../../../../src/hooks/useTextChat/index.ts"],"sourcesContent":["export * from './useTextChat';\nexport * from './useTextChatStream';\n"],"mappings":";;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,YAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,YAAA,CAAAK,GAAA;EAAAC,OAAA,CAAAD,GAAA,IAAAL,YAAA,CAAAK,GAAA;AAAA;AACA,IAAAE,kBAAA,GAAAN,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAI,kBAAA,EAAAH,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAE,kBAAA,CAAAF,GAAA;EAAAC,OAAA,CAAAD,GAAA,IAAAE,kBAAA,CAAAF,GAAA;AAAA","ignoreList":[]}
1
+ {"version":3,"names":["_useTextChat","require","Object","keys","forEach","key","exports"],"sources":["../../../../src/hooks/useTextChat/index.ts"],"sourcesContent":["export * from './useTextChat';\n"],"mappings":";;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,YAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,YAAA,CAAAK,GAAA;EAAAC,OAAA,CAAAD,GAAA,IAAAL,YAAA,CAAAK,GAAA;AAAA","ignoreList":[]}
@@ -1,3 +1,2 @@
1
1
  export * from './useTextChat';
2
- export * from './useTextChatStream';
3
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["../../../../src/hooks/useTextChat/index.ts"],"sourcesContent":["export * from './useTextChat';\nexport * from './useTextChatStream';\n"],"mappings":"AAAA,cAAc,eAAe;AAC7B,cAAc,qBAAqB","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["../../../../src/hooks/useTextChat/index.ts"],"sourcesContent":["export * from './useTextChat';\n"],"mappings":"AAAA,cAAc,eAAe","ignoreList":[]}
@@ -1,3 +1,2 @@
1
1
  export * from './useTextChat';
2
- export * from './useTextChatStream';
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextChat/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextChat/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.12.0",
2
+ "version": "1.13.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": "a03346051f38dc98d8ce096ff531e8797c8a5b0dd28117dad1d1a9dd"
106
+ "falconPackageHash": "ce0662cc1f7820b842e1a2cfe4822a797d1f6fd1feceaa23d54622a7"
107
107
  }
@@ -1,274 +0,0 @@
1
- "use strict";
2
-
3
- exports.__esModule = true;
4
- exports.useTextChatStream = useTextChatStream;
5
- var _react = require("react");
6
- var _reactQuery = require("@tanstack/react-query");
7
- var _sdkConsumer = require("../../sdk-consumer");
8
- var _streamChatMessage = require("../../services/streamChatMessage");
9
- exports.streamChatMessage = _streamChatMessage.streamChatMessage;
10
- // Re-exported here so consumers can reach the framework-agnostic transport
11
- // alongside the hook.
12
-
13
- /**
14
- * A `ChatMessage` augmented with streaming metadata. The host renderer uses
15
- * `streamStatus`/`streamingText` to drive its typewriter; confirmed
16
- * (non-streamed) messages carry neither.
17
- */
18
-
19
- /**
20
- * Identity for deduping optimistic streamed messages against confirmed ones.
21
- *
22
- * This is a *content* join: the streamed text must equal the server-persisted
23
- * copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-
24
- * whitespace differences, but heavier server-side post-processing (markdown /
25
- * entity normalization, an appended citation) would make the keys diverge and
26
- * briefly surface both copies. A shared message id would be more robust, but
27
- * the streamed frames carry only token deltas — no id — so content equality is
28
- * the best join available without backend support.
29
- */
30
- const messageKey = m => `${m.role}::${(m.content ?? '').trim()}`;
31
-
32
- /**
33
- * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
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.
42
- *
43
- * KNOWN LIMITATION: the streaming path does not run a function-call resolution
44
- * loop — parity with the current unary behavior.
45
- */
46
- function useTextChatStream(options = {}) {
47
- const {
48
- streamUrl,
49
- limit = 50,
50
- headers,
51
- onError,
52
- revealMsPerChar = 16
53
- } = options;
54
- const {
55
- chatId,
56
- namespace,
57
- personaChatService
58
- } = (0, _sdkConsumer.useSdkConsumer)();
59
- const {
60
- data: sdkMessages = [],
61
- status,
62
- fetchNextPage,
63
- refetch
64
- } = (0, _reactQuery.useInfiniteQuery)({
65
- queryKey: ['chat-messages', chatId, namespace],
66
- queryFn: async ({
67
- pageParam
68
- }) => {
69
- if (!personaChatService) {
70
- return {
71
- messages: [],
72
- nextCursor: undefined
73
- };
74
- }
75
- return personaChatService.queryChatMessages({
76
- chatId,
77
- namespace,
78
- limit,
79
- cursor: pageParam
80
- });
81
- },
82
- getNextPageParam: lastPage => lastPage.nextCursor,
83
- initialPageParam: undefined,
84
- select: data => data.pages.flatMap(page => page.messages)
85
- });
86
-
87
- // Local state for the in-progress streaming exchange (user + assistant messages).
88
- const [streamingMessages, setStreamingMessages] = (0, _react.useState)([]);
89
- // Exchanges that finished streaming, kept for the lifetime of this chat. They
90
- // keep their stable optimistic ids and stand in for the SDK's confirmed copies
91
- // (which are hidden as duplicates) — this both bridges the refetch lag and
92
- // avoids remounting the message (and restarting its typewriter) when the
93
- // confirmed copy, with a different id, arrives.
94
- const [completedMessages, setCompletedMessages] = (0, _react.useState)([]);
95
- const abortRef = (0, _react.useRef)(null);
96
- // Handle of the pending reveal-drip animation frame, if any.
97
- const rafRef = (0, _react.useRef)(null);
98
- // Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;
99
- // falling back to this keeps the history from disappearing and reappearing.
100
- const lastConfirmedRef = (0, _react.useRef)([]);
101
- // Monotonic per-hook counter so two sends within the same millisecond still
102
- // produce distinct message ids (used as React keys downstream).
103
- const sendSeqRef = (0, _react.useRef)(0);
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;
110
- }
111
- }, []);
112
-
113
- // Stop any in-flight stream (and reveal drip) on unmount.
114
- (0, _react.useEffect)(() => {
115
- return () => {
116
- var _abortRef$current;
117
- (_abortRef$current = abortRef.current) == null || _abortRef$current.abort();
118
- stopRaf();
119
- };
120
- }, [stopRaf]);
121
- const messages = (0, _react.useMemo)(() => {
122
- const converted = [...sdkMessages].reverse();
123
- const confirmedMessages = converted.length > 0 ? converted : lastConfirmedRef.current;
124
-
125
- // Show our locally-tracked streamed exchanges (stable ids) and hide the
126
- // SDK's confirmed duplicates. Swapping to a confirmed copy would remount the
127
- // message (different id) and restart its typewriter — that's the flicker.
128
- // The in-progress streaming messages are always shown as-is.
129
- const completedKeys = new Set(completedMessages.map(messageKey));
130
- const confirmedShown = confirmedMessages.filter(m => !completedKeys.has(messageKey(m)));
131
- return [...confirmedShown, ...completedMessages, ...streamingMessages];
132
- }, [sdkMessages, streamingMessages, completedMessages]);
133
-
134
- // Remember the latest non-empty confirmed list so a transient empty during a
135
- // refetch doesn't blank the history.
136
- (0, _react.useEffect)(() => {
137
- const converted = [...sdkMessages].reverse();
138
- if (converted.length > 0) {
139
- lastConfirmedRef.current = converted;
140
- }
141
- }, [sdkMessages]);
142
- const send = (0, _react.useCallback)(async ({
143
- message,
144
- chatConfig,
145
- attachments,
146
- metadata
147
- }) => {
148
- var _abortRef$current2;
149
- // Abort any in-flight stream before starting a new one.
150
- (_abortRef$current2 = abortRef.current) == null || _abortRef$current2.abort();
151
- const controller = new AbortController();
152
- abortRef.current = controller;
153
- const seq = sendSeqRef.current += 1;
154
- const userMsgId = `stream-user-${Date.now()}-${seq}`;
155
- const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;
156
- const optimisticUserMsg = {
157
- id: userMsgId,
158
- role: 'USER',
159
- content: message,
160
- attachments
161
- };
162
- const streamingAssistantMsg = {
163
- id: assistantMsgId,
164
- role: 'ASSISTANT',
165
- content: '',
166
- streamStatus: 'streaming',
167
- streamingText: ''
168
- };
169
- setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);
170
-
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
-
181
- const settle = () => {
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
185
- // `completedMessages` (stable id) so the confirmed copy from refetch is
186
- // hidden and never remounts.
187
- stopRaf();
188
- const finalAssistantMsg = {
189
- ...streamingAssistantMsg,
190
- content: accumulated,
191
- streamStatus: 'settled',
192
- streamingText: accumulated
193
- };
194
- setCompletedMessages(prev => [...prev, optimisticUserMsg, finalAssistantMsg]);
195
- setStreamingMessages([]);
196
- void refetch();
197
- };
198
- const frame = now => {
199
- if (lastFrameTime === 0) {
200
- lastFrameTime = now;
201
- }
202
- const elapsedMs = now - lastFrameTime;
203
- lastFrameTime = now;
204
- if (revealedLen < accumulated.length) {
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);
217
- } else if (streamDone) {
218
- settle();
219
- } else {
220
- // Caught up, waiting for more tokens; onChunk will restart the loop.
221
- stopRaf();
222
- }
223
- };
224
- const startRaf = () => {
225
- if (rafRef.current === null) {
226
- lastFrameTime = 0;
227
- rafRef.current = requestAnimationFrame(frame);
228
- }
229
- };
230
-
231
- // Streaming deliberately bypasses the injected `personaChatService` (the
232
- // DI seam every other hook uses): SSE needs a raw fetch with cookie auth
233
- // to a caller-supplied url, which the Api-Key `IPersonaChatService` can't
234
- // model. History reads still go through the service (see the query above).
235
- await (0, _streamChatMessage.streamChatMessage)({
236
- url: streamUrl,
237
- chatId,
238
- message,
239
- namespace,
240
- chatConfig,
241
- attachments,
242
- metadata,
243
- headers
244
- }, {
245
- onChunk: token => {
246
- accumulated += token;
247
- startRaf();
248
- },
249
- onDone: () => {
250
- streamDone = true;
251
- startRaf();
252
- },
253
- onError: err => {
254
- stopRaf();
255
- onError == null || onError(err);
256
- setStreamingMessages([]);
257
- }
258
- }, controller.signal);
259
- }, [streamUrl, chatId, namespace, headers, onError, refetch, revealMsPerChar, stopRaf]);
260
- return {
261
- status,
262
- // Present for symmetry with `useTextChat`; the streaming path tracks its own
263
- // optimistic state, so this is always empty.
264
- pending: [],
265
- messages,
266
- fetchNext,
267
- refetch,
268
- send
269
- };
270
- function fetchNext() {
271
- void fetchNextPage();
272
- }
273
- }
274
- //# sourceMappingURL=useTextChatStream.js.map
@@ -1 +0,0 @@
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 // Optional endpoint override. Defaults to the same-origin Wix persona-chat\n // gateway path, so a consumer needs no proxy route of its own.\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":[]}
@@ -1,166 +0,0 @@
1
- "use strict";
2
-
3
- exports.__esModule = true;
4
- exports.createObjectScanner = createObjectScanner;
5
- exports.extractContent = extractContent;
6
- exports.streamChatMessage = streamChatMessage;
7
- // Same-origin Wix persona-chat gateway path. Used when the caller doesn't
8
- // supply a `url`, so a consumer needs no proxy route of its own.
9
- const DEFAULT_STREAM_PATH = '/_api/persona-chat-service/v1/chat/messages/stream';
10
- // Read a cookie value by name in the browser. Returns undefined outside a
11
- // browser or when the cookie is absent. Dependency-free by design.
12
- function readCookie(name) {
13
- var _document$cookie$spli;
14
- if (typeof document === 'undefined') {
15
- return undefined;
16
- }
17
- return (_document$cookie$spli = document.cookie.split(';').map(part => part.trim()).find(part => part.startsWith(`${name}=`))) == null ? void 0 : _document$cookie$spli.slice(name.length + 1);
18
- }
19
-
20
- // Pull the assistant text out of one streamed frame. Two framings reach us: a
21
- // proxy route yields `{ content }` objects, while the gateway wraps each frame
22
- // as `{ body: base64(json) }`. Returns null for frames without content.
23
- function extractContent(frame) {
24
- try {
25
- const parsed = JSON.parse(frame);
26
- if (typeof parsed.content === 'string') {
27
- return parsed.content;
28
- }
29
- if (typeof parsed.body === 'string') {
30
- const decoded = new TextDecoder().decode(Uint8Array.from(atob(parsed.body), char => char.charCodeAt(0)));
31
- const inner = JSON.parse(decoded);
32
- return typeof inner.content === 'string' ? inner.content : null;
33
- }
34
- } catch {
35
- // Incomplete or non-object frame — skip it.
36
- }
37
- return null;
38
- }
39
-
40
- // Feeds decoded text through a running scanner that emits each complete
41
- // top-level `{…}` object as its closing brace arrives. This keeps a message
42
- // that's split across reads intact, and parses a newline-free JSON array (the
43
- // gateway framing) incrementally. Anything outside a top-level object is ignored.
44
- function createObjectScanner(onObject) {
45
- let buffer = '';
46
- let cursor = 0;
47
- let braceDepth = 0;
48
- let objectStart = -1;
49
- let inString = false;
50
- let escaped = false;
51
- return function feed(text) {
52
- buffer += text;
53
- for (; cursor < buffer.length; cursor++) {
54
- const char = buffer[cursor];
55
- if (inString) {
56
- if (escaped) {
57
- escaped = false;
58
- } else if (char === '\\') {
59
- escaped = true;
60
- } else if (char === '"') {
61
- inString = false;
62
- }
63
- continue;
64
- }
65
- if (char === '"') {
66
- inString = true;
67
- } else if (char === '{') {
68
- if (braceDepth === 0) {
69
- objectStart = cursor;
70
- }
71
- braceDepth++;
72
- } else if (char === '}' && braceDepth > 0) {
73
- braceDepth--;
74
- if (braceDepth === 0 && objectStart >= 0) {
75
- onObject(buffer.slice(objectStart, cursor + 1));
76
- objectStart = -1;
77
- }
78
- }
79
- }
80
- };
81
- }
82
-
83
- /**
84
- * Framework-agnostic streaming transport. POSTs the message to the same-origin
85
- * Wix persona-chat gateway (or a caller-supplied `url` override), reads the
86
- * response body incrementally, and fires `onChunk` per assistant token,
87
- * `onDone` on close, `onError` on failure. A caller-supplied `AbortSignal`
88
- * cancels the request; `AbortError` is swallowed.
89
- */
90
- async function streamChatMessage(params, callbacks, signal) {
91
- const {
92
- url,
93
- chatId,
94
- message,
95
- namespace,
96
- chatConfig,
97
- attachments,
98
- metadata,
99
- headers
100
- } = params;
101
- const {
102
- onChunk,
103
- onDone,
104
- onError
105
- } = callbacks;
106
- const endpoint = url ?? DEFAULT_STREAM_PATH;
107
-
108
- // Derive the XSRF header in the browser only when the caller didn't supply
109
- // one — the same-origin gateway is CSRF-protected and the token lives in the
110
- // JS-readable `XSRF-TOKEN` cookie. An explicit caller header always wins.
111
- const xsrfToken = readCookie('XSRF-TOKEN');
112
- const autoXsrf = !(headers != null && headers['x-xsrf-token']) && xsrfToken ? {
113
- 'x-xsrf-token': xsrfToken
114
- } : undefined;
115
- try {
116
- const response = await fetch(endpoint, {
117
- method: 'POST',
118
- credentials: 'include',
119
- headers: {
120
- 'Content-Type': 'application/json',
121
- ...autoXsrf,
122
- ...headers
123
- },
124
- body: JSON.stringify({
125
- chatId,
126
- message,
127
- namespace,
128
- chatConfig,
129
- attachments,
130
- metadata
131
- }),
132
- signal
133
- });
134
- if (!response.ok || !response.body) {
135
- onError(new Error(`Stream request failed: ${response.status}`));
136
- return;
137
- }
138
- const scanForObjects = createObjectScanner(frame => {
139
- const content = extractContent(frame);
140
- if (content !== null) {
141
- onChunk(content);
142
- }
143
- });
144
- const reader = response.body.getReader();
145
- const decoder = new TextDecoder();
146
- for (;;) {
147
- const {
148
- done,
149
- value
150
- } = await reader.read();
151
- if (done) {
152
- break;
153
- }
154
- scanForObjects(decoder.decode(value, {
155
- stream: true
156
- }));
157
- }
158
- scanForObjects(decoder.decode());
159
- onDone();
160
- } catch (err) {
161
- if (err.name !== 'AbortError') {
162
- onError(err);
163
- }
164
- }
165
- }
166
- //# sourceMappingURL=streamChatMessage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["DEFAULT_STREAM_PATH","readCookie","name","_document$cookie$spli","document","undefined","cookie","split","map","part","trim","find","startsWith","slice","length","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","streamChatMessage","params","callbacks","signal","url","chatId","message","namespace","chatConfig","attachments","metadata","headers","onChunk","onDone","onError","endpoint","xsrfToken","autoXsrf","response","fetch","method","credentials","stringify","ok","Error","status","scanForObjects","reader","getReader","decoder","done","value","read","stream","err"],"sources":["../../../src/services/streamChatMessage.ts"],"sourcesContent":["import type { ChatMessageAttachment } from '../types';\n\n// Same-origin Wix persona-chat gateway path. Used when the caller doesn't\n// supply a `url`, so a consumer needs no proxy route of its own.\nconst DEFAULT_STREAM_PATH =\n '/_api/persona-chat-service/v1/chat/messages/stream';\n\nexport type StreamCallbacks = {\n onChunk: (token: string) => void;\n onDone: () => void;\n onError: (err: Error) => void;\n};\n\nexport type StreamChatMessageParams = {\n // Optional endpoint override. Defaults to the same-origin Wix persona-chat\n // gateway path (`DEFAULT_STREAM_PATH`), so a consumer needs no proxy route.\n //\n // SECURITY: the request is sent with `credentials: 'include'`, so the session\n // cookie is delivered to whatever origin this resolves to. Any override MUST\n // come from 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// Read a cookie value by name in the browser. Returns undefined outside a\n// browser or when the cookie is absent. Dependency-free by design.\nfunction readCookie(name: string): string | undefined {\n if (typeof document === 'undefined') {\n return undefined;\n }\n return document.cookie\n .split(';')\n .map((part) => part.trim())\n .find((part) => part.startsWith(`${name}=`))\n ?.slice(name.length + 1);\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 same-origin\n * Wix persona-chat gateway (or a caller-supplied `url` override), reads the\n * response body incrementally, and fires `onChunk` per assistant token,\n * `onDone` on close, `onError` on failure. A caller-supplied `AbortSignal`\n * 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 const endpoint = url ?? DEFAULT_STREAM_PATH;\n\n // Derive the XSRF header in the browser only when the caller didn't supply\n // one — the same-origin gateway is CSRF-protected and the token lives in the\n // JS-readable `XSRF-TOKEN` cookie. An explicit caller header always wins.\n const xsrfToken = readCookie('XSRF-TOKEN');\n const autoXsrf =\n !headers?.['x-xsrf-token'] && xsrfToken\n ? { 'x-xsrf-token': xsrfToken }\n : undefined;\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', ...autoXsrf, ...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":[]}
@@ -1,276 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { useInfiniteQuery } from '@tanstack/react-query';
3
- import { useSdkConsumer } from '../../sdk-consumer';
4
- import { streamChatMessage } from '../../services/streamChatMessage';
5
-
6
- // Re-exported here so consumers can reach the framework-agnostic transport
7
- // alongside the hook.
8
- export { streamChatMessage } from '../../services/streamChatMessage';
9
-
10
- /**
11
- * A `ChatMessage` augmented with streaming metadata. The host renderer uses
12
- * `streamStatus`/`streamingText` to drive its typewriter; confirmed
13
- * (non-streamed) messages carry neither.
14
- */
15
-
16
- /**
17
- * Identity for deduping optimistic streamed messages against confirmed ones.
18
- *
19
- * This is a *content* join: the streamed text must equal the server-persisted
20
- * copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-
21
- * whitespace differences, but heavier server-side post-processing (markdown /
22
- * entity normalization, an appended citation) would make the keys diverge and
23
- * briefly surface both copies. A shared message id would be more robust, but
24
- * the streamed frames carry only token deltas — no id — so content equality is
25
- * the best join available without backend support.
26
- */
27
- const messageKey = m => `${m.role}::${(m.content ?? '').trim()}`;
28
-
29
- /**
30
- * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
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.
39
- *
40
- * KNOWN LIMITATION: the streaming path does not run a function-call resolution
41
- * loop — parity with the current unary behavior.
42
- */
43
- export function useTextChatStream(options) {
44
- if (options === void 0) {
45
- options = {};
46
- }
47
- const _options = options,
48
- streamUrl = _options.streamUrl,
49
- _options$limit = _options.limit,
50
- limit = _options$limit === void 0 ? 50 : _options$limit,
51
- headers = _options.headers,
52
- onError = _options.onError,
53
- _options$revealMsPerC = _options.revealMsPerChar,
54
- revealMsPerChar = _options$revealMsPerC === void 0 ? 16 : _options$revealMsPerC;
55
- const _useSdkConsumer = useSdkConsumer(),
56
- chatId = _useSdkConsumer.chatId,
57
- namespace = _useSdkConsumer.namespace,
58
- personaChatService = _useSdkConsumer.personaChatService;
59
- const _useInfiniteQuery = useInfiniteQuery({
60
- queryKey: ['chat-messages', chatId, namespace],
61
- queryFn: async _ref => {
62
- let pageParam = _ref.pageParam;
63
- if (!personaChatService) {
64
- return {
65
- messages: [],
66
- nextCursor: undefined
67
- };
68
- }
69
- return personaChatService.queryChatMessages({
70
- chatId,
71
- namespace,
72
- limit,
73
- cursor: pageParam
74
- });
75
- },
76
- getNextPageParam: lastPage => lastPage.nextCursor,
77
- initialPageParam: undefined,
78
- select: data => data.pages.flatMap(page => page.messages)
79
- }),
80
- _useInfiniteQuery$dat = _useInfiniteQuery.data,
81
- sdkMessages = _useInfiniteQuery$dat === void 0 ? [] : _useInfiniteQuery$dat,
82
- status = _useInfiniteQuery.status,
83
- fetchNextPage = _useInfiniteQuery.fetchNextPage,
84
- refetch = _useInfiniteQuery.refetch;
85
-
86
- // Local state for the in-progress streaming exchange (user + assistant messages).
87
- const _useState = useState([]),
88
- streamingMessages = _useState[0],
89
- setStreamingMessages = _useState[1];
90
- // Exchanges that finished streaming, kept for the lifetime of this chat. They
91
- // keep their stable optimistic ids and stand in for the SDK's confirmed copies
92
- // (which are hidden as duplicates) — this both bridges the refetch lag and
93
- // avoids remounting the message (and restarting its typewriter) when the
94
- // confirmed copy, with a different id, arrives.
95
- const _useState2 = useState([]),
96
- completedMessages = _useState2[0],
97
- setCompletedMessages = _useState2[1];
98
- const abortRef = useRef(null);
99
- // Handle of the pending reveal-drip animation frame, if any.
100
- const rafRef = useRef(null);
101
- // Last non-empty confirmed list. `refetch()` can transiently empty sdkMessages;
102
- // falling back to this keeps the history from disappearing and reappearing.
103
- const lastConfirmedRef = useRef([]);
104
- // Monotonic per-hook counter so two sends within the same millisecond still
105
- // produce distinct message ids (used as React keys downstream).
106
- const sendSeqRef = useRef(0);
107
-
108
- // Cancel any pending reveal-drip frame.
109
- const stopRaf = useCallback(() => {
110
- if (rafRef.current !== null) {
111
- cancelAnimationFrame(rafRef.current);
112
- rafRef.current = null;
113
- }
114
- }, []);
115
-
116
- // Stop any in-flight stream (and reveal drip) on unmount.
117
- useEffect(() => {
118
- return () => {
119
- var _abortRef$current;
120
- (_abortRef$current = abortRef.current) == null || _abortRef$current.abort();
121
- stopRaf();
122
- };
123
- }, [stopRaf]);
124
- const messages = useMemo(() => {
125
- const converted = [...sdkMessages].reverse();
126
- const confirmedMessages = converted.length > 0 ? converted : lastConfirmedRef.current;
127
-
128
- // Show our locally-tracked streamed exchanges (stable ids) and hide the
129
- // SDK's confirmed duplicates. Swapping to a confirmed copy would remount the
130
- // message (different id) and restart its typewriter — that's the flicker.
131
- // The in-progress streaming messages are always shown as-is.
132
- const completedKeys = new Set(completedMessages.map(messageKey));
133
- const confirmedShown = confirmedMessages.filter(m => !completedKeys.has(messageKey(m)));
134
- return [...confirmedShown, ...completedMessages, ...streamingMessages];
135
- }, [sdkMessages, streamingMessages, completedMessages]);
136
-
137
- // Remember the latest non-empty confirmed list so a transient empty during a
138
- // refetch doesn't blank the history.
139
- useEffect(() => {
140
- const converted = [...sdkMessages].reverse();
141
- if (converted.length > 0) {
142
- lastConfirmedRef.current = converted;
143
- }
144
- }, [sdkMessages]);
145
- const send = useCallback(async _ref2 => {
146
- var _abortRef$current2;
147
- let message = _ref2.message,
148
- chatConfig = _ref2.chatConfig,
149
- attachments = _ref2.attachments,
150
- metadata = _ref2.metadata;
151
- // Abort any in-flight stream before starting a new one.
152
- (_abortRef$current2 = abortRef.current) == null || _abortRef$current2.abort();
153
- const controller = new AbortController();
154
- abortRef.current = controller;
155
- const seq = sendSeqRef.current += 1;
156
- const userMsgId = `stream-user-${Date.now()}-${seq}`;
157
- const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;
158
- const optimisticUserMsg = {
159
- id: userMsgId,
160
- role: 'USER',
161
- content: message,
162
- attachments
163
- };
164
- const streamingAssistantMsg = {
165
- id: assistantMsgId,
166
- role: 'ASSISTANT',
167
- content: '',
168
- streamStatus: 'streaming',
169
- streamingText: ''
170
- };
171
- setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);
172
-
173
- // Tokens accumulate as they arrive, but we reveal them into
174
- // `streamingText` at a steady time-paced rate via requestAnimationFrame.
175
- // Pacing the reveal below the host typer's per-char rate stops it from
176
- // building a backlog it would skip through (the jumpy/"super fast" look).
177
- let accumulated = ''; // full text received so far
178
- let revealedLen = 0; // how many chars we've dripped into streamingText
179
- let streamDone = false; // network stream ended
180
- let carry = 0; // fractional chars carried between frames
181
- let lastFrameTime = 0; // 0 = uninitialized
182
-
183
- const settle = () => {
184
- // Settle only after the drip has revealed everything AND the stream
185
- // ended. Mirror the text into both `content` and `streamingText` so a
186
- // host typer hands off with no re-type or flicker. Kept in
187
- // `completedMessages` (stable id) so the confirmed copy from refetch is
188
- // hidden and never remounts.
189
- stopRaf();
190
- const finalAssistantMsg = {
191
- ...streamingAssistantMsg,
192
- content: accumulated,
193
- streamStatus: 'settled',
194
- streamingText: accumulated
195
- };
196
- setCompletedMessages(prev => [...prev, optimisticUserMsg, finalAssistantMsg]);
197
- setStreamingMessages([]);
198
- void refetch();
199
- };
200
- const frame = now => {
201
- if (lastFrameTime === 0) {
202
- lastFrameTime = now;
203
- }
204
- const elapsedMs = now - lastFrameTime;
205
- lastFrameTime = now;
206
- if (revealedLen < accumulated.length) {
207
- carry += elapsedMs / revealMsPerChar;
208
- const step = Math.floor(carry);
209
- if (step > 0) {
210
- carry -= step;
211
- revealedLen = Math.min(accumulated.length, revealedLen + step);
212
- setStreamingMessages([optimisticUserMsg, {
213
- ...streamingAssistantMsg,
214
- streamStatus: 'streaming',
215
- streamingText: accumulated.slice(0, revealedLen)
216
- }]);
217
- }
218
- rafRef.current = requestAnimationFrame(frame);
219
- } else if (streamDone) {
220
- settle();
221
- } else {
222
- // Caught up, waiting for more tokens; onChunk will restart the loop.
223
- stopRaf();
224
- }
225
- };
226
- const startRaf = () => {
227
- if (rafRef.current === null) {
228
- lastFrameTime = 0;
229
- rafRef.current = requestAnimationFrame(frame);
230
- }
231
- };
232
-
233
- // Streaming deliberately bypasses the injected `personaChatService` (the
234
- // DI seam every other hook uses): SSE needs a raw fetch with cookie auth
235
- // to a caller-supplied url, which the Api-Key `IPersonaChatService` can't
236
- // model. History reads still go through the service (see the query above).
237
- await streamChatMessage({
238
- url: streamUrl,
239
- chatId,
240
- message,
241
- namespace,
242
- chatConfig,
243
- attachments,
244
- metadata,
245
- headers
246
- }, {
247
- onChunk: token => {
248
- accumulated += token;
249
- startRaf();
250
- },
251
- onDone: () => {
252
- streamDone = true;
253
- startRaf();
254
- },
255
- onError: err => {
256
- stopRaf();
257
- onError == null || onError(err);
258
- setStreamingMessages([]);
259
- }
260
- }, controller.signal);
261
- }, [streamUrl, chatId, namespace, headers, onError, refetch, revealMsPerChar, stopRaf]);
262
- return {
263
- status,
264
- // Present for symmetry with `useTextChat`; the streaming path tracks its own
265
- // optimistic state, so this is always empty.
266
- pending: [],
267
- messages,
268
- fetchNext,
269
- refetch,
270
- send
271
- };
272
- function fetchNext() {
273
- void fetchNextPage();
274
- }
275
- }
276
- //# sourceMappingURL=useTextChatStream.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","useInfiniteQuery","useSdkConsumer","streamChatMessage","messageKey","m","role","content","trim","useTextChatStream","options","_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 // Optional endpoint override. Defaults to the same-origin Wix persona-chat\n // gateway path, so a consumer needs no proxy route of its own.\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":[]}
@@ -1,155 +0,0 @@
1
- // Same-origin Wix persona-chat gateway path. Used when the caller doesn't
2
- // supply a `url`, so a consumer needs no proxy route of its own.
3
- const DEFAULT_STREAM_PATH = '/_api/persona-chat-service/v1/chat/messages/stream';
4
- // Read a cookie value by name in the browser. Returns undefined outside a
5
- // browser or when the cookie is absent. Dependency-free by design.
6
- function readCookie(name) {
7
- var _document$cookie$spli;
8
- if (typeof document === 'undefined') {
9
- return undefined;
10
- }
11
- return (_document$cookie$spli = document.cookie.split(';').map(part => part.trim()).find(part => part.startsWith(`${name}=`))) == null ? void 0 : _document$cookie$spli.slice(name.length + 1);
12
- }
13
-
14
- // Pull the assistant text out of one streamed frame. Two framings reach us: a
15
- // proxy route yields `{ content }` objects, while the gateway wraps each frame
16
- // as `{ body: base64(json) }`. Returns null for frames without content.
17
- export function extractContent(frame) {
18
- try {
19
- const parsed = JSON.parse(frame);
20
- if (typeof parsed.content === 'string') {
21
- return parsed.content;
22
- }
23
- if (typeof parsed.body === 'string') {
24
- const decoded = new TextDecoder().decode(Uint8Array.from(atob(parsed.body), char => char.charCodeAt(0)));
25
- const inner = JSON.parse(decoded);
26
- return typeof inner.content === 'string' ? inner.content : null;
27
- }
28
- } catch {
29
- // Incomplete or non-object frame — skip it.
30
- }
31
- return null;
32
- }
33
-
34
- // Feeds decoded text through a running scanner that emits each complete
35
- // top-level `{…}` object as its closing brace arrives. This keeps a message
36
- // that's split across reads intact, and parses a newline-free JSON array (the
37
- // gateway framing) incrementally. Anything outside a top-level object is ignored.
38
- export function createObjectScanner(onObject) {
39
- let buffer = '';
40
- let cursor = 0;
41
- let braceDepth = 0;
42
- let objectStart = -1;
43
- let inString = false;
44
- let escaped = false;
45
- return function feed(text) {
46
- buffer += text;
47
- for (; cursor < buffer.length; cursor++) {
48
- const char = buffer[cursor];
49
- if (inString) {
50
- if (escaped) {
51
- escaped = false;
52
- } else if (char === '\\') {
53
- escaped = true;
54
- } else if (char === '"') {
55
- inString = false;
56
- }
57
- continue;
58
- }
59
- if (char === '"') {
60
- inString = true;
61
- } else if (char === '{') {
62
- if (braceDepth === 0) {
63
- objectStart = cursor;
64
- }
65
- braceDepth++;
66
- } else if (char === '}' && braceDepth > 0) {
67
- braceDepth--;
68
- if (braceDepth === 0 && objectStart >= 0) {
69
- onObject(buffer.slice(objectStart, cursor + 1));
70
- objectStart = -1;
71
- }
72
- }
73
- }
74
- };
75
- }
76
-
77
- /**
78
- * Framework-agnostic streaming transport. POSTs the message to the same-origin
79
- * Wix persona-chat gateway (or a caller-supplied `url` override), reads the
80
- * response body incrementally, and fires `onChunk` per assistant token,
81
- * `onDone` on close, `onError` on failure. A caller-supplied `AbortSignal`
82
- * cancels the request; `AbortError` is swallowed.
83
- */
84
- export async function streamChatMessage(params, callbacks, signal) {
85
- const url = params.url,
86
- chatId = params.chatId,
87
- message = params.message,
88
- namespace = params.namespace,
89
- chatConfig = params.chatConfig,
90
- attachments = params.attachments,
91
- metadata = params.metadata,
92
- headers = params.headers;
93
- const onChunk = callbacks.onChunk,
94
- onDone = callbacks.onDone,
95
- onError = callbacks.onError;
96
- const endpoint = url ?? DEFAULT_STREAM_PATH;
97
-
98
- // Derive the XSRF header in the browser only when the caller didn't supply
99
- // one — the same-origin gateway is CSRF-protected and the token lives in the
100
- // JS-readable `XSRF-TOKEN` cookie. An explicit caller header always wins.
101
- const xsrfToken = readCookie('XSRF-TOKEN');
102
- const autoXsrf = !(headers != null && headers['x-xsrf-token']) && xsrfToken ? {
103
- 'x-xsrf-token': xsrfToken
104
- } : undefined;
105
- try {
106
- const response = await fetch(endpoint, {
107
- method: 'POST',
108
- credentials: 'include',
109
- headers: {
110
- 'Content-Type': 'application/json',
111
- ...autoXsrf,
112
- ...headers
113
- },
114
- body: JSON.stringify({
115
- chatId,
116
- message,
117
- namespace,
118
- chatConfig,
119
- attachments,
120
- metadata
121
- }),
122
- signal
123
- });
124
- if (!response.ok || !response.body) {
125
- onError(new Error(`Stream request failed: ${response.status}`));
126
- return;
127
- }
128
- const scanForObjects = createObjectScanner(frame => {
129
- const content = extractContent(frame);
130
- if (content !== null) {
131
- onChunk(content);
132
- }
133
- });
134
- const reader = response.body.getReader();
135
- const decoder = new TextDecoder();
136
- for (;;) {
137
- const _await$reader$read = await reader.read(),
138
- done = _await$reader$read.done,
139
- value = _await$reader$read.value;
140
- if (done) {
141
- break;
142
- }
143
- scanForObjects(decoder.decode(value, {
144
- stream: true
145
- }));
146
- }
147
- scanForObjects(decoder.decode());
148
- onDone();
149
- } catch (err) {
150
- if (err.name !== 'AbortError') {
151
- onError(err);
152
- }
153
- }
154
- }
155
- //# sourceMappingURL=streamChatMessage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["DEFAULT_STREAM_PATH","readCookie","name","_document$cookie$spli","document","undefined","cookie","split","map","part","trim","find","startsWith","slice","length","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","streamChatMessage","params","callbacks","signal","url","chatId","message","namespace","chatConfig","attachments","metadata","headers","onChunk","onDone","onError","endpoint","xsrfToken","autoXsrf","response","fetch","method","credentials","stringify","ok","Error","status","scanForObjects","reader","getReader","decoder","_await$reader$read","read","done","value","stream","err"],"sources":["../../../src/services/streamChatMessage.ts"],"sourcesContent":["import type { ChatMessageAttachment } from '../types';\n\n// Same-origin Wix persona-chat gateway path. Used when the caller doesn't\n// supply a `url`, so a consumer needs no proxy route of its own.\nconst DEFAULT_STREAM_PATH =\n '/_api/persona-chat-service/v1/chat/messages/stream';\n\nexport type StreamCallbacks = {\n onChunk: (token: string) => void;\n onDone: () => void;\n onError: (err: Error) => void;\n};\n\nexport type StreamChatMessageParams = {\n // Optional endpoint override. Defaults to the same-origin Wix persona-chat\n // gateway path (`DEFAULT_STREAM_PATH`), so a consumer needs no proxy route.\n //\n // SECURITY: the request is sent with `credentials: 'include'`, so the session\n // cookie is delivered to whatever origin this resolves to. Any override MUST\n // come from 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// Read a cookie value by name in the browser. Returns undefined outside a\n// browser or when the cookie is absent. Dependency-free by design.\nfunction readCookie(name: string): string | undefined {\n if (typeof document === 'undefined') {\n return undefined;\n }\n return document.cookie\n .split(';')\n .map((part) => part.trim())\n .find((part) => part.startsWith(`${name}=`))\n ?.slice(name.length + 1);\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 same-origin\n * Wix persona-chat gateway (or a caller-supplied `url` override), reads the\n * response body incrementally, and fires `onChunk` per assistant token,\n * `onDone` on close, `onError` on failure. A caller-supplied `AbortSignal`\n * 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 const endpoint = url ?? DEFAULT_STREAM_PATH;\n\n // Derive the XSRF header in the browser only when the caller didn't supply\n // one — the same-origin gateway is CSRF-protected and the token lives in the\n // JS-readable `XSRF-TOKEN` cookie. An explicit caller header always wins.\n const xsrfToken = readCookie('XSRF-TOKEN');\n const autoXsrf =\n !headers?.['x-xsrf-token'] && xsrfToken\n ? { 'x-xsrf-token': xsrfToken }\n : undefined;\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', ...autoXsrf, ...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":[]}
@@ -1,47 +0,0 @@
1
- import type { ChatMessage, ChatMessageAttachment } from '../../types';
2
- export { streamChatMessage, type StreamCallbacks, type StreamChatMessageParams, } from '../../services/streamChatMessage';
3
- /**
4
- * A `ChatMessage` augmented with streaming metadata. The host renderer uses
5
- * `streamStatus`/`streamingText` to drive its typewriter; confirmed
6
- * (non-streamed) messages carry neither.
7
- */
8
- export type StreamingChatMessage = ChatMessage & {
9
- streamingText?: string;
10
- streamStatus?: 'streaming' | 'settled';
11
- };
12
- export interface UseTextChatStreamOptions {
13
- streamUrl?: string;
14
- limit?: number;
15
- headers?: Record<string, string>;
16
- onError?: (err: Error) => void;
17
- revealMsPerChar?: number;
18
- }
19
- export interface SendStreamMessageParams {
20
- message: string;
21
- chatConfig?: object;
22
- attachments?: ChatMessageAttachment[];
23
- metadata?: object;
24
- }
25
- /**
26
- * Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
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.
35
- *
36
- * KNOWN LIMITATION: the streaming path does not run a function-call resolution
37
- * loop — parity with the current unary behavior.
38
- */
39
- export declare function useTextChatStream(options?: UseTextChatStreamOptions): {
40
- status: "pending" | "error" | "success";
41
- pending: never[];
42
- messages: StreamingChatMessage[];
43
- fetchNext: () => void;
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>>;
45
- send: ({ message, chatConfig, attachments, metadata, }: SendStreamMessageParams) => Promise<void>;
46
- };
47
- //# sourceMappingURL=useTextChatStream.d.ts.map
@@ -1 +0,0 @@
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;IAGvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,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,GAAE,wBAA6B;;;;;;4DAwGjE,uBAAuB;EA4J7B"}
@@ -1,27 +0,0 @@
1
- import type { ChatMessageAttachment } from '../types';
2
- export type StreamCallbacks = {
3
- onChunk: (token: string) => void;
4
- onDone: () => void;
5
- onError: (err: Error) => void;
6
- };
7
- export type StreamChatMessageParams = {
8
- url?: string;
9
- chatId: string;
10
- message: string;
11
- namespace: string;
12
- chatConfig?: object;
13
- attachments?: ChatMessageAttachment[];
14
- metadata?: object;
15
- headers?: Record<string, string>;
16
- };
17
- export declare function extractContent(frame: string): string | null;
18
- export declare function createObjectScanner(onObject: (frame: string) => void): (text: string) => void;
19
- /**
20
- * Framework-agnostic streaming transport. POSTs the message to the same-origin
21
- * Wix persona-chat gateway (or a caller-supplied `url` override), reads the
22
- * response body incrementally, and fires `onChunk` per assistant token,
23
- * `onDone` on close, `onError` on failure. A caller-supplied `AbortSignal`
24
- * cancels the request; `AbortError` is swallowed.
25
- */
26
- export declare function streamChatMessage(params: StreamChatMessageParams, callbacks: StreamCallbacks, signal?: AbortSignal): Promise<void>;
27
- //# sourceMappingURL=streamChatMessage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"streamChatMessage.d.ts","sourceRoot":"","sources":["../../../src/services/streamChatMessage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAOtD,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,CAAC,EAAE,MAAM,CAAC;IACb,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;AAkBF,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;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,EAC/B,SAAS,EAAE,eAAe,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAoEf"}