@gram-ai/elements 1.20.1 → 1.21.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/bin/cli.js +14 -12
- package/dist/components/Chat/stories/ConnectionConfiguration.stories.d.ts +2 -2
- package/dist/components/Chat/stories/{ColorScheme.stories.d.ts → ErrorBoundary.stories.d.ts} +4 -4
- package/dist/components/Chat/stories/ToolApproval.stories.d.ts +2 -0
- package/dist/components/assistant-ui/error-boundary.d.ts +28 -0
- package/dist/components/ui/dialog.d.ts +1 -1
- package/dist/components/ui/tooltip.d.ts +3 -1
- package/dist/constants/tailwind.d.ts +1 -0
- package/dist/contexts/portal-container-context.d.ts +2 -0
- package/dist/contexts/portal-container.d.ts +7 -0
- package/dist/elements.cjs +1 -160
- package/dist/elements.cjs.map +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +11 -47174
- package/dist/elements.js.map +1 -1
- package/dist/hooks/usePortalContainer.d.ts +8 -0
- package/dist/hooks/useSession.d.ts +1 -2
- package/dist/index-B48xzOEm.cjs +169 -0
- package/dist/index-B48xzOEm.cjs.map +1 -0
- package/dist/{index-DaF9fGY-.js → index-BwdTXSZG.js} +4 -3
- package/dist/{index-DaF9fGY-.js.map → index-BwdTXSZG.js.map} +1 -1
- package/dist/index-C-iaUGd_.js +54687 -0
- package/dist/index-C-iaUGd_.js.map +1 -0
- package/dist/{index-B52U8PL6.cjs → index-D8g4LkEy.cjs} +3 -3
- package/dist/{index-B52U8PL6.cjs.map → index-D8g4LkEy.cjs.map} +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/lib/auth.d.ts +2 -2
- package/dist/lib/errorTracking.config.d.ts +16 -0
- package/dist/lib/errorTracking.d.ts +24 -0
- package/dist/lib/tools.d.ts +3 -2
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/profiler-WPgSewiM.js +278 -0
- package/dist/profiler-WPgSewiM.js.map +1 -0
- package/dist/profiler-j7uDglf5.cjs +2 -0
- package/dist/profiler-j7uDglf5.cjs.map +1 -0
- package/dist/startRecording-Cahc4WH4.cjs +3 -0
- package/dist/startRecording-Cahc4WH4.cjs.map +1 -0
- package/dist/startRecording-DpwlHYPJ.js +1212 -0
- package/dist/startRecording-DpwlHYPJ.js.map +1 -0
- package/dist/types/index.d.ts +45 -15
- package/package.json +16 -2
- package/src/components/Chat/index.tsx +39 -3
- package/src/components/Chat/stories/Composer.stories.tsx +0 -7
- package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +7 -14
- package/src/components/Chat/stories/CustomComponents.stories.tsx +0 -7
- package/src/components/Chat/stories/Density.stories.tsx +0 -7
- package/src/components/Chat/stories/ErrorBoundary.stories.tsx +202 -0
- package/src/components/Chat/stories/FrontendTools.stories.tsx +0 -7
- package/src/components/Chat/stories/Model.stories.tsx +0 -7
- package/src/components/Chat/stories/Plugins.stories.tsx +0 -7
- package/src/components/Chat/stories/Radius.stories.tsx +0 -7
- package/src/components/Chat/stories/ToolApproval.stories.tsx +51 -7
- package/src/components/Chat/stories/Tools.stories.tsx +0 -7
- package/src/components/Chat/stories/Variants.stories.tsx +5 -2
- package/src/components/Chat/stories/Welcome.stories.tsx +0 -8
- package/src/components/assistant-ui/assistant-modal.tsx +4 -1
- package/src/components/assistant-ui/assistant-sidecar.tsx +5 -5
- package/src/components/assistant-ui/attachment.tsx +1 -4
- package/src/components/assistant-ui/error-boundary.tsx +119 -0
- package/src/components/assistant-ui/thread-list.tsx +3 -1
- package/src/components/assistant-ui/thread.tsx +7 -8
- package/src/components/ui/dialog.tsx +10 -1
- package/src/components/ui/popover.tsx +10 -12
- package/src/components/ui/tooltip.tsx +7 -2
- package/src/constants/tailwind.ts +2 -0
- package/src/contexts/ElementsProvider.tsx +29 -2
- package/src/contexts/portal-container-context.ts +4 -0
- package/src/contexts/portal-container.tsx +20 -0
- package/src/global.css +129 -16
- package/src/hooks/useAuth.ts +6 -16
- package/src/hooks/usePortalContainer.ts +16 -0
- package/src/hooks/useSession.ts +1 -3
- package/src/index.ts +5 -0
- package/src/lib/api.test.ts +5 -5
- package/src/lib/auth.ts +4 -4
- package/src/lib/errorTracking.config.ts +16 -0
- package/src/lib/errorTracking.ts +104 -0
- package/src/lib/tools.ts +37 -8
- package/src/types/index.ts +48 -16
- package/src/vite-env.d.ts +3 -0
- package/src/components/Chat/stories/ColorScheme.stories.tsx +0 -52
|
@@ -11,13 +11,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
11
11
|
parameters: {
|
|
12
12
|
layout: 'fullscreen',
|
|
13
13
|
},
|
|
14
|
-
decorators: [
|
|
15
|
-
(Story) => (
|
|
16
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
17
|
-
<Story />
|
|
18
|
-
</div>
|
|
19
|
-
),
|
|
20
|
-
],
|
|
21
14
|
} satisfies Meta<typeof Chat>
|
|
22
15
|
|
|
23
16
|
export default meta
|
|
@@ -8,13 +8,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
8
8
|
parameters: {
|
|
9
9
|
layout: 'fullscreen',
|
|
10
10
|
},
|
|
11
|
-
decorators: [
|
|
12
|
-
(Story) => (
|
|
13
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
14
|
-
<Story />
|
|
15
|
-
</div>
|
|
16
|
-
),
|
|
17
|
-
],
|
|
18
11
|
} satisfies Meta<typeof Chat>
|
|
19
12
|
|
|
20
13
|
export default meta
|
|
@@ -8,13 +8,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
8
8
|
parameters: {
|
|
9
9
|
layout: 'fullscreen',
|
|
10
10
|
},
|
|
11
|
-
decorators: [
|
|
12
|
-
(Story) => (
|
|
13
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
14
|
-
<Story />
|
|
15
|
-
</div>
|
|
16
|
-
),
|
|
17
|
-
],
|
|
18
11
|
} satisfies Meta<typeof Chat>
|
|
19
12
|
|
|
20
13
|
export default meta
|
|
@@ -8,13 +8,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
8
8
|
parameters: {
|
|
9
9
|
layout: 'fullscreen',
|
|
10
10
|
},
|
|
11
|
-
decorators: [
|
|
12
|
-
(Story) => (
|
|
13
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
14
|
-
<Story />
|
|
15
|
-
</div>
|
|
16
|
-
),
|
|
17
|
-
],
|
|
18
11
|
} satisfies Meta<typeof Chat>
|
|
19
12
|
|
|
20
13
|
export default meta
|
|
@@ -10,13 +10,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
10
10
|
parameters: {
|
|
11
11
|
layout: 'fullscreen',
|
|
12
12
|
},
|
|
13
|
-
decorators: [
|
|
14
|
-
(Story) => (
|
|
15
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
16
|
-
<Story />
|
|
17
|
-
</div>
|
|
18
|
-
),
|
|
19
|
-
],
|
|
20
13
|
} satisfies Meta<typeof Chat>
|
|
21
14
|
|
|
22
15
|
export default meta
|
|
@@ -44,6 +37,30 @@ SingleTool.parameters = {
|
|
|
44
37
|
},
|
|
45
38
|
}
|
|
46
39
|
|
|
40
|
+
export const SingleToolWithFunction: Story = () => <Chat />
|
|
41
|
+
SingleToolWithFunction.storyName =
|
|
42
|
+
'Single Tool Requiring Approval with Function'
|
|
43
|
+
SingleToolWithFunction.parameters = {
|
|
44
|
+
elements: {
|
|
45
|
+
config: {
|
|
46
|
+
variant: 'standalone',
|
|
47
|
+
tools: {
|
|
48
|
+
toolsRequiringApproval: ({ toolName }: { toolName: string }) =>
|
|
49
|
+
toolName.endsWith('salutation'),
|
|
50
|
+
},
|
|
51
|
+
welcome: {
|
|
52
|
+
suggestions: [
|
|
53
|
+
{
|
|
54
|
+
title: 'Call a tool requiring approval',
|
|
55
|
+
label: 'Get a salutation',
|
|
56
|
+
action: 'Get a salutation',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
export const MultipleGroupedTools: Story = () => <Chat />
|
|
48
65
|
MultipleGroupedTools.storyName = 'Multiple Grouped Tools'
|
|
49
66
|
MultipleGroupedTools.parameters = {
|
|
@@ -108,3 +125,30 @@ FrontendTool.parameters = {
|
|
|
108
125
|
},
|
|
109
126
|
},
|
|
110
127
|
}
|
|
128
|
+
|
|
129
|
+
export const FrontendToolWithFunction: Story = () => <Chat />
|
|
130
|
+
FrontendToolWithFunction.storyName =
|
|
131
|
+
'Frontend Tool Requiring Approval with Function'
|
|
132
|
+
FrontendToolWithFunction.parameters = {
|
|
133
|
+
elements: {
|
|
134
|
+
config: {
|
|
135
|
+
variant: 'standalone',
|
|
136
|
+
tools: {
|
|
137
|
+
frontendTools: {
|
|
138
|
+
deleteFile,
|
|
139
|
+
},
|
|
140
|
+
toolsRequiringApproval: ({ toolName }: { toolName: string }) =>
|
|
141
|
+
toolName.startsWith('delete'),
|
|
142
|
+
},
|
|
143
|
+
welcome: {
|
|
144
|
+
suggestions: [
|
|
145
|
+
{
|
|
146
|
+
title: 'Delete a file',
|
|
147
|
+
label: 'Delete a file',
|
|
148
|
+
action: 'Delete file with ID 123',
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|
|
@@ -9,13 +9,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
9
9
|
parameters: {
|
|
10
10
|
layout: 'fullscreen',
|
|
11
11
|
},
|
|
12
|
-
decorators: [
|
|
13
|
-
(Story) => (
|
|
14
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
15
|
-
<Story />
|
|
16
|
-
</div>
|
|
17
|
-
),
|
|
18
|
-
],
|
|
19
12
|
} satisfies Meta<typeof Chat>
|
|
20
13
|
|
|
21
14
|
export default meta
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Chat } from '..'
|
|
2
2
|
import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite'
|
|
3
3
|
import { ThreadList } from '@/components/assistant-ui/thread-list'
|
|
4
|
+
import { ROOT_SELECTOR } from '@/constants/tailwind'
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Chat> = {
|
|
6
7
|
title: 'Chat/Variants',
|
|
@@ -46,8 +47,10 @@ export const StandaloneWithHistory: StoryObj<typeof Chat> = {
|
|
|
46
47
|
}
|
|
47
48
|
StandaloneWithHistory.decorators = [
|
|
48
49
|
(Story) => (
|
|
49
|
-
<div className=
|
|
50
|
-
<
|
|
50
|
+
<div className={ROOT_SELECTOR}>
|
|
51
|
+
<div className="m-auto flex h-screen w-full items-center justify-center border bg-linear-to-r from-violet-600 to-indigo-800">
|
|
52
|
+
<Story />
|
|
53
|
+
</div>
|
|
51
54
|
</div>
|
|
52
55
|
),
|
|
53
56
|
]
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
1
|
import { Chat } from '..'
|
|
3
2
|
import type { Meta, StoryFn } from '@storybook/react-vite'
|
|
4
3
|
|
|
@@ -8,13 +7,6 @@ const meta: Meta<typeof Chat> = {
|
|
|
8
7
|
parameters: {
|
|
9
8
|
layout: 'fullscreen',
|
|
10
9
|
},
|
|
11
|
-
decorators: [
|
|
12
|
-
(Story) => (
|
|
13
|
-
<div className="m-auto flex h-screen w-full max-w-3xl flex-col">
|
|
14
|
-
<Story />
|
|
15
|
-
</div>
|
|
16
|
-
),
|
|
17
|
-
],
|
|
18
10
|
} satisfies Meta<typeof Chat>
|
|
19
11
|
|
|
20
12
|
export default meta
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { LazyMotion, domMax, AnimatePresence, MotionConfig } from 'motion/react'
|
|
12
12
|
import * as m from 'motion/react-m'
|
|
13
13
|
|
|
14
|
+
import { ErrorBoundary } from '@/components/assistant-ui/error-boundary'
|
|
14
15
|
import { Thread } from '@/components/assistant-ui/thread'
|
|
15
16
|
import { ThreadList } from '@/components/assistant-ui/thread-list'
|
|
16
17
|
import { useThemeProps } from '@/hooks/useThemeProps'
|
|
@@ -240,7 +241,9 @@ export const AssistantModal: FC<AssistantModalProps> = ({ className }) => {
|
|
|
240
241
|
|
|
241
242
|
{/* Thread content */}
|
|
242
243
|
<div className="aui-modal-thread w-full flex-1 overflow-hidden">
|
|
243
|
-
<
|
|
244
|
+
<ErrorBoundary>
|
|
245
|
+
<Thread />
|
|
246
|
+
</ErrorBoundary>
|
|
244
247
|
</div>
|
|
245
248
|
</m.div>
|
|
246
249
|
</m.div>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { type FC } from 'react'
|
|
4
4
|
import { Loader, PanelRightClose, PanelRightOpen } from 'lucide-react'
|
|
5
|
+
import { ErrorBoundary } from '@/components/assistant-ui/error-boundary'
|
|
5
6
|
import { Thread } from '@/components/assistant-ui/thread'
|
|
6
7
|
import { ThreadList } from '@/components/assistant-ui/thread-list'
|
|
7
8
|
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
|
|
@@ -36,10 +37,7 @@ export const AssistantSidecar: FC<AssistantSidecarProps> = ({ className }) => {
|
|
|
36
37
|
return (
|
|
37
38
|
<LazyMotion features={domMax}>
|
|
38
39
|
<m.div
|
|
39
|
-
initial={
|
|
40
|
-
width: dimensions?.default?.width ?? '400px',
|
|
41
|
-
height: dimensions?.default?.height ?? '100vh',
|
|
42
|
-
}}
|
|
40
|
+
initial={false}
|
|
43
41
|
animate={{
|
|
44
42
|
width: isExpanded
|
|
45
43
|
? (dimensions?.expanded?.width ?? '800px')
|
|
@@ -99,7 +97,9 @@ export const AssistantSidecar: FC<AssistantSidecarProps> = ({ className }) => {
|
|
|
99
97
|
|
|
100
98
|
{/* Thread content */}
|
|
101
99
|
<div className="aui-sidecar-content flex-1 overflow-hidden">
|
|
102
|
-
<
|
|
100
|
+
<ErrorBoundary>
|
|
101
|
+
<Thread />
|
|
102
|
+
</ErrorBoundary>
|
|
103
103
|
</div>
|
|
104
104
|
</div>
|
|
105
105
|
</m.div>
|
|
@@ -87,10 +87,7 @@ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
|
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<Dialog>
|
|
90
|
-
<DialogTrigger
|
|
91
|
-
className="aui-attachment-preview-trigger hover:bg-accent/50 cursor-pointer transition-colors"
|
|
92
|
-
asChild
|
|
93
|
-
>
|
|
90
|
+
<DialogTrigger className="aui-attachment-preview-trigger" asChild>
|
|
94
91
|
{children}
|
|
95
92
|
</DialogTrigger>
|
|
96
93
|
<DialogContent className="aui-attachment-preview-dialog-content [&_svg]:text-background [&>button]:bg-foreground/60 [&>button]:hover:[&_svg]:text-destructive p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:p-1 [&>button]:opacity-100 [&>button]:!ring-0">
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { AlertCircle } from 'lucide-react'
|
|
4
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react'
|
|
5
|
+
import { trackError } from '@/lib/errorTracking'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button } from '../ui/button'
|
|
8
|
+
|
|
9
|
+
interface ErrorBoundaryProps {
|
|
10
|
+
children: ReactNode
|
|
11
|
+
fallback?: ReactNode
|
|
12
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void
|
|
13
|
+
onReset?: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ErrorBoundaryState {
|
|
17
|
+
hasError: boolean
|
|
18
|
+
error: Error | null
|
|
19
|
+
resetKey: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ErrorFallbackProps {
|
|
23
|
+
error: Error | null
|
|
24
|
+
onRetry: () => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
28
|
+
const ErrorFallback = ({ error, onRetry }: ErrorFallbackProps) => {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={cn(
|
|
32
|
+
'aui-root aui-error-boundary bg-background flex h-full w-full flex-col items-center justify-center p-6'
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<div className="flex flex-col items-center gap-4 text-center">
|
|
36
|
+
<div className="text-destructive">
|
|
37
|
+
<AlertCircle className="size-12 stroke-[1.5px]" />
|
|
38
|
+
</div>
|
|
39
|
+
<div className="flex flex-col gap-2">
|
|
40
|
+
<h3 className="text-foreground text-xl font-semibold">
|
|
41
|
+
Something went wrong
|
|
42
|
+
</h3>
|
|
43
|
+
<p className="text-muted-foreground text-base">
|
|
44
|
+
An error occurred while loading the chat.
|
|
45
|
+
</p>
|
|
46
|
+
{error && (
|
|
47
|
+
<p className="text-muted-foreground/60 max-w-md truncate text-sm">
|
|
48
|
+
{error.message}
|
|
49
|
+
</p>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
<Button onClick={onRetry} variant="default" className="mt-2">
|
|
53
|
+
Try again
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
61
|
+
const Remounter = ({ children }: { children: ReactNode }) => <>{children}</>
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Global error boundary for the Elements library. Catches unexpected errors and renders a fallback UI.
|
|
65
|
+
* We wrap the assistant-modal, assistant-sidecar, and chat components with this error boundary.
|
|
66
|
+
* Each variant needs to have the error boundary rendered at the appropriate level e.g if using
|
|
67
|
+
* the widget variant, then the error screen must be rendered within the widget modal.
|
|
68
|
+
* TODO: We should add more granular error boundaries (e.g wrapping AssistantMessage, ThreadWelcome, etc.)
|
|
69
|
+
* TODO: We should also wrap ChatHistory, which may yield its own errors.
|
|
70
|
+
*/
|
|
71
|
+
export class ErrorBoundary extends Component<
|
|
72
|
+
ErrorBoundaryProps,
|
|
73
|
+
ErrorBoundaryState
|
|
74
|
+
> {
|
|
75
|
+
constructor(props: ErrorBoundaryProps) {
|
|
76
|
+
super(props)
|
|
77
|
+
this.state = { hasError: false, error: null, resetKey: 0 }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
|
|
81
|
+
return { hasError: true, error }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
|
85
|
+
// Track error to Datadog RUM
|
|
86
|
+
trackError(error, {
|
|
87
|
+
source: 'error-boundary',
|
|
88
|
+
componentStack: errorInfo.componentStack ?? undefined,
|
|
89
|
+
})
|
|
90
|
+
this.props.onError?.(error, errorInfo)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
handleRetry = () => {
|
|
94
|
+
// Increment resetKey to force remount of children, reinitializing the chat
|
|
95
|
+
this.setState((state) => ({
|
|
96
|
+
hasError: false,
|
|
97
|
+
error: null,
|
|
98
|
+
resetKey: state.resetKey + 1,
|
|
99
|
+
}))
|
|
100
|
+
this.props.onReset?.()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
render(): ReactNode {
|
|
104
|
+
if (this.state.hasError) {
|
|
105
|
+
if (this.props.fallback) {
|
|
106
|
+
return this.props.fallback
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<ErrorFallback error={this.state.error} onRetry={this.handleRetry} />
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Use Remounter with key to force unmount/remount of children when retry is clicked
|
|
115
|
+
return (
|
|
116
|
+
<Remounter key={this.state.resetKey}>{this.props.children}</Remounter>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -11,6 +11,7 @@ import { Skeleton } from '@/components/ui/skeleton'
|
|
|
11
11
|
import { useRadius } from '@/hooks/useRadius'
|
|
12
12
|
import { cn } from '@/lib/utils'
|
|
13
13
|
import { useDensity } from '@/hooks/useDensity'
|
|
14
|
+
import { ROOT_SELECTOR } from '@/constants/tailwind'
|
|
14
15
|
|
|
15
16
|
interface ThreadListProps {
|
|
16
17
|
className?: string
|
|
@@ -23,7 +24,8 @@ export const ThreadList: FC<ThreadListProps> = ({ className }) => {
|
|
|
23
24
|
className={cn(
|
|
24
25
|
'aui-root aui-thread-list-root bg-background flex flex-col items-stretch',
|
|
25
26
|
d('gap-sm'),
|
|
26
|
-
className
|
|
27
|
+
className,
|
|
28
|
+
ROOT_SELECTOR
|
|
27
29
|
)}
|
|
28
30
|
>
|
|
29
31
|
<div
|
|
@@ -52,19 +52,18 @@ import {
|
|
|
52
52
|
} from '../ui/tooltip'
|
|
53
53
|
import { ToolGroup } from './tool-group'
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const StaticSessionWarning = () => (
|
|
56
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
|
|
58
|
-
Please{' '}
|
|
57
|
+
<strong>Warning:</strong> You are using a static session token in the
|
|
58
|
+
client. It will expire shortly. Please{' '}
|
|
59
59
|
<a
|
|
60
60
|
href="https://github.com/speakeasy-api/gram/tree/main/elements#setting-up-your-backend"
|
|
61
61
|
target="_blank"
|
|
62
62
|
rel="noopener noreferrer"
|
|
63
63
|
className="text-amber-700 underline hover:text-amber-800 dark:text-amber-300 dark:hover:text-amber-200"
|
|
64
64
|
>
|
|
65
|
-
set up a session endpoint
|
|
66
|
-
</a>
|
|
67
|
-
before deploying to production.
|
|
65
|
+
set up a session endpoint to avoid this warning.
|
|
66
|
+
</a>
|
|
68
67
|
</div>
|
|
69
68
|
)
|
|
70
69
|
|
|
@@ -77,7 +76,7 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
|
|
|
77
76
|
const d = useDensity()
|
|
78
77
|
const { config } = useElements()
|
|
79
78
|
const components = config.components ?? {}
|
|
80
|
-
const
|
|
79
|
+
const showStaticSessionWarning = config.api && 'sessionToken' in config.api
|
|
81
80
|
|
|
82
81
|
return (
|
|
83
82
|
<LazyMotion features={domAnimation}>
|
|
@@ -103,7 +102,7 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
|
|
|
103
102
|
)}
|
|
104
103
|
</ThreadPrimitive.If>
|
|
105
104
|
|
|
106
|
-
{
|
|
105
|
+
{showStaticSessionWarning && <StaticSessionWarning />}
|
|
107
106
|
|
|
108
107
|
<ThreadPrimitive.Messages
|
|
109
108
|
components={{
|
|
@@ -3,6 +3,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
|
3
3
|
import { XIcon } from 'lucide-react'
|
|
4
4
|
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { usePortalContainer } from '@/hooks/usePortalContainer'
|
|
6
7
|
|
|
7
8
|
function Dialog({
|
|
8
9
|
...props
|
|
@@ -17,9 +18,17 @@ function DialogTrigger({
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function DialogPortal({
|
|
21
|
+
container,
|
|
20
22
|
...props
|
|
21
23
|
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
22
|
-
|
|
24
|
+
const portalContainer = usePortalContainer()
|
|
25
|
+
return (
|
|
26
|
+
<DialogPrimitive.Portal
|
|
27
|
+
data-slot="dialog-portal"
|
|
28
|
+
container={container ?? portalContainer}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
function DialogClose({
|
|
@@ -22,18 +22,16 @@ function PopoverContent({
|
|
|
22
22
|
...props
|
|
23
23
|
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
24
24
|
return (
|
|
25
|
-
<PopoverPrimitive.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/>
|
|
36
|
-
</PopoverPrimitive.Portal>
|
|
25
|
+
<PopoverPrimitive.Content
|
|
26
|
+
data-slot="popover-content"
|
|
27
|
+
align={align}
|
|
28
|
+
sideOffset={sideOffset}
|
|
29
|
+
className={cn(
|
|
30
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-20 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
37
35
|
)
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -4,6 +4,7 @@ import * as React from 'react'
|
|
|
4
4
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
5
5
|
|
|
6
6
|
import { cn } from '@/lib/utils'
|
|
7
|
+
import { usePortalContainer } from '@/hooks/usePortalContainer'
|
|
7
8
|
|
|
8
9
|
function TooltipProvider({
|
|
9
10
|
delayDuration = 0,
|
|
@@ -38,10 +39,14 @@ function TooltipContent({
|
|
|
38
39
|
className,
|
|
39
40
|
sideOffset = 0,
|
|
40
41
|
children,
|
|
42
|
+
container,
|
|
41
43
|
...props
|
|
42
|
-
}: React.ComponentProps<typeof TooltipPrimitive.Content>
|
|
44
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content> & {
|
|
45
|
+
container?: HTMLElement | null
|
|
46
|
+
}) {
|
|
47
|
+
const portalContainer = usePortalContainer()
|
|
43
48
|
return (
|
|
44
|
-
<TooltipPrimitive.Portal>
|
|
49
|
+
<TooltipPrimitive.Portal container={container ?? portalContainer}>
|
|
45
50
|
<TooltipPrimitive.Content
|
|
46
51
|
data-slot="tooltip-content"
|
|
47
52
|
sideOffset={sideOffset}
|
|
@@ -2,6 +2,7 @@ import { FrontendTools } from '@/components/FrontendTools'
|
|
|
2
2
|
import { useMCPTools } from '@/hooks/useMCPTools'
|
|
3
3
|
import { useToolApproval } from '@/hooks/useToolApproval'
|
|
4
4
|
import { getApiUrl } from '@/lib/api'
|
|
5
|
+
import { initErrorTracking, trackError } from '@/lib/errorTracking'
|
|
5
6
|
import { MODELS } from '@/lib/models'
|
|
6
7
|
import {
|
|
7
8
|
clearFrontendToolApprovalConfig,
|
|
@@ -125,6 +126,15 @@ const ElementsProviderWithApproval = ({
|
|
|
125
126
|
plugins
|
|
126
127
|
)
|
|
127
128
|
|
|
129
|
+
// Initialize error tracking on mount
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
initErrorTracking({
|
|
132
|
+
enabled: config.errorTracking?.enabled,
|
|
133
|
+
projectSlug: config.projectSlug,
|
|
134
|
+
variant: config.variant,
|
|
135
|
+
})
|
|
136
|
+
}, [])
|
|
137
|
+
|
|
128
138
|
const { data: mcpTools } = useMCPTools({
|
|
129
139
|
auth,
|
|
130
140
|
mcp: config.mcp,
|
|
@@ -157,7 +167,7 @@ const ElementsProviderWithApproval = ({
|
|
|
157
167
|
|
|
158
168
|
// Set up frontend tool approval config for runtime checking
|
|
159
169
|
useEffect(() => {
|
|
160
|
-
if (config.tools?.toolsRequiringApproval
|
|
170
|
+
if (config.tools?.toolsRequiringApproval) {
|
|
161
171
|
setFrontendToolApprovalConfig(
|
|
162
172
|
getApprovalHelpers(),
|
|
163
173
|
config.tools.toolsRequiringApproval
|
|
@@ -173,6 +183,10 @@ const ElementsProviderWithApproval = ({
|
|
|
173
183
|
// but runtime is created using transport. The ref gets populated after runtime creation.
|
|
174
184
|
const runtimeRef = useRef<ReturnType<typeof useChatRuntime> | null>(null)
|
|
175
185
|
|
|
186
|
+
// Generate a stable chat ID for server-side persistence (when history is disabled)
|
|
187
|
+
// When history is enabled, the thread adapter manages chat IDs instead
|
|
188
|
+
const chatIdRef = useRef<string | null>(null)
|
|
189
|
+
|
|
176
190
|
// Create chat transport configuration
|
|
177
191
|
const transport = useMemo<ChatTransport<UIMessage>>(
|
|
178
192
|
() => ({
|
|
@@ -183,18 +197,29 @@ const ElementsProviderWithApproval = ({
|
|
|
183
197
|
throw new Error('Session is loading')
|
|
184
198
|
}
|
|
185
199
|
|
|
200
|
+
// Generate chat ID on first message if not already set
|
|
201
|
+
if (!chatIdRef.current) {
|
|
202
|
+
chatIdRef.current = crypto.randomUUID()
|
|
203
|
+
}
|
|
204
|
+
|
|
186
205
|
const context = runtimeRef.current?.thread.getModelContext()
|
|
187
206
|
const frontendTools = toAISDKTools(
|
|
188
207
|
getEnabledTools(context?.tools ?? {})
|
|
189
208
|
)
|
|
190
209
|
|
|
210
|
+
// Include Gram-Chat-ID header for chat persistence
|
|
211
|
+
const headersWithChatId = {
|
|
212
|
+
...auth.headers,
|
|
213
|
+
'Gram-Chat-ID': chatIdRef.current,
|
|
214
|
+
}
|
|
215
|
+
|
|
191
216
|
// Create OpenRouter model (only needed when not using custom model)
|
|
192
217
|
const openRouterModel = usingCustomModel
|
|
193
218
|
? null
|
|
194
219
|
: createOpenRouter({
|
|
195
220
|
baseURL: apiUrl,
|
|
196
221
|
apiKey: 'unused, but must be set',
|
|
197
|
-
headers:
|
|
222
|
+
headers: headersWithChatId,
|
|
198
223
|
})
|
|
199
224
|
|
|
200
225
|
if (config.languageModel) {
|
|
@@ -234,12 +259,14 @@ const ElementsProviderWithApproval = ({
|
|
|
234
259
|
abortSignal,
|
|
235
260
|
onError: ({ error }) => {
|
|
236
261
|
console.error('Stream error in onError callback:', error)
|
|
262
|
+
trackError(error, { source: 'streaming' })
|
|
237
263
|
},
|
|
238
264
|
})
|
|
239
265
|
|
|
240
266
|
return result.toUIMessageStream()
|
|
241
267
|
} catch (error) {
|
|
242
268
|
console.error('Error creating stream:', error)
|
|
269
|
+
trackError(error, { source: 'stream-creation' })
|
|
243
270
|
throw error
|
|
244
271
|
}
|
|
245
272
|
},
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type RefObject } from 'react'
|
|
4
|
+
import { PortalContainerContext } from './portal-container-context'
|
|
5
|
+
|
|
6
|
+
export { PortalContainerContext }
|
|
7
|
+
|
|
8
|
+
export function PortalContainerProvider({
|
|
9
|
+
containerRef,
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
containerRef: RefObject<HTMLElement | null>
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<PortalContainerContext.Provider value={containerRef}>
|
|
17
|
+
{children}
|
|
18
|
+
</PortalContainerContext.Provider>
|
|
19
|
+
)
|
|
20
|
+
}
|