@gram-ai/elements 1.25.2 → 1.26.1

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 (162) hide show
  1. package/dist/components/Chat/stories/Charts.stories.d.ts +37 -0
  2. package/dist/components/Chat/stories/GenerativeUI.stories.d.ts +17 -0
  3. package/dist/components/ui/button.d.ts +1 -1
  4. package/dist/components/ui/buttonVariants.d.ts +1 -1
  5. package/dist/components/ui/charts.stories.d.ts +43 -0
  6. package/dist/components/ui/generative-ui.stories.d.ts +53 -0
  7. package/dist/components/ui/tool-ui.d.ts +16 -1
  8. package/dist/elements.cjs +1 -1
  9. package/dist/elements.css +1 -1
  10. package/dist/elements.js +6 -6
  11. package/dist/index-BpJstUh1.cjs +280 -0
  12. package/dist/index-BpJstUh1.cjs.map +1 -0
  13. package/dist/index-CUitXazZ.js +30426 -0
  14. package/dist/index-CUitXazZ.js.map +1 -0
  15. package/dist/index-DdrZQXwQ.cjs +147 -0
  16. package/dist/index-DdrZQXwQ.cjs.map +1 -0
  17. package/dist/index-DfqYP0CD.js +37062 -0
  18. package/dist/index-DfqYP0CD.js.map +1 -0
  19. package/dist/plugins/chart/catalog.d.ts +123 -0
  20. package/dist/plugins/chart/index.d.ts +1 -1
  21. package/dist/plugins/chart/ui/area-chart.d.ts +16 -0
  22. package/dist/plugins/chart/ui/bar-chart.d.ts +16 -0
  23. package/dist/plugins/chart/ui/donut-chart.d.ts +17 -0
  24. package/dist/plugins/chart/ui/index.d.ts +7 -0
  25. package/dist/plugins/chart/ui/line-chart.d.ts +17 -0
  26. package/dist/plugins/chart/ui/pie-chart.d.ts +15 -0
  27. package/dist/plugins/chart/ui/radar-chart.d.ts +14 -0
  28. package/dist/plugins/chart/ui/scatter-chart.d.ts +18 -0
  29. package/dist/plugins/components/MacOSWindowFrame.d.ts +13 -0
  30. package/dist/plugins/components/PluginLoadingState.d.ts +1 -1
  31. package/dist/plugins/generative-ui/catalog.d.ts +293 -0
  32. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +18 -0
  33. package/dist/plugins/generative-ui/ui/accordion.d.ts +7 -0
  34. package/dist/plugins/generative-ui/ui/action-button.d.ts +10 -0
  35. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +9 -0
  36. package/dist/plugins/generative-ui/ui/alert.d.ts +9 -0
  37. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +9 -0
  38. package/dist/plugins/generative-ui/ui/avatar.d.ts +11 -0
  39. package/dist/plugins/generative-ui/ui/badge.d.ts +12 -0
  40. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +15 -0
  41. package/dist/plugins/generative-ui/ui/button.d.ts +10 -0
  42. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +10 -0
  43. package/dist/plugins/generative-ui/ui/card.d.ts +9 -0
  44. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +10 -0
  45. package/dist/plugins/generative-ui/ui/checkbox.d.ts +4 -0
  46. package/dist/plugins/generative-ui/ui/data-table.d.ts +10 -0
  47. package/dist/plugins/generative-ui/ui/dialog.d.ts +17 -0
  48. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +25 -0
  49. package/dist/plugins/generative-ui/ui/grid.d.ts +6 -0
  50. package/dist/plugins/generative-ui/ui/index.d.ts +40 -0
  51. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +11 -0
  52. package/dist/plugins/generative-ui/ui/input.d.ts +3 -0
  53. package/dist/plugins/generative-ui/ui/label.d.ts +4 -0
  54. package/dist/plugins/generative-ui/ui/list.d.ts +6 -0
  55. package/dist/plugins/generative-ui/ui/metric.d.ts +7 -0
  56. package/dist/plugins/generative-ui/ui/pagination.d.ts +13 -0
  57. package/dist/plugins/generative-ui/ui/popover.d.ts +10 -0
  58. package/dist/plugins/generative-ui/ui/progress.d.ts +10 -0
  59. package/dist/plugins/generative-ui/ui/radio-group.d.ts +5 -0
  60. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +13 -0
  61. package/dist/plugins/generative-ui/ui/select.d.ts +15 -0
  62. package/dist/plugins/generative-ui/ui/separator.d.ts +4 -0
  63. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +9 -0
  64. package/dist/plugins/generative-ui/ui/skeleton.d.ts +2 -0
  65. package/dist/plugins/generative-ui/ui/stack.d.ts +8 -0
  66. package/dist/plugins/generative-ui/ui/switch.d.ts +6 -0
  67. package/dist/plugins/generative-ui/ui/table.d.ts +10 -0
  68. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +21 -0
  69. package/dist/plugins/generative-ui/ui/tabs.d.ts +11 -0
  70. package/dist/plugins/generative-ui/ui/text.d.ts +7 -0
  71. package/dist/plugins/generative-ui/ui/textarea.d.ts +3 -0
  72. package/dist/plugins/generative-ui/ui/tooltip.d.ts +7 -0
  73. package/dist/plugins.cjs +1 -1
  74. package/dist/plugins.js +1 -1
  75. package/dist/{profiler-BaG0scxd.js → profiler-WoFj2UH8.js} +2 -2
  76. package/dist/{profiler-BaG0scxd.js.map → profiler-WoFj2UH8.js.map} +1 -1
  77. package/dist/{profiler-CuqENACf.cjs → profiler-ZLr2-8s7.cjs} +2 -2
  78. package/dist/{profiler-CuqENACf.cjs.map → profiler-ZLr2-8s7.cjs.map} +1 -1
  79. package/dist/{startRecording-BiLmoqZa.cjs → startRecording-BGnWDInp.cjs} +2 -2
  80. package/dist/{startRecording-BiLmoqZa.cjs.map → startRecording-BGnWDInp.cjs.map} +1 -1
  81. package/dist/{startRecording-86bHmd-l.js → startRecording-DzQo16WK.js} +2 -2
  82. package/dist/{startRecording-86bHmd-l.js.map → startRecording-DzQo16WK.js.map} +1 -1
  83. package/package.json +4 -1
  84. package/src/components/Chat/stories/Charts.stories.tsx +260 -0
  85. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -6
  86. package/src/components/Chat/stories/GenerativeUI.stories.tsx +113 -0
  87. package/src/components/Replay.stories.tsx +1 -1
  88. package/src/components/Replay.tsx +18 -13
  89. package/src/components/ui/charts.stories.tsx +246 -0
  90. package/src/components/ui/generative-ui.stories.tsx +557 -0
  91. package/src/components/ui/generative-ui.tsx +60 -360
  92. package/src/components/ui/tool-ui.stories.tsx +6 -3
  93. package/src/components/ui/tool-ui.tsx +31 -2
  94. package/src/hooks/useAuth.ts +17 -1
  95. package/src/hooks/useFollowOnSuggestions.ts +6 -1
  96. package/src/plugins/chart/catalog.ts +141 -0
  97. package/src/plugins/chart/component.tsx +79 -125
  98. package/src/plugins/chart/index.ts +141 -89
  99. package/src/plugins/chart/ui/area-chart.tsx +133 -0
  100. package/src/plugins/chart/ui/bar-chart.tsx +137 -0
  101. package/src/plugins/chart/ui/donut-chart.tsx +167 -0
  102. package/src/plugins/chart/ui/index.ts +7 -0
  103. package/src/plugins/chart/ui/line-chart.tsx +135 -0
  104. package/src/plugins/chart/ui/pie-chart.tsx +148 -0
  105. package/src/plugins/chart/ui/radar-chart.tsx +105 -0
  106. package/src/plugins/chart/ui/scatter-chart.tsx +132 -0
  107. package/src/plugins/components/MacOSWindowFrame.tsx +55 -0
  108. package/src/plugins/components/PluginLoadingState.tsx +9 -13
  109. package/src/plugins/generative-ui/catalog.ts +277 -0
  110. package/src/plugins/generative-ui/component.tsx +112 -21
  111. package/src/plugins/generative-ui/index.ts +20 -141
  112. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +57 -0
  113. package/src/plugins/generative-ui/ui/accordion.tsx +66 -0
  114. package/src/plugins/generative-ui/ui/action-button.tsx +68 -0
  115. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +26 -0
  116. package/src/plugins/generative-ui/ui/alert.tsx +66 -0
  117. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +22 -0
  118. package/src/plugins/generative-ui/ui/avatar.tsx +109 -0
  119. package/src/plugins/generative-ui/ui/badge.tsx +65 -0
  120. package/src/plugins/generative-ui/ui/button-wrapper.tsx +32 -0
  121. package/src/plugins/generative-ui/ui/button.tsx +65 -0
  122. package/src/plugins/generative-ui/ui/card-wrapper.tsx +36 -0
  123. package/src/plugins/generative-ui/ui/card.tsx +92 -0
  124. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +39 -0
  125. package/src/plugins/generative-ui/ui/checkbox.tsx +32 -0
  126. package/src/plugins/generative-ui/ui/data-table.tsx +53 -0
  127. package/src/plugins/generative-ui/ui/dialog.tsx +158 -0
  128. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +257 -0
  129. package/src/plugins/generative-ui/ui/grid.tsx +29 -0
  130. package/src/plugins/generative-ui/ui/index.ts +43 -0
  131. package/src/plugins/generative-ui/ui/input-wrapper.tsx +38 -0
  132. package/src/plugins/generative-ui/ui/input.tsx +21 -0
  133. package/src/plugins/generative-ui/ui/label.tsx +24 -0
  134. package/src/plugins/generative-ui/ui/list.tsx +34 -0
  135. package/src/plugins/generative-ui/ui/metric.tsx +53 -0
  136. package/src/plugins/generative-ui/ui/pagination.tsx +127 -0
  137. package/src/plugins/generative-ui/ui/popover.tsx +89 -0
  138. package/src/plugins/generative-ui/ui/progress.tsx +57 -0
  139. package/src/plugins/generative-ui/ui/radio-group.tsx +45 -0
  140. package/src/plugins/generative-ui/ui/select-wrapper.tsx +41 -0
  141. package/src/plugins/generative-ui/ui/select.tsx +190 -0
  142. package/src/plugins/generative-ui/ui/separator.tsx +28 -0
  143. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +30 -0
  144. package/src/plugins/generative-ui/ui/skeleton.tsx +13 -0
  145. package/src/plugins/generative-ui/ui/stack.tsx +54 -0
  146. package/src/plugins/generative-ui/ui/switch.tsx +35 -0
  147. package/src/plugins/generative-ui/ui/table.tsx +116 -0
  148. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +51 -0
  149. package/src/plugins/generative-ui/ui/tabs.tsx +92 -0
  150. package/src/plugins/generative-ui/ui/text.tsx +33 -0
  151. package/src/plugins/generative-ui/ui/textarea.tsx +18 -0
  152. package/src/plugins/generative-ui/ui/tooltip.tsx +57 -0
  153. package/dist/components/Chat/stories/Plugins.stories.d.ts +0 -12
  154. package/dist/index-B8nSCdu4.cjs +0 -251
  155. package/dist/index-B8nSCdu4.cjs.map +0 -1
  156. package/dist/index-CAtaLV1E.cjs +0 -187
  157. package/dist/index-CAtaLV1E.cjs.map +0 -1
  158. package/dist/index-CJrwma08.js +0 -27232
  159. package/dist/index-CJrwma08.js.map +0 -1
  160. package/dist/index-DLWQ91ow.js +0 -40049
  161. package/dist/index-DLWQ91ow.js.map +0 -1
  162. package/src/components/Chat/stories/Plugins.stories.tsx +0 -158
