@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
@@ -0,0 +1,132 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { FC } from 'react'
5
+ import {
6
+ ScatterChart as RechartsScatterChart,
7
+ Scatter,
8
+ XAxis,
9
+ YAxis,
10
+ CartesianGrid,
11
+ Tooltip,
12
+ ResponsiveContainer,
13
+ ZAxis,
14
+ Cell,
15
+ TooltipProps,
16
+ } from 'recharts'
17
+
18
+ interface ScatterDataPoint {
19
+ x: number
20
+ y: number
21
+ label?: string
22
+ size?: number
23
+ color?: string
24
+ }
25
+
26
+ const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
27
+ if (!active || !payload || payload.length === 0) return null
28
+ const point = payload[0]?.payload as ScatterDataPoint | undefined
29
+ return (
30
+ <div className="bg-background text-foreground border-border rounded-md border px-2 py-1.5 text-xs shadow-sm">
31
+ {point?.label && <div className="font-medium">{point.label}</div>}
32
+ <div>x: {point?.x?.toLocaleString()}</div>
33
+ <div>y: {point?.y?.toLocaleString()}</div>
34
+ </div>
35
+ )
36
+ }
37
+
38
+ const COLORS = [
39
+ 'var(--chart-1)',
40
+ 'var(--chart-2)',
41
+ 'var(--chart-3)',
42
+ 'var(--chart-4)',
43
+ 'var(--chart-5)',
44
+ ]
45
+
46
+ export interface ScatterChartProps {
47
+ title?: string
48
+ data: ScatterDataPoint[]
49
+ xLabel?: string
50
+ yLabel?: string
51
+ showGrid?: boolean
52
+ className?: string
53
+ }
54
+
55
+ export const ScatterChart: FC<ScatterChartProps> = ({
56
+ title,
57
+ data,
58
+ xLabel,
59
+ yLabel,
60
+ showGrid = true,
61
+ className,
62
+ }) => {
63
+ // Check if we have size data for bubble chart effect
64
+ const hasSizeData = data.some((d) => d.size !== undefined)
65
+
66
+ return (
67
+ <div className={cn('flex flex-col gap-2', className)}>
68
+ {title && (
69
+ <h3 className="text-foreground text-sm font-medium">{title}</h3>
70
+ )}
71
+ <div className="h-[250px] w-full">
72
+ <ResponsiveContainer width="100%" height="100%">
73
+ <RechartsScatterChart
74
+ margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
75
+ >
76
+ {showGrid && (
77
+ <CartesianGrid
78
+ strokeDasharray="3 3"
79
+ className="stroke-muted/30"
80
+ />
81
+ )}
82
+ <XAxis
83
+ type="number"
84
+ dataKey="x"
85
+ name={xLabel || 'x'}
86
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
87
+ axisLine={{ stroke: 'var(--border)' }}
88
+ tickLine={{ stroke: 'var(--border)' }}
89
+ label={
90
+ xLabel
91
+ ? {
92
+ value: xLabel,
93
+ position: 'bottom',
94
+ offset: -5,
95
+ fill: 'var(--foreground)',
96
+ }
97
+ : undefined
98
+ }
99
+ />
100
+ <YAxis
101
+ type="number"
102
+ dataKey="y"
103
+ name={yLabel || 'y'}
104
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
105
+ axisLine={{ stroke: 'var(--border)' }}
106
+ tickLine={{ stroke: 'var(--border)' }}
107
+ label={
108
+ yLabel
109
+ ? {
110
+ value: yLabel,
111
+ angle: -90,
112
+ position: 'left',
113
+ fill: 'var(--foreground)',
114
+ }
115
+ : undefined
116
+ }
117
+ />
118
+ {hasSizeData && (
119
+ <ZAxis type="number" dataKey="size" range={[50, 400]} />
120
+ )}
121
+ <Tooltip content={<CustomTooltip />} />
122
+ <Scatter data={data} fill={COLORS[0]} isAnimationActive={false}>
123
+ {data.map((entry, index) => (
124
+ <Cell key={`cell-${index}`} fill={entry.color || COLORS[0]} />
125
+ ))}
126
+ </Scatter>
127
+ </RechartsScatterChart>
128
+ </ResponsiveContainer>
129
+ </div>
130
+ </div>
131
+ )
132
+ }
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+
3
+ import { useRadius } from '@/hooks/useRadius'
4
+ import { cn } from '@/lib/utils'
5
+ import { FC, ReactNode } from 'react'
6
+
7
+ interface MacOSWindowFrameProps {
8
+ children: ReactNode
9
+ className?: string
10
+ /** Optional title to display in the title bar */
11
+ title?: string
12
+ }
13
+
14
+ /**
15
+ * A macOS-style window frame with traffic light buttons.
16
+ * Wraps content in a bordered container with a title bar.
17
+ */
18
+ export const MacOSWindowFrame: FC<MacOSWindowFrameProps> = ({
19
+ children,
20
+ className,
21
+ title,
22
+ }) => {
23
+ const r = useRadius()
24
+
25
+ return (
26
+ <div className="@container my-4 w-full first:mt-0">
27
+ <div
28
+ className={cn(
29
+ // after:hidden prevents assistant-ui from showing its default code block loading indicator
30
+ 'border-border w-full overflow-hidden border after:hidden @sm:max-w-md @md:max-w-lg @lg:max-w-xl @xl:max-w-2xl',
31
+ r('lg'),
32
+ className
33
+ )}
34
+ >
35
+ {/* Title bar */}
36
+ <div className="border-border bg-muted/50 flex h-8 items-center gap-2 border-b px-3">
37
+ {/* Traffic lights */}
38
+ <div className="flex items-center gap-1.5">
39
+ <div className="size-3 rounded-full bg-[#FF5F57]" />
40
+ <div className="size-3 rounded-full bg-[#FEBC2E]" />
41
+ <div className="size-3 rounded-full bg-[#28C840]" />
42
+ </div>
43
+ {/* Title */}
44
+ {title && (
45
+ <span className="text-muted-foreground flex-1 text-center text-xs font-medium">
46
+ {title}
47
+ </span>
48
+ )}
49
+ </div>
50
+ {/* Content */}
51
+ {children}
52
+ </div>
53
+ </div>
54
+ )
55
+ }
@@ -1,8 +1,8 @@
1
1
  'use client'
