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