@gram-ai/elements 1.20.0 → 1.20.2

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 (29) hide show
  1. package/dist/components/Chat/index.d.ts +5 -1
  2. package/dist/components/Chat/stories/ErrorBoundary.stories.d.ts +8 -0
  3. package/dist/components/Chat/stories/Variants.stories.d.ts +2 -1
  4. package/dist/components/assistant-ui/assistant-modal.d.ts +5 -1
  5. package/dist/components/assistant-ui/assistant-sidecar.d.ts +5 -1
  6. package/dist/components/assistant-ui/error-boundary.d.ts +28 -0
  7. package/dist/components/assistant-ui/thread-list.d.ts +5 -1
  8. package/dist/components/assistant-ui/thread.d.ts +5 -1
  9. package/dist/elements.cjs +42 -42
  10. package/dist/elements.cjs.map +1 -1
  11. package/dist/elements.css +1 -1
  12. package/dist/elements.js +6010 -5965
  13. package/dist/elements.js.map +1 -1
  14. package/dist/{index-DaF9fGY-.js → index-BwdTXSZG.js} +4 -3
  15. package/dist/{index-DaF9fGY-.js.map → index-BwdTXSZG.js.map} +1 -1
  16. package/dist/{index-B52U8PL6.cjs → index-D8g4LkEy.cjs} +3 -3
  17. package/dist/{index-B52U8PL6.cjs.map → index-D8g4LkEy.cjs.map} +1 -1
  18. package/dist/plugins.cjs +1 -1
  19. package/dist/plugins.js +1 -1
  20. package/package.json +4 -2
  21. package/src/components/Chat/index.tsx +15 -3
  22. package/src/components/Chat/stories/ErrorBoundary.stories.tsx +221 -0
  23. package/src/components/Chat/stories/Variants.stories.tsx +28 -1
  24. package/src/components/assistant-ui/assistant-modal.tsx +11 -3
  25. package/src/components/assistant-ui/assistant-sidecar.tsx +11 -3
  26. package/src/components/assistant-ui/error-boundary.tsx +113 -0
  27. package/src/components/assistant-ui/thread-list.tsx +8 -3
  28. package/src/components/assistant-ui/thread.tsx +7 -2
  29. package/src/contexts/ElementsProvider.tsx +16 -1
@@ -0,0 +1,113 @@
1
+ 'use client'
2
+
3
+ import { AlertCircle } from 'lucide-react'
4
+ import { Component, type ErrorInfo, type ReactNode } from 'react'
5
+ import { cn } from '@/lib/utils'
6
+ import { Button } from '../ui/button'
7
+
8
+ interface ErrorBoundaryProps {
9
+ children: ReactNode
10
+ fallback?: ReactNode
11
+ onError?: (error: Error, errorInfo: ErrorInfo) => void
12
+ onReset?: () => void
13
+ }
14
+
15
+ interface ErrorBoundaryState {
16
+ hasError: boolean
17
+ error: Error | null
18
+ resetKey: number
19
+ }
20
+
21
+ interface ErrorFallbackProps {
22
+ error: Error | null
23
+ onRetry: () => void
24
+ }
25
+
26
+ // eslint-disable-next-line react-refresh/only-export-components
27
+ const ErrorFallback = ({ error, onRetry }: ErrorFallbackProps) => {
28
+ return (
29
+ <div
30
+ className={cn(
31
+ 'aui-root aui-error-boundary bg-background flex h-full w-full flex-col items-center justify-center p-6'
32
+ )}
33
+ >
34
+ <div className="flex flex-col items-center gap-4 text-center">
35
+ <div className="text-destructive">
36
+ <AlertCircle className="size-12 stroke-[1.5px]" />
37
+ </div>
38
+ <div className="flex flex-col gap-2">
39
+ <h3 className="text-foreground text-xl font-semibold">
40
+ Something went wrong
41
+ </h3>
42
+ <p className="text-muted-foreground text-base">
43
+ An error occurred while loading the chat.
44
+ </p>
45
+ {error && (
46
+ <p className="text-muted-foreground/60 max-w-md truncate text-sm">
47
+ {error.message}
48
+ </p>
49
+ )}
50
+ </div>
51
+ <Button onClick={onRetry} variant="default" className="mt-2">
52
+ Try again
53
+ </Button>
54
+ </div>
55
+ </div>
56
+ )
57
+ }
58
+
59
+ // eslint-disable-next-line react-refresh/only-export-components
60
+ const Remounter = ({ children }: { children: ReactNode }) => <>{children}</>
61
+
62
+ /**
63
+ * Global error boundary for the Elements library. Catches unexpected errors and renders a fallback UI.
64
+ * We wrap the assistant-modal, assistant-sidecar, and chat components with this error boundary.
65
+ * Each variant needs to have the error boundary rendered at the appropriate level e.g if using
66
+ * the widget variant, then the error screen must be rendered within the widget modal.
67
+ * TODO: We should add more granular error boundaries (e.g wrapping AssistantMessage, ThreadWelcome, etc.)
68
+ * TODO: We should also wrap ChatHistory, which may yield its own errors.
69
+ */
70
+ export class ErrorBoundary extends Component<
71
+ ErrorBoundaryProps,
72
+ ErrorBoundaryState
73
+ > {
74
+ constructor(props: ErrorBoundaryProps) {
75
+ super(props)
76
+ this.state = { hasError: false, error: null, resetKey: 0 }
77
+ }
78
+
79
+ static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
80
+ return { hasError: true, error }
81
+ }
82
+
83
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
84
+ this.props.onError?.(error, errorInfo)
85
+ }
86
+
87
+ handleRetry = () => {
88
+ // Increment resetKey to force remount of children, reinitializing the chat
89
+ this.setState((state) => ({
90
+ hasError: false,
91
+ error: null,
92
+ resetKey: state.resetKey + 1,
93
+ }))
94
+ this.props.onReset?.()
95
+ }
96
+
97
+ render(): ReactNode {
98
+ if (this.state.hasError) {
99
+ if (this.props.fallback) {
100
+ return this.props.fallback
101
+ }
102
+
103
+ return (
104
+ <ErrorFallback error={this.state.error} onRetry={this.handleRetry} />
105
+ )
106
+ }
107
+
108
+ // Use Remounter with key to force unmount/remount of children when retry is clicked
109
+ return (
110
+ <Remounter key={this.state.resetKey}>{this.props.children}</Remounter>
111
+ )
112
+ }
113
+ }
@@ -12,13 +12,18 @@ import { useRadius } from '@/hooks/useRadius'
12
12
  import { cn } from '@/lib/utils'
