@gram-ai/elements 1.34.0 → 1.35.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 (233) hide show
  1. package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
  2. package/dist/compat-shims-DxtUrORi.js.map +1 -1
  3. package/dist/compat-shims.d.ts +9 -8
  4. package/dist/components/Chat/index.d.ts +2 -1
  5. package/dist/components/ChatHistory.d.ts +1 -1
  6. package/dist/components/FrontendTools/index.d.ts +1 -1
  7. package/dist/components/Replay.d.ts +1 -1
  8. package/dist/components/Replay.stories.d.ts +2 -2
  9. package/dist/components/ShadowRoot.d.ts +1 -1
  10. package/dist/components/ShareButton/index.d.ts +1 -1
  11. package/dist/components/ui/avatar.d.ts +3 -3
  12. package/dist/components/ui/button.d.ts +2 -2
  13. package/dist/components/ui/buttonVariants.d.ts +2 -2
  14. package/dist/components/ui/calendar.d.ts +2 -1
  15. package/dist/components/ui/collapsible.d.ts +3 -3
  16. package/dist/components/ui/dialog.d.ts +10 -10
  17. package/dist/components/ui/popover.d.ts +4 -4
  18. package/dist/components/ui/skeleton.d.ts +1 -1
  19. package/dist/components/ui/time-range-picker.d.ts +2 -1
  20. package/dist/components/ui/tool-ui.d.ts +7 -7
  21. package/dist/components/ui/tooltip.d.ts +4 -4
  22. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  23. package/dist/contexts/ElementsProvider.d.ts +1 -1
  24. package/dist/contexts/ToolApprovalContext.d.ts +2 -2
  25. package/dist/contexts/ToolExecutionContext.d.ts +1 -1
  26. package/dist/contexts/portal-container.d.ts +1 -1
  27. package/dist/elements.cjs +1 -1
  28. package/dist/elements.css +1 -1
  29. package/dist/elements.js +2 -2
  30. package/dist/hooks/useDensity.d.ts +1 -1
  31. package/dist/hooks/useElements.d.ts +2 -1
  32. package/dist/hooks/useGramThreadListAdapter.d.ts +13 -0
  33. package/dist/hooks/useRadius.d.ts +1 -1
  34. package/dist/hooks/useThemeProps.d.ts +1 -1
  35. package/dist/hooks/useToolApproval.d.ts +2 -1
  36. package/dist/{index-BFU6NvbL.js → index-BhIowiZF.js} +9408 -9204
  37. package/dist/index-BhIowiZF.js.map +1 -0
  38. package/dist/{index-C08dvTEo.cjs → index-D0jIGQr7.cjs} +3 -3
  39. package/dist/index-D0jIGQr7.cjs.map +1 -0
  40. package/dist/{index-B5lZrrO2.js → index-Dz13dSDa.js} +57 -15
  41. package/dist/index-Dz13dSDa.js.map +1 -0
  42. package/dist/index-PXd3rs95.cjs +194 -0
  43. package/dist/index-PXd3rs95.cjs.map +1 -0
  44. package/dist/lib/errorTracking.d.ts +1 -1
  45. package/dist/lib/tools.d.ts +11 -10
  46. package/dist/plugins/generative-ui/catalog.d.ts +3 -3
  47. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  48. package/dist/plugins/generative-ui/ui/accordion.d.ts +4 -4
  49. package/dist/plugins/generative-ui/ui/action-button.d.ts +1 -1
  50. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +2 -1
  51. package/dist/plugins/generative-ui/ui/alert.d.ts +3 -3
  52. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +2 -1
  53. package/dist/plugins/generative-ui/ui/avatar.d.ts +6 -6
  54. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  55. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -1
  56. package/dist/plugins/generative-ui/ui/button.d.ts +3 -3
  57. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +1 -1
  58. package/dist/plugins/generative-ui/ui/card.d.ts +7 -7
  59. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +2 -1
  60. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  61. package/dist/plugins/generative-ui/ui/data-table.d.ts +1 -1
  62. package/dist/plugins/generative-ui/ui/dialog.d.ts +10 -10
  63. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +15 -15
  64. package/dist/plugins/generative-ui/ui/grid.d.ts +1 -1
  65. package/dist/plugins/generative-ui/ui/index.d.ts +57 -40
  66. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +2 -1
  67. package/dist/plugins/generative-ui/ui/input.d.ts +1 -1
  68. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  69. package/dist/plugins/generative-ui/ui/list.d.ts +2 -1
  70. package/dist/plugins/generative-ui/ui/metric.d.ts +1 -1
  71. package/dist/plugins/generative-ui/ui/pagination.d.ts +7 -7
  72. package/dist/plugins/generative-ui/ui/popover.d.ts +7 -7
  73. package/dist/plugins/generative-ui/ui/progress.d.ts +1 -1
  74. package/dist/plugins/generative-ui/ui/radio-group.d.ts +2 -2
  75. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +2 -1
  76. package/dist/plugins/generative-ui/ui/select.d.ts +10 -10
  77. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  78. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +2 -1
  79. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  80. package/dist/plugins/generative-ui/ui/stack.d.ts +1 -1
  81. package/dist/plugins/generative-ui/ui/switch.d.ts +1 -1
  82. package/dist/plugins/generative-ui/ui/table.d.ts +8 -8
  83. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +2 -2
  84. package/dist/plugins/generative-ui/ui/tabs.d.ts +4 -4
  85. package/dist/plugins/generative-ui/ui/text.d.ts +1 -1
  86. package/dist/plugins/generative-ui/ui/textarea.d.ts +1 -1
  87. package/dist/plugins/generative-ui/ui/tooltip.d.ts +4 -4
  88. package/dist/plugins.cjs +1 -1
  89. package/dist/plugins.js +1 -1
  90. package/dist/{profiler-KLSTpp6I.js → profiler-CtGKTWWP.js} +2 -2
  91. package/dist/{profiler-KLSTpp6I.js.map → profiler-CtGKTWWP.js.map} +1 -1
  92. package/dist/{profiler-BRnyr1GA.cjs → profiler-l7_HjTyw.cjs} +2 -2
  93. package/dist/{profiler-BRnyr1GA.cjs.map → profiler-l7_HjTyw.cjs.map} +1 -1
  94. package/dist/react-shim.cjs.map +1 -1
  95. package/dist/react-shim.d.ts +1 -1
  96. package/dist/react-shim.js +1 -4
  97. package/dist/react-shim.js.map +1 -1
  98. package/dist/server/bun.cjs.map +1 -1
  99. package/dist/server/bun.js.map +1 -1
  100. package/dist/server/express.cjs.map +1 -1
  101. package/dist/server/express.js.map +1 -1
  102. package/dist/server/fastify.cjs.map +1 -1
  103. package/dist/server/fastify.js.map +1 -1
  104. package/dist/server/hono.cjs.map +1 -1
  105. package/dist/server/hono.js.map +1 -1
  106. package/dist/server/nextjs.cjs.map +1 -1
  107. package/dist/server/nextjs.js.map +1 -1
  108. package/dist/server/tanstack-start.cjs.map +1 -1
  109. package/dist/server/tanstack-start.js.map +1 -1
  110. package/dist/{startRecording-CKx-YWbq.cjs → startRecording-DEw2Aeq4.cjs} +2 -2
  111. package/dist/{startRecording-CKx-YWbq.cjs.map → startRecording-DEw2Aeq4.cjs.map} +1 -1
  112. package/dist/{startRecording-BfxB1xxR.js → startRecording-iYEL0-vr.js} +2 -2
  113. package/dist/{startRecording-BfxB1xxR.js.map → startRecording-iYEL0-vr.js.map} +1 -1
  114. package/dist/types/index.d.ts +29 -3
  115. package/package.json +7 -10
  116. package/src/compat-shims.ts +16 -2
  117. package/src/components/Chat/index.tsx +4 -1
  118. package/src/components/Chat/stories/FrontendTools.stories.tsx +1 -1
  119. package/src/components/Chat/stories/ToolApproval.stories.tsx +2 -2
  120. package/src/components/Chat/stories/Tools.stories.tsx +13 -5
  121. package/src/components/ChatHistory.tsx +3 -1
  122. package/src/components/FrontendTools/index.tsx +1 -1
  123. package/src/components/MessageContent.tsx +1 -0
  124. package/src/components/Replay.stories.tsx +2 -3
  125. package/src/components/Replay.tsx +17 -10
  126. package/src/components/ShadowRoot.tsx +2 -2
  127. package/src/components/ShareButton/index.tsx +4 -2
  128. package/src/components/assistant-ui/assistant-modal.tsx +5 -3
  129. package/src/components/assistant-ui/attachment.tsx +1 -1
  130. package/src/components/assistant-ui/error-boundary.tsx +1 -1
  131. package/src/components/assistant-ui/markdown-text.tsx +1 -1
  132. package/src/components/assistant-ui/thread.tsx +249 -11
  133. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +1 -1
  134. package/src/components/ui/avatar.tsx +3 -3
  135. package/src/components/ui/calendar.tsx +1 -1
  136. package/src/components/ui/collapsible.tsx +7 -3
  137. package/src/components/ui/dialog.tsx +18 -10
  138. package/src/components/ui/generative-ui.tsx +9 -4
  139. package/src/components/ui/popover.tsx +4 -4
  140. package/src/components/ui/skeleton.tsx +4 -1
  141. package/src/components/ui/time-range-picker.stories.tsx +164 -154
  142. package/src/components/ui/time-range-picker.tsx +11 -5
  143. package/src/components/ui/tool-ui.tsx +18 -9
  144. package/src/components/ui/tooltip.tsx +4 -4
  145. package/src/contexts/ChatIdContext.tsx +1 -1
  146. package/src/contexts/ConnectionStatusContext.tsx +6 -5
  147. package/src/contexts/ElementsProvider.tsx +64 -41
  148. package/src/contexts/ReplayContext.ts +1 -1
  149. package/src/contexts/ToolApprovalContext.tsx +5 -1
  150. package/src/contexts/ToolExecutionContext.tsx +1 -1
  151. package/src/contexts/portal-container.tsx +1 -1
  152. package/src/hooks/useAuth.ts +2 -1
  153. package/src/hooks/useDensity.ts +1 -1
  154. package/src/hooks/useElements.ts +2 -1
  155. package/src/hooks/useFollowOnSuggestions.ts +3 -6
  156. package/src/hooks/useGramThreadListAdapter.tsx +50 -3
  157. package/src/hooks/useMCPTools.ts +2 -2
  158. package/src/hooks/useModel.ts +1 -3
  159. package/src/hooks/usePluginComponents.ts +3 -1
  160. package/src/hooks/useRadius.ts +1 -1
  161. package/src/hooks/useSession.ts +3 -1
  162. package/src/hooks/useThemeProps.ts +5 -5
  163. package/src/hooks/useToolApproval.ts +2 -1
  164. package/src/lib/cassette.ts +20 -8
  165. package/src/lib/errorTracking.ts +1 -4
  166. package/src/lib/messageConverter.test.ts +11 -13
  167. package/src/lib/messageConverter.ts +13 -4
  168. package/src/lib/token.ts +2 -5
  169. package/src/lib/tool-mentions.ts +5 -2
  170. package/src/lib/tools.byte-cap.test.ts +1 -1
  171. package/src/lib/tools.test.ts +1 -1
  172. package/src/lib/tools.ts +15 -5
  173. package/src/lib/utils.ts +2 -2
  174. package/src/lib.d.ts +8 -1
  175. package/src/plugins/chart/chart.test.ts +3 -4
  176. package/src/plugins/chart/component.tsx +7 -6
  177. package/src/plugins/chart/ui/area-chart.tsx +1 -1
  178. package/src/plugins/chart/ui/line-chart.tsx +1 -1
  179. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +2 -2
  180. package/src/plugins/generative-ui/ui/accordion.tsx +4 -4
  181. package/src/plugins/generative-ui/ui/action-button.tsx +4 -2
  182. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +1 -1
  183. package/src/plugins/generative-ui/ui/alert.tsx +7 -3
  184. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +5 -1
  185. package/src/plugins/generative-ui/ui/avatar.tsx +12 -6
  186. package/src/plugins/generative-ui/ui/badge.tsx +1 -1
  187. package/src/plugins/generative-ui/ui/button-wrapper.tsx +1 -1
  188. package/src/plugins/generative-ui/ui/button.tsx +1 -1
  189. package/src/plugins/generative-ui/ui/card-wrapper.tsx +1 -1
  190. package/src/plugins/generative-ui/ui/card.tsx +28 -7
  191. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +1 -1
  192. package/src/plugins/generative-ui/ui/checkbox.tsx +1 -1
  193. package/src/plugins/generative-ui/ui/data-table.tsx +1 -1
  194. package/src/plugins/generative-ui/ui/dialog.tsx +15 -10
  195. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +33 -15
  196. package/src/plugins/generative-ui/ui/grid.tsx +1 -1
  197. package/src/plugins/generative-ui/ui/index.ts +154 -40
  198. package/src/plugins/generative-ui/ui/input-wrapper.tsx +1 -1
  199. package/src/plugins/generative-ui/ui/input.tsx +5 -1
  200. package/src/plugins/generative-ui/ui/label.tsx +1 -1
  201. package/src/plugins/generative-ui/ui/list.tsx +5 -1
  202. package/src/plugins/generative-ui/ui/metric.tsx +2 -1
  203. package/src/plugins/generative-ui/ui/pagination.tsx +12 -7
  204. package/src/plugins/generative-ui/ui/popover.tsx +13 -7
  205. package/src/plugins/generative-ui/ui/progress.tsx +1 -1
  206. package/src/plugins/generative-ui/ui/radio-group.tsx +2 -2
  207. package/src/plugins/generative-ui/ui/select-wrapper.tsx +1 -1
  208. package/src/plugins/generative-ui/ui/select.tsx +14 -10
  209. package/src/plugins/generative-ui/ui/separator.tsx +1 -1
  210. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +1 -1
  211. package/src/plugins/generative-ui/ui/skeleton.tsx +4 -1
  212. package/src/plugins/generative-ui/ui/stack.tsx +1 -1
  213. package/src/plugins/generative-ui/ui/switch.tsx +1 -1
  214. package/src/plugins/generative-ui/ui/table.tsx +29 -8
  215. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +5 -2
  216. package/src/plugins/generative-ui/ui/tabs.tsx +4 -4
  217. package/src/plugins/generative-ui/ui/text.tsx +1 -1
  218. package/src/plugins/generative-ui/ui/textarea.tsx +4 -1
  219. package/src/plugins/generative-ui/ui/tooltip.tsx +4 -4
  220. package/src/react-shim.ts +9 -4
  221. package/src/server/bun.ts +1 -1
  222. package/src/server/express.ts +1 -1
  223. package/src/server/fastify.ts +1 -1
  224. package/src/server/hono.ts +1 -1
  225. package/src/server/nextjs.ts +1 -1
  226. package/src/server/tanstack-start.ts +1 -1
  227. package/src/storybook.d.ts +5 -0
  228. package/src/types/index.ts +39 -3
  229. package/dist/index-B5lZrrO2.js.map +0 -1
  230. package/dist/index-BFU6NvbL.js.map +0 -1
  231. package/dist/index-C08dvTEo.cjs.map +0 -1
  232. package/dist/index-DzZ1-jQY.cjs +0 -194
  233. package/dist/index-DzZ1-jQY.cjs.map +0 -1
