@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
@@ -0,0 +1,277 @@
1
+ import { createCatalog } from '@json-render/core'
2
+ import { z } from 'zod'
3
+
4
+ /**
5
+ * Generative UI Catalog
6
+ *
7
+ * Defines all available components for LLM-generated UI.
8
+ * Components map to shadcn/ui primitives in the ui/ directory.
9
+ */
10
+ export const catalog = createCatalog({
11
+ name: 'generative-ui',
12
+ components: {
13
+ // Layout
14
+ Stack: {
15
+ props: z.object({
16
+ direction: z.enum(['horizontal', 'vertical']).optional(),
17
+ gap: z.enum(['sm', 'md', 'lg']).optional(),
18
+ align: z.enum(['start', 'center', 'end', 'stretch']).optional(),
19
+ justify: z
20
+ .enum(['start', 'center', 'end', 'between', 'around'])
21
+ .optional(),
22
+ className: z.string().optional(),
23
+ }),
24
+ hasChildren: true,
25
+ description: 'Flex layout container for arranging child elements',
26
+ },
27
+
28
+ Grid: {
29
+ props: z.object({
30
+ columns: z.number().optional(),
31
+ gap: z.enum(['sm', 'md', 'lg']).optional(),
32
+ className: z.string().optional(),
33
+ }),
34
+ hasChildren: true,
35
+ description: 'Grid layout for arranging items in columns',
36
+ },
37
+
38
+ Card: {
39
+ props: z.object({
40
+ title: z.string().optional(),
41
+ className: z.string().optional(),
42
+ }),
43
+ hasChildren: true,
44
+ description: 'Container with optional title, border and padding',
45
+ },
46
+
47
+ // Typography
48
+ Text: {
49
+ props: z.object({
50
+ content: z.string(),
51
+ variant: z.enum(['heading', 'body', 'caption', 'code']).optional(),
52
+ className: z.string().optional(),
53
+ }),
54
+ description: 'Text content with styling variants',
55
+ },
56
+
57
+ // Data Display
58
+ Metric: {
59
+ props: z.object({
60
+ label: z.string(),
61
+ value: z.union([z.string(), z.number()]),
62
+ format: z.enum(['number', 'currency', 'percent']).optional(),
63
+ className: z.string().optional(),
64
+ }),
65
+ description:
66
+ 'Display a key metric with label and formatted value (e.g., revenue, users)',
67
+ },
68
+
69
+ Badge: {
70
+ props: z.object({
71
+ content: z.string().optional(),
72
+ variant: z
73
+ .enum([
74
+ 'default',
75
+ 'secondary',
76
+ 'destructive',
77
+ 'outline',
78
+ 'success',
79
+ 'warning',
80
+ ])
81
+ .optional(),
82
+ className: z.string().optional(),
83
+ }),
84
+ hasChildren: true,
85
+ description: 'Status badge or tag for categorization',
86
+ },
87
+
88
+ Progress: {
89
+ props: z.object({
90
+ value: z.number(),
91
+ max: z.number().optional(),
92
+ className: z.string().optional(),
93
+ }),
94
+ description: 'Progress bar showing completion percentage',
95
+ },
96
+
97
+ Table: {
98
+ props: z.object({
99
+ headers: z.array(z.string()).optional(),
100
+ rows: z.array(z.array(z.union([z.string(), z.number()]))),
101
+ className: z.string().optional(),
102
+ }),
103
+ description: 'Data table with headers and rows',
104
+ },
105
+
106
+ List: {
107
+ props: z.object({
108
+ items: z.array(z.string()),
109
+ ordered: z.boolean().optional(),
110
+ className: z.string().optional(),
111
+ }),
112
+ description: 'Ordered or unordered list of items',
113
+ },
114
+
115
+ // Feedback
116
+ Alert: {
117
+ props: z.object({
118
+ title: z.string(),
119
+ description: z.string().optional(),
120
+ variant: z.enum(['default', 'destructive']).optional(),
121
+ }),
122
+ description: 'Alert message for important information or errors',
123
+ },
124
+
125
+ // Structure
126
+ Separator: {
127
+ props: z.object({
128
+ orientation: z.enum(['horizontal', 'vertical']).optional(),
129
+ className: z.string().optional(),
130
+ }),
131
+ description: 'Visual divider between content sections',
132
+ },
133
+
134
+ Divider: {
135
+ props: z.object({
136
+ orientation: z.enum(['horizontal', 'vertical']).optional(),
137
+ className: z.string().optional(),
138
+ }),
139
+ description:
140
+ 'Visual divider between content sections (alias for Separator)',
141
+ },
142
+
143
+ // Interactive
144
+ Accordion: {
145
+ props: z.object({
146
+ type: z.enum(['single', 'multiple']).optional(),
147
+ }),
148
+ hasChildren: true,
149
+ description: 'Collapsible accordion container',
150
+ },
151
+
152
+ AccordionItem: {
153
+ props: z.object({
154
+ value: z.string(),
155
+ title: z.string(),
156
+ }),
157
+ hasChildren: true,
158
+ description: 'Individual accordion item with trigger and content',
159
+ },
160
+
161
+ Tabs: {
162
+ props: z.object({
163
+ defaultValue: z.string().optional(),
164
+ tabs: z.array(
165
+ z.object({
166
+ value: z.string(),
167
+ label: z.string(),
168
+ })
169
+ ),
170
+ }),
171
+ hasChildren: true,
172
+ description: 'Tabbed content container',
173
+ },
174
+
175
+ TabContent: {
176
+ props: z.object({
177
+ value: z.string(),
178
+ }),
179
+ hasChildren: true,
180
+ description: 'Content panel for a specific tab',
181
+ },
182
+
183
+ // Actions
184
+ Button: {
185
+ props: z.object({
186
+ label: z.string(),
187
+ variant: z
188
+ .enum(['default', 'secondary', 'destructive', 'outline', 'ghost'])
189
+ .optional(),
190
+ size: z.enum(['default', 'sm', 'lg', 'icon']).optional(),
191
+ disabled: z.boolean().optional(),
192
+ action: z.string().optional(),
193
+ actionParams: z.record(z.string(), z.unknown()).optional(),
194
+ }),
195
+ description:
196
+ 'Clickable button that can trigger actions. Use action/actionParams to call backend functions.',
197
+ },
198
+
199
+ ActionButton: {
200
+ props: z.object({
201
+ label: z.string(),
202
+ action: z.string(),
203
+ args: z.record(z.string(), z.unknown()).optional(),
204
+ variant: z
205
+ .enum(['default', 'secondary', 'destructive', 'outline', 'ghost'])
206
+ .optional(),
207
+ size: z.enum(['default', 'sm', 'lg', 'icon']).optional(),
208
+ className: z.string().optional(),
209
+ }),
210
+ description:
211
+ 'Button that triggers a frontend tool call directly without LLM roundtrip',
212
+ },
213
+
214
+ // Form Elements
215
+ Input: {
216
+ props: z.object({
217
+ label: z.string().optional(),
218
+ placeholder: z.string().optional(),
219
+ type: z.enum(['text', 'email', 'password', 'number', 'tel']).optional(),
220
+ valuePath: z.string(),
221
+ }),
222
+ description: 'Text input field with optional label',
223
+ },
224
+
225
+ Checkbox: {
226
+ props: z.object({
227
+ label: z.string().optional(),
228
+ valuePath: z.string(),
229
+ defaultChecked: z.boolean().optional(),
230
+ }),
231
+ description: 'Checkbox input with label',
232
+ },
233
+
234
+ Select: {
235
+ props: z.object({
236
+ placeholder: z.string().optional(),
237
+ valuePath: z.string(),
238
+ options: z.array(
239
+ z.object({
240
+ value: z.string(),
241
+ label: z.string(),
242
+ })
243
+ ),
244
+ }),
245
+ description: 'Dropdown select input',
246
+ },
247
+
248
+ // Display
249
+ Avatar: {
250
+ props: z.object({
251
+ src: z.string().optional(),
252
+ alt: z.string().optional(),
253
+ fallback: z.string(),
254
+ }),
255
+ description: 'User avatar with image and fallback initials',
256
+ },
257
+
258
+ Skeleton: {
259
+ props: z.object({
260
+ width: z.string().optional(),
261
+ height: z.string().optional(),
262
+ className: z.string().optional(),
263
+ }),
264
+ description: 'Loading placeholder skeleton',
265
+ },
266
+ },
267
+ })
268
+
269
+ export type CatalogComponentProps = typeof catalog extends {
270
+ components: infer C
271
+ }
272
+ ? {
273
+ [K in keyof C]: C[K] extends { props: infer P }
274
+ ? z.infer<P extends z.ZodType ? P : never>
275
+ : never
276
+ }
277
+ : never
@@ -1,23 +1,109 @@
1
1
  'use client'
