@gram-ai/elements 1.18.5 → 1.18.6

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 (85) hide show
  1. package/dist/components/Chat/stories/Sidecar.stories.d.ts +6 -0
  2. package/dist/elements.cjs +22 -21
  3. package/dist/elements.cjs.map +1 -0
  4. package/dist/elements.js +601 -591
  5. package/dist/elements.js.map +1 -0
  6. package/dist/index-Bj7jPiuy.cjs +1 -0
  7. package/dist/index-Bj7jPiuy.cjs.map +1 -0
  8. package/dist/index-CJRypLIa.js +1 -0
  9. package/dist/index-CJRypLIa.js.map +1 -0
  10. package/dist/plugins.cjs +1 -0
  11. package/dist/plugins.cjs.map +1 -0
  12. package/dist/plugins.js +1 -0
  13. package/dist/plugins.js.map +1 -0
  14. package/dist/server.cjs +1 -0
  15. package/dist/server.cjs.map +1 -0
  16. package/dist/server.js +1 -0
  17. package/dist/server.js.map +1 -0
  18. package/package.json +3 -2
  19. package/src/components/Chat/index.tsx +21 -0
  20. package/src/components/Chat/stories/ColorScheme.stories.tsx +52 -0
  21. package/src/components/Chat/stories/Composer.stories.tsx +42 -0
  22. package/src/components/Chat/stories/Customization.stories.tsx +88 -0
  23. package/src/components/Chat/stories/Density.stories.tsx +52 -0
  24. package/src/components/Chat/stories/FrontendTools.stories.tsx +145 -0
  25. package/src/components/Chat/stories/Modal.stories.tsx +84 -0
  26. package/src/components/Chat/stories/Model.stories.tsx +32 -0
  27. package/src/components/Chat/stories/Plugins.stories.tsx +50 -0
  28. package/src/components/Chat/stories/Radius.stories.tsx +52 -0
  29. package/src/components/Chat/stories/Sidecar.stories.tsx +27 -0
  30. package/src/components/Chat/stories/ToolApproval.stories.tsx +110 -0
  31. package/src/components/Chat/stories/Tools.stories.tsx +175 -0
  32. package/src/components/Chat/stories/Variants.stories.tsx +46 -0
  33. package/src/components/Chat/stories/Welcome.stories.tsx +42 -0
  34. package/src/components/FrontendTools/index.tsx +9 -0
  35. package/src/components/assistant-ui/assistant-modal.tsx +255 -0
  36. package/src/components/assistant-ui/assistant-sidecar.tsx +88 -0
  37. package/src/components/assistant-ui/attachment.tsx +233 -0
  38. package/src/components/assistant-ui/markdown-text.tsx +240 -0
  39. package/src/components/assistant-ui/reasoning.tsx +261 -0
  40. package/src/components/assistant-ui/thread-list.tsx +97 -0
  41. package/src/components/assistant-ui/thread.tsx +632 -0
  42. package/src/components/assistant-ui/tool-fallback.tsx +111 -0
  43. package/src/components/assistant-ui/tool-group.tsx +59 -0
  44. package/src/components/assistant-ui/tooltip-icon-button.tsx +57 -0
  45. package/src/components/ui/avatar.tsx +51 -0
  46. package/src/components/ui/button.tsx +27 -0
  47. package/src/components/ui/buttonVariants.ts +33 -0
  48. package/src/components/ui/collapsible.tsx +31 -0
  49. package/src/components/ui/dialog.tsx +141 -0
  50. package/src/components/ui/popover.tsx +46 -0
  51. package/src/components/ui/skeleton.tsx +13 -0
  52. package/src/components/ui/tool-ui.stories.tsx +146 -0
  53. package/src/components/ui/tool-ui.tsx +676 -0
  54. package/src/components/ui/tooltip.tsx +61 -0
  55. package/src/contexts/ElementsProvider.tsx +256 -0
  56. package/src/contexts/ToolApprovalContext.tsx +120 -0
  57. package/src/contexts/contexts.ts +10 -0
  58. package/src/global.css +136 -0
  59. package/src/hooks/useAuth.ts +71 -0
  60. package/src/hooks/useDensity.ts +110 -0
  61. package/src/hooks/useElements.ts +14 -0
  62. package/src/hooks/useExpanded.ts +20 -0
  63. package/src/hooks/useMCPTools.ts +73 -0
  64. package/src/hooks/usePluginComponents.ts +34 -0
  65. package/src/hooks/useRadius.ts +42 -0
  66. package/src/hooks/useSession.ts +38 -0
  67. package/src/hooks/useThemeProps.ts +24 -0
  68. package/src/hooks/useToolApproval.ts +16 -0
  69. package/src/index.ts +45 -0
  70. package/src/lib/api.test.ts +90 -0
  71. package/src/lib/api.ts +8 -0
  72. package/src/lib/auth.ts +10 -0
  73. package/src/lib/easing.ts +1 -0
  74. package/src/lib/humanize.ts +14 -0
  75. package/src/lib/models.ts +22 -0
  76. package/src/lib/tools.ts +210 -0
  77. package/src/lib/utils.ts +16 -0
  78. package/src/plugins/README.md +49 -0
  79. package/src/plugins/chart/component.tsx +102 -0
  80. package/src/plugins/chart/index.ts +27 -0
  81. package/src/plugins/index.ts +7 -0
  82. package/src/server.ts +89 -0
  83. package/src/types/index.ts +726 -0
  84. package/src/types/plugins.ts +65 -0
  85. package/src/vite-env.d.ts +12 -0
