@farming-labs/theme 0.1.60 → 0.1.62
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/ai-search-dialog.d.mts +9 -3
- package/dist/ai-search-dialog.mjs +248 -29
- package/dist/client-analytics.mjs +23 -0
- package/dist/docs-ai-features.d.mts +3 -1
- package/dist/docs-ai-features.mjs +47 -12
- package/dist/docs-api.d.mts +4 -1
- package/dist/docs-api.mjs +352 -59
- package/dist/docs-client-hooks.d.mts +4 -2
- package/dist/docs-client-hooks.mjs +52 -1
- package/dist/docs-command-search.d.mts +3 -1
- package/dist/docs-command-search.mjs +63 -11
- package/dist/docs-feedback.d.mts +3 -1
- package/dist/docs-feedback.mjs +35 -2
- package/dist/docs-layout.mjs +7 -3
- package/dist/docs-page-client.d.mts +2 -0
- package/dist/docs-page-client.mjs +27 -4
- package/dist/page-actions.d.mts +3 -1
- package/dist/page-actions.mjs +39 -7
- package/dist/tanstack-layout.mjs +7 -3
- package/package.json +2 -2
- package/styles/base.css +25 -1
|
@@ -16,7 +16,8 @@ declare function DocsSearchDialog({
|
|
|
16
16
|
loaderVariant,
|
|
17
17
|
loadingComponentHtml,
|
|
18
18
|
models,
|
|
19
|
-
defaultModelId
|
|
19
|
+
defaultModelId,
|
|
20
|
+
analytics
|
|
20
21
|
}: {
|
|
21
22
|
open: boolean;
|
|
22
23
|
onOpenChange: (open: boolean) => void;
|
|
@@ -27,6 +28,7 @@ declare function DocsSearchDialog({
|
|
|
27
28
|
loadingComponentHtml?: string;
|
|
28
29
|
models?: AIModelOption[];
|
|
29
30
|
defaultModelId?: string;
|
|
31
|
+
analytics?: boolean;
|
|
30
32
|
}): react.ReactPortal | null;
|
|
31
33
|
type FloatingPosition = "bottom-right" | "bottom-left" | "bottom-center";
|
|
32
34
|
type FloatingStyle = "panel" | "modal" | "popover" | "full-modal";
|
|
@@ -40,7 +42,8 @@ declare function FloatingAIChat({
|
|
|
40
42
|
loaderVariant,
|
|
41
43
|
loadingComponentHtml,
|
|
42
44
|
models,
|
|
43
|
-
defaultModelId
|
|
45
|
+
defaultModelId,
|
|
46
|
+
analytics
|
|
44
47
|
}: {
|
|
45
48
|
api?: string;
|
|
46
49
|
position?: FloatingPosition;
|
|
@@ -52,6 +55,7 @@ declare function FloatingAIChat({
|
|
|
52
55
|
loadingComponentHtml?: string;
|
|
53
56
|
models?: AIModelOption[];
|
|
54
57
|
defaultModelId?: string;
|
|
58
|
+
analytics?: boolean;
|
|
55
59
|
}): react_jsx_runtime0.JSX.Element | null;
|
|
56
60
|
declare function AIModalDialog({
|
|
57
61
|
open,
|
|
@@ -62,7 +66,8 @@ declare function AIModalDialog({
|
|
|
62
66
|
loaderVariant,
|
|
63
67
|
loadingComponentHtml,
|
|
64
68
|
models,
|
|
65
|
-
defaultModelId
|
|
69
|
+
defaultModelId,
|
|
70
|
+
analytics
|
|
66
71
|
}: {
|
|
67
72
|
open: boolean;
|
|
68
73
|
onOpenChange: (open: boolean) => void;
|
|
@@ -73,6 +78,7 @@ declare function AIModalDialog({
|
|
|
73
78
|
loadingComponentHtml?: string;
|
|
74
79
|
models?: AIModelOption[];
|
|
75
80
|
defaultModelId?: string;
|
|
81
|
+
analytics?: boolean;
|
|
76
82
|
}): react.ReactPortal | null;
|
|
77
83
|
//#endregion
|
|
78
84
|
export { AIModalDialog, DocsSearchDialog, FloatingAIChat };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
|
|
3
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
5
|
import { createPortal } from "react-dom";
|
|
5
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -345,7 +346,7 @@ function ModelSelector({ models, selectedId, onChange, disabled }) {
|
|
|
345
346
|
})]
|
|
346
347
|
});
|
|
347
348
|
}
|
|
348
|
-
function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
349
|
+
function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId, analytics, surface = "chat" }) {
|
|
349
350
|
const label = aiLabel || "AI";
|
|
350
351
|
const aiInputRef = useRef(null);
|
|
351
352
|
const messagesEndRef = useRef(null);
|
|
@@ -373,6 +374,16 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
373
374
|
role: "assistant",
|
|
374
375
|
content: ""
|
|
375
376
|
}]);
|
|
377
|
+
const startedAt = Date.now();
|
|
378
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
379
|
+
type: "ai_question",
|
|
380
|
+
properties: {
|
|
381
|
+
surface,
|
|
382
|
+
questionLength: question.length,
|
|
383
|
+
messageCount: newMessages.length,
|
|
384
|
+
model: effectiveModelId
|
|
385
|
+
}
|
|
386
|
+
});
|
|
376
387
|
try {
|
|
377
388
|
const res = await fetch(api, {
|
|
378
389
|
method: "POST",
|
|
@@ -395,6 +406,15 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
395
406
|
content: errMsg
|
|
396
407
|
}]);
|
|
397
408
|
setIsStreaming(false);
|
|
409
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
410
|
+
type: "ai_error",
|
|
411
|
+
properties: {
|
|
412
|
+
surface,
|
|
413
|
+
status: res.status,
|
|
414
|
+
questionLength: question.length,
|
|
415
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
416
|
+
}
|
|
417
|
+
});
|
|
398
418
|
return;
|
|
399
419
|
}
|
|
400
420
|
const reader = res.body.getReader();
|
|
@@ -426,11 +446,29 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
426
446
|
role: "assistant",
|
|
427
447
|
content: assistantContent
|
|
428
448
|
}]);
|
|
449
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
450
|
+
type: "ai_response",
|
|
451
|
+
properties: {
|
|
452
|
+
surface,
|
|
453
|
+
questionLength: question.length,
|
|
454
|
+
responseLength: assistantContent.length,
|
|
455
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
456
|
+
model: effectiveModelId
|
|
457
|
+
}
|
|
458
|
+
});
|
|
429
459
|
} catch {
|
|
430
460
|
setMessages([...newMessages, {
|
|
431
461
|
role: "assistant",
|
|
432
462
|
content: "Failed to connect. Please try again."
|
|
433
463
|
}]);
|
|
464
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
465
|
+
type: "ai_error",
|
|
466
|
+
properties: {
|
|
467
|
+
surface,
|
|
468
|
+
questionLength: question.length,
|
|
469
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
470
|
+
}
|
|
471
|
+
});
|
|
434
472
|
}
|
|
435
473
|
setIsStreaming(false);
|
|
436
474
|
}, [
|
|
@@ -440,7 +478,9 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
440
478
|
setMessages,
|
|
441
479
|
setAiInput,
|
|
442
480
|
setIsStreaming,
|
|
443
|
-
effectiveModelId
|
|
481
|
+
effectiveModelId,
|
|
482
|
+
analytics,
|
|
483
|
+
surface
|
|
444
484
|
]);
|
|
445
485
|
const handleAskAI = useCallback(async () => {
|
|
446
486
|
await submitQuestion(aiInput);
|
|
@@ -527,6 +567,10 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
527
567
|
onClick: () => {
|
|
528
568
|
setMessages([]);
|
|
529
569
|
setAiInput("");
|
|
570
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
571
|
+
type: "ai_clear",
|
|
572
|
+
properties: { surface }
|
|
573
|
+
});
|
|
530
574
|
},
|
|
531
575
|
className: "fd-ai-clear-btn",
|
|
532
576
|
children: "Clear chat"
|
|
@@ -556,7 +600,7 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
556
600
|
})]
|
|
557
601
|
});
|
|
558
602
|
}
|
|
559
|
-
function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
603
|
+
function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId, analytics = false }) {
|
|
560
604
|
const [tab, setTab] = useState("search");
|
|
561
605
|
const [searchQuery, setSearchQuery] = useState("");
|
|
562
606
|
const [searchResults, setSearchResults] = useState([]);
|
|
@@ -572,6 +616,13 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
572
616
|
})() || (Array.isArray(models) && models.length > 0 ? models[0].id : void 0);
|
|
573
617
|
useEffect(() => {
|
|
574
618
|
if (open) {
|
|
619
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
620
|
+
type: "search_open",
|
|
621
|
+
properties: {
|
|
622
|
+
mode: "ai-dialog",
|
|
623
|
+
tab
|
|
624
|
+
}
|
|
625
|
+
});
|
|
575
626
|
setSearchQuery("");
|
|
576
627
|
setSearchResults([]);
|
|
577
628
|
setActiveIndex(0);
|
|
@@ -579,7 +630,11 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
579
630
|
if (tab === "search") searchInputRef.current?.focus();
|
|
580
631
|
}, 50);
|
|
581
632
|
}
|
|
582
|
-
}, [
|
|
633
|
+
}, [
|
|
634
|
+
analytics,
|
|
635
|
+
open,
|
|
636
|
+
tab
|
|
637
|
+
]);
|
|
583
638
|
useEffect(() => {
|
|
584
639
|
if (!open) return;
|
|
585
640
|
const handler = (e) => {
|
|
@@ -603,19 +658,40 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
603
658
|
}
|
|
604
659
|
setIsSearching(true);
|
|
605
660
|
const timer = setTimeout(async () => {
|
|
661
|
+
const startedAt = Date.now();
|
|
606
662
|
try {
|
|
607
663
|
const requestUrl = new URL(api, window.location.origin);
|
|
608
664
|
requestUrl.searchParams.set("query", searchQuery);
|
|
609
665
|
const res = await fetch(requestUrl.toString());
|
|
610
666
|
if (res.ok) {
|
|
611
|
-
|
|
667
|
+
const data = await res.json();
|
|
668
|
+
setSearchResults(data);
|
|
612
669
|
setActiveIndex(0);
|
|
670
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
671
|
+
type: "search_query",
|
|
672
|
+
properties: {
|
|
673
|
+
mode: "ai-dialog",
|
|
674
|
+
queryLength: searchQuery.length,
|
|
675
|
+
resultCount: Array.isArray(data) ? data.length : 0,
|
|
676
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
677
|
+
}
|
|
678
|
+
});
|
|
613
679
|
}
|
|
614
|
-
} catch {
|
|
680
|
+
} catch {
|
|
681
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
682
|
+
type: "search_error",
|
|
683
|
+
properties: {
|
|
684
|
+
mode: "ai-dialog",
|
|
685
|
+
queryLength: searchQuery.length,
|
|
686
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
615
690
|
setIsSearching(false);
|
|
616
691
|
}, 150);
|
|
617
692
|
return () => clearTimeout(timer);
|
|
618
693
|
}, [
|
|
694
|
+
analytics,
|
|
619
695
|
searchQuery,
|
|
620
696
|
api,
|
|
621
697
|
tab
|
|
@@ -630,6 +706,17 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
630
706
|
} else if (e.key === "Enter" && searchResults[activeIndex]) {
|
|
631
707
|
e.preventDefault();
|
|
632
708
|
onOpenChange(false);
|
|
709
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
710
|
+
type: "search_result_click",
|
|
711
|
+
path: searchResults[activeIndex].url,
|
|
712
|
+
properties: {
|
|
713
|
+
mode: "ai-dialog",
|
|
714
|
+
resultId: searchResults[activeIndex].id,
|
|
715
|
+
resultUrl: searchResults[activeIndex].url,
|
|
716
|
+
resultType: searchResults[activeIndex].type,
|
|
717
|
+
queryLength: searchQuery.length
|
|
718
|
+
}
|
|
719
|
+
});
|
|
633
720
|
window.location.href = searchResults[activeIndex].url;
|
|
634
721
|
}
|
|
635
722
|
};
|
|
@@ -662,7 +749,16 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
662
749
|
children: [/* @__PURE__ */ jsx(SearchIcon, {}), " Search"]
|
|
663
750
|
}),
|
|
664
751
|
/* @__PURE__ */ jsxs("button", {
|
|
665
|
-
onClick: () =>
|
|
752
|
+
onClick: () => {
|
|
753
|
+
setTab("ai");
|
|
754
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
755
|
+
type: "ai_open",
|
|
756
|
+
properties: {
|
|
757
|
+
mode: "ai-dialog",
|
|
758
|
+
trigger: "tab"
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
},
|
|
666
762
|
className: "fd-ai-tab",
|
|
667
763
|
"data-active": tab === "ai",
|
|
668
764
|
children: [
|
|
@@ -712,6 +808,17 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
712
808
|
children: searchResults.length > 0 ? searchResults.map((result, i) => /* @__PURE__ */ jsxs("button", {
|
|
713
809
|
onClick: () => {
|
|
714
810
|
onOpenChange(false);
|
|
811
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
812
|
+
type: "search_result_click",
|
|
813
|
+
path: result.url,
|
|
814
|
+
properties: {
|
|
815
|
+
mode: "ai-dialog",
|
|
816
|
+
resultId: result.id,
|
|
817
|
+
resultUrl: result.url,
|
|
818
|
+
resultType: result.type,
|
|
819
|
+
queryLength: searchQuery.length
|
|
820
|
+
}
|
|
821
|
+
});
|
|
715
822
|
window.location.href = result.url;
|
|
716
823
|
},
|
|
717
824
|
onMouseEnter: () => setActiveIndex(i),
|
|
@@ -740,7 +847,9 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
740
847
|
loaderVariant,
|
|
741
848
|
loadingComponentHtml,
|
|
742
849
|
models,
|
|
743
|
-
defaultModelId: effectiveModelId
|
|
850
|
+
defaultModelId: effectiveModelId,
|
|
851
|
+
analytics,
|
|
852
|
+
surface: "ai-dialog"
|
|
744
853
|
})
|
|
745
854
|
]
|
|
746
855
|
})] }), document.body);
|
|
@@ -814,7 +923,7 @@ function getContainerStyles(style, position) {
|
|
|
814
923
|
function getAnimation(style) {
|
|
815
924
|
return style === "modal" ? "fd-ai-float-center-in 200ms ease-out" : "fd-ai-float-in 200ms ease-out";
|
|
816
925
|
}
|
|
817
|
-
function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
926
|
+
function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId, analytics = false }) {
|
|
818
927
|
const [mounted, setMounted] = useState(false);
|
|
819
928
|
const [isOpen, setIsOpen] = useState(false);
|
|
820
929
|
const [messages, setMessages] = useState([]);
|
|
@@ -823,15 +932,27 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
823
932
|
useEffect(() => {
|
|
824
933
|
setMounted(true);
|
|
825
934
|
}, []);
|
|
935
|
+
const closeFloatingAI = useCallback((trigger) => {
|
|
936
|
+
setIsOpen((wasOpen) => {
|
|
937
|
+
if (wasOpen && analytics) emitClientAnalyticsEvent({
|
|
938
|
+
type: "ai_close",
|
|
939
|
+
properties: {
|
|
940
|
+
mode: floatingStyle === "full-modal" ? "full-modal" : "floating",
|
|
941
|
+
trigger
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return false;
|
|
945
|
+
});
|
|
946
|
+
}, [analytics, floatingStyle]);
|
|
826
947
|
useEffect(() => {
|
|
827
948
|
if (isOpen) {
|
|
828
949
|
const handler = (e) => {
|
|
829
|
-
if (e.key === "Escape")
|
|
950
|
+
if (e.key === "Escape") closeFloatingAI("escape");
|
|
830
951
|
};
|
|
831
952
|
document.addEventListener("keydown", handler);
|
|
832
953
|
return () => document.removeEventListener("keydown", handler);
|
|
833
954
|
}
|
|
834
|
-
}, [isOpen]);
|
|
955
|
+
}, [closeFloatingAI, isOpen]);
|
|
835
956
|
useEffect(() => {
|
|
836
957
|
if (isOpen && (floatingStyle === "modal" || floatingStyle === "full-modal")) document.body.style.overflow = "hidden";
|
|
837
958
|
else document.body.style.overflow = "";
|
|
@@ -844,6 +965,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
844
965
|
api,
|
|
845
966
|
isOpen,
|
|
846
967
|
setIsOpen,
|
|
968
|
+
closeAI: closeFloatingAI,
|
|
847
969
|
messages,
|
|
848
970
|
setMessages,
|
|
849
971
|
aiInput,
|
|
@@ -857,7 +979,8 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
857
979
|
triggerComponentHtml,
|
|
858
980
|
position,
|
|
859
981
|
models,
|
|
860
|
-
defaultModelId
|
|
982
|
+
defaultModelId,
|
|
983
|
+
analytics
|
|
861
984
|
});
|
|
862
985
|
const btnPosition = BTN_POSITIONS[position] || BTN_POSITIONS["bottom-right"];
|
|
863
986
|
const isModal = floatingStyle === "modal";
|
|
@@ -865,7 +988,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
865
988
|
const aiName = aiLabel || "AI";
|
|
866
989
|
return createPortal(/* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
867
990
|
isOpen && isModal && /* @__PURE__ */ jsx("div", {
|
|
868
|
-
onClick: () =>
|
|
991
|
+
onClick: () => closeFloatingAI("overlay"),
|
|
869
992
|
className: "fd-ai-overlay"
|
|
870
993
|
}),
|
|
871
994
|
isOpen && /* @__PURE__ */ jsxs("div", {
|
|
@@ -888,7 +1011,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
888
1011
|
children: "ESC"
|
|
889
1012
|
}),
|
|
890
1013
|
/* @__PURE__ */ jsx("button", {
|
|
891
|
-
onClick: () =>
|
|
1014
|
+
onClick: () => closeFloatingAI("button"),
|
|
892
1015
|
className: "fd-ai-close-btn",
|
|
893
1016
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
894
1017
|
})
|
|
@@ -904,16 +1027,36 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
904
1027
|
suggestedQuestions,
|
|
905
1028
|
aiLabel,
|
|
906
1029
|
loaderVariant,
|
|
907
|
-
loadingComponentHtml
|
|
1030
|
+
loadingComponentHtml,
|
|
1031
|
+
analytics,
|
|
1032
|
+
surface: "floating"
|
|
908
1033
|
})]
|
|
909
1034
|
}),
|
|
910
1035
|
!isOpen && (triggerComponentHtml ? /* @__PURE__ */ jsx("div", {
|
|
911
|
-
onClick: () =>
|
|
1036
|
+
onClick: () => {
|
|
1037
|
+
setIsOpen(true);
|
|
1038
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1039
|
+
type: "ai_open",
|
|
1040
|
+
properties: {
|
|
1041
|
+
mode: "floating",
|
|
1042
|
+
trigger: "custom-trigger"
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
},
|
|
912
1046
|
className: "fd-ai-floating-trigger",
|
|
913
1047
|
style: btnPosition,
|
|
914
1048
|
dangerouslySetInnerHTML: { __html: triggerComponentHtml }
|
|
915
1049
|
}) : /* @__PURE__ */ jsxs("button", {
|
|
916
|
-
onClick: () =>
|
|
1050
|
+
onClick: () => {
|
|
1051
|
+
setIsOpen(true);
|
|
1052
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1053
|
+
type: "ai_open",
|
|
1054
|
+
properties: {
|
|
1055
|
+
mode: "floating",
|
|
1056
|
+
trigger: "button"
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
},
|
|
917
1060
|
"aria-label": `Ask ${aiName}`,
|
|
918
1061
|
className: "fd-ai-floating-btn",
|
|
919
1062
|
style: btnPosition,
|
|
@@ -921,7 +1064,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
921
1064
|
}))
|
|
922
1065
|
] }), document.body);
|
|
923
1066
|
}
|
|
924
|
-
function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, triggerComponentHtml, position, models, defaultModelId }) {
|
|
1067
|
+
function FullModalAIChat({ api, isOpen, setIsOpen, closeAI, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, triggerComponentHtml, position, models, defaultModelId, analytics }) {
|
|
925
1068
|
const label = aiLabel || "AI";
|
|
926
1069
|
const inputRef = useRef(null);
|
|
927
1070
|
const listRef = useRef(null);
|
|
@@ -953,6 +1096,16 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
953
1096
|
role: "assistant",
|
|
954
1097
|
content: ""
|
|
955
1098
|
}]);
|
|
1099
|
+
const startedAt = Date.now();
|
|
1100
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1101
|
+
type: "ai_question",
|
|
1102
|
+
properties: {
|
|
1103
|
+
surface: "full-modal",
|
|
1104
|
+
questionLength: question.length,
|
|
1105
|
+
messageCount: newMessages.length,
|
|
1106
|
+
model: effectiveModelId
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
956
1109
|
try {
|
|
957
1110
|
const res = await fetch(api, {
|
|
958
1111
|
method: "POST",
|
|
@@ -975,6 +1128,15 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
975
1128
|
content: errMsg
|
|
976
1129
|
}]);
|
|
977
1130
|
setIsStreaming(false);
|
|
1131
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1132
|
+
type: "ai_error",
|
|
1133
|
+
properties: {
|
|
1134
|
+
surface: "full-modal",
|
|
1135
|
+
status: res.status,
|
|
1136
|
+
questionLength: question.length,
|
|
1137
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
978
1140
|
return;
|
|
979
1141
|
}
|
|
980
1142
|
const reader = res.body.getReader();
|
|
@@ -1006,11 +1168,29 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
1006
1168
|
role: "assistant",
|
|
1007
1169
|
content: assistantContent
|
|
1008
1170
|
}]);
|
|
1171
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1172
|
+
type: "ai_response",
|
|
1173
|
+
properties: {
|
|
1174
|
+
surface: "full-modal",
|
|
1175
|
+
questionLength: question.length,
|
|
1176
|
+
responseLength: assistantContent.length,
|
|
1177
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
1178
|
+
model: effectiveModelId
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1009
1181
|
} catch {
|
|
1010
1182
|
setMessages([...newMessages, {
|
|
1011
1183
|
role: "assistant",
|
|
1012
1184
|
content: "Failed to connect. Please try again."
|
|
1013
1185
|
}]);
|
|
1186
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1187
|
+
type: "ai_error",
|
|
1188
|
+
properties: {
|
|
1189
|
+
surface: "full-modal",
|
|
1190
|
+
questionLength: question.length,
|
|
1191
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1014
1194
|
}
|
|
1015
1195
|
setIsStreaming(false);
|
|
1016
1196
|
}, [
|
|
@@ -1020,7 +1200,8 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
1020
1200
|
setMessages,
|
|
1021
1201
|
setAiInput,
|
|
1022
1202
|
setIsStreaming,
|
|
1023
|
-
effectiveModelId
|
|
1203
|
+
effectiveModelId,
|
|
1204
|
+
analytics
|
|
1024
1205
|
]);
|
|
1025
1206
|
const canSend = !!(aiInput.trim() && !isStreaming);
|
|
1026
1207
|
const showSuggestions = messages.length === 0 && !isStreaming;
|
|
@@ -1033,12 +1214,12 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
1033
1214
|
return createPortal(/* @__PURE__ */ jsxs(Fragment$1, { children: [isOpen && /* @__PURE__ */ jsxs("div", {
|
|
1034
1215
|
className: "fd-ai-fm-overlay",
|
|
1035
1216
|
onClick: (e) => {
|
|
1036
|
-
if (e.target === e.currentTarget)
|
|
1217
|
+
if (e.target === e.currentTarget) closeAI("overlay");
|
|
1037
1218
|
},
|
|
1038
1219
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1039
1220
|
className: "fd-ai-fm-topbar",
|
|
1040
1221
|
children: /* @__PURE__ */ jsx("button", {
|
|
1041
|
-
onClick: () =>
|
|
1222
|
+
onClick: () => closeAI("button"),
|
|
1042
1223
|
className: "fd-ai-fm-close-btn",
|
|
1043
1224
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
1044
1225
|
})
|
|
@@ -1071,10 +1252,28 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
1071
1252
|
className: `fd-ai-fm-input-bar ${isOpen ? "fd-ai-fm-input-bar--open" : "fd-ai-fm-input-bar--closed"}`,
|
|
1072
1253
|
style: isOpen ? void 0 : btnPosition,
|
|
1073
1254
|
children: !isOpen ? triggerComponentHtml ? /* @__PURE__ */ jsx("div", {
|
|
1074
|
-
onClick: () =>
|
|
1255
|
+
onClick: () => {
|
|
1256
|
+
setIsOpen(true);
|
|
1257
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1258
|
+
type: "ai_open",
|
|
1259
|
+
properties: {
|
|
1260
|
+
mode: "full-modal",
|
|
1261
|
+
trigger: "custom-trigger"
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
},
|
|
1075
1265
|
dangerouslySetInnerHTML: { __html: triggerComponentHtml }
|
|
1076
1266
|
}) : /* @__PURE__ */ jsxs("button", {
|
|
1077
|
-
onClick: () =>
|
|
1267
|
+
onClick: () => {
|
|
1268
|
+
setIsOpen(true);
|
|
1269
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1270
|
+
type: "ai_open",
|
|
1271
|
+
properties: {
|
|
1272
|
+
mode: "full-modal",
|
|
1273
|
+
trigger: "button"
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
},
|
|
1078
1277
|
className: "fd-ai-fm-trigger-btn",
|
|
1079
1278
|
"aria-label": `Ask ${label}`,
|
|
1080
1279
|
children: [/* @__PURE__ */ jsx(SparklesIcon, { size: 16 }), /* @__PURE__ */ jsxs("span", { children: ["Ask ", label] })]
|
|
@@ -1135,6 +1334,10 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
1135
1334
|
if (!isStreaming) {
|
|
1136
1335
|
setMessages([]);
|
|
1137
1336
|
setAiInput("");
|
|
1337
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1338
|
+
type: "ai_clear",
|
|
1339
|
+
properties: { surface: "full-modal" }
|
|
1340
|
+
});
|
|
1138
1341
|
}
|
|
1139
1342
|
},
|
|
1140
1343
|
"aria-disabled": isStreaming,
|
|
@@ -1165,18 +1368,28 @@ function TrashIcon() {
|
|
|
1165
1368
|
]
|
|
1166
1369
|
});
|
|
1167
1370
|
}
|
|
1168
|
-
function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
1371
|
+
function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId, analytics = false }) {
|
|
1169
1372
|
const [messages, setMessages] = useState([]);
|
|
1170
1373
|
const [aiInput, setAiInput] = useState("");
|
|
1171
1374
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
1375
|
+
const closeModalAI = useCallback((trigger) => {
|
|
1376
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1377
|
+
type: "ai_close",
|
|
1378
|
+
properties: {
|
|
1379
|
+
mode: "modal",
|
|
1380
|
+
trigger
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
onOpenChange(false);
|
|
1384
|
+
}, [analytics, onOpenChange]);
|
|
1172
1385
|
useEffect(() => {
|
|
1173
1386
|
if (!open) return;
|
|
1174
1387
|
const handler = (e) => {
|
|
1175
|
-
if (e.key === "Escape")
|
|
1388
|
+
if (e.key === "Escape") closeModalAI("escape");
|
|
1176
1389
|
};
|
|
1177
1390
|
document.addEventListener("keydown", handler);
|
|
1178
1391
|
return () => document.removeEventListener("keydown", handler);
|
|
1179
|
-
}, [
|
|
1392
|
+
}, [closeModalAI, open]);
|
|
1180
1393
|
useEffect(() => {
|
|
1181
1394
|
if (open) document.body.style.overflow = "hidden";
|
|
1182
1395
|
else document.body.style.overflow = "";
|
|
@@ -1187,7 +1400,7 @@ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestio
|
|
|
1187
1400
|
if (!open) return null;
|
|
1188
1401
|
const aiName = aiLabel || "AI";
|
|
1189
1402
|
return createPortal(/* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1190
|
-
onClick: () =>
|
|
1403
|
+
onClick: () => closeModalAI("overlay"),
|
|
1191
1404
|
className: "fd-ai-overlay"
|
|
1192
1405
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1193
1406
|
role: "dialog",
|
|
@@ -1216,7 +1429,7 @@ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestio
|
|
|
1216
1429
|
children: "ESC"
|
|
1217
1430
|
}),
|
|
1218
1431
|
/* @__PURE__ */ jsx("button", {
|
|
1219
|
-
onClick: () =>
|
|
1432
|
+
onClick: () => closeModalAI("button"),
|
|
1220
1433
|
className: "fd-ai-close-btn",
|
|
1221
1434
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
1222
1435
|
})
|
|
@@ -1235,7 +1448,9 @@ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestio
|
|
|
1235
1448
|
loaderVariant,
|
|
1236
1449
|
loadingComponentHtml,
|
|
1237
1450
|
models,
|
|
1238
|
-
defaultModelId
|
|
1451
|
+
defaultModelId,
|
|
1452
|
+
analytics,
|
|
1453
|
+
surface: "modal"
|
|
1239
1454
|
}),
|
|
1240
1455
|
/* @__PURE__ */ jsx("div", {
|
|
1241
1456
|
className: "fd-ai-modal-footer",
|
|
@@ -1245,6 +1460,10 @@ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestio
|
|
|
1245
1460
|
if (!isStreaming) {
|
|
1246
1461
|
setMessages([]);
|
|
1247
1462
|
setAiInput("");
|
|
1463
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
1464
|
+
type: "ai_clear",
|
|
1465
|
+
properties: { surface: "modal" }
|
|
1466
|
+
});
|
|
1248
1467
|
}
|
|
1249
1468
|
},
|
|
1250
1469
|
"aria-disabled": isStreaming,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
//#region src/client-analytics.ts
|
|
4
|
+
function emitClientAnalyticsEvent(event) {
|
|
5
|
+
if (typeof window === "undefined") return;
|
|
6
|
+
const normalized = {
|
|
7
|
+
...event,
|
|
8
|
+
source: "client",
|
|
9
|
+
path: window.location.pathname,
|
|
10
|
+
url: window.location.href,
|
|
11
|
+
referrer: document.referrer || void 0,
|
|
12
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13
|
+
};
|
|
14
|
+
const target = window;
|
|
15
|
+
try {
|
|
16
|
+
if (target.__fdAnalytics__) Promise.resolve(target.__fdAnalytics__(normalized)).catch(() => {});
|
|
17
|
+
else target.__fdAnalyticsQueue__ = [...target.__fdAnalyticsQueue__ ?? [], normalized].slice(-50);
|
|
18
|
+
window.dispatchEvent(new CustomEvent("fd:analytics", { detail: normalized }));
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { emitClientAnalyticsEvent };
|
|
@@ -17,6 +17,7 @@ interface DocsAIFeaturesProps {
|
|
|
17
17
|
label: string;
|
|
18
18
|
}[];
|
|
19
19
|
defaultModelId?: string;
|
|
20
|
+
analytics?: boolean;
|
|
20
21
|
}
|
|
21
22
|
declare function DocsAIFeatures({
|
|
22
23
|
mode,
|
|
@@ -30,7 +31,8 @@ declare function DocsAIFeatures({
|
|
|
30
31
|
loaderVariant,
|
|
31
32
|
loadingComponentHtml,
|
|
32
33
|
models,
|
|
33
|
-
defaultModelId
|
|
34
|
+
defaultModelId,
|
|
35
|
+
analytics
|
|
34
36
|
}: DocsAIFeaturesProps): react_jsx_runtime0.JSX.Element;
|
|
35
37
|
//#endregion
|
|
36
38
|
export { DocsAIFeatures };
|