@gram-ai/elements 1.25.2 → 1.26.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 (160) 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/elements.cjs +1 -1
  8. package/dist/elements.css +1 -1
  9. package/dist/elements.js +6 -6
  10. package/dist/index-BJnv49-A.js +37057 -0
  11. package/dist/index-BJnv49-A.js.map +1 -0
  12. package/dist/index-BpJstUh1.cjs +280 -0
  13. package/dist/index-BpJstUh1.cjs.map +1 -0
  14. package/dist/index-CUitXazZ.js +30426 -0
  15. package/dist/index-CUitXazZ.js.map +1 -0
  16. package/dist/index-ChW-CSuu.cjs +147 -0
  17. package/dist/index-ChW-CSuu.cjs.map +1 -0
  18. package/dist/plugins/chart/catalog.d.ts +123 -0
  19. package/dist/plugins/chart/index.d.ts +1 -1
  20. package/dist/plugins/chart/ui/area-chart.d.ts +16 -0
  21. package/dist/plugins/chart/ui/bar-chart.d.ts +16 -0
  22. package/dist/plugins/chart/ui/donut-chart.d.ts +17 -0
  23. package/dist/plugins/chart/ui/index.d.ts +7 -0
  24. package/dist/plugins/chart/ui/line-chart.d.ts +17 -0
  25. package/dist/plugins/chart/ui/pie-chart.d.ts +15 -0
  26. package/dist/plugins/chart/ui/radar-chart.d.ts +14 -0
  27. package/dist/plugins/chart/ui/scatter-chart.d.ts +18 -0
  28. package/dist/plugins/components/MacOSWindowFrame.d.ts +13 -0
  29. package/dist/plugins/components/PluginLoadingState.d.ts +1 -1
  30. package/dist/plugins/generative-ui/catalog.d.ts +293 -0
  31. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +18 -0
  32. package/dist/plugins/generative-ui/ui/accordion.d.ts +7 -0
  33. package/dist/plugins/generative-ui/ui/action-button.d.ts +10 -0
  34. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +9 -0
  35. package/dist/plugins/generative-ui/ui/alert.d.ts +9 -0
  36. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +9 -0
  37. package/dist/plugins/generative-ui/ui/avatar.d.ts +11 -0
  38. package/dist/plugins/generative-ui/ui/badge.d.ts +12 -0
  39. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +15 -0
  40. package/dist/plugins/generative-ui/ui/button.d.ts +10 -0
  41. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +10 -0
  42. package/dist/plugins/generative-ui/ui/card.d.ts +9 -0
  43. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +10 -0
  44. package/dist/plugins/generative-ui/ui/checkbox.d.ts +4 -0
  45. package/dist/plugins/generative-ui/ui/data-table.d.ts +10 -0
  46. package/dist/plugins/generative-ui/ui/dialog.d.ts +17 -0
  47. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +25 -0
  48. package/dist/plugins/generative-ui/ui/grid.d.ts +6 -0
  49. package/dist/plugins/generative-ui/ui/index.d.ts +40 -0
  50. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +11 -0
  51. package/dist/plugins/generative-ui/ui/input.d.ts +3 -0
  52. package/dist/plugins/generative-ui/ui/label.d.ts +4 -0
  53. package/dist/plugins/generative-ui/ui/list.d.ts +6 -0
  54. package/dist/plugins/generative-ui/ui/metric.d.ts +7 -0
  55. package/dist/plugins/generative-ui/ui/pagination.d.ts +13 -0
  56. package/dist/plugins/generative-ui/ui/popover.d.ts +10 -0
  57. package/dist/plugins/generative-ui/ui/progress.d.ts +10 -0
  58. package/dist/plugins/generative-ui/ui/radio-group.d.ts +5 -0
  59. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +13 -0
  60. package/dist/plugins/generative-ui/ui/select.d.ts +15 -0
  61. package/dist/plugins/generative-ui/ui/separator.d.ts +4 -0
  62. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +9 -0
  63. package/dist/plugins/generative-ui/ui/skeleton.d.ts +2 -0
  64. package/dist/plugins/generative-ui/ui/stack.d.ts +8 -0
  65. package/dist/plugins/generative-ui/ui/switch.d.ts +6 -0
  66. package/dist/plugins/generative-ui/ui/table.d.ts +10 -0
  67. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +21 -0
  68. package/dist/plugins/generative-ui/ui/tabs.d.ts +11 -0
  69. package/dist/plugins/generative-ui/ui/text.d.ts +7 -0
  70. package/dist/plugins/generative-ui/ui/textarea.d.ts +3 -0
  71. package/dist/plugins/generative-ui/ui/tooltip.d.ts +7 -0
  72. package/dist/plugins.cjs +1 -1
  73. package/dist/plugins.js +1 -1
  74. package/dist/{profiler-BaG0scxd.js → profiler-D4Tw5ecI.js} +2 -2
  75. package/dist/{profiler-BaG0scxd.js.map → profiler-D4Tw5ecI.js.map} +1 -1
  76. package/dist/{profiler-CuqENACf.cjs → profiler-DCWYDZ1F.cjs} +2 -2
  77. package/dist/{profiler-CuqENACf.cjs.map → profiler-DCWYDZ1F.cjs.map} +1 -1
  78. package/dist/{startRecording-BiLmoqZa.cjs → startRecording-3sTskM3H.cjs} +2 -2
  79. package/dist/{startRecording-BiLmoqZa.cjs.map → startRecording-3sTskM3H.cjs.map} +1 -1
  80. package/dist/{startRecording-86bHmd-l.js → startRecording-BHhcCWQE.js} +2 -2
  81. package/dist/{startRecording-86bHmd-l.js.map → startRecording-BHhcCWQE.js.map} +1 -1
  82. package/package.json +4 -1
  83. package/src/components/Chat/stories/Charts.stories.tsx +260 -0
  84. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -6
  85. package/src/components/Chat/stories/GenerativeUI.stories.tsx +113 -0
  86. package/src/components/Replay.stories.tsx +1 -1
  87. package/src/components/Replay.tsx +18 -13
  88. package/src/components/ui/charts.stories.tsx +246 -0
  89. package/src/components/ui/generative-ui.stories.tsx +557 -0
  90. package/src/components/ui/generative-ui.tsx +60 -360
  91. package/src/components/ui/tool-ui.stories.tsx +6 -3
  92. package/src/hooks/useAuth.ts +17 -1
  93. package/src/hooks/useFollowOnSuggestions.ts +6 -1
  94. package/src/plugins/chart/catalog.ts +141 -0
  95. package/src/plugins/chart/component.tsx +79 -125
  96. package/src/plugins/chart/index.ts +141 -89
  97. package/src/plugins/chart/ui/area-chart.tsx +133 -0
  98. package/src/plugins/chart/ui/bar-chart.tsx +137 -0
  99. package/src/plugins/chart/ui/donut-chart.tsx +167 -0
  100. package/src/plugins/chart/ui/index.ts +7 -0
  101. package/src/plugins/chart/ui/line-chart.tsx +135 -0
  102. package/src/plugins/chart/ui/pie-chart.tsx +148 -0
  103. package/src/plugins/chart/ui/radar-chart.tsx +105 -0
  104. package/src/plugins/chart/ui/scatter-chart.tsx +132 -0
  105. package/src/plugins/components/MacOSWindowFrame.tsx +55 -0
  106. package/src/plugins/components/PluginLoadingState.tsx +9 -13
  107. package/src/plugins/generative-ui/catalog.ts +277 -0
  108. package/src/plugins/generative-ui/component.tsx +112 -21
  109. package/src/plugins/generative-ui/index.ts +20 -141
  110. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +57 -0
  111. package/src/plugins/generative-ui/ui/accordion.tsx +66 -0
  112. package/src/plugins/generative-ui/ui/action-button.tsx +68 -0
  113. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +26 -0
  114. package/src/plugins/generative-ui/ui/alert.tsx +66 -0
  115. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +22 -0
  116. package/src/plugins/generative-ui/ui/avatar.tsx +109 -0
  117. package/src/plugins/generative-ui/ui/badge.tsx +65 -0
  118. package/src/plugins/generative-ui/ui/button-wrapper.tsx +32 -0
  119. package/src/plugins/generative-ui/ui/button.tsx +65 -0
  120. package/src/plugins/generative-ui/ui/card-wrapper.tsx +36 -0
  121. package/src/plugins/generative-ui/ui/card.tsx +92 -0
  122. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +39 -0
  123. package/src/plugins/generative-ui/ui/checkbox.tsx +32 -0
  124. package/src/plugins/generative-ui/ui/data-table.tsx +53 -0
  125. package/src/plugins/generative-ui/ui/dialog.tsx +158 -0
  126. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +257 -0
  127. package/src/plugins/generative-ui/ui/grid.tsx +29 -0
  128. package/src/plugins/generative-ui/ui/index.ts +43 -0
  129. package/src/plugins/generative-ui/ui/input-wrapper.tsx +38 -0
  130. package/src/plugins/generative-ui/ui/input.tsx +21 -0
  131. package/src/plugins/generative-ui/ui/label.tsx +24 -0
  132. package/src/plugins/generative-ui/ui/list.tsx +34 -0
  133. package/src/plugins/generative-ui/ui/metric.tsx +53 -0
  134. package/src/plugins/generative-ui/ui/pagination.tsx +127 -0
  135. package/src/plugins/generative-ui/ui/popover.tsx +89 -0
  136. package/src/plugins/generative-ui/ui/progress.tsx +57 -0
  137. package/src/plugins/generative-ui/ui/radio-group.tsx +45 -0
  138. package/src/plugins/generative-ui/ui/select-wrapper.tsx +41 -0
  139. package/src/plugins/generative-ui/ui/select.tsx +190 -0
  140. package/src/plugins/generative-ui/ui/separator.tsx +28 -0
  141. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +30 -0
  142. package/src/plugins/generative-ui/ui/skeleton.tsx +13 -0
  143. package/src/plugins/generative-ui/ui/stack.tsx +54 -0
  144. package/src/plugins/generative-ui/ui/switch.tsx +35 -0
  145. package/src/plugins/generative-ui/ui/table.tsx +116 -0
  146. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +51 -0
  147. package/src/plugins/generative-ui/ui/tabs.tsx +92 -0
  148. package/src/plugins/generative-ui/ui/text.tsx +33 -0
  149. package/src/plugins/generative-ui/ui/textarea.tsx +18 -0
  150. package/src/plugins/generative-ui/ui/tooltip.tsx +57 -0
  151. package/dist/components/Chat/stories/Plugins.stories.d.ts +0 -12
  152. package/dist/index-B8nSCdu4.cjs +0 -251
  153. package/dist/index-B8nSCdu4.cjs.map +0 -1
  154. package/dist/index-CAtaLV1E.cjs +0 -187
  155. package/dist/index-CAtaLV1E.cjs.map +0 -1
  156. package/dist/index-CJrwma08.js +0 -27232
  157. package/dist/index-CJrwma08.js.map +0 -1
  158. package/dist/index-DLWQ91ow.js +0 -40049
  159. package/dist/index-DLWQ91ow.js.map +0 -1
  160. 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
  ],