2
2
 
3
3
  import { GenerativeUI } from '@/components/ui/generative-ui'
4
+ import { cn } from '@/lib/utils'
4
5
  import { SyntaxHighlighterProps } from '@assistant-ui/react-markdown'
5
- import { FC, useMemo } from 'react'
6
- import { PluginLoadingState } from '../components/PluginLoadingState'
6
+ import { FC, useEffect, useMemo, useState } from 'react'
7
+ import { MacOSWindowFrame } from '../components/MacOSWindowFrame'
7
8
 
8
9
  const loadingMessages = [
9
- 'Preparing your data...',
10
- 'Building your view...',
11
- 'Generating results...',
12
- 'Loading content...',
13
- 'Fetching information...',
14
- 'Processing your request...',
15
- 'Almost ready...',
16
- 'Setting things up...',
10
+ // Crafting & Creating
11
+ 'Arranging pixels with care...',
12
+ 'Brewing something beautiful...',
13
+ 'Crafting your masterpiece...',
14
+ 'Painting with data...',
15
+ 'Weaving digital magic...',
16
+ 'Assembling the good stuff...',
17
+ 'Polishing the details...',
18
+ 'Putting the finishing touches...',
19
+ // Cooking & Food
20
+ 'Simmering the results...',
21
+ 'Letting the data marinate...',
22
+ 'Adding a pinch of style...',
23
+ 'Fresh out of the oven soon...',
24
+ 'Whisking up your view...',
25
+ // Nature & Growth
26
+ 'Growing your garden of data...',
27
+ 'Watching the seeds sprout...',
28
+ 'Letting things bloom...',
29
+ 'Nature is taking its course...',
30
+ // Space & Magic
31
+ 'Consulting the stars...',
32
+ 'Channeling cosmic energy...',
33
+ 'Summoning the results...',
34
+ 'Waving the magic wand...',
35
+ 'Sprinkling some stardust...',
36
+ 'Aligning the planets...',
37
+ // Building & Engineering
38
+ 'Tightening the bolts...',
39
+ 'Connecting the dots...',
40
+ 'Stacking the blocks...',
41
+ 'Laying the foundation...',
42
+ 'Raising the scaffolding...',
43
+ // Playful & Cute
44
+ 'Herding the pixels...',
45
+ 'Teaching data to dance...',
46
+ 'Convincing bits to cooperate...',
47
+ 'Giving electrons a pep talk...',
48
+ 'Wrangling the numbers...',
49
+ 'Coaxing the results out...',
50
+ 'Almost there, pinky promise...',
51
+ 'Good things take a moment...',
52
+ 'Worth the wait...',
53
+ 'Patience, grasshopper...',
54
+ 'Hold tight...',
55
+ 'Doing the thing...',
56
+ // Abstract & Poetic
57
+ 'Folding space and time...',
58
+ 'Untangling the threads...',
59
+ 'Finding the signal...',
60
+ 'Distilling the essence...',
61
+ 'Turning chaos into order...',
62
+ 'Making sense of it all...',
63
+ // Confident & Reassuring
64
+ 'This is going to be good...',
65
+ "You're gonna love this...",
66
+ 'Something nice is coming...',
67
+ 'Just a heartbeat away...',
17
68
  ]
