@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
@@ -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
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { ChevronDownIcon } from 'lucide-react'
5
+ import { Accordion as AccordionPrimitive } from 'radix-ui'
6
+
7
+ import { cn } from '@/lib/utils'
8
+
9
+ function Accordion({
10
+ ...props
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
12
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />
13
+ }
14
+
15
+ function AccordionItem({
16
+ className,
17
+ ...props
18
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
19
+ return (
20
+ <AccordionPrimitive.Item
21
+ data-slot="accordion-item"
22
+ className={cn('border-b last:border-b-0', className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ function AccordionTrigger({
29
+ className,
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
33
+ return (
34
+ <AccordionPrimitive.Header className="flex">
35
+ <AccordionPrimitive.Trigger
36
+ data-slot="accordion-trigger"
37
+ className={cn(
38
+ 'text-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
39
+ className
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
45
+ </AccordionPrimitive.Trigger>
46
+ </AccordionPrimitive.Header>
47
+ )
48
+ }
49
+
50
+ function AccordionContent({
51
+ className,
52
+ children,
53
+ ...props
54
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
55
+ return (
56
+ <AccordionPrimitive.Content
57
+ data-slot="accordion-content"
58
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
59
+ {...props}
60
+ >
61
+ <div className={cn('pt-0 pb-4', className)}>{children}</div>
62
+ </AccordionPrimitive.Content>
63
+ )
64
+ }
65
+
66
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,68 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Button, buttonVariants } from './button'
5
+ import type { VariantProps } from 'class-variance-authority'
6
+ import { cn } from '@/lib/utils'
7
+ import { useToolExecution } from '@/contexts/ToolExecutionContext'
8
+
9
+ export interface ActionButtonProps
10
+ extends
11
+ Omit<React.ComponentProps<'button'>, 'onClick'>,
12
+ VariantProps<typeof buttonVariants> {
13
+ label: string
14
+ /** Tool name to invoke when clicked (matches LLM prompt) */
15
+ action: string
16
+ args?: Record<string, unknown>
17
+ }
18
+
19
+ export function ActionButton({
20
+ label,
21
+ action,
22
+ args,
23
+ variant = 'default',
24
+ size = 'default',
25
+ className,
26
+ disabled,
27
+ ...props
28
+ }: ActionButtonProps) {
29
+ const { executeTool, isToolAvailable } = useToolExecution()
30
+ const [isLoading, setIsLoading] = React.useState(false)
31
+
32
+ const toolAvailable = isToolAvailable(action)
33
+
34
+ const [error, setError] = React.useState<string | null>(null)
35
+
36
+ const handleClick = React.useCallback(async () => {
37
+ if (!toolAvailable || isLoading) return
38
+
39
+ setIsLoading(true)
40
+ setError(null)
41
+ try {
42
+ const result = await executeTool(action, args ?? {})
43
+ if (!result.success && result.error) {
44
+ setError(result.error)
45
+ }
46
+ } catch (err) {
47
+ setError(err instanceof Error ? err.message : 'Unknown error')
48
+ } finally {
49
+ setIsLoading(false)
50
+ }
51
+ }, [action, args, executeTool, isLoading, toolAvailable])
52
+
53
+ return (
54
+ <div className="inline-flex flex-col gap-1">
55
+ <Button
56
+ variant={error ? 'destructive' : variant}
57
+ size={size}
58
+ className={cn(className)}
59
+ onClick={handleClick}
60
+ disabled={disabled || isLoading || !toolAvailable}
61
+ {...props}
62
+ >
63
+ {isLoading ? 'Loading...' : label}
64
+ </Button>
65
+ {error && <span className="text-destructive text-xs">{error}</span>}
66
+ </div>
67
+ )
68
+ }
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Alert, AlertTitle, AlertDescription } from './alert'
5
+
6
+ export interface AlertWrapperProps {
7
+ title: string
8
+ description?: string
9
+ variant?: 'default' | 'destructive'
10
+ }
11
+
12
+ /**
13
+ * Alert wrapper that takes title and description as props.
14
+ */
15
+ export function AlertWrapper({
16
+ title,
17
+ description,
18
+ variant = 'default',
19
+ }: AlertWrapperProps) {
20
+ return (
21
+ <Alert variant={variant}>
22
+ <AlertTitle>{title}</AlertTitle>
23
+ {description && <AlertDescription>{description}</AlertDescription>}
24
+ </Alert>
25
+ )
26
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from 'react'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const alertVariants = cva(
7
+ 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-card text-card-foreground',
12
+ destructive:
13
+ 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: 'default',
18
+ },
19
+ }
20
+ )
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ 'col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight',
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<'div'>) {
54
+ return (
55
+ <div
56
+ data-slot="alert-description"
57
+ className={cn(
58
+ 'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,22 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Avatar, AvatarImage, AvatarFallback } from './avatar'
5
+
6
+ export interface AvatarWrapperProps {
7
+ src?: string
8
+ alt?: string
9
+ fallback: string
10
+ }
11
+
12
+ /**
13
+ * Avatar wrapper that takes src, alt, and fallback as props.
14
+ */
15
+ export function AvatarWrapper({ src, alt, fallback }: AvatarWrapperProps) {
16
+ return (
17
+ <Avatar>
18
+ {src && <AvatarImage src={src} alt={alt} />}
19
+ <AvatarFallback>{fallback}</AvatarFallback>
20
+ </Avatar>
21
+ )
22
+ }
@@ -0,0 +1,109 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Avatar as AvatarPrimitive } from 'radix-ui'
5
+
6
+ import { cn } from '@/lib/utils'
7
+
8
+ function Avatar({
9
+ className,
10
+ size = 'default',
11
+ ...props
12
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
13
+ size?: 'default' | 'sm' | 'lg'
14
+ }) {
15
+ return (
16
+ <AvatarPrimitive.Root
17
+ data-slot="avatar"
18
+ data-size={size}
19
+ className={cn(
20
+ 'group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6',
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ function AvatarImage({
29
+ className,
30
+ ...props
31
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
32
+ return (
33
+ <AvatarPrimitive.Image
34
+ data-slot="avatar-image"
35
+ className={cn('aspect-square size-full', className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function AvatarFallback({
42
+ className,
43
+ ...props
44
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
45
+ return (
46
+ <AvatarPrimitive.Fallback
47
+ data-slot="avatar-fallback"
48
+ className={cn(
49
+ 'bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs',
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {
58
+ return (
59
+ <span
60
+ data-slot="avatar-badge"
61
+ className={cn(
62
+ 'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none',
63
+ 'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
64
+ 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
65
+ 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
74
+ return (
75
+ <div
76
+ data-slot="avatar-group"
77
+ className={cn(
78
+ '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ )
84
+ }
85
+
86
+ function AvatarGroupCount({
87
+ className,
88
+ ...props
89
+ }: React.ComponentProps<'div'>) {
90
+ return (
91
+ <div
92
+ data-slot="avatar-group-count"
93
+ className={cn(
94
+ 'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',
95
+ className
96
+ )}
97
+ {...props}
98
+ />
99
+ )
100
+ }
101
+
102
+ export {
103
+ Avatar,
104
+ AvatarImage,
105
+ AvatarFallback,
106
+ AvatarBadge,
107
+ AvatarGroup,
108
+ AvatarGroupCount,
109
+ }
@@ -0,0 +1,65 @@
1
+ import * as React from 'react'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+ import { Slot } from 'radix-ui'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const badgeVariants = cva(
8
+ 'inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
13
+ secondary:
14
+ 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
15
+ /** Matches LLM prompt variant */
16
+ success:
17
+ 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100',
18
+ /** Matches LLM prompt variant */
19
+ warning:
20
+ 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100',
21
+ /** Matches LLM prompt variant */
22
+ error: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100',
23
+ destructive:
24
+ 'bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
25
+ outline:
26
+ 'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ },
32
+ }
33
+ )
34
+
35
+ interface BadgeProps
36
+ extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {
37
+ asChild?: boolean
38
+ /** Content text (matches LLM prompt) - rendered as children */
39
+ content?: string
40
+ }
41
+
42
+ function Badge({
43
+ className,
44
+ variant = 'default',
45
+ asChild = false,
46
+ content,
47
+ children,
48
+ ...props
49
+ }: BadgeProps) {
50
+ const Comp = asChild ? Slot.Root : 'span'
51
+
52
+ return (
53
+ <Comp
54
+ data-slot="badge"
55
+ data-variant={variant}
56
+ className={cn(badgeVariants({ variant }), className)}
57
+ {...props}
58
+ >
59
+ {content ?? children}
60
+ </Comp>
61
+ )
62
+ }
63
+
64
+ // eslint-disable-next-line react-refresh/only-export-components
65
+ export { Badge, badgeVariants }
@@ -0,0 +1,32 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Button } from './button'
5
+
6
+ export interface ButtonWrapperProps {
7
+ label: string
8
+ variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost'
9
+ size?: 'default' | 'sm' | 'lg' | 'icon'
10
+ disabled?: boolean
11
+ /** Backend action to trigger (future use) */
12
+ action?: string
13
+ /** Parameters for the action (future use) */
14
+ actionParams?: Record<string, unknown>
15
+ }
16
+
17
+ /**
18
+ * Button wrapper that takes label as a prop.
19
+ * The action/actionParams props are for future backend integration.
20
+ */
21
+ export function ButtonWrapper({
22
+ label,
23
+ variant = 'default',
24
+ size = 'default',
25
+ disabled = false,
26
+ }: ButtonWrapperProps) {
27
+ return (
28
+ <Button variant={variant} size={size} disabled={disabled}>
29
+ {label}
30
+ </Button>
31
+ )
32
+ }