13
13
  import { useDensity } from '@/hooks/useDensity'
14
14
 
15
- export const ThreadList: FC = () => {
15
+ interface ThreadListProps {
16
+ className?: string
17
+ }
18
+
19
+ export const ThreadList: FC<ThreadListProps> = ({ className }) => {
16
20
  const d = useDensity()
17
21
  return (
18
22
  <ThreadListPrimitive.Root
19
23
  className={cn(
20
- 'aui-root aui-thread-list-root flex flex-col items-stretch',
21
- d('gap-sm')
24
+ 'aui-root aui-thread-list-root bg-background flex flex-col items-stretch',
25
+ d('gap-sm'),
26
+ className
22
27
  )}
23
28
  >
24
29
  <div
@@ -68,7 +68,11 @@ const ApiKeyWarning = () => (
68
68
  </div>
69
69
  )
70
70
 
71
- export const Thread: FC = () => {
71
+ interface ThreadProps {
72
+ className?: string
73
+ }
74
+
75
+ export const Thread: FC<ThreadProps> = ({ className }) => {
72
76
  const themeProps = useThemeProps()
73
77
  const d = useDensity()
74
78
  const { config } = useElements()
@@ -81,7 +85,8 @@ export const Thread: FC = () => {
81
85
  <ThreadPrimitive.Root
82
86
  className={cn(
83
87
  'aui-root aui-thread-root bg-background @container flex h-full flex-col',
84
- themeProps.className
88
+ themeProps.className,
89
+ className
85
90
  )}
86
91
  >
87
92
  <ThreadPrimitive.Viewport
@@ -173,6 +173,10 @@ const ElementsProviderWithApproval = ({
173
173
  // but runtime is created using transport. The ref gets populated after runtime creation.
174
174
  const runtimeRef = useRef<ReturnType<typeof useChatRuntime> | null>(null)
175
175
 
176
+ // Generate a stable chat ID for server-side persistence (when history is disabled)
177
+ // When history is enabled, the thread adapter manages chat IDs instead
178
+ const chatIdRef = useRef<string | null>(null)
179
+
176
180
  // Create chat transport configuration
177
181
  const transport = useMemo<ChatTransport<UIMessage>>(
178
182
  () => ({
@@ -183,18 +187,29 @@ const ElementsProviderWithApproval = ({
183
187
  throw new Error('Session is loading')
184
188
  }
185
189
 
190
+ // Generate chat ID on first message if not already set
191
+ if (!chatIdRef.current) {
192
+ chatIdRef.current = crypto.randomUUID()
193
+ }
194
+
186
195
  const context = runtimeRef.current?.thread.getModelContext()
187
196
  const frontendTools = toAISDKTools(
188
197
  getEnabledTools(context?.tools ?? {})
189
198
  )
190
199
 
200
+ // Include Gram-Chat-ID header for chat persistence
201
+ const headersWithChatId = {
202
+ ...auth.headers,
203
+ 'Gram-Chat-ID': chatIdRef.current,
204
+ }
205
+
191
206
  // Create OpenRouter model (only needed when not using custom model)
192
207
  const openRouterModel = usingCustomModel
193
208
  ? null
194
209
  : createOpenRouter({
195
210
  baseURL: apiUrl,
196
211
  apiKey: 'unused, but must be set',
197
- headers: auth.headers,
212
+ headers: headersWithChatId,
198
213
  })
199
214
 
200
215
  if (config.languageModel) {