@@ -1,18 +1,36 @@
1
1
  'use client'
2
2
 
3
- import { useToolExecution } from '@/contexts/ToolExecutionContext'
4
3
  import { useDensity } from '@/hooks/useDensity'
5
- import { useRadius } from '@/hooks/useRadius'
6
4
  import { cn } from '@/lib/utils'
7
5
  import { isJsonRenderTree, type JsonRenderNode } from '@/lib/generative-ui'
8
- import { useThreadRuntime } from '@assistant-ui/react'
6
+ import { AlertCircleIcon } from 'lucide-react'
7
+ import { FC, useMemo } from 'react'
8
+
9
+ // Import all components from the generative-ui plugin ui directory
9
10
  import {
10
- AlertCircleIcon,
11
- CheckCircleIcon,
12
- Loader2Icon,
13
- XCircleIcon,
14
- } from 'lucide-react'
15
- import { FC, useMemo, useState, useCallback } from 'react'
11
+ AccordionWrapper,
12
+ AccordionItemWrapper,
13
+ ActionButton,
14
+ AlertWrapper,
15
+ AvatarWrapper,
16
+ Badge,
17
+ ButtonWrapper,
18
+ CardWrapper,
19
+ CheckboxWrapper,
20
+ DataTable,
21
+ Grid,
22
+ InputWrapper,
23
+ List,
24
+ Metric,
25
+ Progress,
26
+ SelectWrapper,
27
+ Separator,
28
+ SkeletonWrapper,
29
+ Stack,
30
+ TabsWrapper,
31
+ TabContentWrapper,
32
+ Text,
33
+ } from '@/plugins/generative-ui/ui'
16
34
 