@@ -0,0 +1,632 @@
1
+ import {
2
+ ArrowDownIcon,
3
+ ArrowUpIcon,
4
+ CheckIcon,
5
+ ChevronLeftIcon,
6
+ ChevronRightIcon,
7
+ CopyIcon,
8
+ PencilIcon,
9
+ RefreshCwIcon,
10
+ Settings2,
11
+ Square,
12
+ } from 'lucide-react'
13
+
14
+ import {
15
+ ActionBarPrimitive,
16
+ BranchPickerPrimitive,
17
+ ComposerPrimitive,
18
+ ErrorPrimitive,
19
+ ImageMessagePartProps,
20
+ MessagePrimitive,
21
+ ThreadPrimitive,
22
+ } from '@assistant-ui/react'
23
+
24
+ import { LazyMotion, MotionConfig, domAnimation } from 'motion/react'
25
+ import * as m from 'motion/react-m'
26
+ import { useEffect, useRef, useState, type FC } from 'react'
27
+
28
+ import {
29
+ ComposerAddAttachment,
30
+ ComposerAttachments,
31
+ UserMessageAttachments,
32
+ } from '@/components/assistant-ui/attachment'
33
+ import { MarkdownText } from '@/components/assistant-ui/markdown-text'
34
+ import { Reasoning, ReasoningGroup } from '@/components/assistant-ui/reasoning'
35
+ import { ToolFallback } from '@/components/assistant-ui/tool-fallback'
36
+ import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
37
+ import { Button } from '@/components/ui/button'
38
+
39
+ import { useDensity } from '@/hooks/useDensity'
40
+ import { useElements } from '@/hooks/useElements'
41
+ import { useRadius } from '@/hooks/useRadius'
42
+ import { useThemeProps } from '@/hooks/useThemeProps'
43
+ import { EASE_OUT_QUINT } from '@/lib/easing'
44
+ import { MODELS } from '@/lib/models'
45
+ import { cn } from '@/lib/utils'
46
+ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
47
+ import {
48
+ Tooltip,
49
+ TooltipContent,
50
+ TooltipProvider,
51
+ TooltipTrigger,
52
+ } from '../ui/tooltip'
53
+ import { ToolGroup } from './tool-group'
54
+
55
+ const ApiKeyWarning = () => (
56
+ <div className="m-2 rounded-md border border-amber-500 bg-amber-100 px-4 py-3 text-sm text-amber-800 dark:border-amber-600 dark:bg-amber-900/30 dark:text-amber-200">
57
+ <strong>Warning:</strong> You are using an API key directly in the client.
58
+ Please{' '}
59
+ <a
60
+ href="https://github.com/speakeasy-api/gram/tree/main/elements#setting-up-your-backend"
61
+ target="_blank"
62
+ rel="noopener noreferrer"
63
+ className="text-amber-700 underline hover:text-amber-800 dark:text-amber-300 dark:hover:text-amber-200"
64
+ >
65
+ set up a session endpoint
66
+ </a>{' '}
67
+ before deploying to production.
68
+ </div>
69
+ )
70
+
71
+ export const Thread: FC = () => {
72
+ const themeProps = useThemeProps()
73
+ const d = useDensity()
74
+ const { config } = useElements()
75
+ const components = config.components ?? {}
76
+ const showApiKeyWarning = config.api && 'UNSAFE_apiKey' in config.api
77
+
78
+ return (
79
+ <LazyMotion features={domAnimation}>
80
+ <MotionConfig reducedMotion="user">
81
+ <ThreadPrimitive.Root
82
+ className={cn(
83
+ 'aui-root aui-thread-root bg-background @container flex h-full flex-col',
84
+ themeProps.className
85
+ )}
86
+ >
87
+ <ThreadPrimitive.Viewport
88
+ className={cn(
89
+ 'aui-thread-viewport relative mx-auto flex w-full flex-1 flex-col overflow-x-auto overflow-y-scroll pb-0!',
90
+ d('p-lg')
91
+ )}
92
+ >
93
+ <ThreadPrimitive.If empty>
94
+ {components.ThreadWelcome ? (
95
+ <components.ThreadWelcome />
96
+ ) : (
97
+ <ThreadWelcome />
98
+ )}
99
+ </ThreadPrimitive.If>
100
+
101
+ {showApiKeyWarning && <ApiKeyWarning />}
102
+
103
+ <ThreadPrimitive.Messages
104
+ components={{
105
+ UserMessage: components.UserMessage ?? UserMessage,
106
+ EditComposer: components.EditComposer ?? EditComposer,
107
+ AssistantMessage:
108
+ components.AssistantMessage ?? AssistantMessage,
109
+ }}
110
+ />
111
+
112
+ <ThreadPrimitive.If empty={false}>
113
+ <div className="aui-thread-viewport-spacer min-h-8 grow" />
114
+ </ThreadPrimitive.If>
115
+
116
+ <Composer />
117
+ </ThreadPrimitive.Viewport>
118
+ </ThreadPrimitive.Root>
119
+ </MotionConfig>
120
+ </LazyMotion>
121
+ )
122
+ }
123
+
124
+ const ThreadScrollToBottom: FC = () => {
125
+ return (
126
+ <ThreadPrimitive.ScrollToBottom asChild>
127
+ <TooltipIconButton
128
+ tooltip="Scroll to bottom"
129
+ variant="outline"
130
+ className="aui-thread-scroll-to-bottom dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
131
+ >
132
+ <ArrowDownIcon />
133
+ </TooltipIconButton>
134
+ </ThreadPrimitive.ScrollToBottom>
135
+ )
136
+ }
137
+
138
+ const ThreadWelcome: FC = () => {
139
+ const { config } = useElements()
140
+ const d = useDensity()
141
+ const { title, subtitle } = config.welcome ?? {}
142
+ const isStandalone = config.variant === 'standalone'
143
+
144
+ return (
145
+ <div
146
+ className={cn(
147
+ 'aui-thread-welcome-root my-auto flex w-full grow flex-col',
148
+ isStandalone ? 'items-center justify-center' : '',
149
+ d('gap-lg')
150
+ )}
151
+ >
152
+ <div
153
+ className={cn(
154
+ 'aui-thread-welcome-center flex w-full grow flex-col items-center justify-start'
155
+ )}
156
+ >
157
+ <div
158
+ className={cn(
159
+ 'aui-thread-welcome-message flex flex-col',
160
+ isStandalone
161
+ ? 'items-center text-center'
162
+ : 'size-full justify-start',
163
+ d('gap-sm'),
164
+ !isStandalone && d('py-md')
165
+ )}
166
+ >
167
+ <m.div
168
+ initial={{ opacity: 0, y: 10 }}
169
+ animate={{ opacity: 1, y: 0 }}
170
+ exit={{ opacity: 0, y: 10 }}
171
+ transition={{ duration: 0.25, ease: EASE_OUT_QUINT }}
172
+ className={cn(
173
+ 'aui-thread-welcome-message-motion-1 text-foreground font-semibold',
174
+ d('text-title')
175
+ )}
176
+ >
177
+ {title}
178
+ </m.div>
179
+ <m.div
180
+ initial={{ opacity: 0, y: 10 }}
181
+ animate={{ opacity: 1, y: 0 }}
182
+ exit={{ opacity: 0, y: 10 }}
183
+ transition={{ duration: 0.25, delay: 0.05, ease: EASE_OUT_QUINT }}
184
+ className={cn(
185
+ 'aui-thread-welcome-message-motion-2 text-muted-foreground/65',
186
+ d('text-subtitle')
187
+ )}
188
+ >
189
+ {subtitle}
190
+ </m.div>
191
+ </div>
192
+ </div>
193
+ <ThreadSuggestions />
194
+ </div>
195
+ )
196
+ }
197
+
198
+ const ThreadSuggestions: FC = () => {
199
+ const { config } = useElements()
200
+ const r = useRadius()
201
+ const d = useDensity()
202
+ const suggestions = config.welcome?.suggestions ?? []
203
+ const isStandalone = config.variant === 'standalone'
204
+
205
+ if (suggestions.length === 0) return null
206
+
207
+ return (
208
+ <div
209
+ className={cn(
210
+ 'aui-thread-welcome-suggestions w-full',
211
+ d('gap-md'),
212
+ d('py-lg'),
213
+ isStandalone
214
+ ? 'flex flex-wrap items-center justify-center'
215
+ : suggestions.length === 1
216
+ ? 'flex'
217
+ : 'grid max-w-fit @md:grid-cols-2'
218
+ )}
219
+ >
220
+ {suggestions.map((suggestion, index) => (
221
+ <m.div
222
+ initial={{ opacity: 0, y: 20 }}
223
+ animate={{ opacity: 1, y: 0 }}
224
+ exit={{ opacity: 0, y: 20 }}
225
+ transition={{
226
+ duration: 0.25,
227
+ delay: 0.03 * index,
228
+ ease: EASE_OUT_QUINT,
229
+ }}
230
+ key={`suggested-action-${suggestion.title}-${index}`}
231
+ className={cn(
232
+ 'aui-thread-welcome-suggestion-display',
233
+ !isStandalone && 'nth-[n+3]:hidden @md:nth-[n+3]:block'
234
+ )}
235
+ >
236
+ <ThreadPrimitive.Suggestion prompt={suggestion.action} send asChild>
237
+ <Button
238
+ variant="ghost"
239
+ className={cn(
240
+ 'aui-thread-welcome-suggestion dark:hover:bg-accent/60 h-auto w-full border text-left whitespace-break-spaces',
241
+ d('text-base'),
242
+ isStandalone
243
+ ? `flex-row items-center ${d('gap-sm')} ${d('px-md')} ${d('py-sm')} ${r('full')}`
244
+ : `w-full flex-1 flex-col flex-wrap items-start justify-start ${d('gap-sm')} ${d('px-lg')} ${d('py-md')} ${r('xl')}`
245
+ )}
246
+ aria-label={suggestion.action}
247
+ >
248
+ <span className="aui-thread-welcome-suggestion-text-1 text-foreground text-sm font-medium">
249
+ {suggestion.title}
250
+ </span>
251
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground text-sm">
252
+ {suggestion.label}
253
+ </span>
254
+ </Button>
255
+ </ThreadPrimitive.Suggestion>
256
+ </m.div>
257
+ ))}
258
+ </div>
259
+ )
260
+ }
261
+
262
+ const Composer: FC = () => {
263
+ const { config } = useElements()
264
+ const r = useRadius()
265
+ const d = useDensity()
266
+ const composerConfig = config.composer ?? {
267
+ placeholder: 'Send a message...',
268
+ attachments: true,
269
+ }
270
+ const components = config.components ?? {}
271
+
272
+ if (components.Composer) {
273
+ return <components.Composer />
274
+ }
275
+
276
+ return (
277
+ <div
278
+ className={cn(
279
+ 'aui-composer-wrapper bg-background sticky bottom-0 flex w-full flex-col overflow-visible',
280
+ d('gap-md'),
281
+ d('py-md'),
282
+ r('xl')
283
+ )}
284
+ >
285
+ <ThreadScrollToBottom />
286
+ <ComposerPrimitive.Root
287
+ className={cn(
288
+ 'aui-composer-root group/input-group border-input bg-background has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-ring/5 dark:bg-background relative flex w-full flex-col border px-1 pt-2 shadow-xs transition-[color,box-shadow] outline-none has-[textarea:focus-visible]:ring-1',
289
+ r('xl')
290
+ )}
291
+ >
292
+ {composerConfig.attachments && <ComposerAttachments />}
293
+
294
+ <ComposerPrimitive.Input
295
+ placeholder={composerConfig.placeholder}
296
+ className={cn(
297
+ 'aui-composer-input placeholder:text-muted-foreground mb-1 max-h-32 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 outline-none focus-visible:ring-0',
298
+ d('h-input'),
299
+ d('text-base')
300
+ )}
301
+ rows={1}
302
+ autoFocus
303
+ aria-label="Message input"
304
+ />
305
+ <ComposerAction />
306
+ </ComposerPrimitive.Root>
307
+ </div>
308
+ )
309
+ }
310
+
311
+ const ComposerModelPicker: FC = () => {
312
+ const { model, setModel } = useElements()
313
+ const [popoverOpen, setPopoverOpen] = useState(false)
314
+ const [tooltipOpen, setTooltipOpen] = useState(false)
315
+ const scrollContainerRef = useRef<HTMLDivElement>(null)
316
+ const savedScrollPosition = useRef(0)
317
+ const previousOpenRef = useRef(false)
318
+
319
+ useEffect(() => {
320
+ // Restore scroll position when opening
321
+ if (popoverOpen && !previousOpenRef.current) {
322
+ requestAnimationFrame(() => {
323
+ const container = scrollContainerRef.current
324
+ if (container && container.scrollHeight > 0) {
325
+ container.scrollTop = savedScrollPosition.current
326
+ }
327
+ })
328
+ }
329
+
330
+ previousOpenRef.current = popoverOpen
331
+ }, [popoverOpen])
332
+
333
+ // Close tooltip when popover opens
334
+ useEffect(() => {
335
+ if (popoverOpen) {
336
+ setTooltipOpen(false)
337
+ }
338
+ }, [popoverOpen])
339
+
340
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
341
+ savedScrollPosition.current = e.currentTarget.scrollTop
342
+ }
343
+
344
+ return (
345
+ <TooltipProvider>
346
+ <Tooltip open={tooltipOpen && !popoverOpen} onOpenChange={setTooltipOpen}>
347
+ <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
348
+ <TooltipTrigger asChild>
349
+ <PopoverTrigger asChild>
350
+ <Button
351
+ variant="ghost"
352
+ size="icon"
353
+ data-state={popoverOpen ? 'open' : 'closed'}
354
+ className="aui-composer-model-picker data-[state=open]:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 flex w-fit items-center gap-2 rounded-full px-2.5 py-1 text-xs font-semibold"
355
+ aria-label="Model Settings"
356
+ >
357
+ <Settings2 className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
358
+ </Button>
359
+ </PopoverTrigger>
360
+ </TooltipTrigger>
361
+ <PopoverContent
362
+ side="top"
363
+ align="start"
364
+ className="w-min p-0 shadow-none"
365
+ >
366
+ <div
367
+ ref={scrollContainerRef}
368
+ className="max-h-48 overflow-y-auto"
369
+ onScroll={handleScroll}
370
+ >
371
+ {MODELS.map((m) => (
372
+ <Button
373
+ key={m}
374
+ onClick={() => {
375
+ setModel(m)
376
+ }}
377
+ variant="ghost"
378
+ className="w-full justify-start gap-2 rounded-none px-2"
379
+ >
380
+ {m === model ? (
381
+ <div>
382
+ <CheckIcon className="size-4 text-emerald-500" />
383
+ </div>
384
+ ) : (
385
+ <div className="size-4">&nbsp;</div>
386
+ )}
387
+ {m}
388
+ </Button>
389
+ ))}
390
+ </div>
391
+ </PopoverContent>
392
+ </Popover>
393
+ <TooltipContent side="bottom" align="start">
394
+ Model Settings
395
+ </TooltipContent>
396
+ </Tooltip>
397
+ </TooltipProvider>
398
+ )
399
+ }
400
+
401
+ const ComposerAction: FC = () => {
402
+ const { config } = useElements()
403
+ const r = useRadius()
404
+ const composerConfig = config.composer ?? { attachments: true }
405
+ return (
406
+ <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between">
407
+ <div className="aui-composer-action-wrapper-inner flex items-center">
408
+ {composerConfig.attachments ? (
409
+ <ComposerAddAttachment />
410
+ ) : (
411
+ <div className="aui-composer-add-attachment-placeholder" />
412
+ )}
413
+
414
+ {config.model?.showModelPicker && !config.languageModel && (
415
+ <ComposerModelPicker />
416
+ )}
417
+ </div>
418
+
419
+ <ThreadPrimitive.If running={false}>
420
+ <ComposerPrimitive.Send asChild>
421
+ <TooltipIconButton
422
+ tooltip="Send message"
423
+ side="bottom"
424
+ type="submit"
425
+ variant="default"
426
+ size="icon"
427
+ className={cn('aui-composer-send size-[34px] p-1', r('full'))}
428
+ aria-label="Send message"
429
+ >
430
+ <ArrowUpIcon className="aui-composer-send-icon size-5" />
431
+ </TooltipIconButton>
432
+ </ComposerPrimitive.Send>
433
+ </ThreadPrimitive.If>
434
+
435
+ <ThreadPrimitive.If running>
436
+ <ComposerPrimitive.Cancel asChild>
437
+ <Button
438
+ type="button"
439
+ variant="default"
440
+ size="icon"
441
+ className={cn(
442
+ 'aui-composer-cancel border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90 size-[34px] border',
443
+ r('full')
444
+ )}
445
+ aria-label="Stop generating"
446
+ >
447
+ <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" />
448
+ </Button>
449
+ </ComposerPrimitive.Cancel>
450
+ </ThreadPrimitive.If>
451
+ </div>
452
+ )
453
+ }
454
+
455
+ const MessageError: FC = () => {
456
+ return (
457
+ <MessagePrimitive.Error>
458
+ <ErrorPrimitive.Root className="aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200">
459
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
460
+ </ErrorPrimitive.Root>
461
+ </MessagePrimitive.Error>
462
+ )
463
+ }
464
+
465
+ const AssistantMessage: FC = () => {
466
+ const { config } = useElements()
467
+ const toolsConfig = config.tools ?? {}
468
+ const components = config.components ?? {}
469
+ return (
470
+ <MessagePrimitive.Root asChild>
471
+ <div
472
+ className="aui-assistant-message-root animate-in fade-in slide-in-from-bottom-1 relative mx-auto w-full py-4 duration-150 ease-out last:mb-24"
473
+ data-role="assistant"
474
+ >
475
+ <div className="aui-assistant-message-content text-foreground mx-2 leading-7 wrap-break-word">
476
+ <MessagePrimitive.Parts
477
+ components={{
478
+ Text: components.Text ?? MarkdownText,
479
+ Image: components.Image ?? Image,
480
+ tools: {
481
+ by_name: toolsConfig.components,
482
+ Fallback: components.ToolFallback ?? ToolFallback,
483
+ },
484
+ Reasoning: components.Reasoning ?? Reasoning,
485
+ ReasoningGroup: components.ReasoningGroup ?? ReasoningGroup,
486
+ ToolGroup: components.ToolGroup ?? ToolGroup,
487
+ }}
488
+ />
489
+ <MessageError />
490
+ </div>
491
+
492
+ <div className="aui-assistant-message-footer mt-2 ml-2 flex">
493
+ <BranchPicker />
494
+ <AssistantActionBar />
495
+ </div>
496
+ </div>
497
+ </MessagePrimitive.Root>
498
+ )
499
+ }
500
+
501
+ const Image: FC<ImageMessagePartProps> = (props) => {
502
+ return <img src={props.image} />
503
+ }
504
+
505
+ const AssistantActionBar: FC = () => {
506
+ return (
507
+ <ActionBarPrimitive.Root
508
+ hideWhenRunning
509
+ autohide="not-last"
510
+ autohideFloat="single-branch"
511
+ className="aui-assistant-action-bar-root text-muted-foreground data-floating:bg-background col-start-3 row-start-2 -ml-1 flex gap-1 data-floating:absolute data-floating:rounded-md data-floating:border data-floating:p-1 data-floating:shadow-sm"
512
+ >
513
+ <ActionBarPrimitive.Copy asChild>
514
+ <TooltipIconButton tooltip="Copy">
515
+ <MessagePrimitive.If copied>
516
+ <CheckIcon />
517
+ </MessagePrimitive.If>
518
+ <MessagePrimitive.If copied={false}>
519
+ <CopyIcon />
520
+ </MessagePrimitive.If>
521
+ </TooltipIconButton>
522
+ </ActionBarPrimitive.Copy>
523
+ <ActionBarPrimitive.Reload asChild>
524
+ <TooltipIconButton tooltip="Refresh">
525
+ <RefreshCwIcon />
526
+ </TooltipIconButton>
527
+ </ActionBarPrimitive.Reload>
528
+ </ActionBarPrimitive.Root>
529
+ )
530
+ }
531
+
532
+ const UserMessage: FC = () => {
533
+ const r = useRadius()
534
+ return (
535
+ <MessagePrimitive.Root asChild>
536
+ <div
537
+ className="aui-user-message-root animate-in fade-in slide-in-from-bottom-1 mx-auto grid w-full auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 px-2 py-4 duration-150 ease-out first:mt-3 last:mb-5 [&:where(>*)]:col-start-2"
538
+ data-role="user"
539
+ >
540
+ <UserMessageAttachments />
541
+
542
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
543
+ <div
544
+ className={cn(
545
+ 'aui-user-message-content bg-muted text-foreground px-5 py-2.5 wrap-break-word',
546
+ r('xl')
547
+ )}
548
+ >
549
+ <MessagePrimitive.Parts />
550
+ </div>
551
+ <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
552
+ <UserActionBar />
553
+ </div>
554
+ </div>
555
+
556
+ <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
557
+ </div>
558
+ </MessagePrimitive.Root>
559
+ )
560
+ }
561
+
562
+ const UserActionBar: FC = () => {
563
+ return (
564
+ <ActionBarPrimitive.Root
565
+ hideWhenRunning
566
+ autohide="not-last"
567
+ className="aui-user-action-bar-root flex flex-col items-end"
568
+ >
569
+ <ActionBarPrimitive.Edit asChild>
570
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
571
+ <PencilIcon />
572
+ </TooltipIconButton>
573
+ </ActionBarPrimitive.Edit>
574
+ </ActionBarPrimitive.Root>
575
+ )
576
+ }
577
+
578
+ const EditComposer: FC = () => {
579
+ return (
580
+ <div className="aui-edit-composer-wrapper mx-auto flex w-full flex-col gap-4 px-2 first:mt-4">
581
+ <ComposerPrimitive.Root className="aui-edit-composer-root bg-muted ml-auto flex w-full max-w-7/8 flex-col rounded-xl">
582
+ <ComposerPrimitive.Input
583
+ className="aui-edit-composer-input text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
584
+ autoFocus
585
+ />
586
+
587
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end">
588
+ <ComposerPrimitive.Cancel asChild>
589
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
590
+ Cancel
591
+ </Button>
592
+ </ComposerPrimitive.Cancel>
593
+ <ComposerPrimitive.Send asChild>
594
+ <Button size="sm" aria-label="Update message">
595
+ Update
596
+ </Button>
597
+ </ComposerPrimitive.Send>
598
+ </div>
599
+ </ComposerPrimitive.Root>
600
+ </div>
601
+ )
602
+ }
603
+
604
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
605
+ className,
606
+ ...rest
607
+ }) => {
608
+ return (
609
+ <BranchPickerPrimitive.Root
610
+ hideWhenSingleBranch
611
+ className={cn(
612
+ 'aui-branch-picker-root text-muted-foreground mr-2 -ml-2 inline-flex items-center text-xs',
613
+ className
614
+ )}
615
+ {...rest}
616
+ >
617
+ <BranchPickerPrimitive.Previous asChild>
618
+ <TooltipIconButton tooltip="Previous">
619
+ <ChevronLeftIcon />
620
+ </TooltipIconButton>
621
+ </BranchPickerPrimitive.Previous>
622
+ <span className="aui-branch-picker-state font-medium">
623
+ <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
624
+ </span>
625
+ <BranchPickerPrimitive.Next asChild>
626
+ <TooltipIconButton tooltip="Next">
627
+ <ChevronRightIcon />
628
+ </TooltipIconButton>
629
+ </BranchPickerPrimitive.Next>
630
+ </BranchPickerPrimitive.Root>
631
+ )
632
+ }