@gram-ai/elements 1.18.5 → 1.18.7
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 +29 -28
- package/dist/elements.cjs.map +1 -0
- package/dist/elements.css +1 -1
- package/dist/elements.js +1945 -1918
- 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/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 +3 -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 +287 -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,240 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import '@assistant-ui/react-markdown/styles/dot.css'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type CodeHeaderProps,
|
|
7
|
+
MarkdownTextPrimitive,
|
|
8
|
+
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
|
|
9
|
+
useIsMarkdownCodeBlock,
|
|
10
|
+
} from '@assistant-ui/react-markdown'
|
|
11
|
+
import { CheckIcon, CopyIcon } from 'lucide-react'
|
|
12
|
+
import { type FC, memo, useState } from 'react'
|
|
13
|
+
import remarkGfm from 'remark-gfm'
|
|
14
|
+
|
|
15
|
+
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
|
|
16
|
+
import { cn } from '@/lib/utils'
|
|
17
|
+
import { useElements } from '@/hooks/useElements'
|
|
18
|
+
import { useComponentsByLanguage } from '@/hooks/usePluginComponents'
|
|
19
|
+
import { useAssistantState } from '@assistant-ui/react'
|
|
20
|
+
|
|
21
|
+
const MarkdownTextImpl = () => {
|
|
22
|
+
const { plugins } = useElements()
|
|
23
|
+
const componentsByLanguage = useComponentsByLanguage(plugins)
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<MarkdownTextPrimitive
|
|
27
|
+
remarkPlugins={[remarkGfm]}
|
|
28
|
+
className="aui-md"
|
|
29
|
+
components={defaultComponents}
|
|
30
|
+
componentsByLanguage={componentsByLanguage}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const MarkdownText = memo(MarkdownTextImpl)
|
|
36
|
+
|
|
37
|
+
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
|
|
38
|
+
const message = useAssistantState(({ message }) => message)
|
|
39
|
+
const messageIsComplete = message.status?.type === 'complete'
|
|
40
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard()
|
|
41
|
+
const onCopy = () => {
|
|
42
|
+
if (!code || isCopied) return
|
|
43
|
+
copyToClipboard(code)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!messageIsComplete) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
return (
|
|
50
|
+
<div className="aui-code-header-root bg-muted-foreground/15 text-foreground dark:bg-muted-foreground/20 mt-4 flex items-center justify-between gap-4 rounded-t-lg px-4 py-2 text-sm font-semibold">
|
|
51
|
+
<span className="aui-code-header-language lowercase [&>span]:text-xs">
|
|
52
|
+
{language}
|
|
53
|
+
</span>
|
|
54
|
+
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
|
|
55
|
+
{!isCopied && <CopyIcon />}
|
|
56
|
+
{isCopied && <CheckIcon />}
|
|
57
|
+
</TooltipIconButton>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const useCopyToClipboard = ({
|
|
63
|
+
copiedDuration = 3000,
|
|
64
|
+
}: {
|
|
65
|
+
copiedDuration?: number
|
|
66
|
+
} = {}) => {
|
|
67
|
+
const [isCopied, setIsCopied] = useState<boolean>(false)
|
|
68
|
+
|
|
69
|
+
const copyToClipboard = (value: string) => {
|
|
70
|
+
if (!value) return
|
|
71
|
+
|
|
72
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
73
|
+
setIsCopied(true)
|
|
74
|
+
setTimeout(() => setIsCopied(false), copiedDuration)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { isCopied, copyToClipboard }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const defaultComponents = memoizeMarkdownComponents({
|
|
82
|
+
h1: ({ className, ...props }) => (
|
|
83
|
+
<h1
|
|
84
|
+
className={cn(
|
|
85
|
+
'aui-md-h1 mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0',
|
|
86
|
+
className
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
),
|
|
91
|
+
h2: ({ className, ...props }) => (
|
|
92
|
+
<h2
|
|
93
|
+
className={cn(
|
|
94
|
+
'aui-md-h2 mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0',
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
),
|
|
100
|
+
h3: ({ className, ...props }) => (
|
|
101
|
+
<h3
|
|
102
|
+
className={cn(
|
|
103
|
+
'aui-md-h3 mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0',
|
|
104
|
+
className
|
|
105
|
+
)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
),
|
|
109
|
+
h4: ({ className, ...props }) => (
|
|
110
|
+
<h4
|
|
111
|
+
className={cn(
|
|
112
|
+
'aui-md-h4 mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0',
|
|
113
|
+
className
|
|
114
|
+
)}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
),
|
|
118
|
+
h5: ({ className, ...props }) => (
|
|
119
|
+
<h5
|
|
120
|
+
className={cn(
|
|
121
|
+
'aui-md-h5 my-4 text-lg font-semibold first:mt-0 last:mb-0',
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
),
|
|
127
|
+
h6: ({ className, ...props }) => (
|
|
128
|
+
<h6
|
|
129
|
+
className={cn(
|
|
130
|
+
'aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0',
|
|
131
|
+
className
|
|
132
|
+
)}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
),
|
|
136
|
+
p: ({ className, ...props }) => (
|
|
137
|
+
<p
|
|
138
|
+
className={cn(
|
|
139
|
+
'aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0',
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
),
|
|
145
|
+
a: ({ className, ...props }) => (
|
|
146
|
+
<a
|
|
147
|
+
className={cn(
|
|
148
|
+
'aui-md-a text-primary font-medium underline underline-offset-4',
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
/>
|
|
153
|
+
),
|
|
154
|
+
blockquote: ({ className, ...props }) => (
|
|
155
|
+
<blockquote
|
|
156
|
+
className={cn('aui-md-blockquote border-l-2 pl-6 italic', className)}
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
),
|
|
160
|
+
ul: ({ className, ...props }) => (
|
|
161
|
+
<ul
|
|
162
|
+
className={cn('aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2', className)}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
),
|
|
166
|
+
ol: ({ className, ...props }) => (
|
|
167
|
+
<ol
|
|
168
|
+
className={cn('aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2', className)}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
),
|
|
172
|
+
hr: ({ className, ...props }) => (
|
|
173
|
+
<hr className={cn('aui-md-hr my-5 border-b', className)} {...props} />
|
|
174
|
+
),
|
|
175
|
+
table: ({ className, ...props }) => (
|
|
176
|
+
<table
|
|
177
|
+
className={cn(
|
|
178
|
+
'aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto',
|
|
179
|
+
className
|
|
180
|
+
)}
|
|
181
|
+
{...props}
|
|
182
|
+
/>
|
|
183
|
+
),
|
|
184
|
+
th: ({ className, ...props }) => (
|
|
185
|
+
<th
|
|
186
|
+
className={cn(
|
|
187
|
+
'aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [[align=center]]:text-center [[align=right]]:text-right',
|
|
188
|
+
className
|
|
189
|
+
)}
|
|
190
|
+
{...props}
|
|
191
|
+
/>
|
|
192
|
+
),
|
|
193
|
+
td: ({ className, ...props }) => (
|
|
194
|
+
<td
|
|
195
|
+
className={cn(
|
|
196
|
+
'aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right',
|
|
197
|
+
className
|
|
198
|
+
)}
|
|
199
|
+
{...props}
|
|
200
|
+
/>
|
|
201
|
+
),
|
|
202
|
+
tr: ({ className, ...props }) => (
|
|
203
|
+
<tr
|
|
204
|
+
className={cn(
|
|
205
|
+
'aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg',
|
|
206
|
+
className
|
|
207
|
+
)}
|
|
208
|
+
{...props}
|
|
209
|
+
/>
|
|
210
|
+
),
|
|
211
|
+
sup: ({ className, ...props }) => (
|
|
212
|
+
<sup
|
|
213
|
+
className={cn('aui-md-sup [&>a]:text-xs [&>a]:no-underline', className)}
|
|
214
|
+
{...props}
|
|
215
|
+
/>
|
|
216
|
+
),
|
|
217
|
+
pre: ({ className, ...props }) => (
|
|
218
|
+
<pre
|
|
219
|
+
className={cn(
|
|
220
|
+
'aui-md-pre text-foreground bg-muted overflow-x-auto rounded-t-none! rounded-b-lg border border-t-0 p-4',
|
|
221
|
+
className
|
|
222
|
+
)}
|
|
223
|
+
{...props}
|
|
224
|
+
/>
|
|
225
|
+
),
|
|
226
|
+
code: function Code({ className, ...props }) {
|
|
227
|
+
const isCodeBlock = useIsMarkdownCodeBlock()
|
|
228
|
+
return (
|
|
229
|
+
<code
|
|
230
|
+
className={cn(
|
|
231
|
+
!isCodeBlock &&
|
|
232
|
+
'aui-md-inline-code bg-muted rounded border font-semibold',
|
|
233
|
+
className
|
|
234
|
+
)}
|
|
235
|
+
{...props}
|
|
236
|
+
/>
|
|
237
|
+
)
|
|
238
|
+
},
|
|
239
|
+
CodeHeader,
|
|
240
|
+
})
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { BrainIcon, ChevronDownIcon } from 'lucide-react'
|
|
4
|
+
import {
|
|
5
|
+
memo,
|
|
6
|
+
useCallback,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type FC,
|
|
10
|
+
type PropsWithChildren,
|
|
11
|
+
} from 'react'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
useAssistantState,
|
|
15
|
+
useScrollLock,
|
|
16
|
+
type ReasoningGroupComponent,
|
|
17
|
+
type ReasoningMessagePartComponent,
|
|
18
|
+
} from '@assistant-ui/react'
|
|
19
|
+
|
|
20
|
+
import { MarkdownText } from '@/components/assistant-ui/markdown-text'
|
|
21
|
+
import {
|
|
22
|
+
Collapsible,
|
|
23
|
+
CollapsibleContent,
|
|
24
|
+
CollapsibleTrigger,
|
|
25
|
+
} from '@/components/ui/collapsible'
|
|
26
|
+
import { cn } from '@/lib/utils'
|
|
27
|
+
|
|
28
|
+
const ANIMATION_DURATION = 200
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Root collapsible container that manages open/closed state and scroll lock.
|
|
32
|
+
* Provides animation timing via CSS variable and prevents scroll jumps on collapse.
|
|
33
|
+
*/
|
|
34
|
+
const ReasoningRoot: FC<
|
|
35
|
+
PropsWithChildren<{
|
|
36
|
+
className?: string
|
|
37
|
+
}>
|
|
38
|
+
> = ({ className, children }) => {
|
|
39
|
+
const collapsibleRef = useRef<HTMLDivElement>(null)
|
|
40
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
41
|
+
const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION)
|
|
42
|
+
|
|
43
|
+
const handleOpenChange = useCallback(
|
|
44
|
+
(open: boolean) => {
|
|
45
|
+
if (!open) {
|
|
46
|
+
lockScroll()
|
|
47
|
+
}
|
|
48
|
+
setIsOpen(open)
|
|
49
|
+
},
|
|
50
|
+
[lockScroll]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Collapsible
|
|
55
|
+
ref={collapsibleRef}
|
|
56
|
+
open={isOpen}
|
|
57
|
+
onOpenChange={handleOpenChange}
|
|
58
|
+
className={cn('aui-reasoning-root mb-4 w-full', className)}
|
|
59
|
+
style={
|
|
60
|
+
{
|
|
61
|
+
'--animation-duration': `${ANIMATION_DURATION}ms`,
|
|
62
|
+
} as React.CSSProperties
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</Collapsible>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ReasoningRoot.displayName = 'ReasoningRoot'
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gradient overlay that softens the bottom edge during expand/collapse animations.
|
|
74
|
+
* Animation: Fades out with delay when opening and fades back in when closing.
|
|
75
|
+
*/
|
|
76
|
+
const GradientFade: FC<{ className?: string }> = ({ className }) => (
|
|
77
|
+
<div
|
|
78
|
+
className={cn(
|
|
79
|
+
'aui-reasoning-fade pointer-events-none absolute inset-x-0 bottom-0 z-10 h-16',
|
|
80
|
+
'bg-[linear-gradient(to_top,var(--color-background),transparent)]',
|
|
81
|
+
'fade-in-0 animate-in',
|
|
82
|
+
'group-data-[state=open]/collapsible-content:animate-out',
|
|
83
|
+
'group-data-[state=open]/collapsible-content:fade-out-0',
|
|
84
|
+
'group-data-[state=open]/collapsible-content:delay-[calc(var(--animation-duration)*0.75)]', // calc for timing the delay
|
|
85
|
+
'group-data-[state=open]/collapsible-content:fill-mode-forwards',
|
|
86
|
+
'duration-(--animation-duration)',
|
|
87
|
+
'group-data-[state=open]/collapsible-content:duration-(--animation-duration)',
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Trigger button for the Reasoning collapsible.
|
|
95
|
+
* Composed of icons, label, and text shimmer animation when reasoning is being streamed.
|
|
96
|
+
*/
|
|
97
|
+
const ReasoningTrigger: FC<{ active: boolean; className?: string }> = ({
|
|
98
|
+
active,
|
|
99
|
+
className,
|
|
100
|
+
}) => (
|
|
101
|
+
<CollapsibleTrigger
|
|
102
|
+
className={cn(
|
|
103
|
+
'aui-reasoning-trigger group/trigger text-muted-foreground hover:text-foreground -mb-2 flex max-w-[75%] items-center gap-2 py-2 text-sm transition-colors',
|
|
104
|
+
className,
|
|
105
|
+
active && 'shimmer'
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<BrainIcon className="aui-reasoning-trigger-icon size-4 shrink-0" />
|
|
109
|
+
<span className="aui-reasoning-trigger-label-wrapper relative inline-block leading-none">
|
|
110
|
+
<span>Reasoning</span>
|
|
111
|
+
{active ? (
|
|
112
|
+
<span
|
|
113
|
+
aria-hidden
|
|
114
|
+
className="aui-reasoning-trigger-shimmer shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none"
|
|
115
|
+
>
|
|
116
|
+
Reasoning
|
|
117
|
+
</span>
|
|
118
|
+
) : null}
|
|
119
|
+
</span>
|
|
120
|
+
<ChevronDownIcon
|
|
121
|
+
className={cn(
|
|
122
|
+
'aui-reasoning-trigger-chevron mt-0.5 size-4 shrink-0',
|
|
123
|
+
'transition-transform duration-(--animation-duration) ease-out',
|
|
124
|
+
'group-data-[state=closed]/trigger:-rotate-90',
|
|
125
|
+
'group-data-[state=open]/trigger:rotate-0'
|
|
126
|
+
)}
|
|
127
|
+
/>
|
|
128
|
+
</CollapsibleTrigger>
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Collapsible content wrapper that handles height expand/collapse animation.
|
|
133
|
+
* Animation: Height animates up (collapse) and down (expand).
|
|
134
|
+
* Also provides group context for child animations via data-state attributes.
|
|
135
|
+
*/
|
|
136
|
+
const ReasoningContent: FC<
|
|
137
|
+
PropsWithChildren<{
|
|
138
|
+
className?: string
|
|
139
|
+
'aria-busy'?: boolean
|
|
140
|
+
}>
|
|
141
|
+
> = ({ className, children, 'aria-busy': ariaBusy }) => (
|
|
142
|
+
<CollapsibleContent
|
|
143
|
+
className={cn(
|
|
144
|
+
'aui-reasoning-content text-muted-foreground relative overflow-hidden text-sm outline-none',
|
|
145
|
+
'group/collapsible-content ease-out',
|
|
146
|
+
'data-[state=closed]:animate-collapsible-up',
|
|
147
|
+
'data-[state=open]:animate-collapsible-down',
|
|
148
|
+
'data-[state=closed]:fill-mode-forwards',
|
|
149
|
+
'data-[state=closed]:pointer-events-none',
|
|
150
|
+
'data-[state=open]:duration-(--animation-duration)',
|
|
151
|
+
'data-[state=closed]:duration-(--animation-duration)',
|
|
152
|
+
className
|
|
153
|
+
)}
|
|
154
|
+
aria-busy={ariaBusy}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
<GradientFade />
|
|
158
|
+
</CollapsibleContent>
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
ReasoningContent.displayName = 'ReasoningContent'
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Text content wrapper that animates the reasoning text visibility.
|
|
165
|
+
* Animation: Slides in from top + fades in when opening, reverses when closing.
|
|
166
|
+
* Reacts to parent ReasoningContent's data-state via Radix group selectors.
|
|
167
|
+
*/
|
|
168
|
+
const ReasoningText: FC<
|
|
169
|
+
PropsWithChildren<{
|
|
170
|
+
className?: string
|
|
171
|
+
}>
|
|
172
|
+
> = ({ className, children }) => (
|
|
173
|
+
<div
|
|
174
|
+
className={cn(
|
|
175
|
+
'aui-reasoning-text relative z-0 space-y-4 pt-4 pl-6 leading-relaxed',
|
|
176
|
+
'transform-gpu transition-[transform,opacity]',
|
|
177
|
+
'group-data-[state=open]/collapsible-content:animate-in',
|
|
178
|
+
'group-data-[state=closed]/collapsible-content:animate-out',
|
|
179
|
+
'group-data-[state=open]/collapsible-content:fade-in-0',
|
|
180
|
+
'group-data-[state=closed]/collapsible-content:fade-out-0',
|
|
181
|
+
'group-data-[state=open]/collapsible-content:slide-in-from-top-4',
|
|
182
|
+
'group-data-[state=closed]/collapsible-content:slide-out-to-top-4',
|
|
183
|
+
'group-data-[state=open]/collapsible-content:duration-(--animation-duration)',
|
|
184
|
+
'group-data-[state=closed]/collapsible-content:duration-(--animation-duration)',
|
|
185
|
+
'[&_p]:-mb-2',
|
|
186
|
+
className
|
|
187
|
+
)}
|
|
188
|
+
>
|
|
189
|
+
{children}
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
ReasoningText.displayName = 'ReasoningText'
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Renders a single reasoning part's text with markdown support.
|
|
197
|
+
* Consecutive reasoning parts are automatically grouped by ReasoningGroup.
|
|
198
|
+
*
|
|
199
|
+
* Pass Reasoning to MessagePrimitive.Parts in thread.tsx
|
|
200
|
+
*
|
|
201
|
+
* @example:
|
|
202
|
+
* ```tsx
|
|
203
|
+
* <MessagePrimitive.Parts
|
|
204
|
+
* components={{
|
|
205
|
+
* Reasoning: Reasoning,
|
|
206
|
+
* ReasoningGroup: ReasoningGroup,
|
|
207
|
+
* }}
|
|
208
|
+
* />
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
const ReasoningImpl: ReasoningMessagePartComponent = () => <MarkdownText />
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Collapsible wrapper that groups consecutive reasoning parts together.
|
|
215
|
+
* Includes scroll lock to prevent page jumps during collapse animation.
|
|
216
|
+
*
|
|
217
|
+
* Pass ReasoningGroup to MessagePrimitive.Parts in thread.tsx
|
|
218
|
+
*
|
|
219
|
+
* @example:
|
|
220
|
+
* ```tsx
|
|
221
|
+
* <MessagePrimitive.Parts
|
|
222
|
+
* components={{
|
|
223
|
+
* Reasoning: Reasoning,
|
|
224
|
+
* ReasoningGroup: ReasoningGroup,
|
|
225
|
+
* }}
|
|
226
|
+
* />
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
const ReasoningGroupImpl: ReasoningGroupComponent = ({
|
|
230
|
+
children,
|
|
231
|
+
startIndex,
|
|
232
|
+
endIndex,
|
|
233
|
+
}) => {
|
|
234
|
+
/**
|
|
235
|
+
* Detects if reasoning is currently streaming within this group's range.
|
|
236
|
+
*/
|
|
237
|
+
const isReasoningStreaming = useAssistantState(({ message }) => {
|
|
238
|
+
if (message.status?.type !== 'running') return false
|
|
239
|
+
const lastIndex = message.parts.length - 1
|
|
240
|
+
if (lastIndex < 0) return false
|
|
241
|
+
const lastType = message.parts[lastIndex]?.type
|
|
242
|
+
if (lastType !== 'reasoning') return false
|
|
243
|
+
return lastIndex >= startIndex && lastIndex <= endIndex
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<ReasoningRoot>
|
|
248
|
+
<ReasoningTrigger active={isReasoningStreaming} />
|
|
249
|
+
|
|
250
|
+
<ReasoningContent aria-busy={isReasoningStreaming}>
|
|
251
|
+
<ReasoningText>{children}</ReasoningText>
|
|
252
|
+
</ReasoningContent>
|
|
253
|
+
</ReasoningRoot>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const Reasoning = memo(ReasoningImpl)
|
|
258
|
+
Reasoning.displayName = 'Reasoning'
|
|
259
|
+
|
|
260
|
+
export const ReasoningGroup = memo(ReasoningGroupImpl)
|
|
261
|
+
ReasoningGroup.displayName = 'ReasoningGroup'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ThreadListItemPrimitive,
|
|
4
|
+
ThreadListPrimitive,
|
|
5
|
+
useAssistantState,
|
|
6
|
+
} from '@assistant-ui/react'
|
|
7
|
+
import { ArchiveIcon, PlusIcon } from 'lucide-react'
|
|
8
|
+
|
|
9
|
+
import { Button } from '@/components/ui/button'
|
|
10
|
+
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
|
|
11
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
|
12
|
+
|
|
13
|
+
// NOTE: These components are currently not used but they will be
|
|
14
|
+
// when we add support for thread / chat history
|
|
15
|
+
export const ThreadList: FC = () => {
|
|
16
|
+
return (
|
|
17
|
+
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col items-stretch gap-1.5">
|
|
18
|
+
<ThreadListNew />
|
|
19
|
+
<ThreadListItems />
|
|
20
|
+
</ThreadListPrimitive.Root>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ThreadListNew: FC = () => {
|
|
25
|
+
return (
|
|
26
|
+
<ThreadListPrimitive.New asChild>
|
|
27
|
+
<Button
|
|
28
|
+
className="aui-thread-list-new hover:bg-muted data-active:bg-muted flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start"
|
|
29
|
+
variant="ghost"
|
|
30
|
+
>
|
|
31
|
+
<PlusIcon />
|
|
32
|
+
New Thread
|
|
33
|
+
</Button>
|
|
34
|
+
</ThreadListPrimitive.New>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ThreadListItems: FC = () => {
|
|
39
|
+
const isLoading = useAssistantState(({ threads }) => threads.isLoading)
|
|
40
|
+
|
|
41
|
+
if (isLoading) {
|
|
42
|
+
return <ThreadListSkeleton />
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return <ThreadListPrimitive.Items components={{ ThreadListItem }} />
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ThreadListSkeleton: FC = () => {
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
{Array.from({ length: 5 }, (_, i) => (
|
|
52
|
+
<div
|
|
53
|
+
key={i}
|
|
54
|
+
role="status"
|
|
55
|
+
aria-label="Loading threads"
|
|
56
|
+
aria-live="polite"
|
|
57
|
+
className="aui-thread-list-skeleton-wrapper flex items-center gap-2 rounded-md px-3 py-2"
|
|
58
|
+
>
|
|
59
|
+
<Skeleton className="aui-thread-list-skeleton h-[22px] grow" />
|
|
60
|
+
</div>
|
|
61
|
+
))}
|
|
62
|
+
</>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ThreadListItem: FC = () => {
|
|
67
|
+
return (
|
|
68
|
+
<ThreadListItemPrimitive.Root className="aui-thread-list-item hover:bg-muted focus-visible:bg-muted focus-visible:ring-ring data-active:bg-muted flex items-center gap-2 rounded-lg transition-all focus-visible:ring-2 focus-visible:outline-none">
|
|
69
|
+
<ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger grow px-3 py-2 text-start">
|
|
70
|
+
<ThreadListItemTitle />
|
|
71
|
+
</ThreadListItemPrimitive.Trigger>
|
|
72
|
+
<ThreadListItemArchive />
|
|
73
|
+
</ThreadListItemPrimitive.Root>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ThreadListItemTitle: FC = () => {
|
|
78
|
+
return (
|
|
79
|
+
<span className="aui-thread-list-item-title text-sm">
|
|
80
|
+
<ThreadListItemPrimitive.Title fallback="New Chat" />
|
|
81
|
+
</span>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ThreadListItemArchive: FC = () => {
|
|
86
|
+
return (
|
|
87
|
+
<ThreadListItemPrimitive.Archive asChild>
|
|
88
|
+
<TooltipIconButton
|
|
89
|
+
className="aui-thread-list-item-archive text-foreground hover:text-primary mr-3 ml-auto size-4 p-0"
|
|
90
|
+
variant="ghost"
|
|
91
|
+
tooltip="Archive thread"
|
|
92
|
+
>
|
|
93
|
+
<ArchiveIcon />
|
|
94
|
+
</TooltipIconButton>
|
|
95
|
+
</ThreadListItemPrimitive.Archive>
|
|
96
|
+
)
|
|
97
|
+
}
|