@btst/stack 4.1.1 → 4.2.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 +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +15 -9
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +16 -10
- package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.cjs +28 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.mjs +29 -2
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +91 -8
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +91 -8
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +76 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +80 -7
- package/dist/packages/stack/src/plugins/ai-chat/client/components/tool-call-display.cjs +17 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/components/tool-call-display.mjs +18 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/context/chat-analytics.cjs +42 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/context/chat-analytics.mjs +39 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +2 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +2 -1
- package/dist/plugins/ai-chat/api/index.d.cts +2 -2
- package/dist/plugins/ai-chat/api/index.d.mts +2 -2
- package/dist/plugins/ai-chat/api/index.d.ts +2 -2
- package/dist/plugins/ai-chat/client/components/index.d.cts +2 -1
- package/dist/plugins/ai-chat/client/components/index.d.mts +2 -1
- package/dist/plugins/ai-chat/client/components/index.d.ts +2 -1
- package/dist/plugins/ai-chat/client/context/page-ai-context.cjs +4 -0
- package/dist/plugins/ai-chat/client/context/page-ai-context.d.cts +6 -2
- package/dist/plugins/ai-chat/client/context/page-ai-context.d.mts +6 -2
- package/dist/plugins/ai-chat/client/context/page-ai-context.d.ts +6 -2
- package/dist/plugins/ai-chat/client/context/page-ai-context.mjs +4 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/client/index.d.cts +7 -3
- package/dist/plugins/ai-chat/client/index.d.mts +7 -3
- package/dist/plugins/ai-chat/client/index.d.ts +7 -3
- package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.d.cts +1 -1
- package/dist/plugins/blog/query-keys.d.mts +1 -1
- package/dist/plugins/blog/query-keys.d.ts +1 -1
- package/dist/plugins/cms/api/index.d.cts +1 -1
- package/dist/plugins/cms/api/index.d.mts +1 -1
- package/dist/plugins/cms/api/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.d.cts +1 -1
- package/dist/plugins/cms/query-keys.d.mts +1 -1
- package/dist/plugins/cms/query-keys.d.ts +1 -1
- package/dist/plugins/form-builder/api/index.d.cts +1 -1
- package/dist/plugins/form-builder/api/index.d.mts +1 -1
- package/dist/plugins/form-builder/api/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.d.cts +1 -1
- package/dist/plugins/form-builder/query-keys.d.mts +1 -1
- package/dist/plugins/form-builder/query-keys.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
- package/dist/shared/{stack.B5yPgHsp.d.mts → stack.B7Mj74BQ.d.mts} +18 -2
- package/dist/shared/{stack.rVF0f509.d.mts → stack.C-eQN_Jt.d.ts} +60 -4
- package/dist/shared/{stack.rVF0f509.d.ts → stack.C5FZr-jJ.d.mts} +60 -4
- package/dist/shared/{stack.3Sr6qRtT.d.ts → stack.DBH1Bjko.d.ts} +18 -2
- package/dist/shared/{stack.C4oVW2qK.d.cts → stack.Dgt3MCtW.d.cts} +18 -2
- package/dist/shared/{stack.rVF0f509.d.cts → stack.xAiy2VtO.d.cts} +60 -4
- package/package.json +1 -1
- package/src/plugins/ai-chat/__tests__/authorization.test.ts +106 -1
- package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +4 -1
- package/src/plugins/ai-chat/__tests__/page-content.test.ts +177 -0
- package/src/plugins/ai-chat/__tests__/page-widget.test.tsx +573 -0
- package/src/plugins/ai-chat/api/operations.ts +29 -13
- package/src/plugins/ai-chat/api/page-tools.ts +56 -2
- package/src/plugins/ai-chat/api/plugin.ts +13 -0
- package/src/plugins/ai-chat/client/analytics.ts +35 -0
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +113 -10
- package/src/plugins/ai-chat/client/components/chat-layout.tsx +118 -7
- package/src/plugins/ai-chat/client/components/tool-call-display.tsx +29 -1
- package/src/plugins/ai-chat/client/context/chat-analytics.tsx +46 -0
- package/src/plugins/ai-chat/client/context/page-ai-context.tsx +9 -1
- package/src/plugins/ai-chat/client/index.ts +2 -0
- package/src/plugins/ai-chat/client/localization/index.ts +8 -0
- package/src/plugins/ai-chat/client/overrides.ts +5 -0
- package/src/plugins/ai-chat/client/plugin.tsx +3 -0
- package/dist/shared/{stack.lsbdKRj7.d.mts → stack.B8PG21zX.d.mts} +16 -16
- package/dist/shared/{stack.BuxUQqL8.d.ts → stack.CGmh-Lpe.d.ts} +12 -12
- package/dist/shared/{stack.BggTHKlo.d.mts → stack.CTneIW0u.d.mts} +2 -2
- package/dist/shared/{stack.CtrnoQgH.d.cts → stack.Ce6V7eDj.d.cts} +2 -2
- package/dist/shared/{stack.BMlTZnZp.d.ts → stack.CyalJ0j6.d.ts} +16 -16
- package/dist/shared/{stack.lPKwd_v0.d.ts → stack.D5b9l24t.d.ts} +2 -2
- package/dist/shared/{stack.BV0uNV4f.d.cts → stack.D6Pg-ufX.d.cts} +16 -16
- package/dist/shared/{stack.Egr3RWhA.d.mts → stack.Doy2_wgl.d.mts} +12 -12
- package/dist/shared/{stack.CX83YNuS.d.cts → stack.zch70Loj.d.cts} +12 -12
|
@@ -17,6 +17,7 @@ import { createApiClient } from '@btst/stack/plugins/client';
|
|
|
17
17
|
import { aiChatIdentityKey, createAiChatQueryKeys } from '../../../../../../../plugins/ai-chat/query-keys.mjs';
|
|
18
18
|
import { useAiChatIdentityPartition, useConversation, useConversations } from '../hooks/chat-hooks.mjs';
|
|
19
19
|
import { usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
|
|
20
|
+
import { useChatAnalytics } from '../context/chat-analytics.mjs';
|
|
20
21
|
import { navigateAiChatCrossOrigin } from '../navigation.mjs';
|
|
21
22
|
|
|
22
23
|
function reconcilePersistedMessageIds(messages, persistedMessages) {
|
|
@@ -129,6 +130,8 @@ function PermissionedChatMessage({
|
|
|
129
130
|
conversationId,
|
|
130
131
|
ownerId,
|
|
131
132
|
messageId,
|
|
133
|
+
toolNames,
|
|
134
|
+
routeName,
|
|
132
135
|
...messageProps
|
|
133
136
|
}) {
|
|
134
137
|
if (!action) return /* @__PURE__ */ jsx(ChatMessage, { ...messageProps });
|
|
@@ -140,6 +143,8 @@ function PermissionedChatMessage({
|
|
|
140
143
|
conversationId,
|
|
141
144
|
ownerId,
|
|
142
145
|
messageId,
|
|
146
|
+
toolNames,
|
|
147
|
+
routeName,
|
|
143
148
|
children: (allowed) => /* @__PURE__ */ jsx(
|
|
144
149
|
ChatMessage,
|
|
145
150
|
{
|
|
@@ -184,6 +189,10 @@ function ChatInterface({
|
|
|
184
189
|
const siteBasePath = plugins?.aiChat?.site.basePath ?? stackBasePath;
|
|
185
190
|
const isPublicMode = mode === "public";
|
|
186
191
|
const pageAIContext = usePageAIContext();
|
|
192
|
+
const effectiveToolNames = [
|
|
193
|
+
...Object.keys(pageAIContext?.clientTools ?? {}),
|
|
194
|
+
...plugins?.aiChat?.config?.pageContent ? ["readPage"] : []
|
|
195
|
+
];
|
|
187
196
|
const tr = useAiChatTranslation(customLocalization);
|
|
188
197
|
const queryClient = useQueryClient(stackQueryClient);
|
|
189
198
|
const identityPartition = useAiChatIdentityPartition();
|
|
@@ -328,6 +337,29 @@ function ChatInterface({
|
|
|
328
337
|
});
|
|
329
338
|
}, [browserHeaders, credentials, isPublicMode, resolvedApiPath]);
|
|
330
339
|
const addToolOutputRef = useRef(null);
|
|
340
|
+
const mounted = useRef(true);
|
|
341
|
+
const track = useChatAnalytics();
|
|
342
|
+
const inputKind = useRef("typed");
|
|
343
|
+
const responseStarted = useRef(null);
|
|
344
|
+
const trackSubmission = useCallback(
|
|
345
|
+
(kind, inputLength, messageCount, attachmentCount = 0) => {
|
|
346
|
+
responseStarted.current = Date.now();
|
|
347
|
+
track({
|
|
348
|
+
type: "message_submitted",
|
|
349
|
+
inputKind: kind,
|
|
350
|
+
inputLength,
|
|
351
|
+
messageCount,
|
|
352
|
+
attachmentCount
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
[track]
|
|
356
|
+
);
|
|
357
|
+
const trackFailure = useCallback(() => {
|
|
358
|
+
if (!mounted.current || responseStarted.current === null) return;
|
|
359
|
+
const durationMs = Math.max(0, Date.now() - responseStarted.current);
|
|
360
|
+
responseStarted.current = null;
|
|
361
|
+
track({ type: "response_failed", durationMs });
|
|
362
|
+
}, [track]);
|
|
331
363
|
const {
|
|
332
364
|
messages,
|
|
333
365
|
sendMessage,
|
|
@@ -341,15 +373,16 @@ function ChatInterface({
|
|
|
341
373
|
id: `${chatInstanceId}:${isPublicMode ? "public" : identitySessionVersion}`,
|
|
342
374
|
transport,
|
|
343
375
|
// Automatically resubmit after all client-side tool results are provided
|
|
344
|
-
sendAutomaticallyWhen: (options) => (isPublicMode || identitySessionVersion === identitySessionGeneration.current) && lastAssistantMessageIsCompleteWithToolCalls(options),
|
|
376
|
+
sendAutomaticallyWhen: (options) => mounted.current && (isPublicMode || identitySessionVersion === identitySessionGeneration.current) && lastAssistantMessageIsCompleteWithToolCalls(options),
|
|
345
377
|
onToolCall: async ({ toolCall }) => {
|
|
378
|
+
if (!mounted.current) return;
|
|
346
379
|
const toolRequest = pendingStreamRequests.current.at(-1);
|
|
347
380
|
if (!isPublicMode && (identitySessionVersion !== identitySessionGeneration.current || !toolRequest || toolRequest.identityPartitionKey !== latestIdentityPartitionKey.current || toolRequest.generation !== latestStreamRequestGeneration.current || activeStreamPartitionKey.current !== toolRequest.identityPartitionKey)) {
|
|
348
381
|
return;
|
|
349
382
|
}
|
|
350
383
|
const toolIdentityPartitionKey = toolRequest?.identityPartitionKey ?? latestIdentityPartitionKey.current;
|
|
351
384
|
const toolStreamRequestGeneration = toolRequest?.generation ?? latestStreamRequestGeneration.current;
|
|
352
|
-
const toolRequestIsCurrent = () => isPublicMode || identitySessionVersion === identitySessionGeneration.current && toolIdentityPartitionKey === latestIdentityPartitionKey.current && toolStreamRequestGeneration === latestStreamRequestGeneration.current;
|
|
385
|
+
const toolRequestIsCurrent = () => mounted.current && (isPublicMode || identitySessionVersion === identitySessionGeneration.current && toolIdentityPartitionKey === latestIdentityPartitionKey.current && toolStreamRequestGeneration === latestStreamRequestGeneration.current);
|
|
353
386
|
const toolName = toolCall.toolName;
|
|
354
387
|
const handler = pageAIContextRef.current?.clientTools?.[toolName];
|
|
355
388
|
if (handler) {
|
|
@@ -389,6 +422,7 @@ function ChatInterface({
|
|
|
389
422
|
}
|
|
390
423
|
},
|
|
391
424
|
onError: (err) => {
|
|
425
|
+
if (!mounted.current) return;
|
|
392
426
|
console.error("useChat onError:", err);
|
|
393
427
|
if (!isPublicMode && identitySessionVersion !== identitySessionGeneration.current) {
|
|
394
428
|
return;
|
|
@@ -398,8 +432,30 @@ function ChatInterface({
|
|
|
398
432
|
if (!isPublicMode && failedPartitionKey !== latestIdentityPartitionKey.current) {
|
|
399
433
|
return;
|
|
400
434
|
}
|
|
435
|
+
trackFailure();
|
|
401
436
|
},
|
|
402
|
-
onFinish: async () => {
|
|
437
|
+
onFinish: async (completion) => {
|
|
438
|
+
if (!mounted.current || !isPublicMode && identitySessionVersion !== identitySessionGeneration.current)
|
|
439
|
+
return;
|
|
440
|
+
if (completion?.isError || completion?.isDisconnect) trackFailure();
|
|
441
|
+
else if (completion?.isAbort) responseStarted.current = null;
|
|
442
|
+
else if (completion && responseStarted.current !== null && !lastAssistantMessageIsCompleteWithToolCalls({
|
|
443
|
+
messages: completion.messages
|
|
444
|
+
})) {
|
|
445
|
+
const durationMs = Math.max(0, Date.now() - responseStarted.current);
|
|
446
|
+
responseStarted.current = null;
|
|
447
|
+
track({
|
|
448
|
+
type: "response_completed",
|
|
449
|
+
durationMs,
|
|
450
|
+
outputLength: completion.message.parts.reduce(
|
|
451
|
+
(count, part) => count + (part.type === "text" ? part.text.length : 0),
|
|
452
|
+
0
|
|
453
|
+
),
|
|
454
|
+
toolCallCount: completion.message.parts.filter(
|
|
455
|
+
(part) => part.type === "dynamic-tool" || part.type.startsWith("tool-")
|
|
456
|
+
).length
|
|
457
|
+
});
|
|
458
|
+
}
|
|
403
459
|
if (isPublicMode) {
|
|
404
460
|
activeStreamPartitionKey.current = void 0;
|
|
405
461
|
return;
|
|
@@ -415,7 +471,7 @@ function ChatInterface({
|
|
|
415
471
|
if (!finishedRequest || finishedRequest.generation === latestStreamRequestGeneration.current) {
|
|
416
472
|
activeStreamPartitionKey.current = void 0;
|
|
417
473
|
}
|
|
418
|
-
const identityIsCurrent = () => finishingPartitionKey === latestIdentityPartitionKey.current && (!finishedRequest || finishedRequest.generation === latestStreamRequestGeneration.current);
|
|
474
|
+
const identityIsCurrent = () => mounted.current && finishingPartitionKey === latestIdentityPartitionKey.current && (!finishedRequest || finishedRequest.generation === latestStreamRequestGeneration.current);
|
|
419
475
|
const finishingIdentityPartition = latestIdentityPartition.current;
|
|
420
476
|
const finishingConversationsListQueryKey = latestConversationsListQueryKey.current;
|
|
421
477
|
const finishingHeaders = latestHeaders.current;
|
|
@@ -596,6 +652,15 @@ function ChatInterface({
|
|
|
596
652
|
useLayoutEffect(() => {
|
|
597
653
|
latestMessages.current = messages;
|
|
598
654
|
}, [messages]);
|
|
655
|
+
const stopRef = useRef(stop);
|
|
656
|
+
stopRef.current = stop;
|
|
657
|
+
useEffect(() => {
|
|
658
|
+
mounted.current = true;
|
|
659
|
+
return () => {
|
|
660
|
+
mounted.current = false;
|
|
661
|
+
void stopRef.current();
|
|
662
|
+
};
|
|
663
|
+
}, []);
|
|
599
664
|
const previousIdentityPartition = useRef(identityPartitionKey);
|
|
600
665
|
useLayoutEffect(() => {
|
|
601
666
|
const nextPartition = identityPartitionKey;
|
|
@@ -609,6 +674,7 @@ function ChatInterface({
|
|
|
609
674
|
activeStreamPartitionKey.current = void 0;
|
|
610
675
|
pendingStreamRequests.current = [];
|
|
611
676
|
identitySessionGeneration.current += 1;
|
|
677
|
+
responseStarted.current = null;
|
|
612
678
|
setIdentitySessionVersion(identitySessionGeneration.current);
|
|
613
679
|
latestStreamRequestGeneration.current = ++nextStreamRequestGeneration.current;
|
|
614
680
|
void stop();
|
|
@@ -691,12 +757,20 @@ function ChatInterface({
|
|
|
691
757
|
}
|
|
692
758
|
}, [messages, isPublicMode, onMessagesChange, isMessagesInitialized]);
|
|
693
759
|
const handleInputChange = (e) => {
|
|
760
|
+
inputKind.current = "typed";
|
|
694
761
|
setInput(e.target.value);
|
|
695
762
|
};
|
|
696
763
|
const handleSubmit = async (e, files) => {
|
|
697
764
|
e.preventDefault();
|
|
698
765
|
const text = input.trim();
|
|
699
766
|
if (!text && (!files || files.length === 0)) return;
|
|
767
|
+
trackSubmission(
|
|
768
|
+
inputKind.current,
|
|
769
|
+
text.length,
|
|
770
|
+
messages.length + 1,
|
|
771
|
+
files?.length ?? 0
|
|
772
|
+
);
|
|
773
|
+
inputKind.current = "typed";
|
|
700
774
|
if (!isPublicMode && !id && messages.length === 0) {
|
|
701
775
|
isFirstMessageSentRef.current = true;
|
|
702
776
|
}
|
|
@@ -735,6 +809,7 @@ function ChatInterface({
|
|
|
735
809
|
if (!isPublicMode && (sendIdentityPartitionKey !== latestIdentityPartitionKey.current || sendIdentitySessionGeneration !== identitySessionGeneration.current)) {
|
|
736
810
|
return;
|
|
737
811
|
}
|
|
812
|
+
trackFailure();
|
|
738
813
|
setInput(savedInput);
|
|
739
814
|
setAttachedFiles(savedFiles);
|
|
740
815
|
if (isFirstMessageSentRef.current && !hasNavigatedRef.current) {
|
|
@@ -748,17 +823,19 @@ function ChatInterface({
|
|
|
748
823
|
const handleRetry = useCallback(() => {
|
|
749
824
|
setHistorySyncError(null);
|
|
750
825
|
activeStreamPartitionKey.current = latestIdentityPartitionKey.current;
|
|
826
|
+
trackSubmission("retry", 0, messages.length);
|
|
751
827
|
regenerate();
|
|
752
|
-
}, [regenerate]);
|
|
828
|
+
}, [regenerate, trackSubmission, messages.length]);
|
|
753
829
|
useEffect(() => {
|
|
754
830
|
if (pendingEdit && pendingEdit.identityPartitionKey === latestIdentityPartitionKey.current && pendingEdit.streamRequestGeneration === latestStreamRequestGeneration.current && messages.length === pendingEdit.expectedLength) {
|
|
755
831
|
const textToSend = pendingEdit.text;
|
|
756
832
|
setPendingEdit(null);
|
|
757
833
|
isEditInProgressRef.current = false;
|
|
758
834
|
activeStreamPartitionKey.current = latestIdentityPartitionKey.current;
|
|
835
|
+
trackSubmission("edit", textToSend.length, messages.length + 1);
|
|
759
836
|
sendMessage({ text: textToSend });
|
|
760
837
|
}
|
|
761
|
-
}, [messages.length, pendingEdit, sendMessage]);
|
|
838
|
+
}, [messages.length, pendingEdit, sendMessage, trackSubmission]);
|
|
762
839
|
const handleEditMessage = useCallback(
|
|
763
840
|
(messageId, newText) => {
|
|
764
841
|
setHistorySyncError(null);
|
|
@@ -817,7 +894,11 @@ function ChatInterface({
|
|
|
817
894
|
"button",
|
|
818
895
|
{
|
|
819
896
|
type: "button",
|
|
820
|
-
onClick: () =>
|
|
897
|
+
onClick: () => {
|
|
898
|
+
inputKind.current = "suggestion";
|
|
899
|
+
track({ type: "suggestion_selected", index });
|
|
900
|
+
setInput(suggestion);
|
|
901
|
+
},
|
|
821
902
|
className: "px-3 py-2 text-sm rounded-lg border border-border bg-background hover:bg-accent hover:text-accent-foreground transition-colors text-foreground",
|
|
822
903
|
children: suggestion
|
|
823
904
|
},
|
|
@@ -828,6 +909,8 @@ function ChatInterface({
|
|
|
828
909
|
PermissionedChatMessage,
|
|
829
910
|
{
|
|
830
911
|
publicMode: isPublicMode,
|
|
912
|
+
toolNames: effectiveToolNames,
|
|
913
|
+
routeName: pageAIContext?.routeName,
|
|
831
914
|
action: m.role === "user" ? "edit" : m.role === "assistant" && index === messages.length - 1 ? "retry" : void 0,
|
|
832
915
|
conversationId: currentConversationId,
|
|
833
916
|
ownerId: currentConversationOwnerId,
|
|
@@ -878,7 +961,7 @@ function ChatInterface({
|
|
|
878
961
|
action: "send",
|
|
879
962
|
conversationId: currentConversationId,
|
|
880
963
|
ownerId: currentConversationOwnerId,
|
|
881
|
-
toolNames:
|
|
964
|
+
toolNames: effectiveToolNames,
|
|
882
965
|
routeName: pageAIContext?.routeName,
|
|
883
966
|
children: (allowed) => allowed && !historySyncError ? /* @__PURE__ */ jsx(
|
|
884
967
|
"div",
|
|
@@ -13,13 +13,19 @@ const chatInterface = require('./chat-interface.cjs');
|
|
|
13
13
|
const plugins_aiChat_client_context_pageAiContext = require('../../../../../../../plugins/ai-chat/client/context/page-ai-context.cjs');
|
|
14
14
|
const context = require('@btst/stack/context');
|
|
15
15
|
const overrides = require('../overrides.cjs');
|
|
16
|
+
const chatAnalytics = require('../context/chat-analytics.cjs');
|
|
16
17
|
const index = require('../localization/index.cjs');
|
|
17
18
|
|
|
18
19
|
function ChatLayout(props) {
|
|
20
|
+
return /* @__PURE__ */ jsxRuntime.jsx(plugins_aiChat_client_context_pageAiContext.PageAIContextScope.Provider, { value: props.pageContext, children: /* @__PURE__ */ jsxRuntime.jsx(chatAnalytics.ChatAnalyticsPageKey.Provider, { value: props.pageKey, children: /* @__PURE__ */ jsxRuntime.jsx(ChatLayoutContent, { ...props }, props.pageKey) }) });
|
|
21
|
+
}
|
|
22
|
+
function ChatLayoutContent(props) {
|
|
23
|
+
const track = chatAnalytics.useChatAnalytics();
|
|
19
24
|
const {
|
|
20
25
|
conversationId,
|
|
21
26
|
layout = "full",
|
|
22
27
|
className,
|
|
28
|
+
style,
|
|
23
29
|
showSidebar: requestedShowSidebar = true,
|
|
24
30
|
initialMessages,
|
|
25
31
|
onMessagesChange,
|
|
@@ -30,8 +36,8 @@ function ChatLayout(props) {
|
|
|
30
36
|
const resolvedMode = overrides.resolveAiChatMode(plugins?.aiChat?.config);
|
|
31
37
|
const showSidebar = requestedShowSidebar && resolvedMode !== "public";
|
|
32
38
|
const tr = index.useAiChatTranslation(localization);
|
|
33
|
-
const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "600px" :
|
|
34
|
-
const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "380px" :
|
|
39
|
+
const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "min(600px, calc(100dvh - 112px))" : void 0;
|
|
40
|
+
const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "min(380px, calc(100vw - 32px))" : void 0;
|
|
35
41
|
const defaultOpen = props.layout === "widget" ? props.defaultOpen ?? false : false;
|
|
36
42
|
const showTrigger = props.layout === "widget" ? props.showTrigger ?? true : true;
|
|
37
43
|
const [sidebarOpen, setSidebarOpen] = React.useState(true);
|
|
@@ -40,6 +46,37 @@ function ChatLayout(props) {
|
|
|
40
46
|
const [widgetOpen, setWidgetOpen] = React.useState(defaultOpen);
|
|
41
47
|
const [widgetResetKey, setWidgetResetKey] = React.useState(0);
|
|
42
48
|
const [widgetEverOpened, setWidgetEverOpened] = React.useState(defaultOpen);
|
|
49
|
+
const [tipDismissed, setTipDismissed] = React.useState(defaultOpen);
|
|
50
|
+
const introTip = props.layout === "widget" ? props.introTip : void 0;
|
|
51
|
+
const introTipDuration = props.layout === "widget" ? props.introTipDuration ?? 6e3 : 6e3;
|
|
52
|
+
const tipShown = React.useRef(false);
|
|
53
|
+
const tipDismissedRef = React.useRef(defaultOpen);
|
|
54
|
+
const dismissTip = React.useCallback(
|
|
55
|
+
(reason) => {
|
|
56
|
+
if (tipShown.current && !tipDismissedRef.current) {
|
|
57
|
+
tipDismissedRef.current = true;
|
|
58
|
+
track({ type: "intro_tip_dismissed", reason });
|
|
59
|
+
}
|
|
60
|
+
setTipDismissed(true);
|
|
61
|
+
},
|
|
62
|
+
[track]
|
|
63
|
+
);
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
if (!introTip || !showTrigger || tipDismissed) return;
|
|
66
|
+
if (!tipShown.current) {
|
|
67
|
+
tipShown.current = true;
|
|
68
|
+
track({ type: "intro_tip_shown" });
|
|
69
|
+
}
|
|
70
|
+
const timeout = setTimeout(() => dismissTip("timeout"), introTipDuration);
|
|
71
|
+
return () => clearTimeout(timeout);
|
|
72
|
+
}, [
|
|
73
|
+
introTip,
|
|
74
|
+
introTipDuration,
|
|
75
|
+
showTrigger,
|
|
76
|
+
tipDismissed,
|
|
77
|
+
dismissTip,
|
|
78
|
+
track
|
|
79
|
+
]);
|
|
43
80
|
const pageAIContext = plugins_aiChat_client_context_pageAiContext.usePageAIContext();
|
|
44
81
|
const handleNewChat = React.useCallback(() => {
|
|
45
82
|
if (!conversationId) {
|
|
@@ -52,10 +89,38 @@ function ChatLayout(props) {
|
|
|
52
89
|
{
|
|
53
90
|
className: utils.cn(
|
|
54
91
|
"flex flex-col items-end gap-3",
|
|
92
|
+
props.layout === "widget" && props.floating && "fixed right-4 bottom-[max(1rem,env(safe-area-inset-bottom))] z-50",
|
|
55
93
|
className,
|
|
56
94
|
classNames?.container
|
|
57
95
|
),
|
|
96
|
+
style,
|
|
58
97
|
children: [
|
|
98
|
+
introTip && showTrigger && !tipDismissed && !widgetOpen && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
99
|
+
"div",
|
|
100
|
+
{
|
|
101
|
+
role: "status",
|
|
102
|
+
className: "flex max-w-[min(280px,calc(100vw-32px))] items-center gap-2 rounded-lg border bg-background p-3 text-sm text-foreground shadow-lg",
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: introTip }),
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
106
|
+
button.Button,
|
|
107
|
+
{
|
|
108
|
+
type: "button",
|
|
109
|
+
variant: "ghost",
|
|
110
|
+
size: "icon",
|
|
111
|
+
className: "h-7 w-7 shrink-0",
|
|
112
|
+
onClick: () => dismissTip("manual"),
|
|
113
|
+
"aria-label": tr(
|
|
114
|
+
"A11Y_DISMISS_CHAT_TIP",
|
|
115
|
+
"aiChat.a11y.dismissChatTip",
|
|
116
|
+
"Dismiss chat tip"
|
|
117
|
+
),
|
|
118
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
),
|
|
59
124
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
60
125
|
"div",
|
|
61
126
|
{
|
|
@@ -84,6 +149,7 @@ function ChatLayout(props) {
|
|
|
84
149
|
size: "icon",
|
|
85
150
|
className: "h-5 w-5",
|
|
86
151
|
onClick: () => {
|
|
152
|
+
track({ type: "conversation_cleared" });
|
|
87
153
|
onClear?.();
|
|
88
154
|
setWidgetResetKey((prev) => prev + 1);
|
|
89
155
|
},
|
|
@@ -106,7 +172,10 @@ function ChatLayout(props) {
|
|
|
106
172
|
variant: "ghost",
|
|
107
173
|
size: "icon",
|
|
108
174
|
className: "h-5 w-5",
|
|
109
|
-
onClick: () =>
|
|
175
|
+
onClick: () => {
|
|
176
|
+
setWidgetOpen(false);
|
|
177
|
+
track({ type: "widget_closed" });
|
|
178
|
+
},
|
|
110
179
|
"aria-label": tr(
|
|
111
180
|
"A11Y_CLOSE_CHAT",
|
|
112
181
|
"aiChat.a11y.closeChat",
|
|
@@ -135,6 +204,8 @@ function ChatLayout(props) {
|
|
|
135
204
|
size: "icon",
|
|
136
205
|
className: "h-12 w-12 rounded-full shadow-lg",
|
|
137
206
|
onClick: () => {
|
|
207
|
+
dismissTip("open");
|
|
208
|
+
track({ type: widgetOpen ? "widget_closed" : "widget_opened" });
|
|
138
209
|
setWidgetOpen((prev) => !prev);
|
|
139
210
|
setWidgetEverOpened(true);
|
|
140
211
|
},
|
|
@@ -156,6 +227,7 @@ function ChatLayout(props) {
|
|
|
156
227
|
classNames?.container
|
|
157
228
|
),
|
|
158
229
|
"data-testid": "chat-layout",
|
|
230
|
+
style,
|
|
159
231
|
children: [
|
|
160
232
|
showSidebar && /* @__PURE__ */ jsxRuntime.jsx(
|
|
161
233
|
"div",
|
|
@@ -240,6 +312,7 @@ function ChatLayout(props) {
|
|
|
240
312
|
variant: "ghost",
|
|
241
313
|
size: "icon",
|
|
242
314
|
onClick: () => {
|
|
315
|
+
track({ type: "conversation_cleared" });
|
|
243
316
|
onClear();
|
|
244
317
|
setChatResetKey((prev) => prev + 1);
|
|
245
318
|
},
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import { useState, useCallback } from 'react';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
4
4
|
import { Button } from '../../../../../../ui/src/components/button.mjs';
|
|
5
5
|
import { Badge } from '../../../../../../ui/src/components/badge.mjs';
|
|
6
6
|
import { Sheet, SheetTrigger, SheetContent } from '../../../../../../ui/src/components/sheet.mjs';
|
|
7
|
-
import { Sparkles, Trash2,
|
|
7
|
+
import { X, Sparkles, Trash2, Menu, PanelLeftClose, PanelLeft } from 'lucide-react';
|
|
8
8
|
import { cn } from '../../../../../../ui/src/lib/utils.mjs';
|
|
9
9
|
import { ChatSidebar } from './chat-sidebar.mjs';
|
|
10
10
|
import { ChatInterface } from './chat-interface.mjs';
|
|
11
|
-
import { usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
|
|
11
|
+
import { PageAIContextScope, usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
|
|
12
12
|
import { usePluginOverrides, useStack } from '@btst/stack/context';
|
|
13
13
|
import { resolveAiChatMode } from '../overrides.mjs';
|
|
14
|
+
import { ChatAnalyticsPageKey, useChatAnalytics } from '../context/chat-analytics.mjs';
|
|
14
15
|
import { useAiChatTranslation } from '../localization/index.mjs';
|
|
15
16
|
|
|
16
17
|
function ChatLayout(props) {
|
|
18
|
+
return /* @__PURE__ */ jsx(PageAIContextScope.Provider, { value: props.pageContext, children: /* @__PURE__ */ jsx(ChatAnalyticsPageKey.Provider, { value: props.pageKey, children: /* @__PURE__ */ jsx(ChatLayoutContent, { ...props }, props.pageKey) }) });
|
|
19
|
+
}
|
|
20
|
+
function ChatLayoutContent(props) {
|
|
21
|
+
const track = useChatAnalytics();
|
|
17
22
|
const {
|
|
18
23
|
conversationId,
|
|
19
24
|
layout = "full",
|
|
20
25
|
className,
|
|
26
|
+
style,
|
|
21
27
|
showSidebar: requestedShowSidebar = true,
|
|
22
28
|
initialMessages,
|
|
23
29
|
onMessagesChange,
|
|
@@ -28,8 +34,8 @@ function ChatLayout(props) {
|
|
|
28
34
|
const resolvedMode = resolveAiChatMode(plugins?.aiChat?.config);
|
|
29
35
|
const showSidebar = requestedShowSidebar && resolvedMode !== "public";
|
|
30
36
|
const tr = useAiChatTranslation(localization);
|
|
31
|
-
const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "600px" :
|
|
32
|
-
const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "380px" :
|
|
37
|
+
const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "min(600px, calc(100dvh - 112px))" : void 0;
|
|
38
|
+
const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "min(380px, calc(100vw - 32px))" : void 0;
|
|
33
39
|
const defaultOpen = props.layout === "widget" ? props.defaultOpen ?? false : false;
|
|
34
40
|
const showTrigger = props.layout === "widget" ? props.showTrigger ?? true : true;
|
|
35
41
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
@@ -38,6 +44,37 @@ function ChatLayout(props) {
|
|
|
38
44
|
const [widgetOpen, setWidgetOpen] = useState(defaultOpen);
|
|
39
45
|
const [widgetResetKey, setWidgetResetKey] = useState(0);
|
|
40
46
|
const [widgetEverOpened, setWidgetEverOpened] = useState(defaultOpen);
|
|
47
|
+
const [tipDismissed, setTipDismissed] = useState(defaultOpen);
|
|
48
|
+
const introTip = props.layout === "widget" ? props.introTip : void 0;
|
|
49
|
+
const introTipDuration = props.layout === "widget" ? props.introTipDuration ?? 6e3 : 6e3;
|
|
50
|
+
const tipShown = useRef(false);
|
|
51
|
+
const tipDismissedRef = useRef(defaultOpen);
|
|
52
|
+
const dismissTip = useCallback(
|
|
53
|
+
(reason) => {
|
|
54
|
+
if (tipShown.current && !tipDismissedRef.current) {
|
|
55
|
+
tipDismissedRef.current = true;
|
|
56
|
+
track({ type: "intro_tip_dismissed", reason });
|
|
57
|
+
}
|
|
58
|
+
setTipDismissed(true);
|
|
59
|
+
},
|
|
60
|
+
[track]
|
|
61
|
+
);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!introTip || !showTrigger || tipDismissed) return;
|
|
64
|
+
if (!tipShown.current) {
|
|
65
|
+
tipShown.current = true;
|
|
66
|
+
track({ type: "intro_tip_shown" });
|
|
67
|
+
}
|
|
68
|
+
const timeout = setTimeout(() => dismissTip("timeout"), introTipDuration);
|
|
69
|
+
return () => clearTimeout(timeout);
|
|
70
|
+
}, [
|
|
71
|
+
introTip,
|
|
72
|
+
introTipDuration,
|
|
73
|
+
showTrigger,
|
|
74
|
+
tipDismissed,
|
|
75
|
+
dismissTip,
|
|
76
|
+
track
|
|
77
|
+
]);
|
|
41
78
|
const pageAIContext = usePageAIContext();
|
|
42
79
|
const handleNewChat = useCallback(() => {
|
|
43
80
|
if (!conversationId) {
|
|
@@ -50,10 +87,38 @@ function ChatLayout(props) {
|
|
|
50
87
|
{
|
|
51
88
|
className: cn(
|
|
52
89
|
"flex flex-col items-end gap-3",
|
|
90
|
+
props.layout === "widget" && props.floating && "fixed right-4 bottom-[max(1rem,env(safe-area-inset-bottom))] z-50",
|
|
53
91
|
className,
|
|
54
92
|
classNames?.container
|
|
55
93
|
),
|
|
94
|
+
style,
|
|
56
95
|
children: [
|
|
96
|
+
introTip && showTrigger && !tipDismissed && !widgetOpen && /* @__PURE__ */ jsxs(
|
|
97
|
+
"div",
|
|
98
|
+
{
|
|
99
|
+
role: "status",
|
|
100
|
+
className: "flex max-w-[min(280px,calc(100vw-32px))] items-center gap-2 rounded-lg border bg-background p-3 text-sm text-foreground shadow-lg",
|
|
101
|
+
children: [
|
|
102
|
+
/* @__PURE__ */ jsx("span", { children: introTip }),
|
|
103
|
+
/* @__PURE__ */ jsx(
|
|
104
|
+
Button,
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
variant: "ghost",
|
|
108
|
+
size: "icon",
|
|
109
|
+
className: "h-7 w-7 shrink-0",
|
|
110
|
+
onClick: () => dismissTip("manual"),
|
|
111
|
+
"aria-label": tr(
|
|
112
|
+
"A11Y_DISMISS_CHAT_TIP",
|
|
113
|
+
"aiChat.a11y.dismissChatTip",
|
|
114
|
+
"Dismiss chat tip"
|
|
115
|
+
),
|
|
116
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
),
|
|
57
122
|
/* @__PURE__ */ jsxs(
|
|
58
123
|
"div",
|
|
59
124
|
{
|
|
@@ -82,6 +147,7 @@ function ChatLayout(props) {
|
|
|
82
147
|
size: "icon",
|
|
83
148
|
className: "h-5 w-5",
|
|
84
149
|
onClick: () => {
|
|
150
|
+
track({ type: "conversation_cleared" });
|
|
85
151
|
onClear?.();
|
|
86
152
|
setWidgetResetKey((prev) => prev + 1);
|
|
87
153
|
},
|
|
@@ -104,7 +170,10 @@ function ChatLayout(props) {
|
|
|
104
170
|
variant: "ghost",
|
|
105
171
|
size: "icon",
|
|
106
172
|
className: "h-5 w-5",
|
|
107
|
-
onClick: () =>
|
|
173
|
+
onClick: () => {
|
|
174
|
+
setWidgetOpen(false);
|
|
175
|
+
track({ type: "widget_closed" });
|
|
176
|
+
},
|
|
108
177
|
"aria-label": tr(
|
|
109
178
|
"A11Y_CLOSE_CHAT",
|
|
110
179
|
"aiChat.a11y.closeChat",
|
|
@@ -133,6 +202,8 @@ function ChatLayout(props) {
|
|
|
133
202
|
size: "icon",
|
|
134
203
|
className: "h-12 w-12 rounded-full shadow-lg",
|
|
135
204
|
onClick: () => {
|
|
205
|
+
dismissTip("open");
|
|
206
|
+
track({ type: widgetOpen ? "widget_closed" : "widget_opened" });
|
|
136
207
|
setWidgetOpen((prev) => !prev);
|
|
137
208
|
setWidgetEverOpened(true);
|
|
138
209
|
},
|
|
@@ -154,6 +225,7 @@ function ChatLayout(props) {
|
|
|
154
225
|
classNames?.container
|
|
155
226
|
),
|
|
156
227
|
"data-testid": "chat-layout",
|
|
228
|
+
style,
|
|
157
229
|
children: [
|
|
158
230
|
showSidebar && /* @__PURE__ */ jsx(
|
|
159
231
|
"div",
|
|
@@ -238,6 +310,7 @@ function ChatLayout(props) {
|
|
|
238
310
|
variant: "ghost",
|
|
239
311
|
size: "icon",
|
|
240
312
|
onClick: () => {
|
|
313
|
+
track({ type: "conversation_cleared" });
|
|
241
314
|
onClear();
|
|
242
315
|
setChatResetKey((prev) => prev + 1);
|
|
243
316
|
},
|
|
@@ -54,6 +54,23 @@ function ToolCallDisplay({
|
|
|
54
54
|
}) {
|
|
55
55
|
const { localization } = context.usePluginOverrides("aiChat", {});
|
|
56
56
|
const tr = index.useAiChatTranslation(localization);
|
|
57
|
+
const { plugins } = context.useStack();
|
|
58
|
+
if (toolName === "readPage" && plugins?.aiChat?.config?.pageContent === true) {
|
|
59
|
+
const failed = state === "output-error";
|
|
60
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", role: "status", children: failed ? tr(
|
|
61
|
+
"PAGE_CONTENT_ERROR",
|
|
62
|
+
"aiChat.pageContent.error",
|
|
63
|
+
"Unable to read this page."
|
|
64
|
+
) : state === "output-available" ? tr(
|
|
65
|
+
"PAGE_CONTENT_READ",
|
|
66
|
+
"aiChat.pageContent.read",
|
|
67
|
+
"Read page content"
|
|
68
|
+
) : tr(
|
|
69
|
+
"PAGE_CONTENT_READING",
|
|
70
|
+
"aiChat.pageContent.reading",
|
|
71
|
+
"Reading page content\u2026"
|
|
72
|
+
) });
|
|
73
|
+
}
|
|
57
74
|
const displayName = formatToolName(toolName);
|
|
58
75
|
const statusLabel = isLoading || state === "input-streaming" ? tr("TOOL_STATUS_RUNNING", "aiChat.tools.status.running", "Running...") : state === "input-available" ? tr(
|
|
59
76
|
"TOOL_STATUS_EXECUTING",
|
|
@@ -5,7 +5,7 @@ import { MarkdownContent } from '../../../../../../ui/src/components/markdown-co
|
|
|
5
5
|
import { Skeleton } from '../../../../../../ui/src/components/skeleton.mjs';
|
|
6
6
|
import { cn } from '../../../../../../ui/src/lib/utils.mjs';
|
|
7
7
|
import { Wrench, Loader2, AlertCircle, Check } from 'lucide-react';
|
|
8
|
-
import { usePluginOverrides } from '@btst/stack/context';
|
|
8
|
+
import { usePluginOverrides, useStack } from '@btst/stack/context';
|
|
9
9
|
import { useAiChatTranslation } from '../localization/index.mjs';
|
|
10
10
|
|
|
11
11
|
function formatToolName(name) {
|
|
@@ -52,6 +52,23 @@ function ToolCallDisplay({
|
|
|
52
52
|
}) {
|
|
53
53
|
const { localization } = usePluginOverrides("aiChat", {});
|
|
54
54
|
const tr = useAiChatTranslation(localization);
|
|
55
|
+
const { plugins } = useStack();
|
|
56
|
+
if (toolName === "readPage" && plugins?.aiChat?.config?.pageContent === true) {
|
|
57
|
+
const failed = state === "output-error";
|
|
58
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", role: "status", children: failed ? tr(
|
|
59
|
+
"PAGE_CONTENT_ERROR",
|
|
60
|
+
"aiChat.pageContent.error",
|
|
61
|
+
"Unable to read this page."
|
|
62
|
+
) : state === "output-available" ? tr(
|
|
63
|
+
"PAGE_CONTENT_READ",
|
|
64
|
+
"aiChat.pageContent.read",
|
|
65
|
+
"Read page content"
|
|
66
|
+
) : tr(
|
|
67
|
+
"PAGE_CONTENT_READING",
|
|
68
|
+
"aiChat.pageContent.reading",
|
|
69
|
+
"Reading page content\u2026"
|
|
70
|
+
) });
|
|
71
|
+
}
|
|
55
72
|
const displayName = formatToolName(toolName);
|
|
56
73
|
const statusLabel = isLoading || state === "input-streaming" ? tr("TOOL_STATUS_RUNNING", "aiChat.tools.status.running", "Running...") : state === "input-available" ? tr(
|
|
57
74
|
"TOOL_STATUS_EXECUTING",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const React = require('react');
|
|
5
|
+
const context = require('@btst/stack/context');
|
|
6
|
+
const plugins_aiChat_client_context_pageAiContext = require('../../../../../../../plugins/ai-chat/client/context/page-ai-context.cjs');
|
|
7
|
+
const overrides = require('../overrides.cjs');
|
|
8
|
+
|
|
9
|
+
const ChatAnalyticsPageKey = React.createContext(
|
|
10
|
+
void 0
|
|
11
|
+
);
|
|
12
|
+
function useChatAnalytics() {
|
|
13
|
+
const { onAnalyticsEvent } = context.usePluginOverrides("aiChat", {});
|
|
14
|
+
const { plugins } = context.useStack();
|
|
15
|
+
const pageKey = React.useContext(ChatAnalyticsPageKey);
|
|
16
|
+
const routeName = plugins_aiChat_client_context_pageAiContext.usePageAIContext()?.routeName;
|
|
17
|
+
const latest = React.useRef({
|
|
18
|
+
onAnalyticsEvent,
|
|
19
|
+
pageKey,
|
|
20
|
+
routeName,
|
|
21
|
+
mode: overrides.resolveAiChatMode(plugins?.aiChat?.config)
|
|
22
|
+
});
|
|
23
|
+
latest.current = {
|
|
24
|
+
onAnalyticsEvent,
|
|
25
|
+
pageKey,
|
|
26
|
+
routeName,
|
|
27
|
+
mode: overrides.resolveAiChatMode(plugins?.aiChat?.config)
|
|
28
|
+
};
|
|
29
|
+
return React.useCallback((event) => {
|
|
30
|
+
const { onAnalyticsEvent: callback, ...context } = latest.current;
|
|
31
|
+
try {
|
|
32
|
+
void Promise.resolve(callback?.({ ...context, ...event })).catch(
|
|
33
|
+
() => {
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exports.ChatAnalyticsPageKey = ChatAnalyticsPageKey;
|
|
42
|
+
exports.useChatAnalytics = useChatAnalytics;
|