18
69
 
19
- function getRandomLoadingMessage() {
20
- return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]
70
+ function getRandomStartIndex() {
71
+ return Math.floor(Math.random() * loadingMessages.length)
72
+ }
73
+
74
+ const CyclingLoadingMessage: FC = () => {
75
+ const [index, setIndex] = useState(getRandomStartIndex)
76
+ const [isVisible, setIsVisible] = useState(true)
77
+
78
+ useEffect(() => {
79
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
80
+ const interval = setInterval(() => {
81
+ // Fade out
82
+ setIsVisible(false)
83
+
84
+ // After fade out, change message and fade in
85
+ timeoutId = setTimeout(() => {
86
+ setIndex((prev) => (prev + 1) % loadingMessages.length)
87
+ setIsVisible(true)
88
+ }, 200)
89
+ }, 2000)
90
+
91
+ return () => {
92
+ clearInterval(interval)
93
+ if (timeoutId) clearTimeout(timeoutId)
94
+ }
95
+ }, [])
96
+
97
+ return (
98
+ <span
99
+ className={cn(
100
+ 'shimmer text-muted-foreground text-sm transition-opacity duration-200',
101
+ isVisible ? 'opacity-100' : 'opacity-0'
102
+ )}
103
+ >
104
+ {loadingMessages[index]}
105
+ </span>
106
+ )
21
107
  }
