@gram-ai/elements 1.19.0 → 1.20.0
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/Variants.stories.d.ts +2 -0
- package/dist/elements.cjs +55 -53
- package/dist/elements.cjs.map +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +10083 -8852
- package/dist/elements.js.map +1 -1
- package/dist/hooks/useGramThreadListAdapter.d.ts +10 -0
- package/dist/index-B52U8PL6.cjs +99 -0
- package/dist/index-B52U8PL6.cjs.map +1 -0
- package/dist/{index-Cb5sxQuN.js → index-DaF9fGY-.js} +694 -1398
- package/dist/index-DaF9fGY-.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/lib/messageConverter.d.ts +45 -0
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/types/index.d.ts +39 -0
- package/package.json +1 -1
- package/src/components/Chat/stories/Variants.stories.tsx +49 -1
- package/src/components/assistant-ui/assistant-modal.tsx +18 -3
- package/src/components/assistant-ui/assistant-sidecar.tsx +18 -3
- package/src/components/assistant-ui/thread-list.tsx +52 -25
- package/src/contexts/ElementsProvider.tsx +150 -29
- package/src/hooks/useGramThreadListAdapter.tsx +302 -0
- package/src/index.ts +4 -0
- package/src/lib/messageConverter.ts +241 -0
- package/src/plugins/chart/component.tsx +15 -7
- package/src/plugins/chart/index.ts +83 -1
- package/src/types/index.ts +42 -0
- package/dist/index-Cb5sxQuN.js.map +0 -1
- package/dist/index-hrhDHFgW.cjs +0 -19
- package/dist/index-hrhDHFgW.cjs.map +0 -1
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
import { recommended } from '@/plugins'
|
|
15
15
|
import { ElementsConfig, Model } from '@/types'
|
|
16
16
|
import { Plugin } from '@/types/plugins'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
AssistantRuntimeProvider,
|
|
19
|
+
AssistantTool,
|
|
20
|
+
unstable_useRemoteThreadListRuntime as useRemoteThreadListRuntime,
|
|
21
|
+
} from '@assistant-ui/react'
|
|
18
22
|
import {
|
|
19
23
|
frontendTools as convertFrontendToolsToAISDKTools,
|
|
20
24
|
useChatRuntime,
|
|
@@ -41,6 +45,7 @@ import {
|
|
|
41
45
|
import { useAuth } from '../hooks/useAuth'
|
|
42
46
|
import { ElementsContext } from './contexts'
|
|
43
47
|
import { ToolApprovalProvider } from './ToolApprovalContext'
|
|
48
|
+
import { useGramThreadListAdapter } from '@/hooks/useGramThreadListAdapter'
|
|
44
49
|
|
|
45
50
|
export interface ElementsProviderProps {
|
|
46
51
|
children: ReactNode
|
|
@@ -78,8 +83,8 @@ function cleanMessagesForModel(messages: UIMessage[]): UIMessage[] {
|
|
|
78
83
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
84
|
const cleanedParts = partsArray.map((part: any) => {
|
|
80
85
|
// Strip providerOptions and providerMetadata from all remaining parts
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
82
|
-
const { callProviderMetadata, ...cleanPart } = part
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
87
|
+
const { callProviderMetadata: _, ...cleanPart } = part
|
|
83
88
|
return cleanPart
|
|
84
89
|
})
|
|
85
90
|
|
|
@@ -90,6 +95,10 @@ function cleanMessagesForModel(messages: UIMessage[]): UIMessage[] {
|
|
|
90
95
|
})
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Main provider component that sets up auth, tools, and transport.
|
|
100
|
+
* Delegates to either WithHistory or WithoutHistory based on config.
|
|
101
|
+
*/
|
|
93
102
|
const ElementsProviderWithApproval = ({
|
|
94
103
|
children,
|
|
95
104
|
config,
|
|
@@ -109,7 +118,6 @@ const ElementsProviderWithApproval = ({
|
|
|
109
118
|
)
|
|
110
119
|
const [isOpen, setIsOpen] = useState(config.modal?.defaultOpen)
|
|
111
120
|
|
|
112
|
-
// If there are any user provided plugins, use them, otherwise use the recommended plugins
|
|
113
121
|
const plugins = config.plugins ?? recommended
|
|
114
122
|
|
|
115
123
|
const systemPrompt = mergeInternalSystemPromptWith(
|
|
@@ -117,7 +125,7 @@ const ElementsProviderWithApproval = ({
|
|
|
117
125
|
plugins
|
|
118
126
|
)
|
|
119
127
|
|
|
120
|
-
const { data: mcpTools
|
|
128
|
+
const { data: mcpTools } = useMCPTools({
|
|
121
129
|
auth,
|
|
122
130
|
mcp: config.mcp,
|
|
123
131
|
environment: config.environment ?? {},
|
|
@@ -160,8 +168,13 @@ const ElementsProviderWithApproval = ({
|
|
|
160
168
|
}
|
|
161
169
|
}, [config.tools?.toolsRequiringApproval, getApprovalHelpers])
|
|
162
170
|
|
|
163
|
-
//
|
|
164
|
-
|
|
171
|
+
// Ref to access runtime from within transport's sendMessages.
|
|
172
|
+
// This solves a circular dependency: transport needs runtime.thread.getModelContext(),
|
|
173
|
+
// but runtime is created using transport. The ref gets populated after runtime creation.
|
|
174
|
+
const runtimeRef = useRef<ReturnType<typeof useChatRuntime> | null>(null)
|
|
175
|
+
|
|
176
|
+
// Create chat transport configuration
|
|
177
|
+
const transport = useMemo<ChatTransport<UIMessage>>(
|
|
165
178
|
() => ({
|
|
166
179
|
sendMessages: async ({ messages, abortSignal }) => {
|
|
167
180
|
const usingCustomModel = !!config.languageModel
|
|
@@ -170,7 +183,7 @@ const ElementsProviderWithApproval = ({
|
|
|
170
183
|
throw new Error('Session is loading')
|
|
171
184
|
}
|
|
172
185
|
|
|
173
|
-
const context =
|
|
186
|
+
const context = runtimeRef.current?.thread.getModelContext()
|
|
174
187
|
const frontendTools = toAISDKTools(
|
|
175
188
|
getEnabledTools(context?.tools ?? {})
|
|
176
189
|
)
|
|
@@ -231,52 +244,160 @@ const ElementsProviderWithApproval = ({
|
|
|
231
244
|
}
|
|
232
245
|
},
|
|
233
246
|
reconnectToStream: async () => {
|
|
234
|
-
// Not implemented for client-side streaming
|
|
235
247
|
throw new Error('Stream reconnection not supported')
|
|
236
248
|
},
|
|
237
249
|
}),
|
|
238
250
|
[
|
|
239
|
-
config,
|
|
240
251
|
config.languageModel,
|
|
252
|
+
config.tools?.toolsRequiringApproval,
|
|
241
253
|
model,
|
|
242
254
|
systemPrompt,
|
|
243
255
|
mcpTools,
|
|
244
|
-
mcpToolsLoading,
|
|
245
256
|
getApprovalHelpers,
|
|
246
257
|
apiUrl,
|
|
247
258
|
auth.headers,
|
|
259
|
+
auth.isLoading,
|
|
248
260
|
]
|
|
249
261
|
)
|
|
250
262
|
|
|
251
|
-
const
|
|
252
|
-
|
|
263
|
+
const historyEnabled = config.history?.enabled ?? false
|
|
264
|
+
|
|
265
|
+
// Shared context value for ElementsContext
|
|
266
|
+
const contextValue = useMemo(
|
|
267
|
+
() => ({
|
|
268
|
+
config,
|
|
269
|
+
setModel,
|
|
270
|
+
model,
|
|
271
|
+
isExpanded,
|
|
272
|
+
setIsExpanded,
|
|
273
|
+
isOpen: isOpen ?? false,
|
|
274
|
+
setIsOpen,
|
|
275
|
+
plugins,
|
|
276
|
+
}),
|
|
277
|
+
[config, model, isExpanded, isOpen, plugins]
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const frontendTools = config.tools?.frontendTools ?? {}
|
|
281
|
+
|
|
282
|
+
// Render the appropriate runtime provider based on history config.
|
|
283
|
+
// We use separate components to avoid conditional hook calls.
|
|
284
|
+
if (historyEnabled && !auth.isLoading) {
|
|
285
|
+
return (
|
|
286
|
+
<ElementsProviderWithHistory
|
|
287
|
+
transport={transport}
|
|
288
|
+
apiUrl={apiUrl}
|
|
289
|
+
headers={auth.headers}
|
|
290
|
+
contextValue={contextValue}
|
|
291
|
+
runtimeRef={runtimeRef}
|
|
292
|
+
frontendTools={frontendTools}
|
|
293
|
+
>
|
|
294
|
+
{children}
|
|
295
|
+
</ElementsProviderWithHistory>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<ElementsProviderWithoutHistory
|
|
301
|
+
transport={transport}
|
|
302
|
+
contextValue={contextValue}
|
|
303
|
+
runtimeRef={runtimeRef}
|
|
304
|
+
frontendTools={frontendTools}
|
|
305
|
+
>
|
|
306
|
+
{children}
|
|
307
|
+
</ElementsProviderWithoutHistory>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Separate component for history-enabled mode to avoid conditional hook calls
|
|
312
|
+
interface ElementsProviderWithHistoryProps {
|
|
313
|
+
children: ReactNode
|
|
314
|
+
transport: ChatTransport<UIMessage>
|
|
315
|
+
apiUrl: string
|
|
316
|
+
headers: Record<string, string>
|
|
317
|
+
contextValue: React.ContextType<typeof ElementsContext>
|
|
318
|
+
runtimeRef: React.RefObject<ReturnType<typeof useChatRuntime> | null>
|
|
319
|
+
frontendTools: Record<string, AssistantTool>
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const ElementsProviderWithHistory = ({
|
|
323
|
+
children,
|
|
324
|
+
transport,
|
|
325
|
+
apiUrl,
|
|
326
|
+
headers,
|
|
327
|
+
contextValue,
|
|
328
|
+
runtimeRef,
|
|
329
|
+
frontendTools,
|
|
330
|
+
}: ElementsProviderWithHistoryProps) => {
|
|
331
|
+
const threadListAdapter = useGramThreadListAdapter({ apiUrl, headers })
|
|
332
|
+
|
|
333
|
+
// Hook factory for creating the base chat runtime
|
|
334
|
+
const useChatRuntimeHook = useCallback(() => {
|
|
335
|
+
return useChatRuntime({ transport })
|
|
336
|
+
}, [transport])
|
|
337
|
+
|
|
338
|
+
const runtime = useRemoteThreadListRuntime({
|
|
339
|
+
adapter: threadListAdapter,
|
|
340
|
+
runtimeHook: useChatRuntimeHook,
|
|
253
341
|
})
|
|
254
342
|
|
|
343
|
+
// Populate runtimeRef so transport can access thread context
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
runtimeRef.current = runtime as ReturnType<typeof useChatRuntime>
|
|
346
|
+
}, [runtime, runtimeRef])
|
|
347
|
+
|
|
348
|
+
// Get the Provider from our adapter to wrap the content
|
|
349
|
+
const HistoryProvider =
|
|
350
|
+
threadListAdapter.unstable_Provider ??
|
|
351
|
+
(({ children }: { children: React.ReactNode }) => <>{children}</>)
|
|
352
|
+
|
|
255
353
|
return (
|
|
256
354
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
257
|
-
<
|
|
258
|
-
value={
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
plugins,
|
|
267
|
-
}}
|
|
268
|
-
>
|
|
269
|
-
{children}
|
|
355
|
+
<HistoryProvider>
|
|
356
|
+
<ElementsContext.Provider value={contextValue}>
|
|
357
|
+
{children}
|
|
358
|
+
<FrontendTools tools={frontendTools} />
|
|
359
|
+
</ElementsContext.Provider>
|
|
360
|
+
</HistoryProvider>
|
|
361
|
+
</AssistantRuntimeProvider>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
270
364
|
|
|
271
|
-
|
|
272
|
-
|
|
365
|
+
// Separate component for non-history mode to avoid conditional hook calls
|
|
366
|
+
interface ElementsProviderWithoutHistoryProps {
|
|
367
|
+
children: ReactNode
|
|
368
|
+
transport: ChatTransport<UIMessage>
|
|
369
|
+
contextValue: React.ContextType<typeof ElementsContext>
|
|
370
|
+
runtimeRef: React.RefObject<ReturnType<typeof useChatRuntime> | null>
|
|
371
|
+
frontendTools: Record<string, AssistantTool>
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const ElementsProviderWithoutHistory = ({
|
|
375
|
+
children,
|
|
376
|
+
transport,
|
|
377
|
+
contextValue,
|
|
378
|
+
runtimeRef,
|
|
379
|
+
frontendTools,
|
|
380
|
+
}: ElementsProviderWithoutHistoryProps) => {
|
|
381
|
+
const runtime = useChatRuntime({ transport })
|
|
382
|
+
|
|
383
|
+
// Populate runtimeRef so transport can access thread context
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
runtimeRef.current = runtime
|
|
386
|
+
}, [runtime, runtimeRef])
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
390
|
+
<ElementsContext.Provider value={contextValue}>
|
|
391
|
+
{children}
|
|
392
|
+
<FrontendTools tools={frontendTools} />
|
|
273
393
|
</ElementsContext.Provider>
|
|
274
394
|
</AssistantRuntimeProvider>
|
|
275
395
|
)
|
|
276
396
|
}
|
|
277
397
|
|
|
398
|
+
const queryClient = new QueryClient()
|
|
399
|
+
|
|
278
400
|
export const ElementsProvider = (props: ElementsProviderProps) => {
|
|
279
|
-
const queryClient = new QueryClient()
|
|
280
401
|
return (
|
|
281
402
|
<QueryClientProvider client={queryClient}>
|
|
282
403
|
<ToolApprovalProvider>
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {
|
|
2
|
+
unstable_RemoteThreadListAdapter as RemoteThreadListAdapter,
|
|
3
|
+
ThreadMessage,
|
|
4
|
+
RuntimeAdapterProvider,
|
|
5
|
+
ThreadHistoryAdapter,
|
|
6
|
+
useAssistantApi,
|
|
7
|
+
type AssistantApi,
|
|
8
|
+
} from '@assistant-ui/react'
|
|
9
|
+
import type { AssistantStream } from 'assistant-stream'
|
|
10
|
+
import {
|
|
11
|
+
GramChatOverview,
|
|
12
|
+
GramChat,
|
|
13
|
+
convertGramMessagesToExported,
|
|
14
|
+
} from '@/lib/messageConverter'
|
|
15
|
+
import {
|
|
16
|
+
useCallback,
|
|
17
|
+
useEffect,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
useState,
|
|
21
|
+
type PropsWithChildren,
|
|
22
|
+
} from 'react'
|
|
23
|
+
|
|
24
|
+
export interface ThreadListAdapterOptions {
|
|
25
|
+
apiUrl: string
|
|
26
|
+
headers: Record<string, string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ListChatsResponse {
|
|
30
|
+
chats: GramChatOverview[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Thread history adapter that loads messages from Gram API.
|
|
35
|
+
* Note: We use `as ThreadHistoryAdapter` cast because the withFormat generic
|
|
36
|
+
* signature doesn't match our concrete implementation, but it works at runtime.
|
|
37
|
+
*/
|
|
38
|
+
class GramThreadHistoryAdapter {
|
|
39
|
+
private apiUrl: string
|
|
40
|
+
private headers: Record<string, string>
|
|
41
|
+
private store: AssistantApi
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
apiUrl: string,
|
|
45
|
+
headers: Record<string, string>,
|
|
46
|
+
store: AssistantApi
|
|
47
|
+
) {
|
|
48
|
+
this.apiUrl = apiUrl
|
|
49
|
+
this.headers = headers
|
|
50
|
+
this.store = store
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async load() {
|
|
54
|
+
const remoteId = this.store.threadListItem().getState().remoteId
|
|
55
|
+
if (!remoteId) {
|
|
56
|
+
return { messages: [], headId: null }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(
|
|
61
|
+
`${this.apiUrl}/rpc/chat.load?id=${encodeURIComponent(remoteId)}`,
|
|
62
|
+
{ headers: this.headers }
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
console.error('Failed to load chat:', response.status)
|
|
67
|
+
return { messages: [], headId: null }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const chat = (await response.json()) as GramChat
|
|
71
|
+
return convertGramMessagesToExported(chat.messages)
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error loading chat:', error)
|
|
74
|
+
return { messages: [], headId: null }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async append() {
|
|
79
|
+
// No-op: Gram persists messages server-side during streaming.
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Required by ThreadHistoryAdapter - wraps adapter with format conversion.
|
|
83
|
+
// The _formatAdapter param is part of the interface but unused since we handle conversion ourselves.
|
|
84
|
+
// Using arrow functions to capture `this` lexically.
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
86
|
+
withFormat(_formatAdapter: unknown) {
|
|
87
|
+
return {
|
|
88
|
+
load: async () => {
|
|
89
|
+
const remoteId = this.store.threadListItem().getState().remoteId
|
|
90
|
+
if (!remoteId) {
|
|
91
|
+
return { messages: [], headId: null }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch(
|
|
96
|
+
`${this.apiUrl}/rpc/chat.load?id=${encodeURIComponent(remoteId)}`,
|
|
97
|
+
{ headers: this.headers }
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
console.error('Failed to load chat (withFormat):', response.status)
|
|
102
|
+
return { messages: [], headId: null }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const chat = (await response.json()) as GramChat
|
|
106
|
+
|
|
107
|
+
// Filter out system messages (assistant-ui doesn't support them in the import path)
|
|
108
|
+
const filteredMessages = chat.messages.filter(
|
|
109
|
+
(msg) => msg.role !== 'system'
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if (filteredMessages.length === 0) {
|
|
113
|
+
return { messages: [], headId: null }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Convert to the format expected by useExternalHistory
|
|
117
|
+
// It expects UIMessage format with role and parts array
|
|
118
|
+
let prevId: string | null = null
|
|
119
|
+
const messages = filteredMessages.map((msg, index) => {
|
|
120
|
+
// Generate a fallback ID if missing (required by assistant-ui's MessageRepository)
|
|
121
|
+
const messageId = msg.id || `fallback-${index}-${Date.now()}`
|
|
122
|
+
const uiMessage = {
|
|
123
|
+
parentId: prevId,
|
|
124
|
+
message: {
|
|
125
|
+
id: messageId,
|
|
126
|
+
role: msg.role as 'user' | 'assistant',
|
|
127
|
+
parts: [{ type: 'text' as const, text: msg.content || '' }],
|
|
128
|
+
createdAt: msg.createdAt ? new Date(msg.createdAt) : new Date(),
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
prevId = messageId
|
|
132
|
+
return uiMessage
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
headId: prevId,
|
|
137
|
+
messages,
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error loading chat (withFormat):', error)
|
|
141
|
+
return { messages: [], headId: null }
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
append: async () => {
|
|
145
|
+
// No-op
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Hook to create a Gram thread history adapter.
|
|
153
|
+
*/
|
|
154
|
+
function useGramThreadHistoryAdapter(
|
|
155
|
+
optionsRef: React.RefObject<ThreadListAdapterOptions>
|
|
156
|
+
): ThreadHistoryAdapter {
|
|
157
|
+
const store = useAssistantApi()
|
|
158
|
+
const [adapter] = useState(
|
|
159
|
+
() =>
|
|
160
|
+
new GramThreadHistoryAdapter(
|
|
161
|
+
optionsRef.current.apiUrl,
|
|
162
|
+
optionsRef.current.headers,
|
|
163
|
+
store
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
// Cast to ThreadHistoryAdapter - the withFormat generic doesn't match but works at runtime
|
|
167
|
+
return adapter as unknown as ThreadHistoryAdapter
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Hook that creates a RemoteThreadListAdapter for the Gram API.
|
|
172
|
+
* This properly handles React component identity for the Provider.
|
|
173
|
+
*/
|
|
174
|
+
export function useGramThreadListAdapter(
|
|
175
|
+
options: ThreadListAdapterOptions
|
|
176
|
+
): RemoteThreadListAdapter {
|
|
177
|
+
const optionsRef = useRef(options)
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
optionsRef.current = options
|
|
180
|
+
}, [options])
|
|
181
|
+
|
|
182
|
+
// Create stable Provider component using useCallback
|
|
183
|
+
const unstable_Provider = useCallback(function GramHistoryProvider({
|
|
184
|
+
children,
|
|
185
|
+
}: PropsWithChildren) {
|
|
186
|
+
const history = useGramThreadHistoryAdapter(optionsRef)
|
|
187
|
+
const adapters = useMemo(() => ({ history }), [history])
|
|
188
|
+
return (
|
|
189
|
+
<RuntimeAdapterProvider adapters={adapters}>
|
|
190
|
+
{children}
|
|
191
|
+
</RuntimeAdapterProvider>
|
|
192
|
+
)
|
|
193
|
+
}, [])
|
|
194
|
+
|
|
195
|
+
// Return adapter with stable methods
|
|
196
|
+
return useMemo(
|
|
197
|
+
() => ({
|
|
198
|
+
unstable_Provider,
|
|
199
|
+
|
|
200
|
+
async list() {
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetch(
|
|
203
|
+
`${optionsRef.current.apiUrl}/rpc/chat.list`,
|
|
204
|
+
{
|
|
205
|
+
headers: optionsRef.current.headers,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
console.error('Failed to list chats:', response.status)
|
|
211
|
+
return { threads: [] }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const data = (await response.json()) as ListChatsResponse
|
|
215
|
+
return {
|
|
216
|
+
threads: data.chats.map((chat) => ({
|
|
217
|
+
remoteId: chat.id,
|
|
218
|
+
externalId: chat.id,
|
|
219
|
+
status: 'regular' as const,
|
|
220
|
+
title: chat.title || 'New Chat',
|
|
221
|
+
})),
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Error listing chats:', error)
|
|
225
|
+
return { threads: [] }
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
async initialize(threadId: string) {
|
|
230
|
+
return {
|
|
231
|
+
remoteId: threadId,
|
|
232
|
+
externalId: threadId,
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
async rename() {
|
|
237
|
+
// No-op
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async archive() {
|
|
241
|
+
// No-op
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
async unarchive() {
|
|
245
|
+
// No-op
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
async delete() {
|
|
249
|
+
// No-op
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
async generateTitle(
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
254
|
+
_remoteId: string,
|
|
255
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
256
|
+
_messages: readonly ThreadMessage[]
|
|
257
|
+
): Promise<AssistantStream> {
|
|
258
|
+
// Return an empty stream that immediately completes
|
|
259
|
+
// Server generates titles automatically, so we just provide a placeholder
|
|
260
|
+
return new ReadableStream({
|
|
261
|
+
start(controller) {
|
|
262
|
+
controller.close()
|
|
263
|
+
},
|
|
264
|
+
}) as AssistantStream
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
async fetch(threadId: string) {
|
|
268
|
+
try {
|
|
269
|
+
const response = await fetch(
|
|
270
|
+
`${optionsRef.current.apiUrl}/rpc/chat.load?id=${encodeURIComponent(threadId)}`,
|
|
271
|
+
{
|
|
272
|
+
headers: optionsRef.current.headers,
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
console.error('Failed to fetch thread:', response.status)
|
|
278
|
+
return {
|
|
279
|
+
remoteId: threadId,
|
|
280
|
+
status: 'regular' as const,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const chat = await response.json()
|
|
285
|
+
return {
|
|
286
|
+
remoteId: chat.id,
|
|
287
|
+
externalId: chat.id,
|
|
288
|
+
status: 'regular' as const,
|
|
289
|
+
title: chat.title || 'New Chat',
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error('Error fetching thread:', error)
|
|
293
|
+
return {
|
|
294
|
+
remoteId: threadId,
|
|
295
|
+
status: 'regular' as const,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
}),
|
|
300
|
+
[unstable_Provider]
|
|
301
|
+
)
|
|
302
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,10 +3,13 @@ import './global.css'
|
|
|
3
3
|
|
|
4
4
|
// Context Providers
|
|
5
5
|
export { ElementsProvider as GramElementsProvider } from './contexts/ElementsProvider'
|
|
6
|
+
export { ElementsProvider } from './contexts/ElementsProvider'
|
|
6
7
|
export { useElements as useGramElements } from './hooks/useElements'
|
|
8
|
+
export { useElements } from './hooks/useElements'
|
|
7
9
|
|
|
8
10
|
// Core Components
|
|
9
11
|
export { Chat } from '@/components/Chat'
|
|
12
|
+
export { ThreadList as ChatHistory } from '@/components/assistant-ui/thread-list'
|
|
10
13
|
|
|
11
14
|
// Frontend Tools
|
|
12
15
|
export { defineFrontendTool } from './lib/tools'
|
|
@@ -25,6 +28,7 @@ export type {
|
|
|
25
28
|
Dimensions,
|
|
26
29
|
ElementsConfig,
|
|
27
30
|
GetSessionFn,
|
|
31
|
+
HistoryConfig,
|
|
28
32
|
ModalConfig,
|
|
29
33
|
ModalTriggerPosition,
|
|
30
34
|
Model,
|