@gram-ai/elements 1.34.0 → 1.36.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 (238) 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/assistant-ui/thinking-indicator.d.ts +8 -0
  12. package/dist/components/ui/avatar.d.ts +3 -3
  13. package/dist/components/ui/button.d.ts +2 -2
  14. package/dist/components/ui/buttonVariants.d.ts +2 -2
  15. package/dist/components/ui/calendar.d.ts +2 -1
  16. package/dist/components/ui/collapsible.d.ts +3 -3
  17. package/dist/components/ui/dialog.d.ts +10 -10
  18. package/dist/components/ui/popover.d.ts +4 -4
  19. package/dist/components/ui/skeleton.d.ts +1 -1
  20. package/dist/components/ui/time-range-picker.d.ts +2 -1
  21. package/dist/components/ui/tool-ui.d.ts +9 -7
  22. package/dist/components/ui/tooltip.d.ts +4 -4
  23. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  24. package/dist/contexts/ElementsProvider.d.ts +1 -1
  25. package/dist/contexts/ToolApprovalContext.d.ts +2 -2
  26. package/dist/contexts/ToolExecutionContext.d.ts +1 -1
  27. package/dist/contexts/portal-container.d.ts +1 -1
  28. package/dist/elements.cjs +1 -1
  29. package/dist/elements.css +1 -1
  30. package/dist/elements.js +2 -2
  31. package/dist/hooks/useDensity.d.ts +1 -1
  32. package/dist/hooks/useElements.d.ts +2 -1
  33. package/dist/hooks/useGramThreadListAdapter.d.ts +13 -0
  34. package/dist/hooks/useRadius.d.ts +1 -1
  35. package/dist/hooks/useThemeProps.d.ts +1 -1
  36. package/dist/hooks/useToolApproval.d.ts +2 -1
  37. package/dist/index-7fTI_vaV.cjs +194 -0
  38. package/dist/index-7fTI_vaV.cjs.map +1 -0
  39. package/dist/{index-B5lZrrO2.js → index-Bv-yE4G1.js} +2919 -2806
  40. package/dist/index-Bv-yE4G1.js.map +1 -0
  41. package/dist/{index-BFU6NvbL.js → index-BziIHO9O.js} +9621 -9308
  42. package/dist/index-BziIHO9O.js.map +1 -0
  43. package/dist/{index-C08dvTEo.cjs → index-CGBkMd0d.cjs} +48 -48
  44. package/dist/index-CGBkMd0d.cjs.map +1 -0
  45. package/dist/lib/errorTracking.d.ts +1 -1
  46. package/dist/lib/tools.d.ts +11 -10
  47. package/dist/plugins/generative-ui/catalog.d.ts +3 -3
  48. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  49. package/dist/plugins/generative-ui/ui/accordion.d.ts +4 -4
  50. package/dist/plugins/generative-ui/ui/action-button.d.ts +1 -1
  51. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +2 -1
  52. package/dist/plugins/generative-ui/ui/alert.d.ts +3 -3
  53. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +2 -1
  54. package/dist/plugins/generative-ui/ui/avatar.d.ts +6 -6
  55. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  56. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -1
  57. package/dist/plugins/generative-ui/ui/button.d.ts +3 -3
  58. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +1 -1
  59. package/dist/plugins/generative-ui/ui/card.d.ts +7 -7
  60. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +2 -1
  61. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  62. package/dist/plugins/generative-ui/ui/data-table.d.ts +1 -1
  63. package/dist/plugins/generative-ui/ui/dialog.d.ts +10 -10
  64. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +15 -15
  65. package/dist/plugins/generative-ui/ui/grid.d.ts +1 -1
  66. package/dist/plugins/generative-ui/ui/index.d.ts +57 -40
  67. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +2 -1
  68. package/dist/plugins/generative-ui/ui/input.d.ts +1 -1
  69. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  70. package/dist/plugins/generative-ui/ui/list.d.ts +2 -1
  71. package/dist/plugins/generative-ui/ui/metric.d.ts +1 -1
  72. package/dist/plugins/generative-ui/ui/pagination.d.ts +7 -7
  73. package/dist/plugins/generative-ui/ui/popover.d.ts +7 -7
  74. package/dist/plugins/generative-ui/ui/progress.d.ts +1 -1
  75. package/dist/plugins/generative-ui/ui/radio-group.d.ts +2 -2
  76. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +2 -1
  77. package/dist/plugins/generative-ui/ui/select.d.ts +10 -10
  78. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  79. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +2 -1
  80. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  81. package/dist/plugins/generative-ui/ui/stack.d.ts +1 -1
  82. package/dist/plugins/generative-ui/ui/switch.d.ts +1 -1
  83. package/dist/plugins/generative-ui/ui/table.d.ts +8 -8
  84. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +2 -2
  85. package/dist/plugins/generative-ui/ui/tabs.d.ts +4 -4
  86. package/dist/plugins/generative-ui/ui/text.d.ts +1 -1
  87. package/dist/plugins/generative-ui/ui/textarea.d.ts +1 -1
  88. package/dist/plugins/generative-ui/ui/tooltip.d.ts +4 -4
  89. package/dist/plugins.cjs +1 -1
  90. package/dist/plugins.js +1 -1
  91. package/dist/{profiler-BRnyr1GA.cjs → profiler-DuJEf_S6.cjs} +2 -2
  92. package/dist/{profiler-BRnyr1GA.cjs.map → profiler-DuJEf_S6.cjs.map} +1 -1
  93. package/dist/{profiler-KLSTpp6I.js → profiler-xLXOPfmj.js} +2 -2
  94. package/dist/{profiler-KLSTpp6I.js.map → profiler-xLXOPfmj.js.map} +1 -1
  95. package/dist/react-shim.cjs.map +1 -1
  96. package/dist/react-shim.d.ts +1 -1
  97. package/dist/react-shim.js +1 -4
  98. package/dist/react-shim.js.map +1 -1
  99. package/dist/server/bun.cjs.map +1 -1
  100. package/dist/server/bun.js.map +1 -1
  101. package/dist/server/express.cjs.map +1 -1
  102. package/dist/server/express.js.map +1 -1
  103. package/dist/server/fastify.cjs.map +1 -1
  104. package/dist/server/fastify.js.map +1 -1
  105. package/dist/server/hono.cjs.map +1 -1
  106. package/dist/server/hono.js.map +1 -1
  107. package/dist/server/nextjs.cjs.map +1 -1
  108. package/dist/server/nextjs.js.map +1 -1
  109. package/dist/server/tanstack-start.cjs.map +1 -1
  110. package/dist/server/tanstack-start.js.map +1 -1
  111. package/dist/{startRecording-CKx-YWbq.cjs → startRecording-C2XF9-Ol.cjs} +2 -2
  112. package/dist/{startRecording-CKx-YWbq.cjs.map → startRecording-C2XF9-Ol.cjs.map} +1 -1
  113. package/dist/{startRecording-BfxB1xxR.js → startRecording-qKnXr4lw.js} +2 -2
  114. package/dist/{startRecording-BfxB1xxR.js.map → startRecording-qKnXr4lw.js.map} +1 -1
  115. package/dist/types/index.d.ts +29 -3
  116. package/package.json +8 -11
  117. package/src/compat-shims.ts +16 -2
  118. package/src/components/Chat/index.tsx +4 -1
  119. package/src/components/Chat/stories/FrontendTools.stories.tsx +1 -1
  120. package/src/components/Chat/stories/ToolApproval.stories.tsx +2 -2
  121. package/src/components/Chat/stories/Tools.stories.tsx +13 -5
  122. package/src/components/ChatHistory.tsx +3 -1
  123. package/src/components/FrontendTools/index.tsx +1 -1
  124. package/src/components/MessageContent.tsx +1 -0
  125. package/src/components/Replay.stories.tsx +2 -3
  126. package/src/components/Replay.tsx +17 -10
  127. package/src/components/ShadowRoot.tsx +2 -2
  128. package/src/components/ShareButton/index.tsx +4 -2
  129. package/src/components/assistant-ui/assistant-modal.tsx +5 -3
  130. package/src/components/assistant-ui/attachment.tsx +1 -1
  131. package/src/components/assistant-ui/error-boundary.tsx +1 -1
  132. package/src/components/assistant-ui/markdown-text.tsx +1 -1
  133. package/src/components/assistant-ui/thinking-indicator.tsx +175 -0
  134. package/src/components/assistant-ui/thread.tsx +251 -27
  135. package/src/components/assistant-ui/tool-fallback.tsx +8 -8
  136. package/src/components/assistant-ui/tool-group.tsx +4 -13
  137. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +1 -1
  138. package/src/components/ui/avatar.tsx +3 -3
  139. package/src/components/ui/calendar.tsx +1 -1
  140. package/src/components/ui/collapsible.tsx +7 -3
  141. package/src/components/ui/dialog.tsx +18 -10
  142. package/src/components/ui/generative-ui.tsx +9 -4
  143. package/src/components/ui/popover.tsx +4 -4
  144. package/src/components/ui/skeleton.tsx +4 -1
  145. package/src/components/ui/time-range-picker.stories.tsx +164 -154
  146. package/src/components/ui/time-range-picker.tsx +11 -5
  147. package/src/components/ui/tool-ui.tsx +68 -40
  148. package/src/components/ui/tooltip.tsx +4 -4
  149. package/src/contexts/ChatIdContext.tsx +1 -1
  150. package/src/contexts/ConnectionStatusContext.tsx +6 -5
  151. package/src/contexts/ElementsProvider.tsx +64 -41
  152. package/src/contexts/ReplayContext.ts +1 -1
  153. package/src/contexts/ToolApprovalContext.tsx +5 -1
  154. package/src/contexts/ToolExecutionContext.tsx +1 -1
  155. package/src/contexts/portal-container.tsx +1 -1
  156. package/src/global.css +55 -16
  157. package/src/hooks/useAuth.ts +2 -1
  158. package/src/hooks/useDensity.ts +1 -1
  159. package/src/hooks/useElements.ts +2 -1
  160. package/src/hooks/useFollowOnSuggestions.ts +3 -6
  161. package/src/hooks/useGramThreadListAdapter.tsx +50 -3
  162. package/src/hooks/useMCPTools.ts +2 -2
  163. package/src/hooks/useModel.ts +1 -3
  164. package/src/hooks/usePluginComponents.ts +3 -1
  165. package/src/hooks/useRadius.ts +1 -1
  166. package/src/hooks/useSession.ts +3 -1
  167. package/src/hooks/useThemeProps.ts +5 -5
  168. package/src/hooks/useToolApproval.ts +2 -1
  169. package/src/lib/cassette.ts +20 -8
  170. package/src/lib/errorTracking.ts +1 -4
  171. package/src/lib/messageConverter.test.ts +11 -13
  172. package/src/lib/messageConverter.ts +13 -4
  173. package/src/lib/token.ts +2 -5
  174. package/src/lib/tool-mentions.ts +5 -2
  175. package/src/lib/tools.byte-cap.test.ts +1 -1
  176. package/src/lib/tools.test.ts +1 -1
  177. package/src/lib/tools.ts +15 -5
  178. package/src/lib/utils.ts +2 -2
  179. package/src/lib.d.ts +8 -1
  180. package/src/plugins/chart/chart.test.ts +3 -4
  181. package/src/plugins/chart/component.tsx +7 -6
  182. package/src/plugins/chart/ui/area-chart.tsx +1 -1
  183. package/src/plugins/chart/ui/line-chart.tsx +1 -1
  184. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +2 -2
  185. package/src/plugins/generative-ui/ui/accordion.tsx +4 -4
  186. package/src/plugins/generative-ui/ui/action-button.tsx +4 -2
  187. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +1 -1
  188. package/src/plugins/generative-ui/ui/alert.tsx +7 -3
  189. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +5 -1
  190. package/src/plugins/generative-ui/ui/avatar.tsx +12 -6
  191. package/src/plugins/generative-ui/ui/badge.tsx +1 -1
  192. package/src/plugins/generative-ui/ui/button-wrapper.tsx +1 -1
  193. package/src/plugins/generative-ui/ui/button.tsx +1 -1
  194. package/src/plugins/generative-ui/ui/card-wrapper.tsx +1 -1
  195. package/src/plugins/generative-ui/ui/card.tsx +28 -7
  196. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +1 -1
  197. package/src/plugins/generative-ui/ui/checkbox.tsx +1 -1
  198. package/src/plugins/generative-ui/ui/data-table.tsx +1 -1
  199. package/src/plugins/generative-ui/ui/dialog.tsx +15 -10
  200. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +33 -15
  201. package/src/plugins/generative-ui/ui/grid.tsx +1 -1
  202. package/src/plugins/generative-ui/ui/index.ts +154 -40
  203. package/src/plugins/generative-ui/ui/input-wrapper.tsx +1 -1
  204. package/src/plugins/generative-ui/ui/input.tsx +5 -1
  205. package/src/plugins/generative-ui/ui/label.tsx +1 -1
  206. package/src/plugins/generative-ui/ui/list.tsx +5 -1
  207. package/src/plugins/generative-ui/ui/metric.tsx +2 -1
  208. package/src/plugins/generative-ui/ui/pagination.tsx +12 -7
  209. package/src/plugins/generative-ui/ui/popover.tsx +13 -7
  210. package/src/plugins/generative-ui/ui/progress.tsx +1 -1
  211. package/src/plugins/generative-ui/ui/radio-group.tsx +2 -2
  212. package/src/plugins/generative-ui/ui/select-wrapper.tsx +1 -1
  213. package/src/plugins/generative-ui/ui/select.tsx +14 -10
  214. package/src/plugins/generative-ui/ui/separator.tsx +1 -1
  215. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +1 -1
  216. package/src/plugins/generative-ui/ui/skeleton.tsx +4 -1
  217. package/src/plugins/generative-ui/ui/stack.tsx +1 -1
  218. package/src/plugins/generative-ui/ui/switch.tsx +1 -1
  219. package/src/plugins/generative-ui/ui/table.tsx +29 -8
  220. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +5 -2
  221. package/src/plugins/generative-ui/ui/tabs.tsx +4 -4
  222. package/src/plugins/generative-ui/ui/text.tsx +1 -1
  223. package/src/plugins/generative-ui/ui/textarea.tsx +4 -1
  224. package/src/plugins/generative-ui/ui/tooltip.tsx +4 -4
  225. package/src/react-shim.ts +9 -4
  226. package/src/server/bun.ts +1 -1
  227. package/src/server/express.ts +1 -1
  228. package/src/server/fastify.ts +1 -1
  229. package/src/server/hono.ts +1 -1
  230. package/src/server/nextjs.ts +1 -1
  231. package/src/server/tanstack-start.ts +1 -1
  232. package/src/storybook.d.ts +5 -0
  233. package/src/types/index.ts +39 -3
  234. package/dist/index-B5lZrrO2.js.map +0 -1
  235. package/dist/index-BFU6NvbL.js.map +0 -1
  236. package/dist/index-C08dvTEo.cjs.map +0 -1
  237. package/dist/index-DzZ1-jQY.cjs +0 -194
  238. 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
  });