22
108
 
23
109
  export const GenerativeUIRenderer: FC<SyntaxHighlighterProps> = ({ code }) => {
@@ -39,18 +125,23 @@ export const GenerativeUIRenderer: FC<SyntaxHighlighterProps> = ({ code }) => {
39
125
  }
40
126
  }, [code])
41
127
 
42
- // Memoize the loading message so it doesn't change on every render
43
- const loadingMessage = useMemo(() => getRandomLoadingMessage(), [])
44
-
45
- // Show loading shimmer while JSON is incomplete/streaming
128
+ // Show loading state while JSON is incomplete/streaming
46
129
  if (!content) {
47
- return <PluginLoadingState text={loadingMessage} />
130
+ return (
131
+ <MacOSWindowFrame>
132
+ <div className="bg-background flex min-h-[400px] items-center justify-center">
133
+ <CyclingLoadingMessage />
134
+ </div>
135
+ </MacOSWindowFrame>
136
+ )
48
137
  }
49
138
 
50
- // Render without outer border - the Card component inside provides the border
139
+ // Render with macOS-style window frame
51
140
  return (
52
- <div className="overflow-hidden after:hidden">
53
- <GenerativeUI content={content} />
54
- </div>
141
+ <MacOSWindowFrame>
142
+ <div className="p-4">
143
+ <GenerativeUI content={content} />
144
+ </div>
145
+ </MacOSWindowFrame>
55
146
  )
56
147
  }
@@ -7,147 +7,26 @@ import { GenerativeUIRenderer } from './component'
7
7
  */