2
2
 
3
- import { useRadius } from '@/hooks/useRadius'
4
3
  import { cn } from '@/lib/utils'
5
4
  import { FC } from 'react'
5
+ import { MacOSWindowFrame } from './MacOSWindowFrame'
6
6
 
7
7
  interface PluginLoadingStateProps {
8
8
  text: string
@@ -11,25 +11,21 @@ interface PluginLoadingStateProps {
11
11
 
12
12
  /**
13
13
  * Shared loading state component for plugins.
14
- * Displays a shimmer effect with loading text.
14
+ * Displays a shimmer effect with loading text inside a macOS-style window.
15
15
  */
16
16
  export const PluginLoadingState: FC<PluginLoadingStateProps> = ({
17
17
  text,
18
18
  className,
19
19
  }) => {
20
- const r = useRadius()
21
-
22
20
  return (
23
- <div
24
- className={cn(
25
- 'border-border bg-card relative min-h-[400px] w-fit max-w-full min-w-[400px] overflow-hidden border after:hidden',
26
- r('lg'),
27
- className
28
- )}
29
- >
30
- <div className="bg-muted absolute inset-0 flex items-center justify-center">
21
+ <MacOSWindowFrame className={className}>
22
+ <div
23
+ className={cn(
24
+ 'bg-background relative flex min-h-[400px] items-center justify-center'
25
+ )}
26
+ >
31
27
  <span className="shimmer text-muted-foreground text-sm">{text}</span>
32
28
  </div>
33
- </div>
29
+ </MacOSWindowFrame>
34
30
  )
35
31
  }
@@ -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
  }