@@ -0,0 +1,175 @@
1
+ "use client";
2
+
3
+ import { useAssistantState } from "@assistant-ui/react";
4
+ import { type FC, useEffect, useMemo, useState } from "react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ /**
9
+ * Whimsical verbs cycled while the assistant is "thinking" — i.e. the message
10
+ * is running but no answer text has streamed in yet (or it is waiting on a tool
11
+ * call / reasoning). The word shown is cosmetic and NOT tied to the actual work
12
+ * being done, mirroring the playful verbs Claude shows while it works. To make
13
+ * them reflect the real action instead, map from the streamed tool name in
14
+ * `ThinkingIndicator` below.
15
+ */
16
+ const THINKING_VERBS = [
17
+ "Thinking",
18
+ "Reasoning",
19
+ "Pondering",
20
+ "Synthesizing",
21
+ "Connecting",
22
+ "Analyzing",
23
+ "Inspecting",
24
+ "Correlating",
25
+ "Digging",
26
+ "Untangling",
27
+ "Assembling",
28
+ "Sketching",
29
+ "Reticulating",
30
+ "Wrangling",
31
+ "Distilling",
32
+ "Surveying",
33
+ "Mapping",
34
+ "Cross-referencing",
35
+ "Investigating",
36
+ "Considering",
37
+ "Formulating",
38
+ "Piecing",
39
+ "Tracing",
40
+ "Computing",
41
+ "Noodling",
42
+ "Percolating",
43
+ "Marshalling",
44
+ "Crunching",
45
+ "Orchestrating",
46
+ "Composing",
47
+ ] as const;
48
+
49
+ // Cadence: hold each verb, then crossfade to the next.
50
+ const HOLD_MS = 2000;
51
+ const FADE_MS = 350;
52
+
53
+ function shuffled<T>(items: readonly T[]): T[] {
54
+ const copy = [...items];
55
+ for (let i = copy.length - 1; i > 0; i--) {
56
+ const j = Math.floor(Math.random() * (i + 1));
57
+ [copy[i], copy[j]] = [copy[j]!, copy[i]!];
58
+ }
59
+ return copy;
60
+ }
61
+
62
+ /** Tracks the user's `prefers-reduced-motion` setting, updating if it changes. */
63
+ function usePrefersReducedMotion(): boolean {
64
+ const query = "(prefers-reduced-motion: reduce)";
65
+ const [reduced, setReduced] = useState(
66
+ () =>
67
+ typeof window !== "undefined" &&
68
+ typeof window.matchMedia === "function" &&
69
+ window.matchMedia(query).matches,
70
+ );
71
+
72
+ useEffect(() => {
73
+ if (
74
+ typeof window === "undefined" ||
75
+ typeof window.matchMedia !== "function"
76
+ )
77
+ return;
78
+ const mql = window.matchMedia(query);
79
+ const onChange = () => setReduced(mql.matches);
80
+ mql.addEventListener("change", onChange);
81
+ return () => mql.removeEventListener("change", onChange);
82
+ }, []);
83
+
84
+ return reduced;
85
+ }
86
+
87
+ /**
88
+ * Cycles through a shuffled copy of {@link THINKING_VERBS} while `active`, with
89
+ * a brief opacity dip between words so they crossfade rather than snap. When the
90
+ * user prefers reduced motion, a single verb is shown statically — no timer, no
91
+ * fade — so motion-sensitive users don't get text changing under them.
92
+ */
93
+ function useThinkingVerb(
94
+ active: boolean,
95
+ reduced: boolean,
96
+ ): { verb: string; visible: boolean } {
97
+ const order = useMemo(() => shuffled(THINKING_VERBS), []);
98
+ const [index, setIndex] = useState(0);
99
+ const [visible, setVisible] = useState(true);
100
+
101
+ useEffect(() => {
102
+ // Restore visibility on every (de)activation and motion-preference change —
103
+ // only the cycling path below dims it. This runs before the early return so
104
+ // that flipping reduced-motion on (or deactivating) during a fade-out can't
105
+ // latch the verb at opacity-0 forever.
106
+ setVisible(true);
107
+
108
+ if (!active || reduced) return;
109
+
110
+ let outTimer: ReturnType<typeof setTimeout>;
111
+ let inTimer: ReturnType<typeof setTimeout>;
112
+
113
+ const tick = () => {
114
+ setVisible(false);
115
+ outTimer = setTimeout(() => {
116
+ setIndex((i) => (i + 1) % order.length);
117
+ setVisible(true);
118
+ inTimer = setTimeout(tick, HOLD_MS);
119
+ }, FADE_MS);
120
+ };
121
+
122
+ inTimer = setTimeout(tick, HOLD_MS);
123
+ return () => {
124
+ clearTimeout(outTimer);
125
+ clearTimeout(inTimer);
126
+ };
127
+ }, [active, reduced, order.length]);
128
+
129
+ return { verb: order[index]!, visible };
130
+ }
131
+
132
+ /**
133
+ * Shows a rainbow spinner alongside a cycling "thinking" verb whenever the
134
+ * assistant is running but has not yet produced visible answer text. Once text
135
+ * starts streaming, this hides and the inline trailing dot (see the
136
+ * `aui-md[data-status="running"]` rules in global.css) takes over.
137
+ */
138
+ export const ThinkingIndicator: FC = () => {
139
+ const active = useAssistantState(({ message }) => {
140
+ if (message.status?.type !== "running") return false;
141
+ const parts = message.parts;
142
+ if (parts.length === 0) return true;
143
+ const last = parts[parts.length - 1];
144
+ if (!last) return true;
145
+ if (last.type === "tool-call" || last.type === "reasoning") return true;
146
+ if (last.type === "text") return last.text.trim().length === 0;
147
+ return false;
148
+ });
149
+
150
+ const reducedMotion = usePrefersReducedMotion();
151
+ const { verb, visible } = useThinkingVerb(active, reducedMotion);
152
+
153
+ if (!active) return null;
154
+
155
+ return (
156
+ <div
157
+ className="aui-thinking mt-2 flex items-center gap-2 text-sm text-muted-foreground"
158
+ role="status"
159
+ >
160
+ {/* Stable label for assistive tech — the rotating verb below is purely
161
+ decorative, so announcing each word would just be noise. */}
162
+ <span className="sr-only">Assistant is working…</span>
163
+ <span className="aui-thinking-dot" aria-hidden="true" />
164
+ <span
165
+ aria-hidden="true"
166
+ className={cn(
167
+ "aui-thinking-label transition-opacity duration-300 ease-out motion-reduce:transition-none",
168
+ visible ? "opacity-100" : "opacity-0",
169
+ )}
170
+ >
171
+ {verb}…
172
+ </span>
173
+ </div>
174
+ );
175
+ };