8
8
  export const generativeUI: Plugin = {
9
9
  language: 'ui',
10
- prompt: `WHEN TO USE UI VISUALIZATION:
11
- Proactively render tool results and structured data as visual UI components whenever it would improve comprehension. Use the 'ui' code block format for:
12
- - Tool results that return structured data (lists, objects, metrics)
13
- - Data that benefits from visual hierarchy (dashboards, summaries)
14
- - Information with multiple related fields (user profiles, order details)
15
- - Anything with numbers, statuses, or progress that can be visualized
16
- - Results that would otherwise be displayed as raw JSON or verbose text
17
-
18
- Do NOT use UI for: simple text responses, single values, error messages, or when the user explicitly asks for raw data.
19
-
20
- To render UI, output a json-render specification in a code block with the language identifier 'ui'.
21
-
22
- CRITICAL JSON REQUIREMENTS:
23
- - The code block MUST contain ONLY valid, parseable JSON
24
- - NO comments (no // or /* */ anywhere)
25
- - NO trailing commas
26
- - Use double quotes for all strings and keys
27
- - The JSON must start with { and end with }
28
-
29
- AVAILABLE COMPONENTS:
30
-
31
- Card - Container with optional title
32
- props: { title?: string }
33
- children: any components
34
-
35
- Grid - Multi-column layout
36
- props: { columns?: number } (default: 2)
37
- children: any components
38
-
39
- Stack - Vertical or horizontal flex layout
40
- props: { direction?: "vertical" | "horizontal" } (default: "vertical")
41
- children: any components
42
-
43
- Metric - Displays a label and formatted value
44
- props: { label: string, value: number, format?: "currency" | "percent" | "number" }
45
-
46
- Table - Data table
47
- props: { headers: string[], rows: any[][] }
48
-
49
- Text - Text with variants
50
- props: { content: string, variant?: "heading" | "body" | "caption" | "code" }
51
-
52
- Badge - Status badge
53
- props: { content: string, variant?: "default" | "success" | "warning" | "error" }
54
-
55
- Progress - Progress bar
56
- props: { value: number, max?: number, label?: string }
57
-
58
- List - Bullet or numbered list
59
- props: { items: string[], ordered?: boolean }
60
-
61
- Divider - Horizontal line separator
62
-
63
- ActionButton - Interactive button that triggers a tool call
64
- props: {
65
- label: string, // Button text
66
- action: string, // Tool name to invoke when clicked
67
- args?: object, // Arguments to pass to the tool
68
- variant?: "default" | "secondary" | "outline" | "destructive"
69
- }
70
- NOTE: Only use ActionButton with tools you know are available
71
-
72
- STRUCTURE:
73
- Every UI spec is a tree with:
74
- - "type": component name (required)
75
- - "props": component properties (optional)
76
- - "children": array of child components (optional)
77
-
78
- EXAMPLE - DASHBOARD:
79
- {
80
- "type": "Card",
81
- "props": { "title": "Sales Overview" },
82
- "children": [
83
- {
84
- "type": "Grid",
85
- "props": { "columns": 3 },
86
- "children": [
87
- { "type": "Metric", "props": { "label": "Revenue", "value": 125000, "format": "currency" } },
88
- { "type": "Metric", "props": { "label": "Growth", "value": 0.15, "format": "percent" } },
89
- { "type": "Metric", "props": { "label": "Orders", "value": 1420, "format": "number" } }
90
- ]
91
- },
92
- { "type": "Divider" },
93
- { "type": "Progress", "props": { "label": "Q1 Target", "value": 75, "max": 100 } }
94
- ]
95
- }
96
-
97
- EXAMPLE - TABLE:
98
- {
99
- "type": "Card",
100
- "props": { "title": "Users" },
101
- "children": [
102
- {
103
- "type": "Table",
104
- "props": {
105
- "headers": ["Name", "Email", "Status"],
106
- "rows": [
107
- ["Alice", "alice@example.com", "Active"],
108
- ["Bob", "bob@example.com", "Pending"]
109
- ]
110
- }
111
- }
112
- ]
113
- }
114
-
115
- EXAMPLE - WITH ACTIONS:
116
- {
117
- "type": "Card",
118
- "props": { "title": "Pending Request #123" },
119
- "children": [
120
- { "type": "Text", "props": { "variant": "body" }, "children": [{ "type": "Text", "props": {}, "children": [] }] },
121
- {
122
- "type": "Stack",
123
- "props": { "direction": "horizontal" },
124
- "children": [
125
- { "type": "ActionButton", "props": { "label": "Approve", "action": "approve_request", "args": { "id": 123 } } },
126
- { "type": "ActionButton", "props": { "label": "Reject", "action": "reject_request", "args": { "id": 123 }, "variant": "destructive" } }
127
- ]
128
- }
129
- ]
130
- }
131
-
132
- STYLE GUIDELINES:
133
- - Prefer spacious, breathable layouts with adequate visual hierarchy
134
- - Use Grid with 2-3 columns max for metrics; avoid cramming too many items
135
- - Group related content in Cards with clear titles
136
- - Use Dividers to separate logical sections
137
- - Balance information density: show what matters, hide the noise
138
- - For dashboards, lead with the most important metrics at the top
139
-
140
- CONTENT GUIDELINES:
141
- - Outside the code block, provide context and insights about the data
142
- - Do not describe technical implementation details
143
- - Focus on what the data means, not how it's displayed
144
-
145
- ACTION RESULT HANDLING:
146
- When you receive a message starting with "[Action completed]" or "[Action failed]", the user clicked an action button and the tool has already been executed. Provide a brief, friendly acknowledgment of what happened. Keep your response concise - one sentence is usually enough. Do not re-execute the action or ask if they want to do something they just did.
147
-
148
- Examples:
149
- - "[Action completed] approve_request: Request #123 approved" → "Done! Request #123 has been approved."
150
- - "[Action failed] delete_task: Permission denied" → "I couldn't delete that task - looks like you don't have permission."`,
10
+ prompt: `Render structured data as visual UI using \`\`\`ui code blocks with valid JSON.
11
+
12
+ Components:
13
+ - Card{title?} - container with border
14
+ - Grid{columns?} - multi-column layout
15
+ - Stack{direction?} - vertical/horizontal flex
16
+ - Metric{label,value,format?} - formatted number display (currency/percent/number)
17
+ - Table{headers[],rows[][]} - data table
18
+ - Text{content,variant?} - heading/body/caption/code
19
+ - Badge{content,variant?} - default/success/warning/error
20
+ - Progress{value,max?,label?} - progress bar
21
+ - List{items[],ordered?} - bullet/numbered list
22
+ - Divider - horizontal separator
23
+ - ActionButton{label,action,args?} - triggers tool call
24
+
25
+ Format: {"type":"Name","props":{...},"children":[...]}
26
+
27
+ Example: {"type":"Card","props":{"title":"Stats"},"children":[{"type":"Metric","props":{"label":"Revenue","value":50000,"format":"currency"}}]}
28
+
29
+ Use for dashboards, tables, metrics from tool results. Skip for simple text or errors.`,
151
30
  Component: GenerativeUIRenderer,
152
31
  Header: undefined,
153
32
  }
