@wix/legends-platform-sdk 1.6.0 → 1.8.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/index.js +6 -0
- package/dist/cjs/hooks/useTextChat/index.js.map +1 -1
- package/dist/cjs/hooks/useTextChat/useTextChatStream.js +260 -0
- package/dist/cjs/hooks/useTextChat/useTextChatStream.js.map +1 -0
- package/dist/cjs/hooks/useTextChat/useToolAwareSend.js +1 -1
- package/dist/cjs/hooks/useTextChat/useToolAwareSend.js.map +1 -1
- package/dist/cjs/services/FetchPersonaChatService.js.map +1 -1
- package/dist/cjs/services/streamChatMessage.js +140 -0
- package/dist/cjs/services/streamChatMessage.js.map +1 -0
- package/dist/cjs/types/service.js.map +1 -1
- package/dist/esm/hooks/useTextChat/index.js +1 -0
- package/dist/esm/hooks/useTextChat/index.js.map +1 -1
- package/dist/esm/hooks/useTextChat/useTextChatStream.js +259 -0
- package/dist/esm/hooks/useTextChat/useTextChatStream.js.map +1 -0
- package/dist/esm/hooks/useTextChat/useToolAwareSend.js +1 -1
- package/dist/esm/hooks/useTextChat/useToolAwareSend.js.map +1 -1
- package/dist/esm/services/FetchPersonaChatService.js.map +1 -1
- package/dist/esm/services/streamChatMessage.js +129 -0
- package/dist/esm/services/streamChatMessage.js.map +1 -0
- package/dist/esm/types/service.js.map +1 -1
- package/dist/types/hooks/useTextChat/index.d.ts +1 -0
- package/dist/types/hooks/useTextChat/index.d.ts.map +1 -1
- package/dist/types/hooks/useTextChat/useTextChatStream.d.ts +41 -0
- package/dist/types/hooks/useTextChat/useTextChatStream.d.ts.map +1 -0
- package/dist/types/services/streamChatMessage.d.ts +25 -0
- package/dist/types/services/streamChatMessage.d.ts.map +1 -0
- package/dist/types/types/service.d.ts +1 -1
- package/package.json +2 -2
|
@@ -7,4 +7,10 @@ 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
|
+
});
|
|
10
16
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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":[]}
|
|
@@ -0,0 +1,260 @@
|
|
|
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
|
+
// 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
|
+
/**
|
|
22
|
+
* A `ChatMessage` augmented with streaming metadata. The host renderer uses
|
|
23
|
+
* `streamStatus`/`streamingText` to drive its typewriter; confirmed
|
|
24
|
+
* (non-streamed) messages carry neither.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Identity for deduping optimistic streamed messages against confirmed ones.
|
|
29
|
+
*
|
|
30
|
+
* This is a *content* join: the streamed text must equal the server-persisted
|
|
31
|
+
* copy for the confirmed duplicate to be hidden. `.trim()` absorbs trailing-
|
|
32
|
+
* whitespace differences, but heavier server-side post-processing (markdown /
|
|
33
|
+
* entity normalization, an appended citation) would make the keys diverge and
|
|
34
|
+
* briefly surface both copies. A shared message id would be more robust, but
|
|
35
|
+
* the streamed frames carry only token deltas — no id — so content equality is
|
|
36
|
+
* the best join available without backend support.
|
|
37
|
+
*/
|
|
38
|
+
const messageKey = m => `${m.role}::${(m.content ?? '').trim()}`;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Streaming counterpart to `useTextChat`. Sends a message over the SSE transport
|
|
42
|
+
* and progressively reveals the reply, merging the in-flight and completed
|
|
43
|
+
* streamed exchanges with the confirmed history (oldest-first).
|
|
44
|
+
*
|
|
45
|
+
* KNOWN LIMITATION: the streaming path does not run a function-call resolution
|
|
46
|
+
* loop — parity with the current unary behavior.
|
|
47
|
+
*/
|
|
48
|
+
function useTextChatStream(options) {
|
|
49
|
+
const {
|
|
50
|
+
streamUrl,
|
|
51
|
+
limit = 50,
|
|
52
|
+
revealIntervalMs = REVEAL_INTERVAL_MS,
|
|
53
|
+
revealCharsPerTick = REVEAL_CHARS_PER_TICK,
|
|
54
|
+
headers,
|
|
55
|
+
onError
|
|
56
|
+
} = options;
|
|
57
|
+
const {
|
|
58
|
+
chatId,
|
|
59
|
+
namespace,
|
|
60
|
+
personaChatService
|
|
61
|
+
} = (0, _sdkConsumer.useSdkConsumer)();
|
|
62
|
+
const {
|
|
63
|
+
data: sdkMessages = [],
|
|
64
|
+
status,
|
|
65
|
+
fetchNextPage,
|
|
66
|
+
refetch
|
|
67
|
+
} = (0, _reactQuery.useInfiniteQuery)({
|
|
68
|
+
queryKey: ['chat-messages', chatId, namespace],
|
|
69
|
+
queryFn: async ({
|
|
70
|
+
pageParam
|
|
71
|
+
}) => {
|
|
72
|
+
if (!personaChatService) {
|
|
73
|
+
return {
|
|
74
|
+
messages: [],
|
|
75
|
+
nextCursor: undefined
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return personaChatService.queryChatMessages({
|
|
79
|
+
chatId,
|
|
80
|
+
namespace,
|
|
81
|
+
limit,
|
|
82
|
+
cursor: pageParam
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
getNextPageParam: lastPage => lastPage.nextCursor,
|
|
86
|
+
initialPageParam: undefined,
|
|
87
|
+
select: data => data.pages.flatMap(page => page.messages)
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Local state for the in-progress streaming exchange (user + assistant messages).
|
|
91
|
+
const [streamingMessages, setStreamingMessages] = (0, _react.useState)([]);
|
|
92
|
+
// Exchanges that finished streaming, kept for the lifetime of this chat. They
|
|
93
|
+
// keep their stable optimistic ids and stand in for the SDK's confirmed copies
|
|
94
|
+
// (which are hidden as duplicates) — this both bridges the refetch lag and
|
|
95
|
+
// avoids remounting the message (and restarting its typewriter) when the
|
|
96
|
+
// confirmed copy, with a different id, arrives.
|
|
97
|
+
const [completedMessages, setCompletedMessages] = (0, _react.useState)([]);
|
|
98
|
+
const abortRef = (0, _react.useRef)(null);
|
|
99
|
+
// Interval that drips the buffered reply into the streaming message.
|
|
100
|
+
const revealTimerRef = (0, _react.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 = (0, _react.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 = (0, _react.useRef)(0);
|
|
107
|
+
const stopRevealTimer = (0, _react.useCallback)(() => {
|
|
108
|
+
if (revealTimerRef.current) {
|
|
109
|
+
clearInterval(revealTimerRef.current);
|
|
110
|
+
revealTimerRef.current = null;
|
|
111
|
+
}
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
// Stop any in-flight stream + reveal on unmount.
|
|
115
|
+
(0, _react.useEffect)(() => {
|
|
116
|
+
return () => {
|
|
117
|
+
var _abortRef$current;
|
|
118
|
+
(_abortRef$current = abortRef.current) == null || _abortRef$current.abort();
|
|
119
|
+
stopRevealTimer();
|
|
120
|
+
};
|
|
121
|
+
}, [stopRevealTimer]);
|
|
122
|
+
const messages = (0, _react.useMemo)(() => {
|
|
123
|
+
const converted = [...sdkMessages].reverse();
|
|
124
|
+
const confirmedMessages = converted.length > 0 ? converted : lastConfirmedRef.current;
|
|
125
|
+
|
|
126
|
+
// Show our locally-tracked streamed exchanges (stable ids) and hide the
|
|
127
|
+
// SDK's confirmed duplicates. Swapping to a confirmed copy would remount the
|
|
128
|
+
// message (different id) and restart its typewriter — that's the flicker.
|
|
129
|
+
// The in-progress streaming messages are always shown as-is.
|
|
130
|
+
const completedKeys = new Set(completedMessages.map(messageKey));
|
|
131
|
+
const confirmedShown = confirmedMessages.filter(m => !completedKeys.has(messageKey(m)));
|
|
132
|
+
return [...confirmedShown, ...completedMessages, ...streamingMessages];
|
|
133
|
+
}, [sdkMessages, streamingMessages, completedMessages]);
|
|
134
|
+
|
|
135
|
+
// Remember the latest non-empty confirmed list so a transient empty during a
|
|
136
|
+
// refetch doesn't blank the history.
|
|
137
|
+
(0, _react.useEffect)(() => {
|
|
138
|
+
const converted = [...sdkMessages].reverse();
|
|
139
|
+
if (converted.length > 0) {
|
|
140
|
+
lastConfirmedRef.current = converted;
|
|
141
|
+
}
|
|
142
|
+
}, [sdkMessages]);
|
|
143
|
+
const send = (0, _react.useCallback)(async ({
|
|
144
|
+
message,
|
|
145
|
+
chatConfig,
|
|
146
|
+
attachments
|
|
147
|
+
}) => {
|
|
148
|
+
var _abortRef$current2;
|
|
149
|
+
// Abort any in-flight stream + reveal before starting a new one.
|
|
150
|
+
(_abortRef$current2 = abortRef.current) == null || _abortRef$current2.abort();
|
|
151
|
+
stopRevealTimer();
|
|
152
|
+
const controller = new AbortController();
|
|
153
|
+
abortRef.current = controller;
|
|
154
|
+
const seq = sendSeqRef.current += 1;
|
|
155
|
+
const userMsgId = `stream-user-${Date.now()}-${seq}`;
|
|
156
|
+
const assistantMsgId = `stream-assistant-${Date.now()}-${seq}`;
|
|
157
|
+
const optimisticUserMsg = {
|
|
158
|
+
id: userMsgId,
|
|
159
|
+
role: 'USER',
|
|
160
|
+
content: message,
|
|
161
|
+
attachments
|
|
162
|
+
};
|
|
163
|
+
const streamingAssistantMsg = {
|
|
164
|
+
id: assistantMsgId,
|
|
165
|
+
role: 'ASSISTANT',
|
|
166
|
+
content: '',
|
|
167
|
+
streamStatus: 'streaming',
|
|
168
|
+
streamingText: ''
|
|
169
|
+
};
|
|
170
|
+
setStreamingMessages([optimisticUserMsg, streamingAssistantMsg]);
|
|
171
|
+
|
|
172
|
+
// Tokens are buffered into `accumulated` as they arrive; `revealedLen`
|
|
173
|
+
// tracks how much the drip timer has uncovered so far.
|
|
174
|
+
let accumulated = '';
|
|
175
|
+
let revealedLen = 0;
|
|
176
|
+
let streamDone = false;
|
|
177
|
+
const settle = () => {
|
|
178
|
+
stopRevealTimer();
|
|
179
|
+
// Mirror the streamed text into both `content` and `streamingText`. A
|
|
180
|
+
// host renderer can diff the two to place its typer at the end, so the
|
|
181
|
+
// settled message shows in full with no re-type or flicker. Kept in
|
|
182
|
+
// `completedMessages` (stable id) so the confirmed copy from refetch is
|
|
183
|
+
// hidden and never remounts.
|
|
184
|
+
const finalAssistantMsg = {
|
|
185
|
+
...streamingAssistantMsg,
|
|
186
|
+
content: accumulated,
|
|
187
|
+
streamStatus: 'settled',
|
|
188
|
+
streamingText: accumulated
|
|
189
|
+
};
|
|
190
|
+
setCompletedMessages(prev => [...prev, optimisticUserMsg, finalAssistantMsg]);
|
|
191
|
+
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
|
+
void refetch();
|
|
195
|
+
};
|
|
196
|
+
const tick = () => {
|
|
197
|
+
if (revealedLen < accumulated.length) {
|
|
198
|
+
revealedLen = Math.min(accumulated.length, revealedLen + revealCharsPerTick);
|
|
199
|
+
const revealedText = accumulated.slice(0, revealedLen);
|
|
200
|
+
setStreamingMessages([optimisticUserMsg, {
|
|
201
|
+
...streamingAssistantMsg,
|
|
202
|
+
content: '',
|
|
203
|
+
streamStatus: 'streaming',
|
|
204
|
+
streamingText: revealedText
|
|
205
|
+
}]);
|
|
206
|
+
} else if (streamDone) {
|
|
207
|
+
// Network finished and the reveal has caught up — settle.
|
|
208
|
+
settle();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const startReveal = () => {
|
|
212
|
+
if (!revealTimerRef.current) {
|
|
213
|
+
revealTimerRef.current = setInterval(tick, revealIntervalMs);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Streaming deliberately bypasses the injected `personaChatService` (the
|
|
218
|
+
// DI seam every other hook uses): SSE needs a raw fetch with cookie auth
|
|
219
|
+
// to a caller-supplied url, which the Api-Key `IPersonaChatService` can't
|
|
220
|
+
// model. History reads still go through the service (see the query above).
|
|
221
|
+
await (0, _streamChatMessage.streamChatMessage)({
|
|
222
|
+
url: streamUrl,
|
|
223
|
+
chatId,
|
|
224
|
+
message,
|
|
225
|
+
namespace,
|
|
226
|
+
chatConfig,
|
|
227
|
+
attachments,
|
|
228
|
+
headers
|
|
229
|
+
}, {
|
|
230
|
+
onChunk: token => {
|
|
231
|
+
accumulated += token;
|
|
232
|
+
startReveal();
|
|
233
|
+
},
|
|
234
|
+
onDone: () => {
|
|
235
|
+
// Let the drip finish revealing, then settle (see `tick`).
|
|
236
|
+
streamDone = true;
|
|
237
|
+
startReveal();
|
|
238
|
+
},
|
|
239
|
+
onError: err => {
|
|
240
|
+
stopRevealTimer();
|
|
241
|
+
onError == null || onError(err);
|
|
242
|
+
setStreamingMessages([]);
|
|
243
|
+
}
|
|
244
|
+
}, controller.signal);
|
|
245
|
+
}, [streamUrl, chatId, namespace, revealIntervalMs, revealCharsPerTick, headers, onError, refetch, stopRevealTimer]);
|
|
246
|
+
return {
|
|
247
|
+
status,
|
|
248
|
+
// Present for symmetry with `useTextChat`; the streaming path tracks its own
|
|
249
|
+
// optimistic state, so this is always empty.
|
|
250
|
+
pending: [],
|
|
251
|
+
messages,
|
|
252
|
+
fetchNext,
|
|
253
|
+
refetch,
|
|
254
|
+
send
|
|
255
|
+
};
|
|
256
|
+
function fetchNext() {
|
|
257
|
+
void fetchNextPage();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=useTextChatStream.js.map
|
|
@@ -0,0 +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":";;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,WAAA,GAAAD,OAAA;AAEA,IAAAE,YAAA,GAAAF,OAAA;AACA,IAAAG,kBAAA,GAAAH,OAAA;AAAqEI,OAAA,CAAAC,iBAAA,GAAAF,kBAAA,CAAAE,iBAAA;AAErE;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA,MAAMC,kBAAkB,GAAG,EAAE;AAC7B,MAAMC,qBAAqB,GAAG,CAAC;;AAE/B;AACA;AACA;AACA;AACA;;AAwBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,UAAU,GAAIC,CAAuB,IACzC,GAAGA,CAAC,CAACC,IAAI,KAAK,CAACD,CAAC,CAACE,OAAO,IAAI,EAAE,EAAEC,IAAI,CAAC,CAAC,EAAE;;AAE1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,iBAAiBA,CAACC,OAAiC,EAAE;EACnE,MAAM;IACJC,SAAS;IACTC,KAAK,GAAG,EAAE;IACVC,gBAAgB,GAAGX,kBAAkB;IACrCY,kBAAkB,GAAGX,qBAAqB;IAC1CY,OAAO;IACPC;EACF,CAAC,GAAGN,OAAO;EAEX,MAAM;IAAEO,MAAM;IAAEC,SAAS;IAAEC;EAAmB,CAAC,GAAG,IAAAC,2BAAc,EAAC,CAAC;EAElE,MAAM;IACJC,IAAI,EAAEC,WAAW,GAAG,EAAE;IACtBC,MAAM;IACNC,aAAa;IACbC;EACF,CAAC,GAAG,IAAAC,4BAAgB,EAAC;IACnBC,QAAQ,EAAE,CAAC,eAAe,EAAEV,MAAM,EAAEC,SAAS,CAAC;IAC9CU,OAAO,EAAE,MAAAA,CAAO;MAAEC;IAAkC,CAAC,KAAK;MACxD,IAAI,CAACV,kBAAkB,EAAE;QACvB,OAAO;UAAEW,QAAQ,EAAE,EAAE;UAAEC,UAAU,EAAEC;QAAU,CAAC;MAChD;MACA,OAAOb,kBAAkB,CAACc,iBAAiB,CAAC;QAC1ChB,MAAM;QACNC,SAAS;QACTN,KAAK;QACLsB,MAAM,EAAEL;MACV,CAAC,CAAC;IACJ,CAAC;IACDM,gBAAgB,EAAGC,QAAQ,IAAKA,QAAQ,CAACL,UAAU;IACnDM,gBAAgB,EAAEL,SAA+B;IACjDM,MAAM,EAAGjB,IAAI,IACXA,IAAI,CAACkB,KAAK,CAACC,OAAO,CAAEC,IAAI,IAAKA,IAAI,CAACX,QAAyB;EAC/D,CAAC,CAAC;;EAEF;EACA,MAAM,CAACY,iBAAiB,EAAEC,oBAAoB,CAAC,GAAG,IAAAC,eAAQ,EAExD,EAAE,CAAC;EACL;EACA;EACA;EACA;EACA;EACA,MAAM,CAACC,iBAAiB,EAAEC,oBAAoB,CAAC,GAAG,IAAAF,eAAQ,EAExD,EAAE,CAAC;EACL,MAAMG,QAAQ,GAAG,IAAAC,aAAM,EAAyB,IAAI,CAAC;EACrD;EACA,MAAMC,cAAc,GAAG,IAAAD,aAAM,EAAwC,IAAI,CAAC;EAC1E;EACA;EACA,MAAME,gBAAgB,GAAG,IAAAF,aAAM,EAAyB,EAAE,CAAC;EAC3D;EACA;EACA,MAAMG,UAAU,GAAG,IAAAH,aAAM,EAAC,CAAC,CAAC;EAE5B,MAAMI,eAAe,GAAG,IAAAC,kBAAW,EAAC,MAAM;IACxC,IAAIJ,cAAc,CAACK,OAAO,EAAE;MAC1BC,aAAa,CAACN,cAAc,CAACK,OAAO,CAAC;MACrCL,cAAc,CAACK,OAAO,GAAG,IAAI;IAC/B;EACF,CAAC,EAAE,EAAE,CAAC;;EAEN;EACA,IAAAE,gBAAS,EAAC,MAAM;IACd,OAAO,MAAM;MAAA,IAAAC,iBAAA;MACX,CAAAA,iBAAA,GAAAV,QAAQ,CAACO,OAAO,aAAhBG,iBAAA,CAAkBC,KAAK,CAAC,CAAC;MACzBN,eAAe,CAAC,CAAC;IACnB,CAAC;EACH,CAAC,EAAE,CAACA,eAAe,CAAC,CAAC;EAErB,MAAMtB,QAAQ,GAAG,IAAA6B,cAAO,EAAyB,MAAM;IACrD,MAAMC,SAAS,GAAG,CAAC,GAAGtC,WAAW,CAAC,CAACuC,OAAO,CAAC,CAA2B;IACtE,MAAMC,iBAAiB,GACrBF,SAAS,CAACG,MAAM,GAAG,CAAC,GAAGH,SAAS,GAAGV,gBAAgB,CAACI,OAAO;;IAE7D;IACA;IACA;IACA;IACA,MAAMU,aAAa,GAAG,IAAIC,GAAG,CAACpB,iBAAiB,CAACqB,GAAG,CAAC9D,UAAU,CAAC,CAAC;IAChE,MAAM+D,cAAc,GAAGL,iBAAiB,CAACM,MAAM,CAC5C/D,CAAC,IAAK,CAAC2D,aAAa,CAACK,GAAG,CAACjE,UAAU,CAACC,CAAC,CAAC,CACzC,CAAC;IACD,OAAO,CAAC,GAAG8D,cAAc,EAAE,GAAGtB,iBAAiB,EAAE,GAAGH,iBAAiB,CAAC;EACxE,CAAC,EAAE,CAACpB,WAAW,EAAEoB,iBAAiB,EAAEG,iBAAiB,CAAC,CAAC;;EAEvD;EACA;EACA,IAAAW,gBAAS,EAAC,MAAM;IACd,MAAMI,SAAS,GAAG,CAAC,GAAGtC,WAAW,CAAC,CAACuC,OAAO,CAAC,CAA2B;IACtE,IAAID,SAAS,CAACG,MAAM,GAAG,CAAC,EAAE;MACxBb,gBAAgB,CAACI,OAAO,GAAGM,SAAS;IACtC;EACF,CAAC,EAAE,CAACtC,WAAW,CAAC,CAAC;EAEjB,MAAMgD,IAAI,GAAG,IAAAjB,kBAAW,EACtB,OAAO;IAAEkB,OAAO;IAAEC,UAAU;IAAEC;EAAqC,CAAC,KAAK;IAAA,IAAAC,kBAAA;IACvE;IACA,CAAAA,kBAAA,GAAA3B,QAAQ,CAACO,OAAO,aAAhBoB,kBAAA,CAAkBhB,KAAK,CAAC,CAAC;IACzBN,eAAe,CAAC,CAAC;IACjB,MAAMuB,UAAU,GAAG,IAAIC,eAAe,CAAC,CAAC;IACxC7B,QAAQ,CAACO,OAAO,GAAGqB,UAAU;IAE7B,MAAME,GAAG,GAAI1B,UAAU,CAACG,OAAO,IAAI,CAAE;IACrC,MAAMwB,SAAS,GAAG,eAAeC,IAAI,CAACC,GAAG,CAAC,CAAC,IAAIH,GAAG,EAAE;IACpD,MAAMI,cAAc,GAAG,oBAAoBF,IAAI,CAACC,GAAG,CAAC,CAAC,IAAIH,GAAG,EAAE;IAE9D,MAAMK,iBAAuC,GAAG;MAC9CC,EAAE,EAAEL,SAAS;MACbxE,IAAI,EAAE,MAAM;MACZC,OAAO,EAAEgE,OAAO;MAChBE;IACF,CAAC;IAED,MAAMW,qBAA2C,GAAG;MAClDD,EAAE,EAAEF,cAAc;MAClB3E,IAAI,EAAE,WAAW;MACjBC,OAAO,EAAE,EAAE;MACX8E,YAAY,EAAE,WAAW;MACzBC,aAAa,EAAE;IACjB,CAAC;IAED3C,oBAAoB,CAAC,CAACuC,iBAAiB,EAAEE,qBAAqB,CAAC,CAAC;;IAEhE;IACA;IACA,IAAIG,WAAW,GAAG,EAAE;IACpB,IAAIC,WAAW,GAAG,CAAC;IACnB,IAAIC,UAAU,GAAG,KAAK;IAEtB,MAAMC,MAAM,GAAGA,CAAA,KAAM;MACnBtC,eAAe,CAAC,CAAC;MACjB;MACA;MACA;MACA;MACA;MACA,MAAMuC,iBAAuC,GAAG;QAC9C,GAAGP,qBAAqB;QACxB7E,OAAO,EAAEgF,WAAW;QACpBF,YAAY,EAAE,SAAS;QACvBC,aAAa,EAAEC;MACjB,CAAC;MACDzC,oBAAoB,CAAE8C,IAAI,IAAK,CAC7B,GAAGA,IAAI,EACPV,iBAAiB,EACjBS,iBAAiB,CAClB,CAAC;MACFhD,oBAAoB,CAAC,EAAE,CAAC;MACxB;MACA;MACA,KAAKlB,OAAO,CAAC,CAAC;IAChB,CAAC;IAED,MAAMoE,IAAI,GAAGA,CAAA,KAAM;MACjB,IAAIL,WAAW,GAAGD,WAAW,CAACxB,MAAM,EAAE;QACpCyB,WAAW,GAAGM,IAAI,CAACC,GAAG,CACpBR,WAAW,CAACxB,MAAM,EAClByB,WAAW,GAAG1E,kBAChB,CAAC;QACD,MAAMkF,YAAY,GAAGT,WAAW,CAACU,KAAK,CAAC,CAAC,EAAET,WAAW,CAAC;QACtD7C,oBAAoB,CAAC,CACnBuC,iBAAiB,EACjB;UACE,GAAGE,qBAAqB;UACxB7E,OAAO,EAAE,EAAE;UACX8E,YAAY,EAAE,WAAW;UACzBC,aAAa,EAAEU;QACjB,CAAC,CACF,CAAC;MACJ,CAAC,MAAM,IAAIP,UAAU,EAAE;QACrB;QACAC,MAAM,CAAC,CAAC;MACV;IACF,CAAC;IAED,MAAMQ,WAAW,GAAGA,CAAA,KAAM;MACxB,IAAI,CAACjD,cAAc,CAACK,OAAO,EAAE;QAC3BL,cAAc,CAACK,OAAO,GAAG6C,WAAW,CAACN,IAAI,EAAEhF,gBAAgB,CAAC;MAC9D;IACF,CAAC;;IAED;IACA;IACA;IACA;IACA,MAAM,IAAAZ,oCAAiB,EACrB;MACEmG,GAAG,EAAEzF,SAAS;MACdM,MAAM;MACNsD,OAAO;MACPrD,SAAS;MACTsD,UAAU;MACVC,WAAW;MACX1D;IACF,CAAC,EACD;MACEsF,OAAO,EAAGC,KAAK,IAAK;QAClBf,WAAW,IAAIe,KAAK;QACpBJ,WAAW,CAAC,CAAC;MACf,CAAC;MACDK,MAAM,EAAEA,CAAA,KAAM;QACZ;QACAd,UAAU,GAAG,IAAI;QACjBS,WAAW,CAAC,CAAC;MACf,CAAC;MACDlF,OAAO,EAAGwF,GAAG,IAAK;QAChBpD,eAAe,CAAC,CAAC;QACjBpC,OAAO,YAAPA,OAAO,CAAGwF,GAAG,CAAC;QACd7D,oBAAoB,CAAC,EAAE,CAAC;MAC1B;IACF,CAAC,EACDgC,UAAU,CAAC8B,MACb,CAAC;EACH,CAAC,EACD,CACE9F,SAAS,EACTM,MAAM,EACNC,SAAS,EACTL,gBAAgB,EAChBC,kBAAkB,EAClBC,OAAO,EACPC,OAAO,EACPS,OAAO,EACP2B,eAAe,CAEnB,CAAC;EAED,OAAO;IACL7B,MAAM;IACN;IACA;IACAmF,OAAO,EAAE,EAAE;IACX5E,QAAQ;IACR6E,SAAS;IACTlF,OAAO;IACP6C;EACF,CAAC;EAED,SAASqC,SAASA,CAAA,EAAG;IACnB,KAAKnF,aAAa,CAAC,CAAC;EACtB;AACF","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactQuery","require","_sdkConsumer","_useTools","_useToolCallingRuntime","_helpers","useToolAwareSend","chatId","namespace","personaChatService","useSdkConsumer","toolRuntime","useToolCallingRuntime","queryClient","useQueryClient","mutation","useMutation","mutationKey","mutationFn","params","Error","sendChatMessage","onSuccess","data","variables","_data$messages","queryKey","message","setQueryData","append","id","Date","now","toString","content","role","attachments","messages","length","appendMany","send","isRunning","ToolExecutionInProgressError","initialResponse","mutateAsync","chatConfig","resolveResponseToolCalls","functionCallResults"],"sources":["../../../../src/hooks/useTextChat/useToolAwareSend.ts"],"sourcesContent":["import { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n FunctionCall,\n ChatMessageAttachment,\n SendMessageParams,\n} from '../../types';\nimport { useSdkConsumer } from '../../sdk-consumer';\nimport { ToolExecutionInProgressError } from '../useTools';\nimport { useToolCallingRuntime } from '../useTools/runtime/useToolCallingRuntime';\nimport { append, appendMany } from './helpers';\n\nexport interface SendChatMessageResponse {\n functionCalls?: FunctionCall[];\n}\n\nexport function useToolAwareSend(chatId: string, namespace: string) {\n const { personaChatService } = useSdkConsumer();\n const toolRuntime = useToolCallingRuntime<SendChatMessageResponse>();\n const queryClient = useQueryClient();\n\n const mutation = useMutation({\n mutationKey: ['pending-chat-message'],\n mutationFn: (params: SendMessageParams) => {\n if (!personaChatService) {\n throw new Error('personaChatService is required to send messages.');\n }\n return personaChatService.sendChatMessage(params);\n },\n onSuccess(data, variables) {\n const queryKey = ['chat-messages', chatId, namespace];\n\n if (variables.message) {\n queryClient.setQueryData(\n queryKey,\n append({\n id: Date.now().toString(),\n content: variables.message,\n role: '
|
|
1
|
+
{"version":3,"names":["_reactQuery","require","_sdkConsumer","_useTools","_useToolCallingRuntime","_helpers","useToolAwareSend","chatId","namespace","personaChatService","useSdkConsumer","toolRuntime","useToolCallingRuntime","queryClient","useQueryClient","mutation","useMutation","mutationKey","mutationFn","params","Error","sendChatMessage","onSuccess","data","variables","_data$messages","queryKey","message","setQueryData","append","id","Date","now","toString","content","role","attachments","messages","length","appendMany","send","isRunning","ToolExecutionInProgressError","initialResponse","mutateAsync","chatConfig","resolveResponseToolCalls","functionCallResults"],"sources":["../../../../src/hooks/useTextChat/useToolAwareSend.ts"],"sourcesContent":["import { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n FunctionCall,\n ChatMessageAttachment,\n SendMessageParams,\n} from '../../types';\nimport { useSdkConsumer } from '../../sdk-consumer';\nimport { ToolExecutionInProgressError } from '../useTools';\nimport { useToolCallingRuntime } from '../useTools/runtime/useToolCallingRuntime';\nimport { append, appendMany } from './helpers';\n\nexport interface SendChatMessageResponse {\n functionCalls?: FunctionCall[];\n}\n\nexport function useToolAwareSend(chatId: string, namespace: string) {\n const { personaChatService } = useSdkConsumer();\n const toolRuntime = useToolCallingRuntime<SendChatMessageResponse>();\n const queryClient = useQueryClient();\n\n const mutation = useMutation({\n mutationKey: ['pending-chat-message'],\n mutationFn: (params: SendMessageParams) => {\n if (!personaChatService) {\n throw new Error('personaChatService is required to send messages.');\n }\n return personaChatService.sendChatMessage(params);\n },\n onSuccess(data, variables) {\n const queryKey = ['chat-messages', chatId, namespace];\n\n if (variables.message) {\n queryClient.setQueryData(\n queryKey,\n append({\n id: Date.now().toString(),\n content: variables.message,\n role: 'USER' as const,\n attachments: variables.attachments,\n }),\n );\n }\n\n if (data.messages?.length) {\n queryClient.setQueryData(queryKey, appendMany(data.messages));\n }\n },\n });\n\n return async function send(message: {\n message?: string;\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n }) {\n if (toolRuntime.isRunning) {\n throw new ToolExecutionInProgressError();\n }\n\n const initialResponse = await mutation.mutateAsync({\n chatId,\n namespace,\n message: message.message ?? '',\n chatConfig: message.chatConfig,\n attachments: message.attachments,\n });\n\n return toolRuntime.resolveResponseToolCalls(\n initialResponse,\n (functionCallResults) =>\n mutation.mutateAsync({\n chatId,\n namespace,\n message: '',\n functionCallResults,\n }),\n );\n };\n}\n"],"mappings":";;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAMA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,SAAA,GAAAF,OAAA;AACA,IAAAG,sBAAA,GAAAH,OAAA;AACA,IAAAI,QAAA,GAAAJ,OAAA;AAMO,SAASK,gBAAgBA,CAACC,MAAc,EAAEC,SAAiB,EAAE;EAClE,MAAM;IAAEC;EAAmB,CAAC,GAAG,IAAAC,2BAAc,EAAC,CAAC;EAC/C,MAAMC,WAAW,GAAG,IAAAC,4CAAqB,EAA0B,CAAC;EACpE,MAAMC,WAAW,GAAG,IAAAC,0BAAc,EAAC,CAAC;EAEpC,MAAMC,QAAQ,GAAG,IAAAC,uBAAW,EAAC;IAC3BC,WAAW,EAAE,CAAC,sBAAsB,CAAC;IACrCC,UAAU,EAAGC,MAAyB,IAAK;MACzC,IAAI,CAACV,kBAAkB,EAAE;QACvB,MAAM,IAAIW,KAAK,CAAC,kDAAkD,CAAC;MACrE;MACA,OAAOX,kBAAkB,CAACY,eAAe,CAACF,MAAM,CAAC;IACnD,CAAC;IACDG,SAASA,CAACC,IAAI,EAAEC,SAAS,EAAE;MAAA,IAAAC,cAAA;MACzB,MAAMC,QAAQ,GAAG,CAAC,eAAe,EAAEnB,MAAM,EAAEC,SAAS,CAAC;MAErD,IAAIgB,SAAS,CAACG,OAAO,EAAE;QACrBd,WAAW,CAACe,YAAY,CACtBF,QAAQ,EACR,IAAAG,eAAM,EAAC;UACLC,EAAE,EAAEC,IAAI,CAACC,GAAG,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;UACzBC,OAAO,EAAEV,SAAS,CAACG,OAAO;UAC1BQ,IAAI,EAAE,MAAe;UACrBC,WAAW,EAAEZ,SAAS,CAACY;QACzB,CAAC,CACH,CAAC;MACH;MAEA,KAAAX,cAAA,GAAIF,IAAI,CAACc,QAAQ,aAAbZ,cAAA,CAAea,MAAM,EAAE;QACzBzB,WAAW,CAACe,YAAY,CAACF,QAAQ,EAAE,IAAAa,mBAAU,EAAChB,IAAI,CAACc,QAAQ,CAAC,CAAC;MAC/D;IACF;EACF,CAAC,CAAC;EAEF,OAAO,eAAeG,IAAIA,CAACb,OAI1B,EAAE;IACD,IAAIhB,WAAW,CAAC8B,SAAS,EAAE;MACzB,MAAM,IAAIC,sCAA4B,CAAC,CAAC;IAC1C;IAEA,MAAMC,eAAe,GAAG,MAAM5B,QAAQ,CAAC6B,WAAW,CAAC;MACjDrC,MAAM;MACNC,SAAS;MACTmB,OAAO,EAAEA,OAAO,CAACA,OAAO,IAAI,EAAE;MAC9BkB,UAAU,EAAElB,OAAO,CAACkB,UAAU;MAC9BT,WAAW,EAAET,OAAO,CAACS;IACvB,CAAC,CAAC;IAEF,OAAOzB,WAAW,CAACmC,wBAAwB,CACzCH,eAAe,EACdI,mBAAmB,IAClBhC,QAAQ,CAAC6B,WAAW,CAAC;MACnBrC,MAAM;MACNC,SAAS;MACTmB,OAAO,EAAE,EAAE;MACXoB;IACF,CAAC,CACL,CAAC;EACH,CAAC;AACH","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_http","require","BASE_URL","resolveLimitMs","limitation","undefined","type","limit","FetchPersonaChatService","constructor","apiKey","_defineProperty2","default","headers","Authorization","post","path","body","response","fetch","method","JSON","stringify","ok","text","catch","HttpError","status","json","createVoiceConversation","params","chatId","namespace","chatConfig","data","url","conversationUrl","id","interactiveConversationId","createInteractiveConversation","endInteractiveConversation","conversationId","sendChatMessage","message","functionCallResults","attachments","queryChatMessages","_data$pagingMetadata","cursor","query","cursorPaging","messages","chatMessages","map","m","content","role","createdAt","nextCursor","pagingMetadata","cursors","next","exports"],"sources":["../../../src/services/FetchPersonaChatService.ts"],"sourcesContent":["import { HttpError } from '../types/http';\nimport type {\n IPersonaChatService,\n CreateConversationParams,\n ConversationResult,\n EndConversationParams,\n SendMessageParams,\n QueryMessagesParams,\n QueryMessagesResult,\n FunctionCall,\n ChatMessage,\n Limitation,\n} from '../types';\n\nconst BASE_URL = 'https://www.wixapis.com';\n\nfunction resolveLimitMs(limitation?: Limitation): number | undefined {\n if (!limitation) {\n return undefined;\n }\n switch (limitation.type) {\n case 'MINUTES':\n return (limitation.limit ?? 0) * 60 * 1000;\n case 'SECONDS':\n return (limitation.limit ?? 0) * 1000;\n case 'TOKENS':\n default:\n return 0;\n }\n}\n\nexport class FetchPersonaChatService implements IPersonaChatService {\n private readonly apiKey: string;\n\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n private headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Api-Key ${this.apiKey}`,\n };\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await fetch(`${BASE_URL}${path}`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new HttpError(\n response.status,\n `Request failed: ${response.status} ${text}`,\n );\n }\n\n return response.json() as Promise<T>;\n }\n\n async createVoiceConversation(\n params: CreateConversationParams,\n ): Promise<ConversationResult> {\n const { chatId, namespace, chatConfig } = params;\n\n const data = await this.post<{\n conversationUrl?: string;\n interactiveConversationId?: string;\n limitation?: Limitation;\n }>(\n `/_api/persona-chat-service/v1/chats/${chatId}/create-voice-conversation`,\n {\n namespace,\n ...(chatConfig ? { chatConfig } : {}),\n },\n );\n\n return {\n url: data.conversationUrl ?? '',\n id: data.interactiveConversationId ?? '',\n limit: resolveLimitMs(data.limitation),\n };\n }\n\n async createInteractiveConversation(\n params: CreateConversationParams,\n ): Promise<ConversationResult> {\n const { chatId, namespace, chatConfig } = params;\n\n const data = await this.post<{\n conversationUrl?: string;\n interactiveConversationId?: string;\n limitation?: Limitation;\n }>(\n `/_api/persona-chat-service/v1/chats/${chatId}/create-interactive-conversation`,\n {\n namespace,\n ...(chatConfig ? { chatConfig } : {}),\n },\n );\n\n return {\n url: data.conversationUrl ?? '',\n id: data.interactiveConversationId ?? '',\n limit: resolveLimitMs(data.limitation),\n };\n }\n\n async endInteractiveConversation(\n params: EndConversationParams,\n ): Promise<void> {\n const { chatId, conversationId, namespace } = params;\n\n await this.post(\n `/_api/persona-chat-service/v1/chats/${chatId}/end-interactive-conversation`,\n { namespace, interactiveConversationId: conversationId },\n ).catch(() => {\n // best-effort\n });\n }\n\n async sendChatMessage(\n params: SendMessageParams,\n ): Promise<{ functionCalls?: FunctionCall[]; messages?: ChatMessage[] }> {\n const {\n chatId,\n namespace,\n message,\n functionCallResults,\n chatConfig,\n attachments,\n } = params;\n\n return this.post<{\n functionCalls?: FunctionCall[];\n messages?: ChatMessage[];\n }>(`/_api/persona-chat-service/v1/chats/${chatId}/messages`, {\n namespace,\n message,\n ...(functionCallResults ? { functionCallResults } : {}),\n ...(chatConfig ? { chatConfig } : {}),\n ...(attachments ? { attachments } : {}),\n });\n }\n\n async queryChatMessages(\n params: QueryMessagesParams,\n ): Promise<QueryMessagesResult> {\n const { chatId, namespace, limit = 100, cursor } = params;\n\n const data = await this.post<{\n chatMessages?: {\n id: string;\n content: string;\n role: string;\n createdAt?: string;\n }[];\n pagingMetadata?: { cursors?: { next?: string } };\n }>(`/_api/persona-chat-service/v1/chats/${chatId}/messages/query`, {\n namespace,\n query: { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } },\n });\n\n return {\n messages: (data.chatMessages ?? []).map((m) => ({\n id: m.id,\n content: m.content,\n role: m.role as '
|
|
1
|
+
{"version":3,"names":["_http","require","BASE_URL","resolveLimitMs","limitation","undefined","type","limit","FetchPersonaChatService","constructor","apiKey","_defineProperty2","default","headers","Authorization","post","path","body","response","fetch","method","JSON","stringify","ok","text","catch","HttpError","status","json","createVoiceConversation","params","chatId","namespace","chatConfig","data","url","conversationUrl","id","interactiveConversationId","createInteractiveConversation","endInteractiveConversation","conversationId","sendChatMessage","message","functionCallResults","attachments","queryChatMessages","_data$pagingMetadata","cursor","query","cursorPaging","messages","chatMessages","map","m","content","role","createdAt","nextCursor","pagingMetadata","cursors","next","exports"],"sources":["../../../src/services/FetchPersonaChatService.ts"],"sourcesContent":["import { HttpError } from '../types/http';\nimport type {\n IPersonaChatService,\n CreateConversationParams,\n ConversationResult,\n EndConversationParams,\n SendMessageParams,\n QueryMessagesParams,\n QueryMessagesResult,\n FunctionCall,\n ChatMessage,\n Limitation,\n} from '../types';\n\nconst BASE_URL = 'https://www.wixapis.com';\n\nfunction resolveLimitMs(limitation?: Limitation): number | undefined {\n if (!limitation) {\n return undefined;\n }\n switch (limitation.type) {\n case 'MINUTES':\n return (limitation.limit ?? 0) * 60 * 1000;\n case 'SECONDS':\n return (limitation.limit ?? 0) * 1000;\n case 'TOKENS':\n default:\n return 0;\n }\n}\n\nexport class FetchPersonaChatService implements IPersonaChatService {\n private readonly apiKey: string;\n\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n private headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Api-Key ${this.apiKey}`,\n };\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await fetch(`${BASE_URL}${path}`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new HttpError(\n response.status,\n `Request failed: ${response.status} ${text}`,\n );\n }\n\n return response.json() as Promise<T>;\n }\n\n async createVoiceConversation(\n params: CreateConversationParams,\n ): Promise<ConversationResult> {\n const { chatId, namespace, chatConfig } = params;\n\n const data = await this.post<{\n conversationUrl?: string;\n interactiveConversationId?: string;\n limitation?: Limitation;\n }>(\n `/_api/persona-chat-service/v1/chats/${chatId}/create-voice-conversation`,\n {\n namespace,\n ...(chatConfig ? { chatConfig } : {}),\n },\n );\n\n return {\n url: data.conversationUrl ?? '',\n id: data.interactiveConversationId ?? '',\n limit: resolveLimitMs(data.limitation),\n };\n }\n\n async createInteractiveConversation(\n params: CreateConversationParams,\n ): Promise<ConversationResult> {\n const { chatId, namespace, chatConfig } = params;\n\n const data = await this.post<{\n conversationUrl?: string;\n interactiveConversationId?: string;\n limitation?: Limitation;\n }>(\n `/_api/persona-chat-service/v1/chats/${chatId}/create-interactive-conversation`,\n {\n namespace,\n ...(chatConfig ? { chatConfig } : {}),\n },\n );\n\n return {\n url: data.conversationUrl ?? '',\n id: data.interactiveConversationId ?? '',\n limit: resolveLimitMs(data.limitation),\n };\n }\n\n async endInteractiveConversation(\n params: EndConversationParams,\n ): Promise<void> {\n const { chatId, conversationId, namespace } = params;\n\n await this.post(\n `/_api/persona-chat-service/v1/chats/${chatId}/end-interactive-conversation`,\n { namespace, interactiveConversationId: conversationId },\n ).catch(() => {\n // best-effort\n });\n }\n\n async sendChatMessage(\n params: SendMessageParams,\n ): Promise<{ functionCalls?: FunctionCall[]; messages?: ChatMessage[] }> {\n const {\n chatId,\n namespace,\n message,\n functionCallResults,\n chatConfig,\n attachments,\n } = params;\n\n return this.post<{\n functionCalls?: FunctionCall[];\n messages?: ChatMessage[];\n }>(`/_api/persona-chat-service/v1/chats/${chatId}/messages`, {\n namespace,\n message,\n ...(functionCallResults ? { functionCallResults } : {}),\n ...(chatConfig ? { chatConfig } : {}),\n ...(attachments ? { attachments } : {}),\n });\n }\n\n async queryChatMessages(\n params: QueryMessagesParams,\n ): Promise<QueryMessagesResult> {\n const { chatId, namespace, limit = 100, cursor } = params;\n\n const data = await this.post<{\n chatMessages?: {\n id: string;\n content: string;\n role: string;\n createdAt?: string;\n }[];\n pagingMetadata?: { cursors?: { next?: string } };\n }>(`/_api/persona-chat-service/v1/chats/${chatId}/messages/query`, {\n namespace,\n query: { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } },\n });\n\n return {\n messages: (data.chatMessages ?? []).map((m) => ({\n id: m.id,\n content: m.content,\n role: m.role as 'USER' | 'ASSISTANT',\n createdAt: m.createdAt,\n })),\n nextCursor: data.pagingMetadata?.cursors?.next,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.createObjectScanner = createObjectScanner;
|
|
5
|
+
exports.extractContent = extractContent;
|
|
6
|
+
exports.streamChatMessage = streamChatMessage;
|
|
7
|
+
// Pull the assistant text out of one streamed frame. Two framings reach us: a
|
|
8
|
+
// proxy route yields `{ content }` objects, while the gateway wraps each frame
|
|
9
|
+
// as `{ body: base64(json) }`. Returns null for frames without content.
|
|
10
|
+
function extractContent(frame) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(frame);
|
|
13
|
+
if (typeof parsed.content === 'string') {
|
|
14
|
+
return parsed.content;
|
|
15
|
+
}
|
|
16
|
+
if (typeof parsed.body === 'string') {
|
|
17
|
+
const decoded = new TextDecoder().decode(Uint8Array.from(atob(parsed.body), char => char.charCodeAt(0)));
|
|
18
|
+
const inner = JSON.parse(decoded);
|
|
19
|
+
return typeof inner.content === 'string' ? inner.content : null;
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// Incomplete or non-object frame — skip it.
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Feeds decoded text through a running scanner that emits each complete
|
|
28
|
+
// top-level `{…}` object as its closing brace arrives. This keeps a message
|
|
29
|
+
// that's split across reads intact, and parses a newline-free JSON array (the
|
|
30
|
+
// gateway framing) incrementally. Anything outside a top-level object is ignored.
|
|
31
|
+
function createObjectScanner(onObject) {
|
|
32
|
+
let buffer = '';
|
|
33
|
+
let cursor = 0;
|
|
34
|
+
let braceDepth = 0;
|
|
35
|
+
let objectStart = -1;
|
|
36
|
+
let inString = false;
|
|
37
|
+
let escaped = false;
|
|
38
|
+
return function feed(text) {
|
|
39
|
+
buffer += text;
|
|
40
|
+
for (; cursor < buffer.length; cursor++) {
|
|
41
|
+
const char = buffer[cursor];
|
|
42
|
+
if (inString) {
|
|
43
|
+
if (escaped) {
|
|
44
|
+
escaped = false;
|
|
45
|
+
} else if (char === '\\') {
|
|
46
|
+
escaped = true;
|
|
47
|
+
} else if (char === '"') {
|
|
48
|
+
inString = false;
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (char === '"') {
|
|
53
|
+
inString = true;
|
|
54
|
+
} else if (char === '{') {
|
|
55
|
+
if (braceDepth === 0) {
|
|
56
|
+
objectStart = cursor;
|
|
57
|
+
}
|
|
58
|
+
braceDepth++;
|
|
59
|
+
} else if (char === '}' && braceDepth > 0) {
|
|
60
|
+
braceDepth--;
|
|
61
|
+
if (braceDepth === 0 && objectStart >= 0) {
|
|
62
|
+
onObject(buffer.slice(objectStart, cursor + 1));
|
|
63
|
+
objectStart = -1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Framework-agnostic streaming transport. POSTs the message to the
|
|
72
|
+
* caller-supplied SSE endpoint, reads the response body incrementally, and
|
|
73
|
+
* fires `onChunk` per assistant token, `onDone` on close, `onError` on failure.
|
|
74
|
+
* A caller-supplied `AbortSignal` cancels the request; `AbortError` is swallowed.
|
|
75
|
+
*/
|
|
76
|
+
async function streamChatMessage(params, callbacks, signal) {
|
|
77
|
+
const {
|
|
78
|
+
url,
|
|
79
|
+
chatId,
|
|
80
|
+
message,
|
|
81
|
+
namespace,
|
|
82
|
+
chatConfig,
|
|
83
|
+
attachments,
|
|
84
|
+
headers
|
|
85
|
+
} = params;
|
|
86
|
+
const {
|
|
87
|
+
onChunk,
|
|
88
|
+
onDone,
|
|
89
|
+
onError
|
|
90
|
+
} = callbacks;
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
credentials: 'include',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
...headers
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
chatId,
|
|
101
|
+
message,
|
|
102
|
+
namespace,
|
|
103
|
+
chatConfig,
|
|
104
|
+
attachments
|
|
105
|
+
}),
|
|
106
|
+
signal
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok || !response.body) {
|
|
109
|
+
onError(new Error(`Stream request failed: ${response.status}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const scanForObjects = createObjectScanner(frame => {
|
|
113
|
+
const content = extractContent(frame);
|
|
114
|
+
if (content !== null) {
|
|
115
|
+
onChunk(content);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const reader = response.body.getReader();
|
|
119
|
+
const decoder = new TextDecoder();
|
|
120
|
+
for (;;) {
|
|
121
|
+
const {
|
|
122
|
+
done,
|
|
123
|
+
value
|
|
124
|
+
} = await reader.read();
|
|
125
|
+
if (done) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
scanForObjects(decoder.decode(value, {
|
|
129
|
+
stream: true
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
scanForObjects(decoder.decode());
|
|
133
|
+
onDone();
|
|
134
|
+
} catch (err) {
|
|
135
|
+
if (err.name !== 'AbortError') {
|
|
136
|
+
onError(err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=streamChatMessage.js.map
|
|
@@ -0,0 +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 { url, chatId, message, namespace, chatConfig, attachments, 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 }),\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 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["../../../src/types/service.ts"],"sourcesContent":["export type FunctionCall = {\n functionCallId: string;\n name: string;\n arguments: string;\n};\n\nexport type FunctionCallResult = {\n functionCallId: string;\n result: string;\n error?: string;\n};\n\nexport type LimitationType = 'MINUTES' | 'SECONDS' | 'TOKENS';\n\nexport interface Limitation {\n type?: LimitationType;\n limit?: number;\n}\n\nexport interface ConversationResult {\n url: string;\n id: string;\n limit?: number;\n}\n\nexport interface CreateConversationParams {\n chatId: string;\n namespace: string;\n chatConfig?: object;\n /** Whether this is the visitor's first-ever conversation with the persona (drives the opening message). */\n firstConversation?: boolean;\n /** Title of the visitor's last meaningful conversation, for a returning-visitor greeting. */\n lastConversationTopic?: string;\n}\n\nexport interface EndConversationParams {\n chatId: string;\n conversationId: string;\n namespace: string;\n}\n\nexport interface ChatMessageAttachment {\n fileName?: string;\n url?: string;\n mimeType?: string;\n textFileContent?: string;\n}\n\nexport interface SendMessageParams {\n chatId: string;\n namespace: string;\n message: string;\n functionCallResults?: FunctionCallResult[];\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n}\n\nexport interface ChatMessage {\n id: string;\n content: string;\n role: '
|
|
1
|
+
{"version":3,"names":[],"sources":["../../../src/types/service.ts"],"sourcesContent":["export type FunctionCall = {\n functionCallId: string;\n name: string;\n arguments: string;\n};\n\nexport type FunctionCallResult = {\n functionCallId: string;\n result: string;\n error?: string;\n};\n\nexport type LimitationType = 'MINUTES' | 'SECONDS' | 'TOKENS';\n\nexport interface Limitation {\n type?: LimitationType;\n limit?: number;\n}\n\nexport interface ConversationResult {\n url: string;\n id: string;\n limit?: number;\n}\n\nexport interface CreateConversationParams {\n chatId: string;\n namespace: string;\n chatConfig?: object;\n /** Whether this is the visitor's first-ever conversation with the persona (drives the opening message). */\n firstConversation?: boolean;\n /** Title of the visitor's last meaningful conversation, for a returning-visitor greeting. */\n lastConversationTopic?: string;\n}\n\nexport interface EndConversationParams {\n chatId: string;\n conversationId: string;\n namespace: string;\n}\n\nexport interface ChatMessageAttachment {\n fileName?: string;\n url?: string;\n mimeType?: string;\n textFileContent?: string;\n}\n\nexport interface SendMessageParams {\n chatId: string;\n namespace: string;\n message: string;\n functionCallResults?: FunctionCallResult[];\n chatConfig?: object;\n attachments?: ChatMessageAttachment[];\n}\n\nexport interface ChatMessage {\n id: string;\n content: string;\n role: 'USER' | 'ASSISTANT';\n createdAt?: string;\n attachments?: ChatMessageAttachment[];\n}\n\nexport interface QueryMessagesParams {\n chatId: string;\n namespace: string;\n limit?: number;\n cursor?: string;\n}\n\nexport interface QueryMessagesResult {\n messages: ChatMessage[];\n nextCursor?: string;\n}\n\nexport interface IPersonaChatService {\n createVoiceConversation(\n params: CreateConversationParams,\n ): Promise<ConversationResult>;\n createInteractiveConversation(\n params: CreateConversationParams,\n ): Promise<ConversationResult>;\n endInteractiveConversation(params: EndConversationParams): Promise<void>;\n sendChatMessage(\n params: SendMessageParams,\n ): Promise<{ functionCalls?: FunctionCall[]; messages?: ChatMessage[] }>;\n queryChatMessages(params: QueryMessagesParams): Promise<QueryMessagesResult>;\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["../../../../src/hooks/useTextChat/index.ts"],"sourcesContent":["export * from './useTextChat';\n"],"mappings":"AAAA,cAAc,eAAe","ignoreList":[]}
|
|
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":[]}
|