@@ -39,6 +39,7 @@ function createUseSyncExternalStoreShim(R: ReactLike) {
39
39
  inst.value = value;
40
40
  inst.getSnapshot = getSnapshot;
41
41
  if (snapshotChanged(inst)) forceUpdate({ inst });
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
43
  }, [subscribe, value, getSnapshot]);
43
44
 
44
45
  R.useEffect(() => {
@@ -46,6 +47,7 @@ function createUseSyncExternalStoreShim(R: ReactLike) {
46
47
  return subscribe(() => {
47
48
  if (snapshotChanged(inst)) forceUpdate({ inst });
48
49
  });
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
51
  }, [subscribe]);
50
52
 
51
53
  return value;
@@ -61,14 +63,26 @@ function createUseIdShim(R: ReactLike) {
61
63
  };
62
64
  }
63
65
 
66
+ export interface ReactShims {
67
+ useSyncExternalStore: <T>(
68
+ subscribe: (cb: () => void) => () => void,
69
+ getSnapshot: () => T,
70
+ ) => T;
71
+ useId: () => string;
72
+ useInsertionEffect: typeof import("react").useLayoutEffect;
73
+ startTransition: (cb: () => void) => void;
74
+ useTransition: () => [boolean, (cb: () => void) => void];
75
+ useDeferredValue: <T>(value: T) => T;
76
+ }
77
+
64
78
  /** Build polyfills for a React instance. Native APIs take precedence via ??. */
