@btst/stack 4.1.1 → 4.3.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.
Files changed (94) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +15 -9
  3. package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +16 -10
  4. package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.cjs +28 -0
  5. package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.mjs +29 -2
  6. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
  7. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
  8. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +91 -8
  9. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +91 -8
  10. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +242 -68
  11. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +246 -72
  12. package/dist/packages/stack/src/plugins/ai-chat/client/components/tool-call-display.cjs +17 -0
  13. package/dist/packages/stack/src/plugins/ai-chat/client/components/tool-call-display.mjs +18 -1
  14. package/dist/packages/stack/src/plugins/ai-chat/client/context/chat-analytics.cjs +42 -0
  15. package/dist/packages/stack/src/plugins/ai-chat/client/context/chat-analytics.mjs +39 -0
  16. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +2 -1
  17. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +2 -1
  18. package/dist/plugins/ai-chat/api/index.d.cts +2 -2
  19. package/dist/plugins/ai-chat/api/index.d.mts +2 -2
  20. package/dist/plugins/ai-chat/api/index.d.ts +2 -2
  21. package/dist/plugins/ai-chat/client/components/index.d.cts +2 -1
  22. package/dist/plugins/ai-chat/client/components/index.d.mts +2 -1
  23. package/dist/plugins/ai-chat/client/components/index.d.ts +2 -1
  24. package/dist/plugins/ai-chat/client/context/page-ai-context.cjs +4 -0
  25. package/dist/plugins/ai-chat/client/context/page-ai-context.d.cts +6 -2
  26. package/dist/plugins/ai-chat/client/context/page-ai-context.d.mts +6 -2
  27. package/dist/plugins/ai-chat/client/context/page-ai-context.d.ts +6 -2
  28. package/dist/plugins/ai-chat/client/context/page-ai-context.mjs +4 -1
  29. package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
  30. package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
  31. package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
  32. package/dist/plugins/ai-chat/client/index.d.cts +7 -3
  33. package/dist/plugins/ai-chat/client/index.d.mts +7 -3
  34. package/dist/plugins/ai-chat/client/index.d.ts +7 -3
  35. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  36. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  37. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  38. package/dist/plugins/blog/api/index.d.cts +2 -2
  39. package/dist/plugins/blog/api/index.d.mts +2 -2
  40. package/dist/plugins/blog/api/index.d.ts +2 -2
  41. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  42. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  43. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  44. package/dist/plugins/blog/client/index.d.cts +2 -2
  45. package/dist/plugins/blog/client/index.d.mts +2 -2
  46. package/dist/plugins/blog/client/index.d.ts +2 -2
  47. package/dist/plugins/blog/query-keys.d.cts +2 -2
  48. package/dist/plugins/blog/query-keys.d.mts +2 -2
  49. package/dist/plugins/blog/query-keys.d.ts +2 -2
  50. package/dist/plugins/form-builder/api/index.d.cts +1 -1
  51. package/dist/plugins/form-builder/api/index.d.mts +1 -1
  52. package/dist/plugins/form-builder/api/index.d.ts +1 -1
  53. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  54. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  55. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  56. package/dist/plugins/form-builder/query-keys.d.cts +1 -1
  57. package/dist/plugins/form-builder/query-keys.d.mts +1 -1
  58. package/dist/plugins/form-builder/query-keys.d.ts +1 -1
  59. package/dist/shared/{stack.B5yPgHsp.d.mts → stack.B7Mj74BQ.d.mts} +18 -2
  60. package/dist/shared/{stack.rVF0f509.d.ts → stack.CHrtGKLz.d.mts} +66 -4
  61. package/dist/shared/{stack.BggTHKlo.d.mts → stack.CZ1h_T4k.d.mts} +90 -90
  62. package/dist/shared/{stack.DfrRYOIl.d.ts → stack.CnDMBj4z.d.ts} +5 -5
  63. package/dist/shared/{stack.94kN3pEK.d.mts → stack.D2y4gl03.d.mts} +5 -5
  64. package/dist/shared/{stack.3Sr6qRtT.d.ts → stack.DBH1Bjko.d.ts} +18 -2
  65. package/dist/shared/{stack.lPKwd_v0.d.ts → stack.DQpyA3Cq.d.ts} +90 -90
  66. package/dist/shared/{stack.C4oVW2qK.d.cts → stack.Dgt3MCtW.d.cts} +18 -2
  67. package/dist/shared/{stack.CtrnoQgH.d.cts → stack.DiYg7Jw1.d.cts} +90 -90
  68. package/dist/shared/{stack.rVF0f509.d.cts → stack.DzJgoNqJ.d.cts} +66 -4
  69. package/dist/shared/{stack.rVF0f509.d.mts → stack.qMp2VzNw.d.ts} +66 -4
  70. package/dist/shared/{stack.BTStHVTy.d.cts → stack.rmWRtSjs.d.cts} +5 -5
  71. package/package.json +2 -2
  72. package/src/plugins/ai-chat/__tests__/authorization.test.ts +106 -1
  73. package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +26 -3
  74. package/src/plugins/ai-chat/__tests__/page-content.test.ts +177 -0
  75. package/src/plugins/ai-chat/__tests__/page-widget.test.tsx +675 -0
  76. package/src/plugins/ai-chat/api/operations.ts +29 -13
  77. package/src/plugins/ai-chat/api/page-tools.ts +56 -2
  78. package/src/plugins/ai-chat/api/plugin.ts +13 -0
  79. package/src/plugins/ai-chat/client/analytics.ts +35 -0
  80. package/src/plugins/ai-chat/client/components/chat-interface.tsx +113 -10
  81. package/src/plugins/ai-chat/client/components/chat-layout.tsx +304 -68
  82. package/src/plugins/ai-chat/client/components/tool-call-display.tsx +29 -1
  83. package/src/plugins/ai-chat/client/context/chat-analytics.tsx +46 -0
  84. package/src/plugins/ai-chat/client/context/page-ai-context.tsx +9 -1
  85. package/src/plugins/ai-chat/client/index.ts +2 -0
  86. package/src/plugins/ai-chat/client/localization/index.ts +12 -0
  87. package/src/plugins/ai-chat/client/overrides.ts +5 -0
  88. package/src/plugins/ai-chat/client/plugin.tsx +3 -0
  89. package/dist/shared/{stack.BuxUQqL8.d.ts → stack.CGmh-Lpe.d.ts} +12 -12
  90. package/dist/shared/{stack.Egr3RWhA.d.mts → stack.Doy2_wgl.d.mts} +12 -12
  91. package/dist/shared/{stack.CKc_Bais.d.cts → stack.DtZgEX84.d.cts} +6 -6
  92. package/dist/shared/{stack.CKc_Bais.d.mts → stack.DtZgEX84.d.mts} +6 -6
  93. package/dist/shared/{stack.CKc_Bais.d.ts → stack.DtZgEX84.d.ts} +6 -6
  94. package/dist/shared/{stack.CX83YNuS.d.cts → stack.zch70Loj.d.cts} +12 -12
