@constructor-io/constructorio-ui-asa 1.7.1 → 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/README.md +21 -17
- package/dist/constructorio-ui-asa-bundled.js +18 -18
- package/lib/cjs/components/Chat/AiMessage.js +2 -3
- package/lib/cjs/components/Chat/Chat.js +38 -10
- package/lib/cjs/components/CioAsa/CioAsa.js +6 -2
- package/lib/cjs/components/CioAsaProvider/CioAsaProvider.js +44 -3
- package/lib/cjs/components/ResultsBlock/ResultsBlock.js +0 -1
- package/lib/cjs/components/ResultsBlock/ResultsGroup.js +5 -5
- package/lib/cjs/hooks/agentStream.js +113 -0
- package/lib/cjs/hooks/asaStreamHandlers.js +2 -1
- package/lib/cjs/hooks/persistence/backgroundTurns.js +121 -0
- package/lib/cjs/hooks/persistence/useChatPersistence.js +485 -0
- package/lib/cjs/hooks/persistence/useForeignStream.js +34 -0
- package/lib/cjs/hooks/persistence/useThreadList.js +45 -0
- package/lib/cjs/hooks/persistence/useWriteQueue.js +17 -0
- package/lib/cjs/hooks/useAsaResults.js +93 -160
- package/lib/cjs/hooks/useAsaTracking.js +3 -3
- package/lib/cjs/hooks/useIsMounted.js +15 -0
- package/lib/cjs/hooks/useLatest.js +10 -0
- package/lib/cjs/hooks/useViewportTracking.js +4 -3
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/utils/chatRecordValidation.js +54 -0
- package/lib/cjs/utils/chatSession.js +98 -0
- package/lib/cjs/utils/chatThreads.js +248 -0
- package/lib/cjs/utils/clientOptions.js +11 -0
- package/lib/cjs/utils/localStoragePersistence.js +391 -0
- package/lib/cjs/utils/translate.js +1 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Chat/AiMessage.js +2 -3
- package/lib/mjs/components/Chat/Chat.js +36 -10
- package/lib/mjs/components/CioAsa/CioAsa.js +6 -2
- package/lib/mjs/components/CioAsaProvider/CioAsaProvider.js +44 -4
- package/lib/mjs/components/ResultsBlock/ResultsBlock.js +0 -1
- package/lib/mjs/components/ResultsBlock/ResultsGroup.js +2 -1
- package/lib/mjs/hooks/agentStream.js +106 -0
- package/lib/mjs/hooks/asaStreamHandlers.js +1 -1
- package/lib/mjs/hooks/persistence/backgroundTurns.js +120 -0
- package/lib/mjs/hooks/persistence/useChatPersistence.js +474 -0
- package/lib/mjs/hooks/persistence/useForeignStream.js +31 -0
- package/lib/mjs/hooks/persistence/useThreadList.js +41 -0
- package/lib/mjs/hooks/persistence/useWriteQueue.js +14 -0
- package/lib/mjs/hooks/useAsaResults.js +87 -157
- package/lib/mjs/hooks/useAsaTracking.js +2 -1
- package/lib/mjs/hooks/useIsMounted.js +12 -0
- package/lib/mjs/hooks/useLatest.js +7 -0
- package/lib/mjs/hooks/useViewportTracking.js +3 -3
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/utils/chatRecordValidation.js +51 -0
- package/lib/mjs/utils/chatSession.js +86 -0
- package/lib/mjs/utils/chatThreads.js +219 -0
- package/lib/mjs/utils/clientOptions.js +7 -0
- package/lib/mjs/utils/localStoragePersistence.js +373 -0
- package/lib/mjs/utils/translate.js +1 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Chat/Chat.d.ts +8 -2
- package/lib/types/components/CioAsa/CioAsa.d.ts +1 -1
- package/lib/types/components/ResultsBlock/ResultsBlock.d.ts +0 -1
- package/lib/types/hooks/agentStream.d.ts +48 -0
- package/lib/types/hooks/asaStreamHandlers.d.ts +1 -0
- package/lib/types/hooks/persistence/backgroundTurns.d.ts +43 -0
- package/lib/types/hooks/persistence/useChatPersistence.d.ts +34 -0
- package/lib/types/hooks/persistence/useForeignStream.d.ts +9 -0
- package/lib/types/hooks/persistence/useThreadList.d.ts +9 -0
- package/lib/types/hooks/persistence/useWriteQueue.d.ts +5 -0
- package/lib/types/hooks/useAsaTracking.d.ts +2 -0
- package/lib/types/hooks/useIsMounted.d.ts +3 -0
- package/lib/types/hooks/useLatest.d.ts +3 -0
- package/lib/types/index.d.ts +2 -1
- package/lib/types/types.d.ts +134 -2
- package/lib/types/utils/chatRecordValidation.d.ts +2 -0
- package/lib/types/utils/chatSession.d.ts +45 -0
- package/lib/types/utils/chatThreads.d.ts +63 -0
- package/lib/types/utils/clientOptions.d.ts +5 -0
- package/lib/types/utils/localStoragePersistence.d.ts +26 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -3,189 +3,116 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const useCioAsaContext_1 = require("./useCioAsaContext");
|
|
6
|
-
const asaStreamHandlers_1 = require("./asaStreamHandlers");
|
|
7
6
|
const useAsaTracking_1 = tslib_1.__importDefault(require("./useAsaTracking"));
|
|
7
|
+
const useChatPersistence_1 = tslib_1.__importDefault(require("./persistence/useChatPersistence"));
|
|
8
|
+
const agentStream_1 = require("./agentStream");
|
|
9
|
+
const chatSession_1 = require("../utils/chatSession");
|
|
10
|
+
const productNormalizer_1 = require("../utils/productNormalizer");
|
|
11
|
+
const useLatest_1 = tslib_1.__importDefault(require("./useLatest"));
|
|
8
12
|
function useAsaResults(options) {
|
|
9
|
-
var _a
|
|
10
|
-
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
11
|
-
const [isStreaming, setIsStreaming] = (0, react_1.useState)(false);
|
|
12
|
-
const isStreamingRef = (0, react_1.useRef)(false);
|
|
13
|
-
const killSwitchRef = (0, react_1.useRef)(false);
|
|
14
|
-
const readerRef = (0, react_1.useRef)(null);
|
|
15
|
-
const threadIdRef = (0, react_1.useRef)((_a = options === null || options === void 0 ? void 0 : options.initialThreadId) !== null && _a !== void 0 ? _a : null);
|
|
16
|
-
const idCounterRef = (0, react_1.useRef)(0);
|
|
17
|
-
// The assistant message the in-flight stream is writing into, so `abort` can
|
|
18
|
-
// settle it without having to guess which message that is.
|
|
19
|
-
const activeAssistantIdRef = (0, react_1.useRef)(null);
|
|
13
|
+
var _a;
|
|
20
14
|
const contextValue = (0, useCioAsaContext_1.useCioAsaContext)();
|
|
21
15
|
if (!contextValue) {
|
|
22
16
|
throw new Error('useAsaResults must be used within a CioAsaProvider.');
|
|
23
17
|
}
|
|
24
|
-
const { cioClient, staticRequestConfigs, callbacks, section } = contextValue;
|
|
18
|
+
const { cioClient, staticRequestConfigs, callbacks, section, persistence, persistenceScope, persistenceIndex, } = contextValue;
|
|
25
19
|
const { domain } = staticRequestConfigs || {};
|
|
26
20
|
if (!cioClient || !domain) {
|
|
27
21
|
throw new Error('useAsaResults requires a configured cioClient and domain. Check your CioAsaProvider props.');
|
|
28
22
|
}
|
|
23
|
+
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
24
|
+
const [isStreaming, setIsStreaming] = (0, react_1.useState)(false);
|
|
25
|
+
const normalizeItemRef = (0, useLatest_1.default)(options === null || options === void 0 ? void 0 : options.normalizeItem);
|
|
26
|
+
const sessionRef = (0, react_1.useRef)(null);
|
|
27
|
+
if (!sessionRef.current) {
|
|
28
|
+
sessionRef.current = (0, chatSession_1.createChatSession)(options === null || options === void 0 ? void 0 : options.initialThreadId, (item) => { var _a; return ((_a = normalizeItemRef.current) !== null && _a !== void 0 ? _a : productNormalizer_1.normalizeItemToProduct)(item); });
|
|
29
|
+
}
|
|
30
|
+
const session = sessionRef.current;
|
|
31
|
+
const turnRef = (0, react_1.useRef)(null);
|
|
29
32
|
const tracking = (0, useAsaTracking_1.default)({
|
|
30
33
|
tracker: cioClient.tracker,
|
|
31
34
|
section,
|
|
32
|
-
threadId: (
|
|
35
|
+
threadId: (_a = session.serverThreadId) !== null && _a !== void 0 ? _a : undefined,
|
|
33
36
|
});
|
|
34
|
-
const trackingRef = (0,
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* cancelling makes the pending `reader.read()` resolve `{ done: true }` — identical
|
|
43
|
-
* to a stream that ended on its own. Without the flag already set, the read loop
|
|
44
|
-
* would take the end-of-stream branch and report a load that never finished.
|
|
45
|
-
*
|
|
46
|
-
* Deliberately touches no state, so the unmount cleanup can share it.
|
|
47
|
-
*/
|
|
48
|
-
const teardownStream = (0, react_1.useCallback)(() => {
|
|
49
|
-
killSwitchRef.current = true;
|
|
50
|
-
if (readerRef.current) {
|
|
51
|
-
readerRef.current.cancel();
|
|
52
|
-
readerRef.current = null;
|
|
53
|
-
}
|
|
54
|
-
}, []);
|
|
55
|
-
const nextMessageId = (0, react_1.useCallback)(() => {
|
|
56
|
-
idCounterRef.current += 1;
|
|
57
|
-
return `msg-${idCounterRef.current}-${Date.now()}`;
|
|
37
|
+
const trackingRef = (0, useLatest_1.default)(tracking);
|
|
38
|
+
const callbacksRef = (0, useLatest_1.default)(callbacks);
|
|
39
|
+
const staticRequestConfigsRef = (0, useLatest_1.default)(staticRequestConfigs);
|
|
40
|
+
/** Stops this tab's stream, if any. `readAgentStream` settles its `done` without touching state. */
|
|
41
|
+
const cancelStream = (0, react_1.useCallback)(() => {
|
|
42
|
+
var _a;
|
|
43
|
+
(_a = turnRef.current) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
44
|
+
turnRef.current = null;
|
|
58
45
|
}, []);
|
|
46
|
+
const store = (0, useChatPersistence_1.default)({
|
|
47
|
+
store: persistence,
|
|
48
|
+
scope: persistenceScope,
|
|
49
|
+
index: persistenceIndex,
|
|
50
|
+
session,
|
|
51
|
+
initialThreadId: options === null || options === void 0 ? void 0 : options.initialThreadId,
|
|
52
|
+
messages,
|
|
53
|
+
setMessages,
|
|
54
|
+
isStreaming,
|
|
55
|
+
setIsStreaming,
|
|
56
|
+
turnRef,
|
|
57
|
+
cancelStream,
|
|
58
|
+
});
|
|
59
|
+
const { beginTurn, screenOwner } = store;
|
|
60
|
+
(0, react_1.useEffect)(() => cancelStream, [cancelStream]);
|
|
59
61
|
const sendMessage = (0, react_1.useCallback)((text, source = 'input') => {
|
|
60
62
|
var _a, _b;
|
|
61
63
|
const intent = text.trim();
|
|
62
|
-
if (!intent ||
|
|
64
|
+
if (!intent || session.isStreaming || session.foreignInFlight)
|
|
63
65
|
return;
|
|
66
|
+
beginTurn();
|
|
64
67
|
trackingRef.current.trackSubmit(intent);
|
|
65
68
|
(_b = (_a = callbacksRef.current) === null || _a === void 0 ? void 0 : _a.onAssistantSubmit) === null || _b === void 0 ? void 0 : _b.call(_a, { intent, source });
|
|
66
69
|
const userMessage = {
|
|
67
|
-
id: nextMessageId(),
|
|
70
|
+
id: (0, chatSession_1.nextMessageId)(session),
|
|
68
71
|
role: 'user',
|
|
69
72
|
text: intent,
|
|
70
73
|
status: 'done',
|
|
71
74
|
};
|
|
72
75
|
const assistantMessage = {
|
|
73
|
-
id: nextMessageId(),
|
|
76
|
+
id: (0, chatSession_1.nextMessageId)(session),
|
|
74
77
|
role: 'assistant',
|
|
75
78
|
text: '',
|
|
76
79
|
groups: [],
|
|
77
80
|
status: 'loading',
|
|
78
81
|
intent,
|
|
79
82
|
};
|
|
80
|
-
activeAssistantIdRef.current = assistantMessage.id;
|
|
81
83
|
setMessages((prev) => [...prev, userMessage, assistantMessage]);
|
|
82
84
|
setIsStreaming(true);
|
|
83
|
-
|
|
84
|
-
killSwitchRef.current = false;
|
|
85
|
-
// `intent` is passed as the first argument and `threadId` is managed per-stream
|
|
86
|
-
// below, so both are stripped here. Everything else configured on the provider
|
|
87
|
-
// (filters, guard, numResultsPerEvent, numResultEvents, qsParam,
|
|
88
|
-
// preFilterExpression, fmtOptions, ...) is forwarded to the agent as-is.
|
|
85
|
+
session.isStreaming = true;
|
|
89
86
|
const agentParams = Object.assign({}, staticRequestConfigsRef.current);
|
|
90
87
|
delete agentParams.intent;
|
|
91
88
|
delete agentParams.threadId;
|
|
92
|
-
const stream = cioClient.agent.getAgentResultsStream(intent, Object.assign(Object.assign(Object.assign({}, agentParams), { domain }), (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
while (!killSwitchRef.current) {
|
|
120
|
-
// eslint-disable-next-line no-await-in-loop
|
|
121
|
-
const res = yield reader.read();
|
|
122
|
-
if (killSwitchRef.current)
|
|
123
|
-
break;
|
|
124
|
-
if (res.done) {
|
|
125
|
-
(0, asaStreamHandlers_1.handleStreamEnd)(assistantMessage.id, setMessages);
|
|
126
|
-
const finishArgs = {
|
|
127
|
-
intent,
|
|
128
|
-
searchResultCount: streamState.groupCount,
|
|
129
|
-
intentResultId: streamState.intentResultId,
|
|
130
|
-
};
|
|
131
|
-
trackingRef.current.trackResultLoadFinished(finishArgs);
|
|
132
|
-
(_d = (_c = callbacksRef.current) === null || _c === void 0 ? void 0 : _c.onResultLoadFinish) === null || _d === void 0 ? void 0 : _d.call(_c, finishArgs);
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
const { type, data } = res.value;
|
|
136
|
-
// `intent_result_id` is shared across the stream; capture it from the first
|
|
137
|
-
// event that carries it so load-start/finish can attribute correctly.
|
|
138
|
-
if (!streamState.intentResultId && (data === null || data === void 0 ? void 0 : data.intent_result_id)) {
|
|
139
|
-
streamState.intentResultId = data.intent_result_id;
|
|
140
|
-
patchAssistant({ intentResultId: streamState.intentResultId });
|
|
141
|
-
}
|
|
142
|
-
if (type === 'start') {
|
|
143
|
-
if (data === null || data === void 0 ? void 0 : data.thread_id) {
|
|
144
|
-
threadIdRef.current = data.thread_id;
|
|
145
|
-
patchAssistant({ threadId: data.thread_id });
|
|
146
|
-
}
|
|
147
|
-
fireLoadStart();
|
|
148
|
-
}
|
|
149
|
-
else if (type === 'group') {
|
|
150
|
-
streamState.pendingGroup = {
|
|
151
|
-
display_name: (_f = (_e = data === null || data === void 0 ? void 0 : data.display_name) !== null && _e !== void 0 ? _e : data === null || data === void 0 ? void 0 : data.group) !== null && _f !== void 0 ? _f : '',
|
|
152
|
-
value: (_h = (_g = data === null || data === void 0 ? void 0 : data.value) !== null && _g !== void 0 ? _g : data === null || data === void 0 ? void 0 : data.group) !== null && _h !== void 0 ? _h : '',
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
else if (type === 'search_result') {
|
|
156
|
-
fireLoadStart();
|
|
157
|
-
streamState.groupCount += 1;
|
|
158
|
-
streamState.pendingGroup = (0, asaStreamHandlers_1.handleSearchResult)(data, streamState.pendingGroup, assistantMessage.id, setMessages);
|
|
159
|
-
}
|
|
160
|
-
else if (type === 'message') {
|
|
161
|
-
fireLoadStart();
|
|
162
|
-
(0, asaStreamHandlers_1.handleMessage)(data, assistantMessage.id, setMessages);
|
|
163
|
-
}
|
|
164
|
-
else if (type === 'follow_up_refinement') {
|
|
165
|
-
fireLoadStart();
|
|
166
|
-
(0, asaStreamHandlers_1.handleFollowUpRefinement)(data, assistantMessage.id, setMessages);
|
|
167
|
-
}
|
|
168
|
-
else if (type === 'server_error') {
|
|
169
|
-
(0, asaStreamHandlers_1.handleServerError)(assistantMessage.id, setMessages);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
catch (_j) {
|
|
175
|
-
(0, asaStreamHandlers_1.handleStreamError)(assistantMessage.id, setMessages);
|
|
176
|
-
}
|
|
177
|
-
finally {
|
|
178
|
-
if (readerRef.current === reader) {
|
|
179
|
-
reader.cancel();
|
|
180
|
-
readerRef.current = null;
|
|
181
|
-
activeAssistantIdRef.current = null;
|
|
182
|
-
setIsStreaming(false);
|
|
183
|
-
isStreamingRef.current = false;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}))();
|
|
187
|
-
}, [cioClient, domain, nextMessageId]);
|
|
188
|
-
(0, react_1.useEffect)(() => () => teardownStream(), [teardownStream]);
|
|
89
|
+
const stream = cioClient.agent.getAgentResultsStream(intent, Object.assign(Object.assign(Object.assign({}, agentParams), { domain }), (session.serverThreadId && { threadId: session.serverThreadId })));
|
|
90
|
+
turnRef.current = (0, agentStream_1.startTurn)(stream, assistantMessage, screenOwner, {
|
|
91
|
+
onLoadStart: (intentResultId) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
const args = { intent, intentResultId };
|
|
94
|
+
trackingRef.current.trackResultLoadStarted(args);
|
|
95
|
+
(_b = (_a = callbacksRef.current) === null || _a === void 0 ? void 0 : _a.onResultLoadStart) === null || _b === void 0 ? void 0 : _b.call(_a, args);
|
|
96
|
+
},
|
|
97
|
+
onFinish: (_a) => {
|
|
98
|
+
var _b, _c;
|
|
99
|
+
var { threadId } = _a, result = tslib_1.__rest(_a, ["threadId"]);
|
|
100
|
+
const args = Object.assign({ intent }, result);
|
|
101
|
+
// The turn may have finished in the background, after the chat moved to another thread.
|
|
102
|
+
trackingRef.current.trackResultLoadFinished(Object.assign(Object.assign({}, args), { threadId }));
|
|
103
|
+
(_c = (_b = callbacksRef.current) === null || _b === void 0 ? void 0 : _b.onResultLoadFinish) === null || _c === void 0 ? void 0 : _c.call(_b, args);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}, [
|
|
107
|
+
cioClient,
|
|
108
|
+
domain,
|
|
109
|
+
session,
|
|
110
|
+
beginTurn,
|
|
111
|
+
screenOwner,
|
|
112
|
+
trackingRef,
|
|
113
|
+
callbacksRef,
|
|
114
|
+
staticRequestConfigsRef,
|
|
115
|
+
]);
|
|
189
116
|
/**
|
|
190
117
|
* Cancels the in-flight request while keeping the conversation: the partial reply is
|
|
191
118
|
* settled as `done` (so the typing indicator stops and streamed text survives) and the
|
|
@@ -202,13 +129,13 @@ function useAsaResults(options) {
|
|
|
202
129
|
* `assistant_result_load_started` with no matching finished event.
|
|
203
130
|
*/
|
|
204
131
|
const abort = (0, react_1.useCallback)(() => {
|
|
205
|
-
|
|
132
|
+
var _a;
|
|
133
|
+
if (!session.isStreaming)
|
|
206
134
|
return;
|
|
207
|
-
|
|
135
|
+
const abortedId = (_a = turnRef.current) === null || _a === void 0 ? void 0 : _a.assistantId;
|
|
136
|
+
cancelStream();
|
|
137
|
+
session.isStreaming = false;
|
|
208
138
|
setIsStreaming(false);
|
|
209
|
-
isStreamingRef.current = false;
|
|
210
|
-
const abortedId = activeAssistantIdRef.current;
|
|
211
|
-
activeAssistantIdRef.current = null;
|
|
212
139
|
if (abortedId) {
|
|
213
140
|
setMessages((prev) => prev.flatMap((msg) => {
|
|
214
141
|
var _a;
|
|
@@ -219,15 +146,21 @@ function useAsaResults(options) {
|
|
|
219
146
|
return [Object.assign(Object.assign({}, msg), { status: 'done' })];
|
|
220
147
|
}));
|
|
221
148
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
149
|
+
// With persistence on, the cancelled turn is stored as it stands.
|
|
150
|
+
session.dirty = true;
|
|
151
|
+
}, [session, cancelStream]);
|
|
152
|
+
return {
|
|
153
|
+
messages,
|
|
154
|
+
sendMessage,
|
|
155
|
+
isStreaming: isStreaming || store.foreignInFlight,
|
|
156
|
+
canAbort: isStreaming,
|
|
157
|
+
abort,
|
|
158
|
+
clearHistory: store.clearHistory,
|
|
159
|
+
isHydrating: store.isHydrating,
|
|
160
|
+
threads: store.threads,
|
|
161
|
+
activeThreadId: store.activeThreadId,
|
|
162
|
+
newThread: store.newThread,
|
|
163
|
+
switchThread: store.switchThread,
|
|
164
|
+
};
|
|
232
165
|
}
|
|
233
166
|
exports.default = useAsaResults;
|
|
@@ -23,9 +23,9 @@ function useAsaTracking({ tracker, section, threadId, }) {
|
|
|
23
23
|
const trackResultLoadStarted = (0, react_1.useCallback)(({ intent, intentResultId }) => {
|
|
24
24
|
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultLoadStarted(Object.assign(Object.assign({ intent }, (intentResultId && { intentResultId })), base));
|
|
25
25
|
}, [assistant, base]);
|
|
26
|
-
const trackResultLoadFinished = (0, react_1.useCallback)(({ intent, searchResultCount, intentResultId }) => {
|
|
27
|
-
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultLoadFinished(Object.assign(Object.assign({ intent,
|
|
28
|
-
searchResultCount }, (intentResultId && { intentResultId })), base));
|
|
26
|
+
const trackResultLoadFinished = (0, react_1.useCallback)(({ intent, searchResultCount, intentResultId, threadId: turnThreadId, }) => {
|
|
27
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultLoadFinished(Object.assign(Object.assign(Object.assign({ intent,
|
|
28
|
+
searchResultCount }, (intentResultId && { intentResultId })), base), (turnThreadId && { threadId: turnThreadId })));
|
|
29
29
|
}, [assistant, base]);
|
|
30
30
|
const trackResultClick = (0, react_1.useCallback)(({ intent, searchResultId, intentResultId, itemId, itemName, variationId, }) => {
|
|
31
31
|
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultClick(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ intent,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
/** A ref that is `true` while mounted, for async work that must not land after unmount. */
|
|
5
|
+
function useIsMounted() {
|
|
6
|
+
const ref = (0, react_1.useRef)(true);
|
|
7
|
+
(0, react_1.useEffect)(() => {
|
|
8
|
+
ref.current = true;
|
|
9
|
+
return () => {
|
|
10
|
+
ref.current = false;
|
|
11
|
+
};
|
|
12
|
+
}, []);
|
|
13
|
+
return ref;
|
|
14
|
+
}
|
|
15
|
+
exports.default = useIsMounted;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
/** A ref that always holds the latest `value`, for async code that must not read stale props. */
|
|
5
|
+
function useLatest(value) {
|
|
6
|
+
const ref = (0, react_1.useRef)(value);
|
|
7
|
+
ref.current = value;
|
|
8
|
+
return ref;
|
|
9
|
+
}
|
|
10
|
+
exports.default = useLatest;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
3
4
|
const react_1 = require("react");
|
|
5
|
+
const useLatest_1 = tslib_1.__importDefault(require("./useLatest"));
|
|
4
6
|
/**
|
|
5
7
|
* Fires `onView` once when the observed element scrolls into the viewport, modeling a
|
|
6
8
|
* per-pod `assistant_search_result_view` impression. Unlike the PIA widget-level view
|
|
7
9
|
* tracker, this fires a single impression per pod (not repeated enter/exit cycles).
|
|
8
10
|
*/
|
|
9
11
|
function useViewportTracking({ onView, enabled = true, }) {
|
|
10
|
-
const onViewRef = (0,
|
|
11
|
-
onViewRef.current = onView;
|
|
12
|
+
const onViewRef = (0, useLatest_1.default)(onView);
|
|
12
13
|
const firedRef = (0, react_1.useRef)(false);
|
|
13
14
|
const cleanupRef = (0, react_1.useRef)(null);
|
|
14
15
|
const ref = (0, react_1.useCallback)((node) => {
|
|
@@ -29,7 +30,7 @@ function useViewportTracking({ onView, enabled = true, }) {
|
|
|
29
30
|
}, { threshold: 0.5 });
|
|
30
31
|
observer.observe(node);
|
|
31
32
|
cleanupRef.current = () => observer.disconnect();
|
|
32
|
-
}, [enabled]);
|
|
33
|
+
}, [enabled, onViewRef]);
|
|
33
34
|
(0, react_1.useEffect)(() => () => {
|
|
34
35
|
if (cleanupRef.current)
|
|
35
36
|
cleanupRef.current();
|
package/lib/cjs/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ConstructorIOClient = exports.normalizeItemToProduct = exports.useCioAsaContext = exports.useAsaTracking = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = exports.CioAsa = void 0;
|
|
6
|
+
exports.ConstructorIOClient = exports.clearPersistedConversations = exports.normalizeItemToProduct = exports.useCioAsaContext = exports.useAsaTracking = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = exports.CioAsa = void 0;
|
|
7
7
|
// Components
|
|
8
8
|
var CioAsa_1 = require("./components/CioAsa/CioAsa");
|
|
9
9
|
Object.defineProperty(exports, "CioAsa", { enumerable: true, get: function () { return __importDefault(CioAsa_1).default; } });
|
|
@@ -25,6 +25,8 @@ Object.defineProperty(exports, "useCioAsaContext", { enumerable: true, get: func
|
|
|
25
25
|
// Utils
|
|
26
26
|
var productNormalizer_1 = require("./utils/productNormalizer");
|
|
27
27
|
Object.defineProperty(exports, "normalizeItemToProduct", { enumerable: true, get: function () { return productNormalizer_1.normalizeItemToProduct; } });
|
|
28
|
+
var localStoragePersistence_1 = require("./utils/localStoragePersistence");
|
|
29
|
+
Object.defineProperty(exports, "clearPersistedConversations", { enumerable: true, get: function () { return localStoragePersistence_1.clearPersistedConversations; } });
|
|
28
30
|
// Re-exported client value from the JS client (used to construct/type a cioClient)
|
|
29
31
|
var types_1 = require("./types");
|
|
30
32
|
Object.defineProperty(exports, "ConstructorIOClient", { enumerable: true, get: function () { return types_1.ConstructorIOClient; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chatThreads_1 = require("./chatThreads");
|
|
4
|
+
const MESSAGE_ROLES = new Set(['user', 'assistant']);
|
|
5
|
+
const MESSAGE_STATUSES = new Set(['idle', 'loading', 'streaming', 'done', 'error']);
|
|
6
|
+
function isProduct(value) {
|
|
7
|
+
if (!value || typeof value !== 'object')
|
|
8
|
+
return false;
|
|
9
|
+
const p = value;
|
|
10
|
+
return typeof p.id === 'string' && typeof p.name === 'string';
|
|
11
|
+
}
|
|
12
|
+
function isResultGroup(value) {
|
|
13
|
+
if (!value || typeof value !== 'object')
|
|
14
|
+
return false;
|
|
15
|
+
const g = value;
|
|
16
|
+
return (Boolean(g.group) &&
|
|
17
|
+
typeof g.group === 'object' &&
|
|
18
|
+
typeof g.group.display_name === 'string' &&
|
|
19
|
+
Array.isArray(g.searchResults) &&
|
|
20
|
+
g.searchResults.every((r) => Boolean(r) && typeof r === 'object') &&
|
|
21
|
+
(g.products === undefined || (Array.isArray(g.products) && g.products.every(isProduct))));
|
|
22
|
+
}
|
|
23
|
+
function isRefinement(value) {
|
|
24
|
+
if (!value || typeof value !== 'object')
|
|
25
|
+
return false;
|
|
26
|
+
const r = value;
|
|
27
|
+
return (typeof r.question === 'string' &&
|
|
28
|
+
Array.isArray(r.options) &&
|
|
29
|
+
r.options.every((o) => typeof o === 'string'));
|
|
30
|
+
}
|
|
31
|
+
function isChatMessage(value) {
|
|
32
|
+
if (!value || typeof value !== 'object')
|
|
33
|
+
return false;
|
|
34
|
+
const m = value;
|
|
35
|
+
return (typeof m.id === 'string' &&
|
|
36
|
+
MESSAGE_ROLES.has(m.role) &&
|
|
37
|
+
typeof m.text === 'string' &&
|
|
38
|
+
MESSAGE_STATUSES.has(m.status) &&
|
|
39
|
+
(m.groups === undefined || (Array.isArray(m.groups) && m.groups.every(isResultGroup))) &&
|
|
40
|
+
(m.refinement === undefined || isRefinement(m.refinement)));
|
|
41
|
+
}
|
|
42
|
+
function isPersistedChat(value) {
|
|
43
|
+
if (!value || typeof value !== 'object')
|
|
44
|
+
return false;
|
|
45
|
+
const chat = value;
|
|
46
|
+
return (chat.version === chatThreads_1.PERSISTED_CHAT_VERSION &&
|
|
47
|
+
typeof chat.threadId === 'string' &&
|
|
48
|
+
Array.isArray(chat.messages) &&
|
|
49
|
+
chat.messages.every(isChatMessage) &&
|
|
50
|
+
typeof chat.createdAt === 'number' &&
|
|
51
|
+
typeof chat.updatedAt === 'number' &&
|
|
52
|
+
(chat.owner === undefined || typeof chat.owner === 'string'));
|
|
53
|
+
}
|
|
54
|
+
exports.default = isPersistedChat;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareSnapshot = exports.adoptSession = exports.forkSession = exports.adoptStoredChat = exports.isOwnMessage = exports.nextMessageId = exports.resetConversation = exports.createChatSession = void 0;
|
|
4
|
+
const chatThreads_1 = require("./chatThreads");
|
|
5
|
+
const productNormalizer_1 = require("./productNormalizer");
|
|
6
|
+
function createChatSession(initialThreadId, normalizeItem = productNormalizer_1.normalizeItemToProduct) {
|
|
7
|
+
return {
|
|
8
|
+
normalizeItem,
|
|
9
|
+
serverThreadId: initialThreadId && !(0, chatThreads_1.isLocalThreadId)(initialThreadId) ? initialThreadId : null,
|
|
10
|
+
storageThreadId: null,
|
|
11
|
+
orphanIds: [],
|
|
12
|
+
createdAt: null,
|
|
13
|
+
lastSyncedAt: 0,
|
|
14
|
+
dirty: false,
|
|
15
|
+
interacted: false,
|
|
16
|
+
loadRequest: 0,
|
|
17
|
+
isStreaming: false,
|
|
18
|
+
foreignInFlight: false,
|
|
19
|
+
idCounter: 0,
|
|
20
|
+
idSuffix: (0, chatThreads_1.randomId)().slice(0, 6),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
exports.createChatSession = createChatSession;
|
|
24
|
+
/** Forget the current conversation. Returns the id it was stored under, if any. */
|
|
25
|
+
function resetConversation(session) {
|
|
26
|
+
const storedId = session.storageThreadId;
|
|
27
|
+
session.serverThreadId = null;
|
|
28
|
+
session.storageThreadId = null;
|
|
29
|
+
session.orphanIds = [];
|
|
30
|
+
session.createdAt = null;
|
|
31
|
+
session.lastSyncedAt = 0;
|
|
32
|
+
session.dirty = false;
|
|
33
|
+
session.loadRequest += 1;
|
|
34
|
+
session.isStreaming = false;
|
|
35
|
+
session.foreignInFlight = false;
|
|
36
|
+
return storedId;
|
|
37
|
+
}
|
|
38
|
+
exports.resetConversation = resetConversation;
|
|
39
|
+
function nextMessageId(session) {
|
|
40
|
+
session.idCounter += 1;
|
|
41
|
+
return `msg-${session.idCounter}-${Date.now()}-${session.idSuffix}`;
|
|
42
|
+
}
|
|
43
|
+
exports.nextMessageId = nextMessageId;
|
|
44
|
+
/** Whether `messageId` was minted by this session rather than by another tab. */
|
|
45
|
+
function isOwnMessage(session, messageId) {
|
|
46
|
+
return messageId.endsWith(`-${session.idSuffix}`);
|
|
47
|
+
}
|
|
48
|
+
exports.isOwnMessage = isOwnMessage;
|
|
49
|
+
/** Makes a stored conversation the one this session continues. */
|
|
50
|
+
function adoptStoredChat(session, chat) {
|
|
51
|
+
session.storageThreadId = chat.threadId;
|
|
52
|
+
session.createdAt = chat.createdAt;
|
|
53
|
+
session.serverThreadId = (0, chatThreads_1.isLocalThreadId)(chat.threadId) ? null : chat.threadId;
|
|
54
|
+
session.idCounter = (0, chatThreads_1.nextMessageCounter)(chat.messages);
|
|
55
|
+
session.lastSyncedAt = chat.updatedAt;
|
|
56
|
+
}
|
|
57
|
+
exports.adoptStoredChat = adoptStoredChat;
|
|
58
|
+
/** A copy that keeps the conversation `session` holds while `session` itself moves on. */
|
|
59
|
+
function forkSession(session) {
|
|
60
|
+
return Object.assign(Object.assign({}, session), { orphanIds: [...session.orphanIds] });
|
|
61
|
+
}
|
|
62
|
+
exports.forkSession = forkSession;
|
|
63
|
+
/** Makes `session` continue the conversation `from` kept, showing `messages`. */
|
|
64
|
+
function adoptSession(session, from, messages) {
|
|
65
|
+
session.serverThreadId = from.serverThreadId;
|
|
66
|
+
session.storageThreadId = from.storageThreadId;
|
|
67
|
+
session.orphanIds = [...from.orphanIds];
|
|
68
|
+
session.createdAt = from.createdAt;
|
|
69
|
+
session.lastSyncedAt = from.lastSyncedAt;
|
|
70
|
+
session.idCounter = (0, chatThreads_1.nextMessageCounter)(messages);
|
|
71
|
+
}
|
|
72
|
+
exports.adoptSession = adoptSession;
|
|
73
|
+
/** Builds the record to write and moves the session onto its key; `staleIds` are the records to retire once stored. */
|
|
74
|
+
function prepareSnapshot(session, messages, now = Date.now()) {
|
|
75
|
+
var _a, _b, _c;
|
|
76
|
+
const previousId = session.storageThreadId;
|
|
77
|
+
const threadId = (_b = (_a = session.serverThreadId) !== null && _a !== void 0 ? _a : previousId) !== null && _b !== void 0 ? _b : (0, chatThreads_1.createLocalThreadId)();
|
|
78
|
+
const staleIds = [previousId !== threadId ? previousId : null, ...session.orphanIds].filter((id) => id !== null && id !== threadId);
|
|
79
|
+
// Strictly increasing so other tabs read a same-millisecond save as newer.
|
|
80
|
+
const updatedAt = Math.max(now, session.lastSyncedAt + 1);
|
|
81
|
+
const createdAt = (_c = session.createdAt) !== null && _c !== void 0 ? _c : updatedAt;
|
|
82
|
+
session.storageThreadId = threadId;
|
|
83
|
+
session.orphanIds = [];
|
|
84
|
+
session.createdAt = createdAt;
|
|
85
|
+
session.lastSyncedAt = updatedAt;
|
|
86
|
+
return {
|
|
87
|
+
staleIds: Array.from(new Set(staleIds)),
|
|
88
|
+
snapshot: {
|
|
89
|
+
version: chatThreads_1.PERSISTED_CHAT_VERSION,
|
|
90
|
+
threadId,
|
|
91
|
+
messages: (0, chatThreads_1.compactMessages)(messages, session.normalizeItem),
|
|
92
|
+
createdAt,
|
|
93
|
+
updatedAt,
|
|
94
|
+
owner: (0, chatThreads_1.getTabId)(),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
exports.prepareSnapshot = prepareSnapshot;
|