@@ -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)
@@ -0,0 +1,141 @@
1
+ import { createCatalog } from '@json-render/core'
2
+ import { z } from 'zod'
3
+
4
+ /**
5
+ * Data point schema - common structure for all chart types
6
+ */
7
+ const dataPointSchema = z.object({
8
+ label: z.string(),
9
+ value: z.number(),
10
+ color: z.string().optional(),
11
+ })
12
+
13
+ /**
14
+ * Multi-series data point for line/area charts
15
+ */
16
+ const seriesDataPointSchema = z
17
+ .object({
18
+ label: z.string(),
19
+ })
20
+ .catchall(z.number())
21
+
22
+ /**
23
+ * Chart Catalog
24
+ *
25
+ * Defines all available chart components for LLM-generated visualizations.
26
+ * Uses Recharts under the hood for rendering.
27
+ */
28
+ export const chartCatalog = createCatalog({
29
+ name: 'chart',
30
+ components: {
31
+ BarChart: {
32
+ props: z.object({
33
+ title: z.string().optional(),
34
+ data: z.array(dataPointSchema),
35
+ layout: z.enum(['vertical', 'horizontal']).optional(),
36
+ showGrid: z.boolean().optional(),
37
+ showLegend: z.boolean().optional(),
38
+ className: z.string().optional(),
39
+ }),
40
+ description:
41
+ 'Bar chart for comparing categorical data. Use vertical for few categories, horizontal for many or long labels.',
42
+ },
43
+
44
+ LineChart: {
45
+ props: z.object({
46
+ title: z.string().optional(),
47
+ data: z.array(seriesDataPointSchema),
48
+ series: z.array(z.string()).optional(),
49
+ showGrid: z.boolean().optional(),
50
+ showLegend: z.boolean().optional(),
51
+ showDots: z.boolean().optional(),
52
+ curved: z.boolean().optional(),
53
+ className: z.string().optional(),
54
+ }),
55
+ description:
56
+ 'Line chart for showing trends over time or continuous data. Supports multiple series.',
57
+ },
58
+
59
+ AreaChart: {
60
+ props: z.object({
61
+ title: z.string().optional(),
62
+ data: z.array(seriesDataPointSchema),
63
+ series: z.array(z.string()).optional(),
64
+ stacked: z.boolean().optional(),
65
+ showGrid: z.boolean().optional(),
66
+ showLegend: z.boolean().optional(),
67
+ className: z.string().optional(),
68
+ }),
69
+ description:
70
+ 'Area chart for showing volume/magnitude over time. Use stacked for part-to-whole relationships.',
71
+ },
72
+
73
+ PieChart: {
74
+ props: z.object({
75
+ title: z.string().optional(),
76
+ data: z.array(dataPointSchema),
77
+ showLabels: z.boolean().optional(),
78
+ showLegend: z.boolean().optional(),
79
+ className: z.string().optional(),
80
+ }),
81
+ description:
82
+ 'Pie chart for showing proportions of a whole. Best for 2-6 categories.',
83
+ },
84
+
85
+ DonutChart: {
86
+ props: z.object({
87
+ title: z.string().optional(),
88
+ data: z.array(dataPointSchema),
89
+ showLabels: z.boolean().optional(),
90
+ showLegend: z.boolean().optional(),
91
+ innerLabel: z.string().optional(),
92
+ innerValue: z.union([z.string(), z.number()]).optional(),
93
+ className: z.string().optional(),
94
+ }),
95
+ description:
96
+ 'Donut chart (pie with center hole). Good for showing a key metric in the center.',
97
+ },
98
+
99
+ ScatterChart: {
100
+ props: z.object({
101
+ title: z.string().optional(),
102
+ data: z.array(
103
+ z.object({
104
+ x: z.number(),
105
+ y: z.number(),
106
+ label: z.string().optional(),
107
+ size: z.number().optional(),
108
+ color: z.string().optional(),
109
+ })
110
+ ),
111
+ xLabel: z.string().optional(),
112
+ yLabel: z.string().optional(),
113
+ showGrid: z.boolean().optional(),
114
+ className: z.string().optional(),
115
+ }),
116
+ description:
117
+ 'Scatter plot for showing correlation between two variables.',
118
+ },
119
+
120
+ RadarChart: {
121
+ props: z.object({
122
+ title: z.string().optional(),
123
+ data: z.array(dataPointSchema),
124
+ showLegend: z.boolean().optional(),
125
+ className: z.string().optional(),
126
+ }),
127
+ description:
128
+ 'Radar/spider chart for comparing multiple attributes. Best for 3-8 dimensions.',
129
+ },
130
+ },
131
+ })
132
+
133
+ export type ChartCatalogComponentProps = typeof chartCatalog extends {
134
+ components: infer C
135
+ }
136
+ ? {
137
+ [K in keyof C]: C[K] extends { props: infer P }
138
+ ? z.infer<P extends z.ZodType ? P : never>
139
+ : never
140
+ }
141
+ : never