@gram-ai/elements 1.25.0 → 1.25.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 (50) hide show
  1. package/dist/components/Chat/stories/MessageFeedback.stories.d.ts +1 -1
  2. package/dist/components/Replay.stories.d.ts +16 -0
  3. package/dist/contexts/ChatIdContext.d.ts +11 -0
  4. package/dist/contexts/contexts.d.ts +1 -0
  5. package/dist/elements.cjs +1 -1
  6. package/dist/elements.css +1 -1
  7. package/dist/elements.js +7 -6
  8. package/dist/{index-iUSSoKFz.cjs → index-B8nSCdu4.cjs} +11 -11
  9. package/dist/index-B8nSCdu4.cjs.map +1 -0
  10. package/dist/{index-wBHCO1r-.cjs → index-CAtaLV1E.cjs} +64 -55
  11. package/dist/index-CAtaLV1E.cjs.map +1 -0
  12. package/dist/{index-CtyV0c-T.js → index-CJrwma08.js} +3737 -3730
  13. package/dist/index-CJrwma08.js.map +1 -0
  14. package/dist/{index-DDb23655.js → index-DLWQ91ow.js} +8494 -8418
  15. package/dist/index-DLWQ91ow.js.map +1 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/lib/messageConverter.d.ts +1 -1
  18. package/dist/lib/messageConverter.test.d.ts +1 -0
  19. package/dist/plugins.cjs +1 -1
  20. package/dist/plugins.js +1 -1
  21. package/dist/{profiler-CGIJBY8c.js → profiler-BaG0scxd.js} +2 -2
  22. package/dist/{profiler-CGIJBY8c.js.map → profiler-BaG0scxd.js.map} +1 -1
  23. package/dist/{profiler-CLtQEzfv.cjs → profiler-CuqENACf.cjs} +2 -2
  24. package/dist/{profiler-CLtQEzfv.cjs.map → profiler-CuqENACf.cjs.map} +1 -1
  25. package/dist/{startRecording-x0G7lOpP.js → startRecording-86bHmd-l.js} +2 -2
  26. package/dist/{startRecording-x0G7lOpP.js.map → startRecording-86bHmd-l.js.map} +1 -1
  27. package/dist/{startRecording-DXZPNn9e.cjs → startRecording-BiLmoqZa.cjs} +2 -2
  28. package/dist/{startRecording-DXZPNn9e.cjs.map → startRecording-BiLmoqZa.cjs.map} +1 -1
  29. package/dist/types/index.d.ts +4 -4
  30. package/package.json +1 -1
  31. package/src/components/Chat/stories/MessageFeedback.stories.tsx +6 -6
  32. package/src/components/Chat/stories/ToolApproval.stories.tsx +10 -10
  33. package/src/components/Chat/stories/Tools.stories.tsx +122 -104
  34. package/src/components/Chat/stories/Variants.stories.tsx +1 -1
  35. package/src/components/Replay.stories.tsx +230 -0
  36. package/src/components/ShadowRoot.tsx +5 -1
  37. package/src/components/assistant-ui/message-feedback.tsx +6 -7
  38. package/src/components/assistant-ui/thread.tsx +76 -11
  39. package/src/contexts/ChatIdContext.tsx +21 -0
  40. package/src/contexts/ElementsProvider.tsx +77 -37
  41. package/src/contexts/contexts.ts +2 -0
  42. package/src/hooks/useAuth.ts +1 -2
  43. package/src/index.ts +1 -0
  44. package/src/lib/messageConverter.test.ts +242 -0
  45. package/src/lib/messageConverter.ts +22 -10
  46. package/src/types/index.ts +4 -4
  47. package/dist/index-CtyV0c-T.js.map +0 -1
  48. package/dist/index-DDb23655.js.map +0 -1
  49. package/dist/index-iUSSoKFz.cjs.map +0 -1
  50. package/dist/index-wBHCO1r-.cjs.map +0 -1
@@ -23,10 +23,16 @@ import {
23
23
  useAssistantState,
24
24
  } from '@assistant-ui/react'