65
- export function createShims(R: ReactLike) {
79
+ export function createShims(R: ReactLike): ReactShims {
66
80
  return {
67
81
  useSyncExternalStore:
68
82
  R.useSyncExternalStore ?? createUseSyncExternalStoreShim(R),
69
83
  useId: R.useId ?? createUseIdShim(R),
70
84
  useInsertionEffect: R.useInsertionEffect ?? R.useLayoutEffect,
71
- startTransition: R.startTransition ?? ((cb: () => void) => cb()),
85
+ startTransition: R.startTransition ?? ((cb: () => void): void => cb()),
72
86
  useTransition:
73
87
  R.useTransition ??
74
88
  ((): [boolean, (cb: () => void) => void] => [false, (cb) => cb()]),
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import * as React from "react";
3
4
  import { useElements } from "@/hooks/useElements";
4
5
  import { AssistantModal } from "../assistant-ui/assistant-modal";
5
6
  import { AssistantSidecar } from "../assistant-ui/assistant-sidecar";
@@ -11,7 +12,7 @@ interface ChatProps {
11
12
  className?: string;
12
13
  }
13
14
 
14
- export const Chat = ({ className }: ChatProps) => {
15
+ export const Chat = ({ className }: ChatProps): React.JSX.Element => {
15
16
  const { config } = useElements();
16
17
 
17
18
  switch (config.variant) {
@@ -34,6 +35,8 @@ export const Chat = ({ className }: ChatProps) => {
34
35
 
35
36
  // If no variant is provided then fallback to the modal
36
37
  // Modal has its own internal ErrorBoundary around Thread
38
+ case "widget":
39
+ case undefined:
37
40
  default:
38
41
  return (
39
42
  <ShadowRoot>
@@ -29,7 +29,7 @@ const FetchTool = defineFrontendTool<{ url: string }, string>(
29
29
  const text = await response.text();
30
30
  return text;
31
31
  } catch (error) {
32
- return `Error fetching ${url}: ${error instanceof Error ? error.message : "Unknown error"}. Note: URL must support CORS for browser requests.`;
32
+ return `Error fetching ${String(url)}: ${error instanceof Error ? error.message : "Unknown error"}. Note: URL must support CORS for browser requests.`;
33
33
  }
34
34
  },
35
35
  },
@@ -93,8 +93,8 @@ const deleteFile = defineFrontendTool<{ fileId: string }, string>(
93
93
  fileId: z.string().describe("The ID of the file to delete"),
94
94
  }),
95
95
  execute: async ({ fileId }) => {
96
- alert(`File ${fileId} deleted`);
97
- return `File ${fileId} deleted`;
96
+ alert(`File ${String(fileId)} deleted`);
97
+ return `File ${String(fileId)} deleted`;
98
98
  },
99
99
  },
100
100
  "deleteFile",
@@ -139,7 +139,9 @@ const ProductCardComponent = ({ result }: ToolCallMessagePartProps) => {
139
139
  ${product.price?.toFixed(2)}
140
140
  </span>
141
141
  <button
142
- onClick={handleAddToCart}
142
+ onClick={() => {
143
+ void handleAddToCart();
144
+ }}
143
145
  disabled={
144
146
  isLoading || addedToCart || !canAddToCart || !product.inStock
145
147
  }
@@ -242,8 +244,10 @@ const ApproveRequestTool = defineFrontendTool<{ id: number }, string>(
242
244
  }),
243
245
  execute: async ({ id }) => {
244
246
  // Simulate API call
245
- await new Promise((resolve) => setTimeout(resolve, 500));
246
- return `Request #${id} has been approved successfully.`;
247
+ await new Promise((resolve) => {
248
+ setTimeout(resolve, 500);
249
+ });
250
+ return `Request #${String(id)} has been approved successfully.`;
247
251
  },
248
252
  },
249
253
  "approve_request",
@@ -260,8 +264,12 @@ const RejectRequestTool = defineFrontendTool<
260
264
  reason: z.string().optional().describe("Reason for rejection"),
261
265
  }),
262
266
  execute: async ({ id, reason }) => {
263
- await new Promise((resolve) => setTimeout(resolve, 500));
264
- return `Request #${id} has been rejected.${reason ? ` Reason: ${reason}` : ""}`;
267
+ await new Promise((resolve) => {
268
+ setTimeout(resolve, 500);
269
+ });
270
+ return `Request #${String(id)} has been rejected.${
271
+ reason ? ` Reason: ${JSON.stringify(reason)}` : ""
272
+ }`;
265
273
  },
266
274
  },