17
35
  interface GenerativeUIProps {
18
36
  /** The JSON content to render - can be a json-render tree or raw object */
@@ -25,357 +43,39 @@ interface GenerativeUIProps {
25
43
  * Built-in components for rendering json-render trees.
26
44
  * These provide a default set of UI primitives for tool results.
27
45
  */
28
- const components: Record<string, FC<Record<string, unknown>>> = {
29
- Card: ({ title, children, className }) => {
30
- const r = useRadius()
31
- const d = useDensity()
32
- const titleStr = title != null ? String(title) : null
33
- return (
34
- <div
35
- className={cn(
36
- 'border-border bg-card border',
37
- r('lg'),
38
- d('p-lg'),
39
- className as string
40
- )}
41
- >
42
- {titleStr && <h3 className="mb-4 text-lg font-semibold">{titleStr}</h3>}
43
- {children as React.ReactNode}
44
- </div>
45
- )
46
- },
47
-
48
- Metric: ({ label, value, format, className }) => {
49
- const formattedValue = useMemo(() => {
50
- const numValue = Number(value)
51
- if (isNaN(numValue)) return String(value)
52
-
53
- switch (format) {
54
- case 'currency':
55
- return new Intl.NumberFormat('en-US', {
56
- style: 'currency',
57
- currency: 'USD',
58
- }).format(numValue)
59
- case 'percent':
60
- return new Intl.NumberFormat('en-US', {
61
- style: 'percent',
62
- minimumFractionDigits: 1,
63
- }).format(numValue)
64
- case 'number':
65
- default:
66
- return new Intl.NumberFormat('en-US').format(numValue)
67
- }
68
- }, [value, format])
69
-
70
- return (
71
- <div className={cn('flex flex-col gap-2', className as string)}>
72
- <span className="text-muted-foreground text-sm">{String(label)}</span>
73
- <span className="text-3xl font-bold">{formattedValue}</span>
74
- </div>
75
- )
76
- },
77
-
78
- Grid: ({ columns = 2, children, className }) => {
79
- const d = useDensity()
80
- return (
81
- <div
82
- className={cn('grid', d('gap-lg'), className as string)}
83
- style={{
84
- gridTemplateColumns: `repeat(${columns as number}, minmax(0, 1fr))`,
85
- }}
86
- >
87
- {children as React.ReactNode}
88
- </div>
89
- )
90
- },
91
-
92
- Stack: ({ direction = 'vertical', children, className }) => {
93
- const d = useDensity()
94
- return (
95
- <div
96
- className={cn(
97
- 'flex',
98
- direction === 'horizontal' ? 'flex-row' : 'flex-col',
99
- d('gap-md'),
100
- className as string
101
- )}
102
- >
103
- {children as React.ReactNode}
104
- </div>
105
- )
106
- },
107
-
108
- Text: ({ children, content, variant = 'body', className }) => {
109
- const variantClasses: Record<string, string> = {
110
- heading: 'text-lg font-semibold',
111
- body: 'text-sm',
112
- caption: 'text-xs text-muted-foreground',
113
- code: 'font-mono text-sm bg-muted px-1 rounded',
114
- }
115
- // Support both content prop (for direct text) and children (for nested components)
116
- const textContent = content != null ? String(content) : null
117
- return (
118
- <span
119
- className={cn(variantClasses[variant as string], className as string)}
120
- >
121
- {textContent ?? (children as React.ReactNode)}
122
- </span>
123
- )
124
- },
125
-
126
- Badge: ({ children, content, variant = 'default', className }) => {
127
- const r = useRadius()
128
- const variantClasses: Record<string, string> = {
129
- default: 'bg-primary text-primary-foreground',
130
- secondary: 'bg-secondary text-secondary-foreground',
131
- success:
132
- 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100',
133
- warning:
134
- 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100',
135
- error: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100',
136
- }
137
- // Support both content prop (for direct text) and children (for nested components)
138
- const textContent = content != null ? String(content) : null
139
- return (
140
- <span
141
- className={cn(
142
- 'inline-flex items-center px-2 py-0.5 text-xs font-medium',
143
- r('sm'),
144
- variantClasses[variant as string] ?? variantClasses.default,
145
- className as string
146
- )}
147
- >
148
- {textContent ?? (children as React.ReactNode)}
149
- </span>
150
- )
151
- },
152
-
153
- Table: ({ headers, rows, className }) => {
154
- const r = useRadius()
155
- const headerArray = Array.isArray(headers) ? (headers as string[]) : []
156
- const rowsArray = Array.isArray(rows) ? (rows as unknown[][]) : []
157
- return (
158
- <div className={cn('overflow-auto', className as string)}>
159
- <table className={cn('w-full border-collapse text-sm', r('lg'))}>
160
- {headerArray.length > 0 && (
161
- <thead>
162
- <tr className="border-border border-b">
163
- {headerArray.map((header, i) => (
164
- <th
165
- key={i}
166
- className="text-muted-foreground px-4 py-3 text-left font-medium"
167
- >
168
- {header}
169
- </th>
170
- ))}
171
- </tr>
172
- </thead>
173
- )}
174
- <tbody>
175
- {rowsArray.map((row, i) => (
176
- <tr key={i} className="border-border border-b last:border-0">
177
- {row.map((cell, j) => (
178
- <td key={j} className="px-4 py-3">
179
- {String(cell)}
180
- </td>
181
- ))}
182
- </tr>
183
- ))}
184
- </tbody>
185
- </table>
186
- </div>
187
- )
188
- },
189
-
190
- List: ({ items, ordered = false, className }) => {
191
- const Tag = ordered ? 'ol' : 'ul'
192
- const itemsArray = Array.isArray(items) ? (items as string[]) : []
193
- return (
194
- <Tag
195
- className={cn(
196
- 'list-inside space-y-2 text-sm',
197
- ordered ? 'list-decimal' : 'list-disc',
198
- className as string
199
- )}
200
- >
201
- {itemsArray.map((item, i) => (
202
- <li key={i}>{item}</li>
203
- ))}
204
- </Tag>
205
- )
206
- },
207
-
208
- Divider: ({ className }) => (
209
- <hr className={cn('border-border my-4', className as string)} />
210
- ),
211
-
212
- Progress: ({ value, max = 100, label, className }) => {
213
- const r = useRadius()
214
- const numValue = Number(value)
215
- const numMax = Number(max)
216
- const percentage =
217
- isNaN(numValue) || isNaN(numMax) || numMax === 0
218
- ? 0
219
- : Math.min(100, Math.max(0, (numValue / numMax) * 100))
220
- const labelStr = label != null ? String(label) : null
221
- return (
222
- <div className={cn('w-full space-y-2', className as string)}>
223
- {labelStr && (
224
- <div className="flex justify-between text-sm">
225
- <span>{labelStr}</span>
226
- <span className="text-muted-foreground">
227
- {percentage.toFixed(0)}%
228
- </span>
229
- </div>
230
- )}
231
- <div className={cn('bg-muted h-3 overflow-hidden', r('sm'))}>
232
- <div
233
- className={cn('bg-primary h-full transition-all', r('sm'))}
234
- style={{ width: `${percentage}%` }}
235
- />
236
- </div>
237
- </div>
238
- )
239
- },
240
-
241
- ActionButton: ({ label, action, args, variant = 'default', className }) => {
242
- const r = useRadius()
243
- const { executeTool, isToolAvailable } = useToolExecution()
244
- const runtime = useThreadRuntime({ optional: true })
245
- const [isLoading, setIsLoading] = useState(false)
246
- const [result, setResult] = useState<{
247
- success: boolean
248
- message?: string
249
- } | null>(null)
250
-
251
- const toolAvailable = action ? isToolAvailable(action as string) : false
252
-
253
- const handleClick = useCallback(async () => {
254
- if (!action) return
255
-
256
- setIsLoading(true)
257
- setResult(null)
258
-
259
- try {
260
- const toolResult = await executeTool(
261
- action as string,
262
- (args as Record<string, unknown>) ?? {}
263
- )
264
-
265
- if (toolResult.success) {
266
- // Format the result message
267
- let message = 'Done'
268
- if (toolResult.result) {
269
- if (typeof toolResult.result === 'string') {
270
- message = toolResult.result
271
- } else if (
272
- typeof toolResult.result === 'object' &&
273
- toolResult.result !== null &&
274
- 'content' in toolResult.result
275
- ) {
276
- // Handle MCP tool result format
277
- const content = (
278
- toolResult.result as { content: Array<{ text?: string }> }
279
- ).content
280
- if (Array.isArray(content) && content[0]?.text) {
281
- message = content[0].text
282
- }
283
- }
284
- }
285
- setResult({ success: true, message })
286
-
287
- // Notify the LLM of the action result so it can respond
288
- if (runtime) {
289
- await runtime.append({
290
- role: 'user',
291
- content: [
292
- {
293
- type: 'text',
294
- text: `[Action completed] ${action}: ${message}`,
295
- },
296
- ],
297
- })
298
- }
299
- } else {
300
- setResult({ success: false, message: toolResult.error })
301
-
302
- // Also notify on failure so LLM can help
303
- if (runtime) {
304
- await runtime.append({
305
- role: 'user',
306
- content: [
307
- {
308
- type: 'text',
309
- text: `[Action failed] ${action}: ${toolResult.error}`,
310
- },
311
- ],
312
- })
313
- }
314
- }
315
- } catch (err) {
316
- const errorMessage =
317
- err instanceof Error ? err.message : 'Unknown error'
318
- setResult({
319
- success: false,
320
- message: errorMessage,
321
- })
322
- } finally {
323
- setIsLoading(false)
324
- }
325
- }, [action, args, executeTool, runtime])
326
-
327
- const variantClasses: Record<string, string> = {
328
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
329
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
330
- outline:
331
- 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
332
- destructive:
333
- 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
334
- }
335
-
336
- // Show result state if we have one
337
- if (result) {
338
- return (
339
- <div
340
- className={cn(
341
- 'inline-flex items-center gap-2 px-4 py-2 text-sm',
342
- r('md'),
343
- result.success
344
- ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100'
345
- : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100',
346
- className as string
347
- )}
348
- >
349
- {result.success ? (
350
- <CheckCircleIcon className="size-4" />
351
- ) : (
352
- <XCircleIcon className="size-4" />
353
- )}
354
- <span className="max-w-[200px] truncate">
355
- {result.message ?? (result.success ? 'Success' : 'Failed')}
356
- </span>
357
- </div>
358
- )
359
- }
360
-
361
- return (
362
- <button
363
- onClick={handleClick}
364
- disabled={isLoading || !toolAvailable}
365
- title={!toolAvailable ? `Tool "${action}" not available` : undefined}
366
- className={cn(
367
- 'inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium transition-colors',
368
- 'disabled:pointer-events-none disabled:opacity-50',
369
- r('md'),
370
- variantClasses[variant as string] ?? variantClasses.default,
371
- className as string
372
- )}
373
- >
374
- {isLoading && <Loader2Icon className="size-4 animate-spin" />}
375
- {String(label)}
376
- </button>
377
- )
378
- },
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ const components: Record<string, FC<any>> = {
48
+ // Layout
49
+ Card: CardWrapper,
50
+ Grid,
51
+ Stack,
52
+ // Typography
53
+ Text,
54
+ // Data Display
55
+ Metric,
56
+ Badge,
57
+ Table: DataTable,
58
+ List,
59
+ Progress,
60
+ Avatar: AvatarWrapper,
61
+ Skeleton: SkeletonWrapper,
62
+ // Feedback
63
+ Alert: AlertWrapper,
64
+ // Structure
65
+ Divider: Separator,
66
+ Separator,
67
+ // Interactive
68
+ Accordion: AccordionWrapper,
69
+ AccordionItem: AccordionItemWrapper,
70
+ Tabs: TabsWrapper,
71
+ TabContent: TabContentWrapper,
72
+ // Actions
73
+ Button: ButtonWrapper,
74
+ ActionButton,
75
+ // Form Elements
76
+ Input: InputWrapper,
77
+ Checkbox: CheckboxWrapper,
78
+ Select: SelectWrapper,
379
79
  }
380
80
 
381
81
  /**
@@ -7,14 +7,17 @@ import { ToolUI } from './tool-ui'
7
7
  // However, to use this design variant, we'd have to add lots of metadata to the tool parts
8
8
 
9
9
  const meta: Meta<typeof ToolUI> = {
10
+ title: 'Components/Tool UI',
10
11
  component: ToolUI,
11
12
  parameters: {
12
- layout: 'centered',
13
+ layout: 'fullscreen',
13
14
  },
14
15
  decorators: [
15
16
  (Story) => (
16
- <div className="w-[400px] p-4">
17
- <Story />
17
+ <div className="bg-background text-foreground flex min-h-screen items-start justify-center p-6">
18
+ <div className="w-[400px]">
19
+ <Story />
20
+ </div>
18
21
  </div>
19
22
  ),
20
23
  ],
@@ -47,6 +47,20 @@ type ContentItem =
47
47
  | { type: 'text'; text: string; _meta?: { 'getgram.ai/mime-type'?: string } }
48
48
  | { type: 'image'; data: string; _meta?: { 'getgram.ai/mime-type'?: string } }
49
49
 
50
+ /** MCP tool annotations providing hints about tool behavior */
51
+ interface ToolAnnotations {
52
+ /** Human-readable display name for the tool */
53
+ title?: string
54
+ /** If true, the tool does not modify its environment */
55
+ readOnlyHint?: boolean
56
+ /** If true, the tool may perform destructive updates */
57
+ destructiveHint?: boolean
58
+ /** If true, repeated calls with same args have no additional effect */
59
+ idempotentHint?: boolean
60
+ /** If true, tool interacts with external entities */
61
+ openWorldHint?: boolean
62
+ }
63
+
50
64
  interface ToolUIProps {
51
65
  /** Display name of the tool */
52
66
  name: string
@@ -64,6 +78,8 @@ interface ToolUIProps {
64
78
  defaultExpanded?: boolean
65
79
  /** Additional class names */
66
80
  className?: string
81
+ /** MCP tool annotations */
82
+ annotations?: ToolAnnotations
67
83
  /** Approval callbacks */
68
84
  onApproveOnce?: () => void
69
85
  onApproveForSession?: () => void
@@ -410,10 +426,13 @@ function ToolUI({
410
426
  result,
411
427
  defaultExpanded = false,
412
428
  className,
429
+ annotations,
413
430
  onApproveOnce,
414
431
  onApproveForSession,
415
432
  onDeny,
416
433
  }: ToolUIProps) {
434
+ // Use annotation title if available, otherwise fall back to name
435
+ const displayName = annotations?.title || name
417
436
  const isApprovalPending =
418
437
  status === 'approval' && onApproveOnce !== undefined && onDeny !== undefined
419
438
  // Auto-expand when approval is pending, collapse when approved
@@ -486,7 +505,7 @@ function ToolUI({
486
505
  !provider && isApprovalPending && 'shimmer'
487
506
  )}
488
507
  >
489
- {name}
508
+ {displayName}
490
509
  </span>
491
510
  {hasContent && (
492
511
  <ChevronDownIcon
@@ -528,10 +547,20 @@ function ToolUI({
528
547
  data-slot="tool-ui-approval-actions"
529
548
  className="border-border flex flex-col gap-2 border-t px-4 py-3 @[320px]:flex-row @[320px]:items-center @[320px]:justify-end"
530
549
  >
531
- <div className="@[320px]:mr-auto">
550
+ <div className="flex items-center gap-2 @[320px]:mr-auto">
532
551
  <span className="text-muted-foreground text-sm">
533
552
  This tool requires approval
534
553
  </span>
554
+ {annotations?.readOnlyHint && (
555
+ <span className="bg-muted text-muted-foreground rounded px-1.5 py-0.5 text-xs">
556
+ Read-only
557
+ </span>
558
+ )}
559
+ {annotations?.destructiveHint && !annotations?.readOnlyHint && (
560
+ <span className="rounded bg-amber-500/10 px-1.5 py-0.5 text-xs text-amber-600 dark:text-amber-400">
561
+ Destructive
562
+ </span>
563
+ )}
535
564
  </div>
536
565
  <div className="flex items-center gap-2 self-end">
537
566
  <Button
@@ -1,3 +1,4 @@
1
+ import { useReplayContext } from '@/contexts/ReplayContext'
1
2
  import { hasExplicitSessionAuth, isStaticSessionAuth } from '@/lib/auth'
2
3
  import { useMemo } from 'react'
3
4
  import { ApiConfig } from '../types'
@@ -40,14 +41,21 @@ export const useAuth = ({
40
41
  auth?: ApiConfig
41
42
  projectSlug: string
42
43
  }): Auth => {
44
+ const replayCtx = useReplayContext()
45
+ const isReplay = replayCtx?.isReplay ?? false
46
+
43
47
  const getSession = useMemo(() => {
48
+ // In replay mode, skip session fetching entirely
49
+ if (isReplay) {
50
+ return null
51
+ }
44
52
  if (isStaticSessionAuth(auth)) {
45
53
  return () => Promise.resolve(auth.sessionToken)
46
54
  }
47
55
  return !isStaticSessionAuth(auth) && hasExplicitSessionAuth(auth)
48
56
  ? auth.sessionFn
49
57
  : defaultGetSession
50
- }, [auth])
58
+ }, [auth, isReplay])
51
59
 
52
60
  // The session request is only neccessary if we are not using static session auth
53
61
  // configuration. If a custom session fetcher is provided, we use it,
@@ -57,6 +65,14 @@ export const useAuth = ({
57
65
  projectSlug,
58
66
  })
59
67
 
68
+ // In replay mode, return immediately without waiting for session
69
+ if (isReplay) {
70
+ return {
71
+ headers: {},
72
+ isLoading: false,
73
+ }
74
+ }
75
+
60
76
  return !session
61
77
  ? {
62
78
  isLoading: true,
@@ -1,3 +1,4 @@
1
+ import { useReplayContext } from '@/contexts/ReplayContext'
1
2
  import { useAssistantState } from '@assistant-ui/react'
2
3
  import { useCallback, useEffect, useRef, useState } from 'react'
3
4
  import { useElements } from './useElements'
@@ -37,13 +38,17 @@ export function useFollowOnSuggestions(): {
37
38
  isLoading: boolean
38
39
  } {
39
40
  const { config } = useElements()
41
+ const replayCtx = useReplayContext()
42
+ const isReplay = replayCtx?.isReplay ?? false
43
+
40
44
  const auth = useAuth({
41
45
  auth: config.api,
42
46
  projectSlug: config.projectSlug,
43
47
  })
44
48
 
45
49
  // Check if follow-up suggestions are enabled (default: true)
46
- const isEnabled = config.thread?.followUpSuggestions !== false
50
+ // Disable in replay mode since we don't need AI-generated suggestions
51
+ const isEnabled = !isReplay && config.thread?.followUpSuggestions !== false
47
52
 
48
53
  const [suggestions, setSuggestions] = useState<FollowOnSuggestion[]>([])
49
54
  const [isLoading, setIsLoading] = useState(false)