@@ -1,23 +1,31 @@
1
1
  "use client";
2
- import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { useState, useCallback } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Dialog, DialogContent, DialogTitle } from '../../../../../../ui/src/components/dialog.mjs';
4
6
  import { Button } from '../../../../../../ui/src/components/button.mjs';
5
7
  import { Badge } from '../../../../../../ui/src/components/badge.mjs';
6
8
  import { Sheet, SheetTrigger, SheetContent } from '../../../../../../ui/src/components/sheet.mjs';
7
- import { Sparkles, Trash2, X, Menu, PanelLeftClose, PanelLeft } from 'lucide-react';
9
+ import { X, Sparkles, Minimize2, Maximize2, Trash2, Menu, PanelLeftClose, PanelLeft } from 'lucide-react';
8
10
  import { cn } from '../../../../../../ui/src/lib/utils.mjs';
9
11
  import { ChatSidebar } from './chat-sidebar.mjs';
10
12
  import { ChatInterface } from './chat-interface.mjs';
11
- import { usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
13
+ import { PageAIContextScope, usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
12
14
  import { usePluginOverrides, useStack } from '@btst/stack/context';
13
15
  import { resolveAiChatMode } from '../overrides.mjs';
16
+ import { ChatAnalyticsPageKey, useChatAnalytics } from '../context/chat-analytics.mjs';
14
17
  import { useAiChatTranslation } from '../localization/index.mjs';
15
18
 
16
19
  function ChatLayout(props) {
20
+ return /* @__PURE__ */ jsx(PageAIContextScope.Provider, { value: props.pageContext, children: /* @__PURE__ */ jsx(ChatAnalyticsPageKey.Provider, { value: props.pageKey, children: /* @__PURE__ */ jsx(ChatLayoutContent, { ...props }, props.pageKey) }) });
21
+ }
22
+ function ChatLayoutContent(props) {
23
+ const track = useChatAnalytics();
17
24
  const {
18
25
  conversationId,
19
26
  layout = "full",
20
27
  className,
28
+ style,
21
29
  showSidebar: requestedShowSidebar = true,
22
30
  initialMessages,
23
31
  onMessagesChange,
@@ -28,16 +36,77 @@ function ChatLayout(props) {
28
36
  const resolvedMode = resolveAiChatMode(plugins?.aiChat?.config);
29
37
  const showSidebar = requestedShowSidebar && resolvedMode !== "public";
30
38
  const tr = useAiChatTranslation(localization);
31
- const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "600px" : "600px";
32
- const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "380px" : "380px";
39
+ const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "min(640px, calc(100dvh - 112px))" : void 0;
40
+ const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "min(440px, calc(100vw - 32px))" : void 0;
33
41
  const defaultOpen = props.layout === "widget" ? props.defaultOpen ?? false : false;
34
42
  const showTrigger = props.layout === "widget" ? props.showTrigger ?? true : true;
43
+ const triggerIcon = props.layout === "widget" ? props.triggerIcon : void 0;
35
44
  const [sidebarOpen, setSidebarOpen] = useState(true);
36
45
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
37
46
  const [chatResetKey, setChatResetKey] = useState(0);
38
47
  const [widgetOpen, setWidgetOpen] = useState(defaultOpen);
48
+ const [expanded, setExpanded] = useState(false);
49
+ const expandable = props.layout === "widget" && props.expandable === true;
50
+ const widgetExpanded = expandable && expanded && widgetOpen;
51
+ useEffect(() => {
52
+ if (!expandable) setExpanded(false);
53
+ }, [expandable]);
54
+ const compactPanelRef = useRef(null);
55
+ const triggerRef = useRef(null);
56
+ const expandRef = useRef(null);
57
+ const [panelContainer, setPanelContainer] = useState(
58
+ null
59
+ );
60
+ const expandLabel = widgetExpanded ? tr("A11Y_COLLAPSE_CHAT", "aiChat.a11y.collapseChat", "Collapse chat") : tr("A11Y_EXPAND_CHAT", "aiChat.a11y.expandChat", "Expand chat");
61
+ useEffect(() => {
62
+ if (layout !== "widget") return;
63
+ const container = document.createElement("div");
64
+ container.className = "flex h-full min-h-0 flex-col";
65
+ compactPanelRef.current?.appendChild(container);
66
+ setPanelContainer(container);
67
+ return () => container.remove();
68
+ }, [layout]);
69
+ const mountExpandedPanel = useCallback(
70
+ (node) => {
71
+ if (panelContainer) {
72
+ (node ?? compactPanelRef.current)?.appendChild(panelContainer);
73
+ }
74
+ },
75
+ [panelContainer]
76
+ );
39
77
  const [widgetResetKey, setWidgetResetKey] = useState(0);
40
78
  const [widgetEverOpened, setWidgetEverOpened] = useState(defaultOpen);
79
+ const [tipDismissed, setTipDismissed] = useState(defaultOpen);
80
+ const introTip = props.layout === "widget" ? props.introTip : void 0;
81
+ const introTipDuration = props.layout === "widget" ? props.introTipDuration ?? 6e3 : 6e3;
82
+ const tipShown = useRef(false);
83
+ const tipDismissedRef = useRef(defaultOpen);
84
+ const dismissTip = useCallback(
85
+ (reason) => {
86
+ if (tipShown.current && !tipDismissedRef.current) {
87
+ tipDismissedRef.current = true;
88
+ track({ type: "intro_tip_dismissed", reason });
89
+ }
90
+ setTipDismissed(true);
91
+ },
92
+ [track]
93
+ );
94
+ useEffect(() => {
95
+ if (!introTip || !showTrigger || tipDismissed) return;
96
+ if (!tipShown.current) {
97
+ tipShown.current = true;
98
+ track({ type: "intro_tip_shown" });
99
+ }
100
+ const timeout = setTimeout(() => dismissTip("timeout"), introTipDuration);
101
+ return () => clearTimeout(timeout);
102
+ }, [
103
+ introTip,
104
+ introTipDuration,
105
+ showTrigger,
106
+ tipDismissed,
107
+ dismissTip,
108
+ track
109
+ ]);
41
110
  const pageAIContext = usePageAIContext();
42
111
  const handleNewChat = useCallback(() => {
43
112
  if (!conversationId) {
@@ -50,95 +119,198 @@ function ChatLayout(props) {
50
119
  {
51
120
  className: cn(
52
121
  "flex flex-col items-end gap-3",
122
+ props.layout === "widget" && props.floating && "fixed right-4 bottom-[max(1rem,env(safe-area-inset-bottom))] z-50",
53
123
  className,
54
124
  classNames?.container
55
125
  ),
126
+ style,
56
127
  children: [
57
- /* @__PURE__ */ jsxs(
128
+ introTip && showTrigger && !tipDismissed && !widgetOpen && /* @__PURE__ */ jsxs(
58
129
  "div",
59
130
  {
60
- className: cn(
61
- "flex flex-col border rounded-xl overflow-hidden bg-background shadow-xl",
62
- widgetOpen ? "flex" : "hidden"
63
- ),
64
- style: { height: widgetHeight, width: widgetWidth },
131
+ role: "status",
132
+ 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",
65
133
  children: [
66
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40", children: [
67
- /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3 text-muted-foreground" }),
68
- pageAIContext ? /* @__PURE__ */ jsx(
69
- Badge,
70
- {
71
- variant: "secondary",
72
- className: "text-xs",
73
- "data-testid": "page-context-badge",
74
- children: pageAIContext.routeName
75
- }
76
- ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-medium", children: tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat") }),
77
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
78
- /* @__PURE__ */ jsx(
79
- Button,
80
- {
81
- variant: "ghost",
82
- size: "icon",
83
- className: "h-5 w-5",
84
- onClick: () => {
85
- onClear?.();
86
- setWidgetResetKey((prev) => prev + 1);
87
- },
88
- "aria-label": tr(
89
- "A11Y_CLEAR_CHAT",
90
- "aiChat.a11y.clearChat",
91
- "Clear chat"
92
- ),
93
- title: tr(
94
- "A11Y_CLEAR_CHAT",
95
- "aiChat.a11y.clearChat",
96
- "Clear chat"
97
- ),
98
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
99
- }
100
- ),
101
- /* @__PURE__ */ jsx(
102
- Button,
103
- {
104
- variant: "ghost",
105
- size: "icon",
106
- className: "h-5 w-5",
107
- onClick: () => setWidgetOpen(false),
108
- "aria-label": tr(
109
- "A11Y_CLOSE_CHAT",
110
- "aiChat.a11y.closeChat",
111
- "Close chat"
112
- ),
113
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
114
- }
115
- )
116
- ] }),
117
- widgetEverOpened && /* @__PURE__ */ jsx(
118
- ChatInterface,
134
+ /* @__PURE__ */ jsx("span", { children: introTip }),
135
+ /* @__PURE__ */ jsx(
136
+ Button,
119
137
  {
120
- id: conversationId,
121
- variant: "widget",
122
- initialMessages,
123
- onMessagesChange
124
- },
125
- `widget-${conversationId ?? "new"}-${widgetResetKey}`
138
+ type: "button",
139
+ variant: "ghost",
140
+ size: "icon",
141
+ className: "h-7 w-7 shrink-0",
142
+ onClick: () => dismissTip("manual"),
143
+ "aria-label": tr(
144
+ "A11Y_DISMISS_CHAT_TIP",
145
+ "aiChat.a11y.dismissChatTip",
146
+ "Dismiss chat tip"
147
+ ),
148
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": "true" })
149
+ }
126
150
  )
127
151
  ]
128
152
  }
129
153
  ),
154
+ /* @__PURE__ */ jsx(
155
+ "div",
156
+ {
157
+ ref: compactPanelRef,
158
+ role: "dialog",
159
+ tabIndex: -1,
160
+ "aria-label": tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat"),
161
+ className: cn(
162
+ "overflow-hidden rounded-xl border bg-background shadow-xl",
163
+ (!widgetOpen || widgetExpanded) && "hidden"
164
+ ),
165
+ style: { height: widgetHeight, width: widgetWidth }
166
+ }
167
+ ),
168
+ /* @__PURE__ */ jsx(Dialog, { open: widgetExpanded, onOpenChange: setExpanded, children: /* @__PURE__ */ jsxs(
169
+ DialogContent,
170
+ {
171
+ showCloseButton: false,
172
+ style: {
173
+ ...style,
174
+ position: "fixed",
175
+ inset: 0,
176
+ top: 0,
177
+ right: 0,
178
+ bottom: 0,
179
+ left: 0,
180
+ width: "100vw",
181
+ height: "100dvh",
182
+ minWidth: 0,
183
+ minHeight: 0,
184
+ maxWidth: "none",
185
+ maxHeight: "none",
186
+ margin: 0,
187
+ transform: "none",
188
+ translate: "none",
189
+ zIndex: 50,
190
+ animation: "none"
191
+ },
192
+ "aria-describedby": void 0,
193
+ className: cn(
194
+ className,
195
+ classNames?.container,
196
+ "fixed inset-0 z-50 flex h-dvh w-screen max-w-none translate-x-0 translate-y-0 flex-col gap-0 rounded-none border-0 p-0 sm:max-w-none pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]"
197
+ ),
198
+ onCloseAutoFocus: (event) => {
199
+ const target = widgetOpen ? expandRef.current ?? compactPanelRef.current : triggerRef.current;
200
+ if (target) {
201
+ event.preventDefault();
202
+ target.focus();
203
+ }
204
+ },
205
+ children: [
206
+ /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat") }),
207
+ /* @__PURE__ */ jsx("div", { ref: mountExpandedPanel, className: "min-h-0 flex-1" })
208
+ ]
209
+ }
210
+ ) }),
211
+ panelContainer && createPortal(
212
+ /* @__PURE__ */ jsxs(Fragment, { children: [
213
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40", children: [
214
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3 text-muted-foreground" }),
215
+ pageAIContext ? /* @__PURE__ */ jsx(
216
+ Badge,
217
+ {
218
+ variant: "secondary",
219
+ className: "text-xs",
220
+ "data-testid": "page-context-badge",
221
+ children: pageAIContext.routeName
222
+ }
223
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-medium", children: tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat") }),
224
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
225
+ expandable && /* @__PURE__ */ jsx(
226
+ Button,
227
+ {
228
+ type: "button",
229
+ variant: "ghost",
230
+ size: "icon",
231
+ className: "h-7 w-7",
232
+ ref: expandRef,
233
+ onClick: () => setExpanded((prev) => !prev),
234
+ "aria-label": expandLabel,
235
+ title: expandLabel,
236
+ "aria-expanded": widgetExpanded,
237
+ children: widgetExpanded ? /* @__PURE__ */ jsx(Minimize2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Maximize2, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
238
+ }
239
+ ),
240
+ /* @__PURE__ */ jsx(
241
+ Button,
242
+ {
243
+ variant: "ghost",
244
+ size: "icon",
245
+ className: "h-5 w-5",
246
+ onClick: () => {
247
+ track({ type: "conversation_cleared" });
248
+ onClear?.();
249
+ setWidgetResetKey((prev) => prev + 1);
250
+ },
251
+ "aria-label": tr(
252
+ "A11Y_CLEAR_CHAT",
253
+ "aiChat.a11y.clearChat",
254
+ "Clear chat"
255
+ ),
256
+ title: tr(
257
+ "A11Y_CLEAR_CHAT",
258
+ "aiChat.a11y.clearChat",
259
+ "Clear chat"
260
+ ),
261
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsx(
265
+ Button,
266
+ {
267
+ variant: "ghost",
268
+ size: "icon",
269
+ className: "h-5 w-5",
270
+ onClick: () => {
271
+ setWidgetOpen(false);
272
+ setExpanded(false);
273
+ triggerRef.current?.focus();
274
+ track({ type: "widget_closed" });
275
+ },
276
+ "aria-label": tr(
277
+ "A11Y_CLOSE_CHAT",
278
+ "aiChat.a11y.closeChat",
279
+ "Close chat"
280
+ ),
281
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
282
+ }
283
+ )
284
+ ] }),
285
+ widgetEverOpened && /* @__PURE__ */ jsx(
286
+ ChatInterface,
287
+ {
288
+ id: conversationId,
289
+ variant: "widget",
290
+ initialMessages,
291
+ onMessagesChange
292
+ },
293
+ `widget-${conversationId ?? "new"}-${widgetResetKey}`
294
+ )
295
+ ] }),
296
+ panelContainer
297
+ ),
130
298
  showTrigger && /* @__PURE__ */ jsx(
131
299
  Button,
132
300
  {
301
+ ref: triggerRef,
133
302
  size: "icon",
134
303
  className: "h-12 w-12 rounded-full shadow-lg",
135
304
  onClick: () => {
305
+ dismissTip("open");
306
+ track({ type: widgetOpen ? "widget_closed" : "widget_opened" });
136
307
  setWidgetOpen((prev) => !prev);
308
+ setExpanded(false);
137
309
  setWidgetEverOpened(true);
138
310
  },
139
311
  "aria-label": widgetOpen ? tr("A11Y_CLOSE_CHAT", "aiChat.a11y.closeChat", "Close chat") : tr("A11Y_OPEN_CHAT", "aiChat.a11y.openChat", "Open chat"),
140
312
  "data-testid": "widget-trigger",
141
- children: widgetOpen ? /* @__PURE__ */ jsx(X, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Sparkles, { className: "h-5 w-5" })
313
+ children: widgetOpen ? /* @__PURE__ */ jsx(X, { className: "h-5 w-5" }) : triggerIcon ?? /* @__PURE__ */ jsx(Sparkles, { className: "h-5 w-5" })
142
314
  }
143
315
  )
144
316
  ]
@@ -154,6 +326,7 @@ function ChatLayout(props) {
154
326
  classNames?.container
155
327
  ),
156
328
  "data-testid": "chat-layout",
329
+ style,
157
330
  children: [
158
331
  showSidebar && /* @__PURE__ */ jsx(
159
332
  "div",
@@ -238,6 +411,7 @@ function ChatLayout(props) {
238
411
  variant: "ghost",
239
412
  size: "icon",
240
413
  onClick: () => {
414
+ track({ type: "conversation_cleared" });
241
415
  onClear();
242
416
  setChatResetKey((prev) => prev + 1);
243
417
  },
@@ -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;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { createContext, useContext, useRef, useCallback } from 'react';
3
+ import { usePluginOverrides, useStack } from '@btst/stack/context';
4
+ import { usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
5
+ import { resolveAiChatMode } from '../overrides.mjs';
6
+
7
+ const ChatAnalyticsPageKey = createContext(
8
+ void 0
9
+ );
10
+ function useChatAnalytics() {
11
+ const { onAnalyticsEvent } = usePluginOverrides("aiChat", {});
12
+ const { plugins } = useStack();
13
+ const pageKey = useContext(ChatAnalyticsPageKey);
14
+ const routeName = usePageAIContext()?.routeName;
15
+ const latest = useRef({
16
+ onAnalyticsEvent,
17
+ pageKey,
18
+ routeName,
19
+ mode: resolveAiChatMode(plugins?.aiChat?.config)
20
+ });
21
+ latest.current = {
22
+ onAnalyticsEvent,
23
+ pageKey,
24
+ routeName,
25
+ mode: resolveAiChatMode(plugins?.aiChat?.config)
26
+ };
27
+ return useCallback((event) => {
28
+ const { onAnalyticsEvent: callback, ...context } = latest.current;
29
+ try {
30
+ void Promise.resolve(callback?.({ ...context, ...event })).catch(
31
+ () => {
32
+ }
33
+ );
34
+ } catch {
35
+ }
36
+ }, []);
37
+ }
38
+
39
+ export { ChatAnalyticsPageKey, useChatAnalytics };
@@ -307,7 +307,8 @@ function resolveAiChatClientPlugin(config) {
307
307
  const aiChatClientPlugin = (config = {}) => client.defineClientPlugin()({
308
308
  id: "aiChat",
309
309
  providerConfig: {
310
- mode: config.mode ?? "authenticated"
310
+ mode: config.mode ?? "authenticated",
311
+ ...config.pageContent ? { pageContent: true } : {}
311
312
  },
312
313
  resolve: (runtime) => resolveAiChatClientPlugin({ ...config, runtime })
313
314
  });
@@ -305,7 +305,8 @@ function resolveAiChatClientPlugin(config) {
305
305
  const aiChatClientPlugin = (config = {}) => defineClientPlugin()({
306
306
  id: "aiChat",
307
307
  providerConfig: {
308
- mode: config.mode ?? "authenticated"
308
+ mode: config.mode ?? "authenticated",
309
+ ...config.pageContent ? { pageContent: true } : {}
309
310
  },
310
311
  resolve: (runtime) => resolveAiChatClientPlugin({ ...config, runtime })
311
312
  });
@@ -1,5 +1,5 @@
1
- import { c as Conversation, M as Message } from '../../../shared/stack.C4oVW2qK.cjs';
2
- export { n as AI_CHAT_OPERATION_INVENTORY, o as AI_CHAT_RAW_ESCAPE_HATCH_INVENTORY, k as AiChatAccess, j as AiChatApiRouter, h as AiChatBackendConfig, l as AiChatBackendHooks, A as AiChatIdentityPartition, p as AiChatOperationError, g as AiChatQueryKeys, m as ChatApiContext, q as ChatOperationInputSchema, r as ConversationOperationInputSchema, C as ConversationWithMessages, b as CreateConversationInput, R as RenameConversationInput, U as UpdateConversationOperationInputSchema, i as aiChatBackendPlugin, d as aiChatIdentityKey, e as aiChatResources, f as createAiChatQueryKeys } from '../../../shared/stack.C4oVW2qK.cjs';
1
+ import { c as Conversation, M as Message } from '../../../shared/stack.Dgt3MCtW.cjs';
2
+ export { o as AI_CHAT_OPERATION_INVENTORY, p as AI_CHAT_RAW_ESCAPE_HATCH_INVENTORY, l as AiChatAccess, j as AiChatApiRouter, h as AiChatBackendConfig, m as AiChatBackendHooks, A as AiChatIdentityPartition, q as AiChatOperationError, k as AiChatPageContentConfig, g as AiChatQueryKeys, n as ChatApiContext, r as ChatOperationInputSchema, s as ConversationOperationInputSchema, C as ConversationWithMessages, b as CreateConversationInput, R as RenameConversationInput, U as UpdateConversationOperationInputSchema, i as aiChatBackendPlugin, d as aiChatIdentityKey, e as aiChatResources, f as createAiChatQueryKeys } from '../../../shared/stack.Dgt3MCtW.cjs';
3
3
  import { DBAdapter } from '@btst/db';
4
4
  import '@btst/stack/plugins/client';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
- import { c as Conversation, M as Message } from '../../../shared/stack.B5yPgHsp.mjs';
2
- export { n as AI_CHAT_OPERATION_INVENTORY, o as AI_CHAT_RAW_ESCAPE_HATCH_INVENTORY, k as AiChatAccess, j as AiChatApiRouter, h as AiChatBackendConfig, l as AiChatBackendHooks, A as AiChatIdentityPartition, p as AiChatOperationError, g as AiChatQueryKeys, m as ChatApiContext, q as ChatOperationInputSchema, r as ConversationOperationInputSchema, C as ConversationWithMessages, b as CreateConversationInput, R as RenameConversationInput, U as UpdateConversationOperationInputSchema, i as aiChatBackendPlugin, d as aiChatIdentityKey, e as aiChatResources, f as createAiChatQueryKeys } from '../../../shared/stack.B5yPgHsp.mjs';
1
+ import { c as Conversation, M as Message } from '../../../shared/stack.B7Mj74BQ.mjs';
2
+ export { o as AI_CHAT_OPERATION_INVENTORY, p as AI_CHAT_RAW_ESCAPE_HATCH_INVENTORY, l as AiChatAccess, j as AiChatApiRouter, h as AiChatBackendConfig, m as AiChatBackendHooks, A as AiChatIdentityPartition, q as AiChatOperationError, k as AiChatPageContentConfig, g as AiChatQueryKeys, n as ChatApiContext, r as ChatOperationInputSchema, s as ConversationOperationInputSchema, C as ConversationWithMessages, b as CreateConversationInput, R as RenameConversationInput, U as UpdateConversationOperationInputSchema, i as aiChatBackendPlugin, d as aiChatIdentityKey, e as aiChatResources, f as createAiChatQueryKeys } from '../../../shared/stack.B7Mj74BQ.mjs';
3
3
  import { DBAdapter } from '@btst/db';
4
4
  import '@btst/stack/plugins/client';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
- import { c as Conversation, M as Message } from '../../../shared/stack.3Sr6qRtT.js';
2
- export { n as AI_CHAT_OPERATION_INVENTORY, o as AI_CHAT_RAW_ESCAPE_HATCH_INVENTORY, k as AiChatAccess, j as AiChatApiRouter, h as AiChatBackendConfig, l as AiChatBackendHooks, A as AiChatIdentityPartition, p as AiChatOperationError, g as AiChatQueryKeys, m as ChatApiContext, q as ChatOperationInputSchema, r as ConversationOperationInputSchema, C as ConversationWithMessages, b as CreateConversationInput, R as RenameConversationInput, U as UpdateConversationOperationInputSchema, i as aiChatBackendPlugin, d as aiChatIdentityKey, e as aiChatResources, f as createAiChatQueryKeys } from '../../../shared/stack.3Sr6qRtT.js';
1
+ import { c as Conversation, M as Message } from '../../../shared/stack.DBH1Bjko.js';
2
+ export { o as AI_CHAT_OPERATION_INVENTORY, p as AI_CHAT_RAW_ESCAPE_HATCH_INVENTORY, l as AiChatAccess, j as AiChatApiRouter, h as AiChatBackendConfig, m as AiChatBackendHooks, A as AiChatIdentityPartition, q as AiChatOperationError, k as AiChatPageContentConfig, g as AiChatQueryKeys, n as ChatApiContext, r as ChatOperationInputSchema, s as ConversationOperationInputSchema, C as ConversationWithMessages, b as CreateConversationInput, R as RenameConversationInput, U as UpdateConversationOperationInputSchema, i as aiChatBackendPlugin, d as aiChatIdentityKey, e as aiChatResources, f as createAiChatQueryKeys } from '../../../shared/stack.DBH1Bjko.js';
3
3
  import { DBAdapter } from '@btst/db';
4
4
  import '@btst/stack/plugins/client';
5
5
  import '@btst/stack/plugins/api';
@@ -1,8 +1,9 @@
1
- export { j as ChatInput, C as ChatInterface, f as ChatLayout, g as ChatLayoutProps, i as ChatMessage, h as ChatSidebar, k as ToolCallDisplay } from '../../../../shared/stack.rVF0f509.cjs';
1
+ export { j as ChatInput, C as ChatInterface, f as ChatLayout, g as ChatLayoutProps, i as ChatMessage, h as ChatSidebar, k as ToolCallDisplay } from '../../../../shared/stack.DzJgoNqJ.cjs';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import { FallbackProps } from 'react-error-boundary';
4
4
  import 'ai';
5
5
  import 'react';
6
+ import '../context/page-ai-context.cjs';
6
7
 
7
8
  interface ChatPageComponentProps {
8
9
  conversationId?: string;
@@ -1,8 +1,9 @@
1
- export { j as ChatInput, C as ChatInterface, f as ChatLayout, g as ChatLayoutProps, i as ChatMessage, h as ChatSidebar, k as ToolCallDisplay } from '../../../../shared/stack.rVF0f509.mjs';
1
+ export { j as ChatInput, C as ChatInterface, f as ChatLayout, g as ChatLayoutProps, i as ChatMessage, h as ChatSidebar, k as ToolCallDisplay } from '../../../../shared/stack.CHrtGKLz.mjs';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import { FallbackProps } from 'react-error-boundary';
4
4
  import 'ai';
5
5
  import 'react';
6
+ import '../context/page-ai-context.mjs';
6
7
 
7
8
  interface ChatPageComponentProps {
8
9
  conversationId?: string;
@@ -1,8 +1,9 @@
1
- export { j as ChatInput, C as ChatInterface, f as ChatLayout, g as ChatLayoutProps, i as ChatMessage, h as ChatSidebar, k as ToolCallDisplay } from '../../../../shared/stack.rVF0f509.js';
1
+ export { j as ChatInput, C as ChatInterface, f as ChatLayout, g as ChatLayoutProps, i as ChatMessage, h as ChatSidebar, k as ToolCallDisplay } from '../../../../shared/stack.qMp2VzNw.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import { FallbackProps } from 'react-error-boundary';
4
4
  import 'ai';
5
5
  import 'react';
6
+ import '../context/page-ai-context.js';
6
7
 
7
8
  interface ChatPageComponentProps {
8
9
  conversationId?: string;
@@ -6,6 +6,7 @@ const React = require('react');
6
6
 
7
7
  const PageAIAPIContext = React.createContext(null);
8
8
  const PageAIVersionContext = React.createContext(0);
9
+ const PageAIContextScope = React.createContext(void 0);
9
10
  function PageAIContextProvider({
10
11
  children
11
12
  }) {
@@ -83,10 +84,13 @@ function useRegisterPageAIContext(config) {
83
84
  function usePageAIContext() {
84
85
  React.useContext(PageAIVersionContext);
85
86
  const ctx = React.useContext(PageAIAPIContext);
87
+ const scoped = React.useContext(PageAIContextScope);
88
+ if (scoped !== void 0) return scoped;
86
89
  if (!ctx) return null;
87
90
  return ctx.getActive();
88
91
  }
89
92
 
90
93
  exports.PageAIContextProvider = PageAIContextProvider;
94
+ exports.PageAIContextScope = PageAIContextScope;
91
95
  exports.usePageAIContext = usePageAIContext;
92
96
  exports.useRegisterPageAIContext = useRegisterPageAIContext;