267
275
  "reject_request",
@@ -7,7 +7,9 @@ interface ChatHistoryProps {
7
7
  className?: string;
8
8
  }
9
9
 
10
- export const ChatHistory = ({ className }: ChatHistoryProps) => {
10
+ export const ChatHistory = ({
11
+ className,
12
+ }: ChatHistoryProps): React.JSX.Element => {
11
13
  return (
12
14
  <ShadowRoot hostStyle={{ height: "inherit", width: "inherit" }}>
13
15
  <ThreadList className={className} />
@@ -5,7 +5,7 @@ export function FrontendTools({
5
5
  tools: frontendTools,
6
6
  }: {
7
7
  tools: FrontendTools;
8
- }) {
8
+ }): React.JSX.Element {
9
9
  return (
10
10
  <>
11
11
  {Object.entries(frontendTools).map(([, tool]) =>
@@ -26,6 +26,7 @@ const STUB_CONTEXT: ElementsContextType = {
26
26
  setIsOpen: () => {},
27
27
  plugins: recommended,
28
28
  mcpTools: undefined,
29
+ mcpToolsLoading: false,
29
30
  };
30
31
 
31
32
  export interface MessageContentProps {
@@ -1,10 +1,9 @@
1
- import type { StoryFn } from "@storybook/react-vite";
1
+ import type { Meta, StoryFn } from "@storybook/react-vite";
2
2
  import { Chat } from "@/components/Chat";
3
3
  import { Replay } from "@/components/Replay";
4
4
  import type { Cassette } from "@/lib/cassette";
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- const meta: any = {
6
+ const meta: Meta<typeof Replay> = {
8
7
  title: "Misc/Replay",
9
8
  component: Replay,
10
9
  parameters: {
@@ -63,17 +63,20 @@ export const Replay = ({
63
63
  userMessageDelay,
64
64
  assistantStartDelay,
65
65
  onComplete,
66
- }: ReplayProps) => {
67
- const replayOptions: ReplayOptions = {
68
- typingSpeed,
69
- userMessageDelay,
70
- assistantStartDelay,
71
- onComplete,
72
- };
66
+ }: ReplayProps): React.JSX.Element => {
67
+ const replayOptions = useMemo<ReplayOptions>(
68
+ () => ({
69
+ typingSpeed,
70
+ userMessageDelay,
71
+ assistantStartDelay,
72
+ onComplete,
73
+ }),
74
+ [typingSpeed, userMessageDelay, assistantStartDelay, onComplete],
75
+ );
73
76
 
74
77
  const transport = useMemo(
75
78
  () => createReplayTransport(cassette, replayOptions),
76
- [cassette, typingSpeed, userMessageDelay, assistantStartDelay, onComplete],
79
+ [cassette, replayOptions],
77
80
  );
78
81
 
79
82
  const runtime = useChatRuntime({ transport });
@@ -108,6 +111,7 @@ export const Replay = ({
108
111
  setIsOpen: () => {},
109
112
  plugins,
110
113
  mcpTools: undefined,
114
+ mcpToolsLoading: false,
111
115
  }),
112
116
  [config, plugins],
113
117
  );
@@ -148,7 +152,9 @@ export const Replay = ({
148
152
  // ---------------------------------------------------------------------------
149
153
 
150
154
  function sleep(ms: number): Promise<void> {
151
- return new Promise((resolve) => setTimeout(resolve, ms));
155
+ return new Promise((resolve) => {
156
+ setTimeout(resolve, ms);
157
+ });
152
158
  }
153
159
 
154
160
  /**
@@ -238,11 +244,12 @@ const ReplayController = ({ cassette, options }: ReplayControllerProps) => {
238
244
  options.onComplete?.();
239
245
  };
240
246
 
241
- runReplay();
247
+ void runReplay();
242
248
 
243
249
  return () => {
244
250
  cancelled = true;
245
251
  };
252
+ // oxlint-disable-next-line react-hooks/exhaustive-deps -- run-once driver, see hasStarted ref
246
253
  }, []);
247
254
 
248
255
  return null;
@@ -26,7 +26,7 @@ export const ShadowRoot = ({
26
26
  children,
27
27
  hostClassName,
28
28
  hostStyle,
29
- }: ShadowRootProps) => {
29
+ }: ShadowRootProps): React.JSX.Element => {
30
30
  const hostRef = useRef<HTMLDivElement>(null);
31
31
  const containerRef = useRef<HTMLDivElement>(null);
32
32
  const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
@@ -66,7 +66,7 @@ export const ShadowRoot = ({
66
66
  styleElement.setAttribute("data-gram-elements", "true");
67
67
  styleElement.textContent = elementsStyles;
68
68
  shadowRoot.prepend(styleElement);
69
- }, [shadowRoot, elementsStyles]);
69
+ }, [shadowRoot]);
70
70
 
71
71
  return (
72
72
  <div
@@ -80,7 +80,7 @@ export function ShareButton({
80
80
  size = "sm",
81
81
  className,
82
82
  children,
83
- }: ShareButtonProps) {
83
+ }: ShareButtonProps): React.JSX.Element {
84
84
  const { threadId } = useThreadId();
85
85
 
86
86
  const handleShare = useCallback(async () => {
@@ -118,7 +118,9 @@ export function ShareButton({
118
118
  <Button
119
119
  variant={variant}
120
120
  size={size}
121
- onClick={handleShare}
121
+ onClick={() => {
122
+ void handleShare();
123
+ }}
122
124
  disabled={!threadId}
123
125
  className={cn("aui-share-button", className)}
124
126
  aria-label="Share chat"
@@ -36,9 +36,9 @@ const LAYOUT_TRANSITION = {
36
36
  } as const;
37
37
 
38
38
  type Dimensions = {
39
- width?: string | number | `${number}%`;
40
- height?: string | number | `${number}%`;
41
- maxHeight?: string | number | `${number}%`;
39
+ width?: string | number;
40
+ height?: string | number;
41
+ maxHeight?: string | number;
42
42
  };
43
43
 
44
44
  interface AssistantModalProps {
@@ -277,6 +277,8 @@ function positionClassname(
277
277
  return "left-4 top-4";
278
278
  case "bottom-right":
279
279
  return "right-4 bottom-4";
280
+ case undefined:
281
+ return "right-4 bottom-4";
280
282
  default:
281
283
  assertNever(position);
282
284
  }
@@ -140,7 +140,7 @@ const AttachmentUI: FC = () => {
140
140
  return "File";
141
141
  default: {
142
142
  const _exhaustiveCheck: never = type;
143
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
143
+ throw new Error(`Unknown attachment type: ${String(_exhaustiveCheck)}`);
144
144
  }
145
145
  }
146
146
  });
@@ -90,7 +90,7 @@ export class ErrorBoundary extends Component<
90
90
  this.props.onError?.(error, errorInfo);
91
91
  }
92
92
 
93
- handleRetry = () => {
93
+ handleRetry = (): void => {
94
94
  // Increment resetKey to force remount of children, reinitializing the chat
95
95
  this.setState((state) => ({
96
96
  hasError: false,
@@ -66,7 +66,7 @@ const useCopyToClipboard = ({
66
66
  const copyToClipboard = (value: string) => {
67
67
  if (!value) return;
68
68
 
69
- navigator.clipboard.writeText(value).then(() => {
69
+ void navigator.clipboard.writeText(value).then(() => {
70
70
  setIsCopied(true);
71
71
  setTimeout(() => setIsCopied(false), copiedDuration);
72
72
  });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ArrowDownIcon,
3
3
  ArrowUpIcon,
4
+ AtSign,
4
5
  CheckIcon,
5
6
  ChevronLeftIcon,
6
7
  ChevronRightIcon,
@@ -8,8 +9,10 @@ import {
8
9
  CopyIcon,
9
10
  DownloadIcon,
10
11
  PencilIcon,
12
+ Search,
11
13
  Settings2,
12
14
  Square,
15
+ Wrench,
13
16
  } from "lucide-react";
14
17
 
15
18
  import {
@@ -20,6 +23,7 @@ import {
20
23
  ImageMessagePartProps,
21
24
  MessagePrimitive,
22
25
  ThreadPrimitive,
26
+ useAssistantApi,
23
27
  useAssistantState,
24
28
  } from "@assistant-ui/react";
25
29
 
@@ -68,6 +72,10 @@ import { useToolMentions } from "@/hooks/useToolMentions";
68
72
  import { getApiUrl } from "@/lib/api";
69
73
  import { EASE_OUT_QUINT } from "@/lib/easing";
70
74
  import { MODELS } from "@/lib/models";
75
+ import {
76
+ type MentionableTool,
77
+ toolSetToMentionableTools,
78
+ } from "@/lib/tool-mentions";
71
79
  import { cn } from "@/lib/utils";
72
80
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
73
81
  import {
@@ -262,7 +270,7 @@ const ThreadScrollToBottom: FC = () => {
262
270
  const ThreadWelcome: FC = () => {
263
271
  const { config } = useElements();
264
272
  const d = useDensity();
265
- const { title, subtitle } = config.welcome ?? {};
273
+ const { logo, title, subtitle } = config.welcome ?? {};
266
274
  const isStandalone = config.variant === "standalone";
267
275
 
268
276
  return (
@@ -288,6 +296,19 @@ const ThreadWelcome: FC = () => {
288
296
  !isStandalone && d("py-md"),
289
297
  )}
290
298
  >
299
+ {logo && (
300
+ <m.img
301
+ src={logo}
302
+ alt=""
303
+ initial={{ opacity: 0, y: 10 }}
304
+ animate={{ opacity: 1, y: 0 }}
305
+ exit={{ opacity: 0, y: 10 }}
306
+ transition={{ duration: 0.25, ease: EASE_OUT_QUINT }}
307
+ className={cn(
308
+ "aui-thread-welcome-logo mb-2 size-12 object-contain",
309
+ )}
310
+ />
311
+ )}
291
312
  <m.div
292
313
  initial={{ opacity: 0, y: 10 }}
293
314
  animate={{ opacity: 1, y: 0 }}
@@ -524,7 +545,9 @@ const ComposerFeedback: FC = () => {
524
545
  <MessageFeedback
525
546
  className="mx-auto"
526
547
  onResolved={setResolved}
527
- onFeedback={handleFeedback}
548
+ onFeedback={(type) => {
549
+ void handleFeedback(type);
550
+ }}
528
551
  />
529
552
  </m.div>
530
553
  )}
@@ -794,6 +817,218 @@ const ComposerCassetteRecorder: FC = () => {
794
817
  );
795
818
  };
796
819
 
820
+ // Sentinel for the "All" pseudo-category in the tool-mention picker.
821
+ const TOOL_MENTION_ALL_CATEGORY = "__all__";
822
+
823
+ function humanizeToolCategory(raw: string): string {
824
+ const cleaned = raw.replace(/[-_]+/g, " ").trim();
825
+ if (!cleaned) return "Tools";
826
+ return cleaned.replace(/\b\w/g, (c) => c.toUpperCase());
827
+ }
828
+
829
+ // Derive a grouping label for a tool. Tools from multiple MCP servers are
830
+ // namespaced as `<server>__<tool>`; otherwise group by the first
831
+ // underscore-delimited segment (e.g. `platform_search_logs` -> "Platform"),
832
+ // falling back to a single "Tools" bucket.
833
+ function deriveToolCategory(name: string): string {
834
+ const namespaceIdx = name.indexOf("__");
835
+ if (namespaceIdx > 0)
836
+ return humanizeToolCategory(name.slice(0, namespaceIdx));
837
+ const underscoreIdx = name.indexOf("_");
838
+ if (underscoreIdx > 0)
839
+ return humanizeToolCategory(name.slice(0, underscoreIdx));
840
+ return "Tools";
841
+ }
842
+
843
+ interface ToolCategory {
844
+ name: string;
845
+ tools: MentionableTool[];
846
+ }
847
+
848
+ // A discoverable counterpart to the type-`@` autocomplete: a composer button
849
+ // that opens a searchable, category-grouped picker of the available tools and
850
+ // inserts an @mention for the chosen one. Inserts through the composer runtime
851
+ // so it stays in sync with the autocomplete's own textarea handling. Hidden when
852
+ // tool mentions are disabled or there are no tools.
853
+ const ComposerToolMentionPicker: FC = () => {
854
+ const { config, mcpTools, mcpToolsLoading } = useElements();
855
+ const api = useAssistantApi();
856
+ // Read the composer text from the same reactive source the tool-mention
857
+ // badges parse, so an inserted mention renders a pill just like the type-`@`
858
+ // autocomplete does.
859
+ const composerText = useAssistantState(({ composer }) => composer.text);
860
+ const [open, setOpen] = useState(false);
861
+ const [query, setQuery] = useState("");
862
+ const [activeCategory, setActiveCategory] = useState(
863
+ TOOL_MENTION_ALL_CATEGORY,
864
+ );
865
+
866
+ const composerConfig = config.composer;
867
+ const toolMentionsEnabled =
868
+ composerConfig?.toolMentions === undefined ||
869
+ composerConfig.toolMentions === true ||
870
+ (typeof composerConfig.toolMentions === "object" &&
871
+ composerConfig.toolMentions.enabled !== false);
872
+
873
+ const tools = useMemo(() => toolSetToMentionableTools(mcpTools), [mcpTools]);
874
+
875
+ const categories = useMemo<ToolCategory[]>(() => {
876
+ const grouped = new Map<string, MentionableTool[]>();
877
+ for (const tool of tools) {
878
+ const category = deriveToolCategory(tool.name);
879
+ const existing = grouped.get(category);
880
+ if (existing) {
881
+ existing.push(tool);
882
+ } else {
883
+ grouped.set(category, [tool]);
884
+ }
885
+ }
886
+ return [...grouped.entries()]
887
+ .map(([name, categoryTools]) => ({ name, tools: categoryTools }))
888
+ .sort((a, b) => a.name.localeCompare(b.name));
889
+ }, [tools]);
890
+
891
+ // Show the button while tools are still loading (so it appears immediately
892
+ // rather than popping in once the async MCP list resolves) or once there are
893
+ // tools — but hide it when the list has loaded and is empty, so we don't
894
+ // expose a dead-end control.
895
+ if (!toolMentionsEnabled || (!mcpToolsLoading && tools.length === 0)) {
896
+ return null;
897
+ }
898
+
899
+ const normalizedQuery = query.trim().toLowerCase();
900
+ const inActiveCategory =
901
+ activeCategory === TOOL_MENTION_ALL_CATEGORY
902
+ ? tools
903
+ : (categories.find((c) => c.name === activeCategory)?.tools ?? []);
904
+ const visibleTools = normalizedQuery
905
+ ? inActiveCategory.filter(
906
+ (tool) =>
907
+ tool.name.toLowerCase().includes(normalizedQuery) ||
908
+ (tool.description?.toLowerCase().includes(normalizedQuery) ?? false),
909
+ )
910
+ : inActiveCategory;
911
+
912
+ const insertMention = (toolName: string) => {
913
+ const base =
914
+ composerText && !/\s$/.test(composerText)
915
+ ? `${composerText} `
916
+ : composerText;
917
+ api.composer().setText(`${base}@${toolName} `);
918
+ setOpen(false);
919
+ setQuery("");
920
+ };
921
+
922
+ const handleOpenChange = (next: boolean) => {
923
+ setOpen(next);
924
+ if (!next) {
925
+ setQuery("");
926
+ setActiveCategory(TOOL_MENTION_ALL_CATEGORY);
927
+ }
928
+ };
929
+
930
+ return (
931
+ <Popover open={open} onOpenChange={handleOpenChange}>
932
+ <PopoverTrigger asChild>
933
+ <Button
934
+ variant="ghost"
935
+ size="icon"
936
+ data-state={open ? "open" : "closed"}
937
+ className="aui-composer-tool-mention-picker flex w-fit items-center gap-2 rounded-full px-2.5 py-1 text-xs font-semibold data-[state=open]:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
938
+ aria-label="Mention a tool"
939
+ >
940
+ <AtSign className="size-5 stroke-[1.5px]" />
941
+ </Button>
942
+ </PopoverTrigger>
943
+ <PopoverContent
944
+ side="top"
945
+ align="start"
946
+ className="aui-composer-tool-mention-popover w-[420px] overflow-hidden p-0"
947
+ >
948
+ <div className="flex items-center gap-2 border-b border-input px-3 py-2">
949
+ <Search className="size-4 shrink-0 text-muted-foreground" />
950
+ <input
951
+ autoFocus
952
+ value={query}
953
+ onChange={(e) => setQuery(e.target.value)}
954
+ placeholder="Search tools…"
955
+ className="w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground"
956
+ aria-label="Search tools"
957
+ />
958
+ </div>
959
+ <div className="flex h-72">
960
+ <div className="w-36 shrink-0 overflow-y-auto border-r border-input p-2">
961
+ <div className="px-2 pb-1 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase">
962
+ Categories
963
+ </div>
964
+ <button
965
+ type="button"
966
+ onClick={() => setActiveCategory(TOOL_MENTION_ALL_CATEGORY)}
967
+ className={cn(
968
+ "flex w-full items-center justify-between rounded px-2 py-1 text-left text-xs transition-colors",
969
+ activeCategory === TOOL_MENTION_ALL_CATEGORY
970
+ ? "bg-muted font-medium text-foreground"
971
+ : "text-muted-foreground hover:bg-muted/60",
972
+ )}
973
+ >
974
+ <span className="truncate">All</span>
975
+ <span className="ml-2 shrink-0 tabular-nums opacity-60">
976
+ {tools.length}
977
+ </span>
978
+ </button>
979
+ {categories.map((category) => (
980
+ <button
981
+ key={category.name}
982
+ type="button"
983
+ onClick={() => setActiveCategory(category.name)}
984
+ className={cn(
985
+ "flex w-full items-center justify-between rounded px-2 py-1 text-left text-xs transition-colors",
986
+ activeCategory === category.name
987
+ ? "bg-muted font-medium text-foreground"
988
+ : "text-muted-foreground hover:bg-muted/60",
989
+ )}
990
+ >
991
+ <span className="truncate">{category.name}</span>
992
+ <span className="ml-2 shrink-0 tabular-nums opacity-60">
993
+ {category.tools.length}
994
+ </span>
995
+ </button>
996
+ ))}
997
+ </div>
998
+ <div className="min-w-0 flex-1 overflow-y-auto p-2">
999
+ {visibleTools.length === 0 ? (
1000
+ <div className="px-2 py-6 text-center text-xs text-muted-foreground">
1001
+ {mcpToolsLoading ? "Loading tools…" : "No tools found"}
1002
+ </div>
1003
+ ) : (
1004
+ visibleTools.map((tool) => (
1005
+ <button
1006
+ key={tool.id}
1007
+ type="button"
1008
+ onClick={() => insertMention(tool.name)}
1009
+ className="flex w-full items-start gap-2 rounded px-2 py-1.5 text-left transition-colors hover:bg-muted"
1010
+ >
1011
+ <Wrench className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
1012
+ <span className="min-w-0 flex-1">
1013
+ <span className="block truncate text-sm font-medium text-foreground">
1014
+ {tool.name}
1015
+ </span>
1016
+ {tool.description && (
1017
+ <span className="line-clamp-2 text-xs text-muted-foreground">
1018
+ {tool.description}
1019
+ </span>
1020
+ )}
1021
+ </span>
1022
+ </button>
1023
+ ))
1024
+ )}
1025
+ </div>
1026
+ </div>
1027
+ </PopoverContent>
1028
+ </Popover>
1029
+ );
1030
+ };
1031
+
797
1032
  const ComposerAction: FC = () => {
798
1033
  const { config } = useElements();
799
1034
  const r = useRadius();
@@ -807,6 +1042,8 @@ const ComposerAction: FC = () => {
807
1042
  <div className="aui-composer-add-attachment-placeholder" />
808
1043
  )}
809
1044
 
1045
+ <ComposerToolMentionPicker />
1046
+
810
1047
  {config.model?.showModelPicker && !config.languageModel && (
811
1048
  <ComposerModelPicker />
812
1049
  )}
@@ -879,21 +1116,22 @@ const ToolCallStreamingIndicator: FC = () => {
879
1116
  const AssistantMessage: FC = () => {
880
1117
  const { config } = useElements();
881
1118
  const toolsConfig = config.tools ?? {};
882
- const components = config.components ?? {};
1119
+ const components = config.components;
1120
+ const toolsComponents = toolsConfig.components;
883
1121
 
884
1122
  const partsComponents = useMemo(
885
1123
  () => ({
886
- Text: components.Text ?? MarkdownText,
887
- Image: components.Image ?? Image,
1124
+ Text: components?.Text ?? MarkdownText,
1125
+ Image: components?.Image ?? Image,
888
1126
  tools: {
889
- by_name: toolsConfig.components,
890
- Fallback: components.ToolFallback ?? ToolFallback,
1127
+ by_name: toolsComponents,
1128
+ Fallback: components?.ToolFallback ?? ToolFallback,
891
1129
  },
892
- Reasoning: components.Reasoning ?? Reasoning,
893
- ReasoningGroup: components.ReasoningGroup ?? ReasoningGroup,
894
- ToolGroup: components.ToolGroup ?? ToolGroup,
1130
+ Reasoning: components?.Reasoning ?? Reasoning,
1131
+ ReasoningGroup: components?.ReasoningGroup ?? ReasoningGroup,
1132
+ ToolGroup: components?.ToolGroup ?? ToolGroup,
895
1133
  }),
896
- [components, toolsConfig.components],
1134
+ [components, toolsComponents],
897
1135
  );
898
1136
 
899
1137
  return (
@@ -74,7 +74,7 @@ export const ToolMentionAutocomplete: FC<ToolMentionAutocompleteProps> = ({
74
74
  onValueChange(result.text, result.cursorPosition);
75
75
  setIsVisible(false);
76
76
  },
77
- [mentionContext, value, cursorPosition, onValueChange, textareaRef],
77
+ [mentionContext, value, cursorPosition, onValueChange],
78
78
  );
79
79
 
80
80
  useEffect(() => {