@gram-ai/elements 1.18.4 → 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.
- package/dist/components/Chat/stories/Sidecar.stories.d.ts +6 -0
- package/dist/elements.cjs +23 -22
- package/dist/elements.cjs.map +1 -0
- package/dist/elements.js +609 -599
- package/dist/elements.js.map +1 -0
- package/dist/index-Bj7jPiuy.cjs +1 -0
- package/dist/index-Bj7jPiuy.cjs.map +1 -0
- package/dist/index-CJRypLIa.js +1 -0
- package/dist/index-CJRypLIa.js.map +1 -0
- package/dist/lib/api.d.ts +2 -0
- package/dist/lib/api.test.d.ts +1 -0
- package/dist/plugins.cjs +1 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.js +1 -0
- package/dist/plugins.js.map +1 -0
- package/dist/server.cjs +1 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -0
- package/package.json +6 -2
- package/src/components/Chat/index.tsx +21 -0
- package/src/components/Chat/stories/ColorScheme.stories.tsx +52 -0
- package/src/components/Chat/stories/Composer.stories.tsx +42 -0
- package/src/components/Chat/stories/Customization.stories.tsx +88 -0
- package/src/components/Chat/stories/Density.stories.tsx +52 -0
- package/src/components/Chat/stories/FrontendTools.stories.tsx +145 -0
- package/src/components/Chat/stories/Modal.stories.tsx +84 -0
- package/src/components/Chat/stories/Model.stories.tsx +32 -0
- package/src/components/Chat/stories/Plugins.stories.tsx +50 -0
- package/src/components/Chat/stories/Radius.stories.tsx +52 -0
- package/src/components/Chat/stories/Sidecar.stories.tsx +27 -0
- package/src/components/Chat/stories/ToolApproval.stories.tsx +110 -0
- package/src/components/Chat/stories/Tools.stories.tsx +175 -0
- package/src/components/Chat/stories/Variants.stories.tsx +46 -0
- package/src/components/Chat/stories/Welcome.stories.tsx +42 -0
- package/src/components/FrontendTools/index.tsx +9 -0
- package/src/components/assistant-ui/assistant-modal.tsx +255 -0
- package/src/components/assistant-ui/assistant-sidecar.tsx +88 -0
- package/src/components/assistant-ui/attachment.tsx +233 -0
- package/src/components/assistant-ui/markdown-text.tsx +240 -0
- package/src/components/assistant-ui/reasoning.tsx +261 -0
- package/src/components/assistant-ui/thread-list.tsx +97 -0
- package/src/components/assistant-ui/thread.tsx +632 -0
- package/src/components/assistant-ui/tool-fallback.tsx +111 -0
- package/src/components/assistant-ui/tool-group.tsx +59 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +57 -0
- package/src/components/ui/avatar.tsx +51 -0
- package/src/components/ui/button.tsx +27 -0
- package/src/components/ui/buttonVariants.ts +33 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/dialog.tsx +141 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/tool-ui.stories.tsx +146 -0
- package/src/components/ui/tool-ui.tsx +676 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/contexts/ElementsProvider.tsx +256 -0
- package/src/contexts/ToolApprovalContext.tsx +120 -0
- package/src/contexts/contexts.ts +10 -0
- package/src/global.css +136 -0
- package/src/hooks/useAuth.ts +71 -0
- package/src/hooks/useDensity.ts +110 -0
- package/src/hooks/useElements.ts +14 -0
- package/src/hooks/useExpanded.ts +20 -0
- package/src/hooks/useMCPTools.ts +73 -0
- package/src/hooks/usePluginComponents.ts +34 -0
- package/src/hooks/useRadius.ts +42 -0
- package/src/hooks/useSession.ts +38 -0
- package/src/hooks/useThemeProps.ts +24 -0
- package/src/hooks/useToolApproval.ts +16 -0
- package/src/index.ts +45 -0
- package/src/lib/api.test.ts +90 -0
- package/src/lib/api.ts +8 -0
- package/src/lib/auth.ts +10 -0
- package/src/lib/easing.ts +1 -0
- package/src/lib/humanize.ts +14 -0
- package/src/lib/models.ts +22 -0
- package/src/lib/tools.ts +210 -0
- package/src/lib/utils.ts +16 -0
- package/src/plugins/README.md +49 -0
- package/src/plugins/chart/component.tsx +102 -0
- package/src/plugins/chart/index.ts +27 -0
- package/src/plugins/index.ts +7 -0
- package/src/server.ts +89 -0
- package/src/types/index.ts +726 -0
- package/src/types/plugins.ts +65 -0
- 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"> </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
|
+
}
|