25
25
 
26
- import { LazyMotion, MotionConfig, domAnimation } from 'motion/react'
26
+ import {
27
+ AnimatePresence,
28
+ LazyMotion,
29
+ MotionConfig,
30
+ domAnimation,
31
+ } from 'motion/react'
27
32
  import * as m from 'motion/react-m'
28
33
  import {
29
34
  createContext,
35
+ useCallback,
30
36
  useContext,
31
37
  useEffect,
32
38
  useMemo,
@@ -34,7 +40,6 @@ import {
34
40
  useState,
35
41
  type FC,
36
42
  } from 'react'
37
- import { AnimatePresence } from 'motion/react'
38
43
 
39
44
  import {
40
45
  ComposerAddAttachment,
@@ -43,25 +48,27 @@ import {
43
48
  } from '@/components/assistant-ui/attachment'
44
49
  import { FollowOnSuggestions } from '@/components/assistant-ui/follow-on-suggestions'
45
50
  import { MarkdownText } from '@/components/assistant-ui/markdown-text'
51
+ import { MentionedToolsBadges } from '@/components/assistant-ui/mentioned-tools-badges'
46
52
  import { MessageFeedback } from '@/components/assistant-ui/message-feedback'
47
53
  import { Reasoning, ReasoningGroup } from '@/components/assistant-ui/reasoning'
48
54
  import { ToolFallback } from '@/components/assistant-ui/tool-fallback'
49
55
  import { ToolMentionAutocomplete } from '@/components/assistant-ui/tool-mention-autocomplete'
50
- import { MentionedToolsBadges } from '@/components/assistant-ui/mentioned-tools-badges'
51
56
  import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
52
57
  import { Button } from '@/components/ui/button'
53
- import { useToolMentions } from '@/hooks/useToolMentions'
54
-
58
+ import { useChatId } from '@/contexts/ChatIdContext'
59
+ import { useReplayContext } from '@/contexts/ReplayContext'
60
+ import { useAuth } from '@/hooks/useAuth'
55
61
  import { useDensity } from '@/hooks/useDensity'
56
62
  import { useElements } from '@/hooks/useElements'
63
+ import { isLocalThreadId } from '@/hooks/useGramThreadListAdapter'
57
64
  import { useRadius } from '@/hooks/useRadius'
65
+ import { useRecordCassette } from '@/hooks/useRecordCassette'
58
66
  import { useThemeProps } from '@/hooks/useThemeProps'
67
+ import { useToolMentions } from '@/hooks/useToolMentions'
68
+ import { getApiUrl } from '@/lib/api'
59
69
  import { EASE_OUT_QUINT } from '@/lib/easing'
60
70
  import { MODELS } from '@/lib/models'
61
71
  import { cn } from '@/lib/utils'
62
- import { useRecordCassette } from '@/hooks/useRecordCassette'
63
- import { useReplayContext } from '@/contexts/ReplayContext'
64
- import { ConnectionStatusIndicatorSafe } from './connection-status-indicator'
65
72
  import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
66
73
  import {
67
74
  Tooltip,
@@ -69,8 +76,11 @@ import {
69
76
  TooltipProvider,
70
77
  TooltipTrigger,
71
78
  } from '../ui/tooltip'
79
+ import { ConnectionStatusIndicatorSafe } from './connection-status-indicator'
72
80
  import { ToolGroup } from './tool-group'
73
81
 
82
+ type Feedback = 'success' | 'failure'
83
+
74
84
  // Context for chat resolution state
75
85
  const ChatResolutionContext = createContext<{
76
86
  isResolved: boolean
@@ -78,12 +88,14 @@ const ChatResolutionContext = createContext<{
78
88
  setResolved: () => void
79
89
  setUnresolved: () => void
80
90
  resetFeedbackHidden: () => void
91
+ submitFeedback: (feedback: Feedback) => Promise<void>
81
92
  }>({
82
93
  isResolved: false,
83
94
  feedbackHidden: false,
84
95
  setResolved: () => {},
85
96
  setUnresolved: () => {},
86
97
  resetFeedbackHidden: () => {},
98
+ submitFeedback: async () => {},
87
99
  })
88
100
 
89
101
  const useChatResolution = () => useContext(ChatResolutionContext)
@@ -113,9 +125,16 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
113
125
  const { config } = useElements()
114
126
  const components = config.components ?? {}
115
127
  const showStaticSessionWarning = config.api && 'sessionToken' in config.api
116
- const showFeedback = config.thread?.experimental_showFeedback ?? false
128
+ const showFeedback = config.thread?.showFeedback ?? false
117
129
  const [isResolved, setIsResolved] = useState(false)
118
130
  const [feedbackHidden, setFeedbackHidden] = useState(false)
131
+ const chatId = useChatId()
132
+
133
+ const apiUrl = getApiUrl(config)
134
+ const auth = useAuth({
135
+ auth: config.api,
136
+ projectSlug: config.projectSlug,
137
+ })
119
138
 
120
139
  const setResolved = () => setIsResolved(true)
121
140
  const setUnresolved = () => {
@@ -124,6 +143,38 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
124
143
  }
125
144
  const resetFeedbackHidden = () => setFeedbackHidden(false)
126
145
 
146
+ // Submit feedback to the API
147
+ const submitFeedback = useCallback(
148
+ async (feedback: Feedback) => {
149
+ if (!chatId) return
150
+ if (isLocalThreadId(chatId)) {
151
+ console.error("Local thread ID, can't submit feedback")
152
+ return
153
+ }
154
+
155
+ try {
156
+ const response = await fetch(`${apiUrl}/rpc/chat.submitFeedback`, {
157
+ method: 'POST',
158
+ headers: {
159
+ 'Content-Type': 'application/json',
160
+ ...auth.headers,
161
+ },
162
+ body: JSON.stringify({
163
+ id: chatId,
164
+ feedback,
165
+ }),
166
+ })
167
+
168
+ if (!response.ok) {
169
+ console.error('Failed to submit feedback:', response.statusText)
170
+ }
171
+ } catch (error) {
172
+ console.error('Failed to submit feedback:', error)
173
+ }
174
+ },
175
+ [chatId, apiUrl, auth.headers]
176
+ )
177
+
127
178
  return (
128
179
  <ChatResolutionContext.Provider
129
180
  value={{
@@ -132,6 +183,7 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
132
183
  setResolved,
133
184
  setUnresolved,
134
185
  resetFeedbackHidden,
186
+ submitFeedback,
135
187
  }}
136
188
  >
137
189
  <LazyMotion features={domAnimation}>
@@ -448,7 +500,16 @@ const FeedbackHiddenResetter: FC = () => {
448
500
  }
449
501
 
450
502
  const ComposerFeedback: FC = () => {
451
- const { isResolved, feedbackHidden, setResolved } = useChatResolution()
503
+ const { isResolved, feedbackHidden, setResolved, submitFeedback } =
504
+ useChatResolution()
505
+
506
+ const handleFeedback = useCallback(
507
+ async (type: 'like' | 'dislike') => {
508
+ const feedback = type === 'like' ? 'success' : 'failure'
509
+ await submitFeedback(feedback)
510
+ },
511
+ [submitFeedback]
512
+ )
452
513
 
453
514
  return (
454
515
  <ThreadPrimitive.If empty={false}>
@@ -466,7 +527,11 @@ const ComposerFeedback: FC = () => {
466
527
  transition={{ duration: 0.2, ease: EASE_OUT_QUINT }}
467
528
  className="mb-3"
468
529
  >
469
- <MessageFeedback className="mx-auto" onResolved={setResolved} />
530
+ <MessageFeedback
531
+ className="mx-auto"
532
+ onResolved={setResolved}
533
+ onFeedback={handleFeedback}
534
+ />
470
535
  </m.div>
471
536
  )}
472
537
  </AnimatePresence>
@@ -0,0 +1,21 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export interface ChatIdContextValue {
4
+ chatId: string | null
5
+ }
6
+
7
+ export const ChatIdContext = createContext<ChatIdContextValue | null>(null)
8
+
9
+ /**
10
+ * Hook to access the current chat ID from the Elements context.
11
+ * Works in both history-enabled and history-disabled modes.
12
+ *
13
+ * @returns The current chat ID, or null if not yet initialized
14
+ */
15
+ export const useChatId = () => {
16
+ const context = useContext(ChatIdContext)
17
+ if (!context) {
18
+ throw new Error('useChatId must be used within ElementsProvider')
19
+ }
20
+ return context.chatId
21
+ }
@@ -59,6 +59,7 @@ import {
59
59
  useConnectionStatusOptional,
60
60
  } from './ConnectionStatusContext'
61
61
  import { ToolExecutionProvider } from './ToolExecutionContext'
62
+ import { ChatIdContext } from './ChatIdContext'
62
63
 
63
64
  /**
64
65
  * Extracts executable tools from frontend tool definitions.
@@ -93,14 +94,25 @@ export interface ElementsProviderProps {
93
94
  config: ElementsConfig
94
95
  }
95
96
 
96
- const BASE_SYSTEM_PROMPT = `You are a helpful assistant that can answer questions and help with tasks.`
97
+ const BASE_SYSTEM_PROMPT = `You are a helpful assistant that can answer questions and help with tasks.
98
+
99
+ Tool Result Display:
100
+ Some tools have custom visual components that automatically render their results (you'll see a rich card/widget appear). For these, do not repeat the data - just add brief context or a follow-up question if needed.
101
+
102
+ For tools WITHOUT custom components, you should present the data clearly - either as plain text for simple results, or using the UI code block format for structured data like lists of items, categories, or dashboards.`
97
103
 
98
104
  function mergeInternalSystemPromptWith(
99
105
  userSystemPrompt: string | undefined,
100
- plugins: Plugin[]
106
+ plugins: Plugin[],
107
+ toolsWithCustomComponents: string[]
101
108
  ) {
109
+ const customToolsSection =
110
+ toolsWithCustomComponents.length > 0
111
+ ? `\n\nTools with custom visual components (DO NOT render UI widgets for these - they already display rich visuals):\n${toolsWithCustomComponents.map((t) => `- ${t}`).join('\n')}`
112
+ : ''
113
+
102
114
  return `
103
- ${BASE_SYSTEM_PROMPT}
115
+ ${BASE_SYSTEM_PROMPT}${customToolsSection}
104
116
 
105
117
  User-provided System Prompt:
106
118
  ${userSystemPrompt ?? 'None provided'}
@@ -158,9 +170,13 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
158
170
 
159
171
  const plugins = config.plugins ?? recommended
160
172
 
173
+ // Get list of tools that have custom components registered
174
+ const toolsWithCustomComponents = Object.keys(config.tools?.components ?? {})
175
+
161
176
  const systemPrompt = mergeInternalSystemPromptWith(
162
177
  config.systemPrompt,
163
- plugins
178
+ plugins,
179
+ toolsWithCustomComponents
164
180
  )
165
181
 
166
182
  // Initialize error tracking on mount
@@ -176,6 +192,9 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
176
192
  // When history is enabled, the thread adapter manages chat IDs instead
177
193
  const chatIdRef = useRef<string | null>(null)
178
194
 
195
+ // State to expose the current chat ID via context
196
+ const [currentChatId, setCurrentChatId] = useState<string | null>(null)
197
+
179
198
  const { data: mcpTools, mcpHeaders } = useMCPTools({
180
199
  auth,
181
200
  mcp: config.mcp,
@@ -288,6 +307,8 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
288
307
  // chat ID on subsequent tool call requests.
289
308
  if (chatId) {
290
309
  mcpHeaders['Gram-Chat-ID'] = chatId
310
+ // Update the context state so consumers can access the current chat ID
311
+ setCurrentChatId(chatId)
291
312
  }
292
313
 
293
314
  const context = runtimeRef.current?.thread.getModelContext()
@@ -467,6 +488,8 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
467
488
  localIdToUuidMap={localIdToUuidMapRef.current}
468
489
  currentRemoteIdRef={currentRemoteIdRef}
469
490
  executableTools={executableTools}
491
+ currentChatId={currentChatId}
492
+ setCurrentChatId={setCurrentChatId}
470
493
  >
471
494
  {children}
472
495
  </ElementsProviderWithHistory>
@@ -480,6 +503,7 @@ const ElementsProviderInner = ({ children, config }: ElementsProviderProps) => {
480
503
  runtimeRef={runtimeRef}
481
504
  frontendTools={frontendTools}
482
505
  executableTools={executableTools}
506
+ currentChatId={currentChatId}
483
507
  >
484
508
  {children}
485
509
  </ElementsProviderWithoutHistory>
@@ -506,23 +530,28 @@ interface ElementsProviderWithHistoryProps {
506
530
  localIdToUuidMap: Map<string, string>
507
531
  currentRemoteIdRef: React.RefObject<string | null>
508
532
  executableTools: ExecutableToolSet
533
+ currentChatId: string | null
534
+ setCurrentChatId: (chatId: string | null) => void
509
535
  }
510
536
 
511
537
  /**
512
- * Component that syncs the current thread's remoteId to a ref.
538
+ * Component that syncs the current thread's remoteId to a ref and updates the chat ID context.
513
539
  * Must be rendered inside AssistantRuntimeProvider to access the state.
514
540
  */
515
541
  const ThreadIdSync = ({
516
542
  remoteIdRef,
543
+ onChatIdChange,
517
544
  }: {
518
545
  remoteIdRef: React.RefObject<string | null>
546
+ onChatIdChange: (chatId: string | null) => void
519
547
  }) => {
520
548
  const remoteId = useAssistantState(
521
549
  ({ threadListItem }) => threadListItem.remoteId ?? null
522
550
  )
523
551
  useEffect(() => {
524
552
  remoteIdRef.current = remoteId
525
- }, [remoteId, remoteIdRef])
553
+ onChatIdChange(remoteId)
554
+ }, [remoteId, remoteIdRef, onChatIdChange])
526
555
  return null
527
556
  }
528
557
 
@@ -537,6 +566,8 @@ const ElementsProviderWithHistory = ({
537
566
  localIdToUuidMap,
538
567
  currentRemoteIdRef,
539
568
  executableTools,
569
+ currentChatId,
570
+ setCurrentChatId,
540
571
  }: ElementsProviderWithHistoryProps) => {
541
572
  const threadListAdapter = useGramThreadListAdapter({
542
573
  apiUrl,
@@ -582,23 +613,28 @@ const ElementsProviderWithHistory = ({
582
613
 
583
614
  return (
584
615
  <AssistantRuntimeProvider runtime={runtime}>
585
- <ThreadIdSync remoteIdRef={currentRemoteIdRef} />
616
+ <ThreadIdSync
617
+ remoteIdRef={currentRemoteIdRef}
618
+ onChatIdChange={setCurrentChatId}
619
+ />
586
620
  <HistoryProvider>
587
- <ElementsContext.Provider value={contextValue}>
588
- <ToolExecutionProvider tools={executableTools}>
589
- <div
590
- className={cn(
591
- ROOT_SELECTOR,
592
- (contextValue?.config.variant === 'standalone' ||
593
- contextValue?.config.variant === 'sidecar') &&
594
- 'h-full'
595
- )}
596
- >
597
- {children}
598
- </div>
599
- <FrontendTools tools={frontendTools} />
600
- </ToolExecutionProvider>
601
- </ElementsContext.Provider>
621
+ <ChatIdContext.Provider value={{ chatId: currentChatId }}>
622
+ <ElementsContext.Provider value={contextValue}>
623
+ <ToolExecutionProvider tools={executableTools}>
624
+ <div
625
+ className={cn(
626
+ ROOT_SELECTOR,
627
+ (contextValue?.config.variant === 'standalone' ||
628
+ contextValue?.config.variant === 'sidecar') &&
629
+ 'h-full'
630
+ )}
631
+ >
632
+ {children}
633
+ </div>
634
+ <FrontendTools tools={frontendTools} />
635
+ </ToolExecutionProvider>
636
+ </ElementsContext.Provider>
637
+ </ChatIdContext.Provider>
602
638
  </HistoryProvider>
603
639
  </AssistantRuntimeProvider>
604
640
  )
@@ -613,6 +649,7 @@ interface ElementsProviderWithoutHistoryProps {
613
649
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
614
650
  frontendTools: Record<string, AssistantTool | FrontendTool<any, any>>
615
651
  executableTools: ExecutableToolSet
652
+ currentChatId: string | null
616
653
  }
617
654
 
618
655
  const ElementsProviderWithoutHistory = ({
@@ -622,6 +659,7 @@ const ElementsProviderWithoutHistory = ({
622
659
  runtimeRef,
623
660
  frontendTools,
624
661
  executableTools,
662
+ currentChatId,
625
663
  }: ElementsProviderWithoutHistoryProps) => {
626
664
  const runtime = useChatRuntime({ transport })
627
665
 
@@ -632,21 +670,23 @@ const ElementsProviderWithoutHistory = ({
632
670
 
633
671
  return (
634
672
  <AssistantRuntimeProvider runtime={runtime}>
635
- <ElementsContext.Provider value={contextValue}>
636
- <ToolExecutionProvider tools={executableTools}>
637
- <div
638
- className={cn(
639
- ROOT_SELECTOR,
640
- (contextValue?.config.variant === 'standalone' ||
641
- contextValue?.config.variant === 'sidecar') &&
642
- 'h-full'
643
- )}
644
- >
645
- {children}
646
- </div>
647
- <FrontendTools tools={frontendTools} />
648
- </ToolExecutionProvider>
649
- </ElementsContext.Provider>
673
+ <ChatIdContext.Provider value={{ chatId: currentChatId }}>
674
+ <ElementsContext.Provider value={contextValue}>
675
+ <ToolExecutionProvider tools={executableTools}>
676
+ <div
677
+ className={cn(
678
+ ROOT_SELECTOR,
679
+ (contextValue?.config.variant === 'standalone' ||
680
+ contextValue?.config.variant === 'sidecar') &&
681
+ 'h-full'
682
+ )}
683
+ >
684
+ {children}
685
+ </div>
686
+ <FrontendTools tools={frontendTools} />
687
+ </ToolExecutionProvider>
688
+ </ElementsContext.Provider>
689
+ </ChatIdContext.Provider>
650
690
  </AssistantRuntimeProvider>
651
691
  )
652
692
  }
@@ -8,3 +8,5 @@ export const ElementsContext = createContext<ElementsContextType | undefined>(
8
8
 
9
9
  export const ToolApprovalContext =
10
10
  createContext<ToolApprovalContextType | null>(null)
11
+
12
+ export { useChatId } from './ChatIdContext'
@@ -49,11 +49,10 @@ export const useAuth = ({
49
49
  : defaultGetSession
50
50
  }, [auth])
51
51
 
52
- // The session request is only neccessary if we are not using an API key auth
52
+ // The session request is only neccessary if we are not using static session auth
53
53
  // configuration. If a custom session fetcher is provided, we use it,
54
54
  // otherwise we fallback to the default session fetcher
55
55
  const session = useSession({
56
- // We want to check it's NOT API key auth, as the default auth scheme is session auth (if the user hasn't provided an explicit API config, we have a session auth config by default)
57
56
  getSession,
58
57
  projectSlug,
59
58
  })
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export { ElementsProvider } from './contexts/ElementsProvider'
10
10
  export { useElements as useGramElements } from './hooks/useElements'
11
11
  export { useElements } from './hooks/useElements'
12
12
  export { useThreadId } from './hooks/useThreadId'
13
+ export { useChatId } from './contexts/ChatIdContext'
13
14
 
14
15
  // Core Components
15
16
  export { Chat } from '@/components/Chat'