@@ -0,0 +1,57 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ Accordion as AccordionPrimitive,
6
+ AccordionItem as AccordionItemPrimitive,
7
+ AccordionTrigger,
8
+ AccordionContent,
9
+ } from './accordion'
10
+
11
+ export interface AccordionWrapperProps {
12
+ type?: 'single' | 'multiple'
13
+ children?: React.ReactNode
14
+ }
15
+
16
+ /**
17
+ * Accordion wrapper that adapts the compound Accordion to the catalog's props-based API.
18
+ */
19
+ export function AccordionWrapper({
20
+ type = 'single',
21
+ children,
22
+ }: AccordionWrapperProps) {
23
+ // Type assertion needed because Radix types are complex
24
+ const AccordionRoot = AccordionPrimitive as React.FC<{
25
+ type: 'single' | 'multiple'
26
+ collapsible?: boolean
27
+ children?: React.ReactNode
28
+ }>
29
+
30
+ return (
31
+ <AccordionRoot type={type} collapsible={type === 'single'}>
32
+ {children}
33
+ </AccordionRoot>
34
+ )
35
+ }
36
+
37
+ export interface AccordionItemWrapperProps {
38
+ value: string
39
+ title: string
40
+ children?: React.ReactNode
41
+ }
42
+
43
+ /**
44
+ * AccordionItem wrapper that takes title as a prop and renders trigger + content.
45
+ */
46
+ export function AccordionItemWrapper({
47
+ value,
48
+ title,
49
+ children,
50
+ }: AccordionItemWrapperProps) {
51
+ return (
52
+ <AccordionItemPrimitive value={value}>
53
+ <AccordionTrigger>{title}</AccordionTrigger>
54
+ <AccordionContent>{children}</AccordionContent>
55
+ </AccordionItemPrimitive>
56
+ )
57
+ }