@gram-ai/elements 1.21.1 → 1.21.3
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/StyleIsolation.stories.d.ts +6 -0
- package/dist/components/Chat/stories/Theme.stories.d.ts +8 -0
- package/dist/components/ChatHistory.d.ts +5 -0
- package/dist/components/ShadowRoot.d.ts +8 -0
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +1 -1
- package/dist/embedded.d.ts +0 -0
- package/dist/hooks/useGramThreadListAdapter.d.ts +7 -0
- package/dist/hooks/useMCPTools.d.ts +2 -1
- package/dist/index-CKBUBkLd.js +37757 -0
- package/dist/index-CKBUBkLd.js.map +1 -0
- package/dist/index-D5cSC22A.cjs +144 -0
- package/dist/index-D5cSC22A.cjs.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/{profiler-j7uDglf5.cjs → profiler-BY6U7KRN.cjs} +2 -2
- package/dist/{profiler-j7uDglf5.cjs.map → profiler-BY6U7KRN.cjs.map} +1 -1
- package/dist/{profiler-WPgSewiM.js → profiler-CRozVGVC.js} +2 -2
- package/dist/{profiler-WPgSewiM.js.map → profiler-CRozVGVC.js.map} +1 -1
- package/dist/{startRecording-Cahc4WH4.cjs → startRecording-Bi82O_dZ.cjs} +2 -2
- package/dist/{startRecording-Cahc4WH4.cjs.map → startRecording-Bi82O_dZ.cjs.map} +1 -1
- package/dist/{startRecording-DpwlHYPJ.js → startRecording-DQXktVbB.js} +2 -2
- package/dist/{startRecording-DpwlHYPJ.js.map → startRecording-DQXktVbB.js.map} +1 -1
- package/dist/types/index.d.ts +6 -0
- package/package.json +13 -4
- package/src/components/Chat/index.tsx +4 -13
- package/src/components/Chat/stories/StyleIsolation.stories.tsx +41 -0
- package/src/components/Chat/stories/Theme.stories.tsx +74 -0
- package/src/components/Chat/stories/ToolApproval.stories.tsx +0 -1
- package/src/components/ChatHistory.tsx +16 -0
- package/src/components/ShadowRoot.tsx +90 -0
- package/src/components/assistant-ui/markdown-text.tsx +0 -2
- package/src/components/assistant-ui/thread-list.tsx +1 -3
- package/src/components/assistant-ui/thread.tsx +3 -4
- package/src/contexts/ElementsProvider.tsx +65 -9
- package/src/embedded.ts +3 -0
- package/src/global.css +45 -0
- package/src/hooks/useGramThreadListAdapter.tsx +90 -9
- package/src/hooks/useMCPTools.ts +4 -1
- package/src/index.ts +1 -1
- package/src/types/index.ts +7 -0
- package/src/vite-env.d.ts +5 -0
- package/dist/index-B48xzOEm.cjs +0 -169
- package/dist/index-B48xzOEm.cjs.map +0 -1
- package/dist/index-C-iaUGd_.js +0 -54687
- package/dist/index-C-iaUGd_.js.map +0 -1
|
@@ -46,7 +46,11 @@ import {
|
|
|
46
46
|
import { useAuth } from '../hooks/useAuth'
|
|
47
47
|
import { ElementsContext } from './contexts'
|
|
48
48
|
import { ToolApprovalProvider } from './ToolApprovalContext'
|
|
49
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
isLocalThreadId,
|
|
51
|
+
useGramThreadListAdapter,
|
|
52
|
+
} from '@/hooks/useGramThreadListAdapter'
|
|
53
|
+
import { ROOT_SELECTOR } from '@/constants/tailwind'
|
|
50
54
|
|
|
51
55
|
export interface ElementsProviderProps {
|
|
52
56
|
children: ReactNode
|
|
@@ -139,6 +143,7 @@ const ElementsProviderWithApproval = ({
|
|
|
139
143
|
auth,
|
|
140
144
|
mcp: config.mcp,
|
|
141
145
|
environment: config.environment ?? {},
|
|
146
|
+
gramEnvironment: config.gramEnvironment,
|
|
142
147
|
})
|
|
143
148
|
|
|
144
149
|
// Store approval helpers in ref so they can be used in async contexts
|
|
@@ -187,6 +192,9 @@ const ElementsProviderWithApproval = ({
|
|
|
187
192
|
// When history is enabled, the thread adapter manages chat IDs instead
|
|
188
193
|
const chatIdRef = useRef<string | null>(null)
|
|
189
194
|
|
|
195
|
+
// Map to share local thread IDs to UUIDs between adapter and transport (for history mode)
|
|
196
|
+
const localIdToUuidMapRef = useRef(new Map<string, string>())
|
|
197
|
+
|
|
190
198
|
// Create chat transport configuration
|
|
191
199
|
const transport = useMemo<ChatTransport<UIMessage>>(
|
|
192
200
|
() => ({
|
|
@@ -197,9 +205,47 @@ const ElementsProviderWithApproval = ({
|
|
|
197
205
|
throw new Error('Session is loading')
|
|
198
206
|
}
|
|
199
207
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
// Get chat ID - try runtime's thread remoteId first (history mode),
|
|
209
|
+
// fall back to generated ID (non-history mode)
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
211
|
+
const runtimeAny = runtimeRef.current as any
|
|
212
|
+
|
|
213
|
+
// Try multiple paths to get thread info
|
|
214
|
+
const threadListItemState = runtimeAny?.threadListItem?.getState?.()
|
|
215
|
+
const threadsState = runtimeAny?.threads?.getState?.()
|
|
216
|
+
|
|
217
|
+
// Get the thread ID - try different sources
|
|
218
|
+
const threadRemoteId = threadListItemState?.remoteId as
|
|
219
|
+
| string
|
|
220
|
+
| undefined
|
|
221
|
+
const localThreadId = (threadListItemState?.id ??
|
|
222
|
+
threadsState?.mainThreadId ??
|
|
223
|
+
threadsState?.threadIds?.[0]) as string | undefined
|
|
224
|
+
|
|
225
|
+
let chatId = threadRemoteId
|
|
226
|
+
|
|
227
|
+
if (isLocalThreadId(chatId) || (!chatId && localThreadId)) {
|
|
228
|
+
const lookupKey = chatId ?? localThreadId
|
|
229
|
+
if (lookupKey) {
|
|
230
|
+
// For local thread IDs, check if we already have a UUID mapping
|
|
231
|
+
const existingUuid = localIdToUuidMapRef.current.get(lookupKey)
|
|
232
|
+
if (existingUuid) {
|
|
233
|
+
chatId = existingUuid
|
|
234
|
+
} else {
|
|
235
|
+
// Generate a new UUID and store the mapping
|
|
236
|
+
const newUuid = crypto.randomUUID()
|
|
237
|
+
localIdToUuidMapRef.current.set(lookupKey, newUuid)
|
|
238
|
+
chatId = newUuid
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!chatId) {
|
|
244
|
+
// Non-history mode fallback - use stable chatIdRef
|
|
245
|
+
if (!chatIdRef.current) {
|
|
246
|
+
chatIdRef.current = crypto.randomUUID()
|
|
247
|
+
}
|
|
248
|
+
chatId = chatIdRef.current
|
|
203
249
|
}
|
|
204
250
|
|
|
205
251
|
const context = runtimeRef.current?.thread.getModelContext()
|
|
@@ -207,10 +253,13 @@ const ElementsProviderWithApproval = ({
|
|
|
207
253
|
getEnabledTools(context?.tools ?? {})
|
|
208
254
|
)
|
|
209
255
|
|
|
210
|
-
// Include Gram-Chat-ID header for chat persistence
|
|
256
|
+
// Include Gram-Chat-ID header for chat persistence and Gram-Environment for environment selection
|
|
211
257
|
const headersWithChatId = {
|
|
212
258
|
...auth.headers,
|
|
213
|
-
'Gram-Chat-ID':
|
|
259
|
+
'Gram-Chat-ID': chatId,
|
|
260
|
+
...(config.gramEnvironment && {
|
|
261
|
+
'Gram-Environment': config.gramEnvironment,
|
|
262
|
+
}),
|
|
214
263
|
}
|
|
215
264
|
|
|
216
265
|
// Create OpenRouter model (only needed when not using custom model)
|
|
@@ -317,6 +366,7 @@ const ElementsProviderWithApproval = ({
|
|
|
317
366
|
contextValue={contextValue}
|
|
318
367
|
runtimeRef={runtimeRef}
|
|
319
368
|
frontendTools={frontendTools}
|
|
369
|
+
localIdToUuidMap={localIdToUuidMapRef.current}
|
|
320
370
|
>
|
|
321
371
|
{children}
|
|
322
372
|
</ElementsProviderWithHistory>
|
|
@@ -344,6 +394,7 @@ interface ElementsProviderWithHistoryProps {
|
|
|
344
394
|
contextValue: React.ContextType<typeof ElementsContext>
|
|
345
395
|
runtimeRef: React.RefObject<ReturnType<typeof useChatRuntime> | null>
|
|
346
396
|
frontendTools: Record<string, AssistantTool>
|
|
397
|
+
localIdToUuidMap: Map<string, string>
|
|
347
398
|
}
|
|
348
399
|
|
|
349
400
|
const ElementsProviderWithHistory = ({
|
|
@@ -354,8 +405,13 @@ const ElementsProviderWithHistory = ({
|
|
|
354
405
|
contextValue,
|
|
355
406
|
runtimeRef,
|
|
356
407
|
frontendTools,
|
|
408
|
+
localIdToUuidMap,
|
|
357
409
|
}: ElementsProviderWithHistoryProps) => {
|
|
358
|
-
const threadListAdapter = useGramThreadListAdapter({
|
|
410
|
+
const threadListAdapter = useGramThreadListAdapter({
|
|
411
|
+
apiUrl,
|
|
412
|
+
headers,
|
|
413
|
+
localIdToUuidMap,
|
|
414
|
+
})
|
|
359
415
|
|
|
360
416
|
// Hook factory for creating the base chat runtime
|
|
361
417
|
const useChatRuntimeHook = useCallback(() => {
|
|
@@ -381,7 +437,7 @@ const ElementsProviderWithHistory = ({
|
|
|
381
437
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
382
438
|
<HistoryProvider>
|
|
383
439
|
<ElementsContext.Provider value={contextValue}>
|
|
384
|
-
{children}
|
|
440
|
+
<div className={`${ROOT_SELECTOR} h-full`}>{children}</div>
|
|
385
441
|
<FrontendTools tools={frontendTools} />
|
|
386
442
|
</ElementsContext.Provider>
|
|
387
443
|
</HistoryProvider>
|
|
@@ -415,7 +471,7 @@ const ElementsProviderWithoutHistory = ({
|
|
|
415
471
|
return (
|
|
416
472
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
417
473
|
<ElementsContext.Provider value={contextValue}>
|
|
418
|
-
{children}
|
|
474
|
+
<div className={`${ROOT_SELECTOR} h-full`}>{children}</div>
|
|
419
475
|
<FrontendTools tools={frontendTools} />
|
|
420
476
|
</ElementsContext.Provider>
|
|
421
477
|
</AssistantRuntimeProvider>
|
package/src/embedded.ts
ADDED
package/src/global.css
CHANGED
|
@@ -247,3 +247,48 @@
|
|
|
247
247
|
[data-radius='pill'] .gram-elements {
|
|
248
248
|
--radius: 9999px;
|
|
249
249
|
}
|
|
250
|
+
|
|
251
|
+
/* assistant-ui loading dot styles (from @assistant-ui/react-markdown/styles/dot.css) */
|
|
252
|
+
@keyframes aui-pulse {
|
|
253
|
+
50% {
|
|
254
|
+
opacity: 0.5;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.gram-elements :where(.aui-md[data-status='running']):empty::after,
|
|
259
|
+
.gram-elements
|
|
260
|
+
:where(.aui-md[data-status='running'])
|
|
261
|
+
> :where(:not(ol):not(ul):not(pre)):last-child::after,
|
|
262
|
+
.gram-elements :where(.aui-md[data-status='running']) > pre:last-child code::after,
|
|
263
|
+
.gram-elements
|
|
264
|
+
:where(.aui-md[data-status='running'])
|
|
265
|
+
> :where(:is(ol, ul):last-child)
|
|
266
|
+
> :where(li:last-child:not(:has(* > li)))::after,
|
|
267
|
+
.gram-elements
|
|
268
|
+
:where(.aui-md[data-status='running'])
|
|
269
|
+
> :where(:is(ol, ul):last-child)
|
|
270
|
+
> :where(li:last-child)
|
|
271
|
+
> :where(:is(ol, ul):last-child)
|
|
272
|
+
> :where(li:last-child:not(:has(* > li)))::after,
|
|
273
|
+
.gram-elements
|
|
274
|
+
:where(.aui-md[data-status='running'])
|
|
275
|
+
> :where(:is(ol, ul):last-child)
|
|
276
|
+
> :where(li:last-child)
|
|
277
|
+
> :where(:is(ol, ul):last-child)
|
|
278
|
+
> :where(li:last-child)
|
|
279
|
+
> :where(:is(ol, ul):last-child)
|
|
280
|
+
> :where(li:last-child)::after {
|
|
281
|
+
animation: aui-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
282
|
+
font-family:
|
|
283
|
+
ui-sans-serif,
|
|
284
|
+
system-ui,
|
|
285
|
+
sans-serif,
|
|
286
|
+
'Apple Color Emoji',
|
|
287
|
+
'Segoe UI Emoji',
|
|
288
|
+
'Segoe UI Symbol',
|
|
289
|
+
'Noto Color Emoji';
|
|
290
|
+
--aui-content: '\25cf';
|
|
291
|
+
content: var(--aui-content);
|
|
292
|
+
margin-left: 0.25rem;
|
|
293
|
+
margin-right: 0.25rem;
|
|
294
|
+
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useAssistantApi,
|
|
7
7
|
type AssistantApi,
|
|
8
8
|
} from '@assistant-ui/react'
|
|
9
|
-
import type
|
|
9
|
+
import { createAssistantStream, type AssistantStream } from 'assistant-stream'
|
|
10
10
|
import {
|
|
11
11
|
GramChatOverview,
|
|
12
12
|
GramChat,
|
|
@@ -21,9 +21,26 @@ import {
|
|
|
21
21
|
type PropsWithChildren,
|
|
22
22
|
} from 'react'
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Prefix used by assistant-ui for local thread IDs that haven't been persisted yet.
|
|
26
|
+
* This is an internal implementation detail of assistant-ui's RemoteThreadListThreadListRuntimeCore.
|
|
27
|
+
* If the library changes this prefix, we only need to update it here.
|
|
28
|
+
*/
|
|
29
|
+
const LOCAL_THREAD_ID_PREFIX = '__LOCALID_'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a thread ID is a local (unpersisted) thread ID.
|
|
33
|
+
* Local IDs are generated by assistant-ui before the thread is initialized with a remote ID.
|
|
34
|
+
*/
|
|
35
|
+
export function isLocalThreadId(threadId: string | undefined): boolean {
|
|
36
|
+
return !!threadId?.startsWith(LOCAL_THREAD_ID_PREFIX)
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
export interface ThreadListAdapterOptions {
|
|
25
40
|
apiUrl: string
|
|
26
41
|
headers: Record<string, string>
|
|
42
|
+
/** Map to translate local thread IDs to UUIDs (shared with transport) */
|
|
43
|
+
localIdToUuidMap?: Map<string, string>
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
interface ListChatsResponse {
|
|
@@ -227,6 +244,26 @@ export function useGramThreadListAdapter(
|
|
|
227
244
|
},
|
|
228
245
|
|
|
229
246
|
async initialize(threadId: string) {
|
|
247
|
+
// For new threads (local IDs), check if sendMessages already created a UUID
|
|
248
|
+
if (isLocalThreadId(threadId)) {
|
|
249
|
+
// Check if transport already generated a UUID for this local ID
|
|
250
|
+
const existingUuid =
|
|
251
|
+
optionsRef.current.localIdToUuidMap?.get(threadId)
|
|
252
|
+
if (existingUuid) {
|
|
253
|
+
return {
|
|
254
|
+
remoteId: existingUuid,
|
|
255
|
+
externalId: existingUuid,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Otherwise generate a new one and store it
|
|
259
|
+
const uuid = crypto.randomUUID()
|
|
260
|
+
optionsRef.current.localIdToUuidMap?.set(threadId, uuid)
|
|
261
|
+
return {
|
|
262
|
+
remoteId: uuid,
|
|
263
|
+
externalId: uuid,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// For existing threads, use the ID as-is
|
|
230
267
|
return {
|
|
231
268
|
remoteId: threadId,
|
|
232
269
|
externalId: threadId,
|
|
@@ -250,21 +287,65 @@ export function useGramThreadListAdapter(
|
|
|
250
287
|
},
|
|
251
288
|
|
|
252
289
|
async generateTitle(
|
|
253
|
-
|
|
254
|
-
_remoteId: string,
|
|
290
|
+
remoteId: string,
|
|
255
291
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
256
292
|
_messages: readonly ThreadMessage[]
|
|
257
293
|
): Promise<AssistantStream> {
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
start(controller) {
|
|
294
|
+
// Skip if this is a local ID that hasn't been persisted yet
|
|
295
|
+
if (!remoteId || isLocalThreadId(remoteId)) {
|
|
296
|
+
return createAssistantStream((controller) => {
|
|
262
297
|
controller.close()
|
|
263
|
-
}
|
|
264
|
-
}
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Call the server to generate and persist the title
|
|
302
|
+
try {
|
|
303
|
+
const response = await fetch(
|
|
304
|
+
`${optionsRef.current.apiUrl}/rpc/chat.generateTitle`,
|
|
305
|
+
{
|
|
306
|
+
method: 'POST',
|
|
307
|
+
headers: {
|
|
308
|
+
...optionsRef.current.headers,
|
|
309
|
+
'Content-Type': 'application/json',
|
|
310
|
+
},
|
|
311
|
+
body: JSON.stringify({ id: remoteId }),
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
const result = (await response.json()) as { title: string }
|
|
317
|
+
const title = result.title || 'New Chat'
|
|
318
|
+
|
|
319
|
+
// Return a stream that emits the title as text
|
|
320
|
+
return createAssistantStream((controller) => {
|
|
321
|
+
controller.appendText(title)
|
|
322
|
+
controller.close()
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 404 is expected for new chats that haven't been persisted yet
|
|
327
|
+
if (response.status !== 404) {
|
|
328
|
+
console.error('Error generating title:', response.status)
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('Error generating title:', error)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Fallback: return empty stream
|
|
335
|
+
return createAssistantStream((controller) => {
|
|
336
|
+
controller.close()
|
|
337
|
+
})
|
|
265
338
|
},
|
|
266
339
|
|
|
267
340
|
async fetch(threadId: string) {
|
|
341
|
+
// Skip if this is a local ID that hasn't been persisted yet
|
|
342
|
+
if (!threadId || isLocalThreadId(threadId)) {
|
|
343
|
+
return {
|
|
344
|
+
remoteId: threadId,
|
|
345
|
+
status: 'regular' as const,
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
268
349
|
try {
|
|
269
350
|
const response = await fetch(
|
|
270
351
|
`${optionsRef.current.apiUrl}/rpc/chat.load?id=${encodeURIComponent(threadId)}`,
|
package/src/hooks/useMCPTools.ts
CHANGED
|
@@ -11,17 +11,19 @@ export function useMCPTools({
|
|
|
11
11
|
auth,
|
|
12
12
|
mcp,
|
|
13
13
|
environment,
|
|
14
|
+
gramEnvironment,
|
|
14
15
|
}: {
|
|
15
16
|
auth: Auth
|
|
16
17
|
mcp: string | undefined
|
|
17
18
|
environment: Record<string, unknown>
|
|
19
|
+
gramEnvironment?: string
|
|
18
20
|
}): UseQueryResult<MCPToolsResult, Error> {
|
|
19
21
|
const authQueryKey = Object.entries(auth.headers ?? {}).map(
|
|
20
22
|
(k, v) => `${k}:${v}`
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
const queryResult = useQuery({
|
|
24
|
-
queryKey: ['mcpTools', mcp, ...authQueryKey],
|
|
26
|
+
queryKey: ['mcpTools', mcp, gramEnvironment, ...authQueryKey],
|
|
25
27
|
queryFn: async () => {
|
|
26
28
|
assert(!auth.isLoading, 'No auth found')
|
|
27
29
|
assert(mcp, 'No MCP URL found')
|
|
@@ -34,6 +36,7 @@ export function useMCPTools({
|
|
|
34
36
|
headers: {
|
|
35
37
|
...transformEnvironmentToHeaders(environment ?? {}),
|
|
36
38
|
...auth.headers,
|
|
39
|
+
...(gramEnvironment && { 'Gram-Environment': gramEnvironment }),
|
|
37
40
|
},
|
|
38
41
|
},
|
|
39
42
|
})
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { useElements } from './hooks/useElements'
|
|
|
9
9
|
|
|
10
10
|
// Core Components
|
|
11
11
|
export { Chat } from '@/components/Chat'
|
|
12
|
-
export {
|
|
12
|
+
export { ChatHistory } from '@/components/ChatHistory'
|
|
13
13
|
|
|
14
14
|
// Frontend Tools
|
|
15
15
|
export { defineFrontendTool } from './lib/tools'
|
package/src/types/index.ts
CHANGED
|
@@ -111,6 +111,13 @@ export interface ElementsConfig {
|
|
|
111
111
|
*/
|
|
112
112
|
environment?: Record<string, unknown>
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* The environment slug to use for resolving secrets.
|
|
116
|
+
* When specified, this is sent as the Gram-Environment header to select
|
|
117
|
+
* which environment's secrets to use for tool execution.
|
|
118
|
+
*/
|
|
119
|
+
gramEnvironment?: string
|
|
120
|
+
|
|
114
121
|
/**
|
|
115
122
|
* The layout variant for the chat interface.
|
|
116
123
|
*
|