@gram-ai/elements 1.18.5 → 1.18.6
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/Sidecar.stories.d.ts +6 -0
- package/dist/elements.cjs +22 -21
- package/dist/elements.cjs.map +1 -0
- package/dist/elements.js +601 -591
- package/dist/elements.js.map +1 -0
- package/dist/index-Bj7jPiuy.cjs +1 -0
- package/dist/index-Bj7jPiuy.cjs.map +1 -0
- package/dist/index-CJRypLIa.js +1 -0
- package/dist/index-CJRypLIa.js.map +1 -0
- package/dist/plugins.cjs +1 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.js +1 -0
- package/dist/plugins.js.map +1 -0
- package/dist/server.cjs +1 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -0
- package/package.json +3 -2
- package/src/components/Chat/index.tsx +21 -0
- package/src/components/Chat/stories/ColorScheme.stories.tsx +52 -0
- package/src/components/Chat/stories/Composer.stories.tsx +42 -0
- package/src/components/Chat/stories/Customization.stories.tsx +88 -0
- package/src/components/Chat/stories/Density.stories.tsx +52 -0
- package/src/components/Chat/stories/FrontendTools.stories.tsx +145 -0
- package/src/components/Chat/stories/Modal.stories.tsx +84 -0
- package/src/components/Chat/stories/Model.stories.tsx +32 -0
- package/src/components/Chat/stories/Plugins.stories.tsx +50 -0
- package/src/components/Chat/stories/Radius.stories.tsx +52 -0
- package/src/components/Chat/stories/Sidecar.stories.tsx +27 -0
- package/src/components/Chat/stories/ToolApproval.stories.tsx +110 -0
- package/src/components/Chat/stories/Tools.stories.tsx +175 -0
- package/src/components/Chat/stories/Variants.stories.tsx +46 -0
- package/src/components/Chat/stories/Welcome.stories.tsx +42 -0
- package/src/components/FrontendTools/index.tsx +9 -0
- package/src/components/assistant-ui/assistant-modal.tsx +255 -0
- package/src/components/assistant-ui/assistant-sidecar.tsx +88 -0
- package/src/components/assistant-ui/attachment.tsx +233 -0
- package/src/components/assistant-ui/markdown-text.tsx +240 -0
- package/src/components/assistant-ui/reasoning.tsx +261 -0
- package/src/components/assistant-ui/thread-list.tsx +97 -0
- package/src/components/assistant-ui/thread.tsx +632 -0
- package/src/components/assistant-ui/tool-fallback.tsx +111 -0
- package/src/components/assistant-ui/tool-group.tsx +59 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +57 -0
- package/src/components/ui/avatar.tsx +51 -0
- package/src/components/ui/button.tsx +27 -0
- package/src/components/ui/buttonVariants.ts +33 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/dialog.tsx +141 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/tool-ui.stories.tsx +146 -0
- package/src/components/ui/tool-ui.tsx +676 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/contexts/ElementsProvider.tsx +256 -0
- package/src/contexts/ToolApprovalContext.tsx +120 -0
- package/src/contexts/contexts.ts +10 -0
- package/src/global.css +136 -0
- package/src/hooks/useAuth.ts +71 -0
- package/src/hooks/useDensity.ts +110 -0
- package/src/hooks/useElements.ts +14 -0
- package/src/hooks/useExpanded.ts +20 -0
- package/src/hooks/useMCPTools.ts +73 -0
- package/src/hooks/usePluginComponents.ts +34 -0
- package/src/hooks/useRadius.ts +42 -0
- package/src/hooks/useSession.ts +38 -0
- package/src/hooks/useThemeProps.ts +24 -0
- package/src/hooks/useToolApproval.ts +16 -0
- package/src/index.ts +45 -0
- package/src/lib/api.test.ts +90 -0
- package/src/lib/api.ts +8 -0
- package/src/lib/auth.ts +10 -0
- package/src/lib/easing.ts +1 -0
- package/src/lib/humanize.ts +14 -0
- package/src/lib/models.ts +22 -0
- package/src/lib/tools.ts +210 -0
- package/src/lib/utils.ts +16 -0
- package/src/plugins/README.md +49 -0
- package/src/plugins/chart/component.tsx +102 -0
- package/src/plugins/chart/index.ts +27 -0
- package/src/plugins/index.ts +7 -0
- package/src/server.ts +89 -0
- package/src/types/index.ts +726 -0
- package/src/types/plugins.ts +65 -0
- package/src/vite-env.d.ts +12 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return (
|
|
25
|
+
<TooltipProvider>
|
|
26
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
27
|
+
</TooltipProvider>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TooltipTrigger({
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
34
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function TooltipContent({
|
|
38
|
+
className,
|
|
39
|
+
sideOffset = 0,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
43
|
+
return (
|
|
44
|
+
<TooltipPrimitive.Portal>
|
|
45
|
+
<TooltipPrimitive.Content
|
|
46
|
+
data-slot="tooltip-content"
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
className={cn(
|
|
49
|
+
'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-20 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]" />
|
|
56
|
+
</TooltipPrimitive.Content>
|
|
57
|
+
</TooltipPrimitive.Portal>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { FrontendTools } from '@/components/FrontendTools'
|
|
2
|
+
import { useMCPTools } from '@/hooks/useMCPTools'
|
|
3
|
+
import { useToolApproval } from '@/hooks/useToolApproval'
|
|
4
|
+
import { MODELS } from '@/lib/models'
|
|
5
|
+
import {
|
|
6
|
+
clearFrontendToolApprovalConfig,
|
|
7
|
+
getEnabledTools,
|
|
8
|
+
setFrontendToolApprovalConfig,
|
|
9
|
+
toAISDKTools,
|
|
10
|
+
wrapToolsWithApproval,
|
|
11
|
+
type ApprovalHelpers,
|
|
12
|
+
} from '@/lib/tools'
|
|
13
|
+
import { recommended } from '@/plugins'
|
|
14
|
+
import { ElementsConfig, Model } from '@/types'
|
|
15
|
+
import { Plugin } from '@/types/plugins'
|
|
16
|
+
import { AssistantRuntimeProvider } from '@assistant-ui/react'
|
|
17
|
+
import {
|
|
18
|
+
frontendTools as convertFrontendToolsToAISDKTools,
|
|
19
|
+
useChatRuntime,
|
|
20
|
+
} from '@assistant-ui/react-ai-sdk'
|
|
21
|
+
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
|
22
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
23
|
+
import {
|
|
24
|
+
convertToModelMessages,
|
|
25
|
+
smoothStream,
|
|
26
|
+
stepCountIs,
|
|
27
|
+
streamText,
|
|
28
|
+
ToolSet,
|
|
29
|
+
type ChatTransport,
|
|
30
|
+
type UIMessage,
|
|
31
|
+
} from 'ai'
|
|
32
|
+
import {
|
|
33
|
+
ReactNode,
|
|
34
|
+
useCallback,
|
|
35
|
+
useEffect,
|
|
36
|
+
useMemo,
|
|
37
|
+
useRef,
|
|
38
|
+
useState,
|
|
39
|
+
} from 'react'
|
|
40
|
+
import { useAuth } from '../hooks/useAuth'
|
|
41
|
+
import { ElementsContext } from './contexts'
|
|
42
|
+
import { ToolApprovalProvider } from './ToolApprovalContext'
|
|
43
|
+
import { getApiUrl } from '@/lib/api'
|
|
44
|
+
|
|
45
|
+
export interface ElementsProviderProps {
|
|
46
|
+
children: ReactNode
|
|
47
|
+
config: ElementsConfig
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const BASE_SYSTEM_PROMPT = `You are a helpful assistant that can answer questions and help with tasks.`
|
|
51
|
+
|
|
52
|
+
function mergeInternalSystemPromptWith(
|
|
53
|
+
userSystemPrompt: string | undefined,
|
|
54
|
+
plugins: Plugin[]
|
|
55
|
+
) {
|
|
56
|
+
return `
|
|
57
|
+
${BASE_SYSTEM_PROMPT}
|
|
58
|
+
|
|
59
|
+
User-provided System Prompt:
|
|
60
|
+
${userSystemPrompt ?? 'None provided'}
|
|
61
|
+
|
|
62
|
+
Utilities:
|
|
63
|
+
${plugins.map((plugin) => `- ${plugin.language}: ${plugin.prompt}`).join('\n')}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ElementsProviderWithApproval = ({
|
|
67
|
+
children,
|
|
68
|
+
config,
|
|
69
|
+
}: ElementsProviderProps) => {
|
|
70
|
+
const apiUrl = getApiUrl(config)
|
|
71
|
+
const auth = useAuth({
|
|
72
|
+
auth: config.api,
|
|
73
|
+
projectSlug: config.projectSlug,
|
|
74
|
+
})
|
|
75
|
+
const toolApproval = useToolApproval()
|
|
76
|
+
|
|
77
|
+
const [model, setModel] = useState<Model>(
|
|
78
|
+
config.model?.defaultModel ?? MODELS[0]
|
|
79
|
+
)
|
|
80
|
+
const [isExpanded, setIsExpanded] = useState(
|
|
81
|
+
config.modal?.defaultExpanded ?? false
|
|
82
|
+
)
|
|
83
|
+
const [isOpen, setIsOpen] = useState(config.modal?.defaultOpen)
|
|
84
|
+
|
|
85
|
+
// If there are any user provided plugins, use them, otherwise use the recommended plugins
|
|
86
|
+
const plugins = config.plugins ?? recommended
|
|
87
|
+
|
|
88
|
+
const systemPrompt = mergeInternalSystemPromptWith(
|
|
89
|
+
config.systemPrompt,
|
|
90
|
+
plugins
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const { data: mcpTools, isLoading: mcpToolsLoading } = useMCPTools({
|
|
94
|
+
auth,
|
|
95
|
+
mcp: config.mcp,
|
|
96
|
+
environment: config.environment ?? {},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Store approval helpers in ref so they can be used in async contexts
|
|
100
|
+
const approvalHelpersRef = useRef<ApprovalHelpers>({
|
|
101
|
+
requestApproval: toolApproval.requestApproval,
|
|
102
|
+
isToolApproved: toolApproval.isToolApproved,
|
|
103
|
+
whitelistTool: toolApproval.whitelistTool,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
approvalHelpersRef.current = {
|
|
107
|
+
requestApproval: toolApproval.requestApproval,
|
|
108
|
+
isToolApproved: toolApproval.isToolApproved,
|
|
109
|
+
whitelistTool: toolApproval.whitelistTool,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const getApprovalHelpers = useCallback((): ApprovalHelpers => {
|
|
113
|
+
return {
|
|
114
|
+
requestApproval: (...args) =>
|
|
115
|
+
approvalHelpersRef.current.requestApproval(...args),
|
|
116
|
+
isToolApproved: (...args) =>
|
|
117
|
+
approvalHelpersRef.current.isToolApproved(...args),
|
|
118
|
+
whitelistTool: (...args) =>
|
|
119
|
+
approvalHelpersRef.current.whitelistTool(...args),
|
|
120
|
+
}
|
|
121
|
+
}, [])
|
|
122
|
+
|
|
123
|
+
// Set up frontend tool approval config for runtime checking
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (config.tools?.toolsRequiringApproval?.length) {
|
|
126
|
+
setFrontendToolApprovalConfig(
|
|
127
|
+
getApprovalHelpers(),
|
|
128
|
+
config.tools.toolsRequiringApproval
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
return () => {
|
|
132
|
+
clearFrontendToolApprovalConfig()
|
|
133
|
+
}
|
|
134
|
+
}, [config.tools?.toolsRequiringApproval, getApprovalHelpers])
|
|
135
|
+
|
|
136
|
+
// Create custom transport
|
|
137
|
+
const transport = useMemo<ChatTransport<UIMessage> | undefined>(
|
|
138
|
+
() => ({
|
|
139
|
+
sendMessages: async ({ messages, abortSignal }) => {
|
|
140
|
+
const usingCustomModel = !!config.languageModel
|
|
141
|
+
|
|
142
|
+
if (auth.isLoading) {
|
|
143
|
+
throw new Error('Session is loading')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const context = runtime.thread.getModelContext()
|
|
147
|
+
const frontendTools = toAISDKTools(
|
|
148
|
+
getEnabledTools(context?.tools ?? {})
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// Create OpenRouter model (only needed when not using custom model)
|
|
152
|
+
const openRouterModel = usingCustomModel
|
|
153
|
+
? null
|
|
154
|
+
: createOpenRouter({
|
|
155
|
+
baseURL: apiUrl,
|
|
156
|
+
apiKey: 'unused, but must be set',
|
|
157
|
+
headers: auth.headers,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
if (config.languageModel) {
|
|
161
|
+
console.log('Using custom language model', config.languageModel)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Combine tools - MCP tools only available when not using custom model
|
|
165
|
+
const combinedTools: ToolSet = {
|
|
166
|
+
...mcpTools,
|
|
167
|
+
...convertFrontendToolsToAISDKTools(frontendTools),
|
|
168
|
+
} as ToolSet
|
|
169
|
+
|
|
170
|
+
// Wrap tools that require approval
|
|
171
|
+
const tools = wrapToolsWithApproval(
|
|
172
|
+
combinedTools,
|
|
173
|
+
config.tools?.toolsRequiringApproval,
|
|
174
|
+
getApprovalHelpers()
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
// Stream the response
|
|
178
|
+
const modelToUse = config.languageModel
|
|
179
|
+
? config.languageModel
|
|
180
|
+
: openRouterModel!.chat(model)
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const result = streamText({
|
|
184
|
+
system: systemPrompt,
|
|
185
|
+
model: modelToUse,
|
|
186
|
+
messages: convertToModelMessages(messages),
|
|
187
|
+
tools,
|
|
188
|
+
stopWhen: stepCountIs(10),
|
|
189
|
+
experimental_transform: smoothStream({ delayInMs: 15 }),
|
|
190
|
+
abortSignal,
|
|
191
|
+
onError: ({ error }) => {
|
|
192
|
+
console.error('Stream error in onError callback:', error)
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return result.toUIMessageStream()
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('Error creating stream:', error)
|
|
199
|
+
throw error
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
reconnectToStream: async () => {
|
|
203
|
+
// Not implemented for client-side streaming
|
|
204
|
+
throw new Error('Stream reconnection not supported')
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
[
|
|
208
|
+
config,
|
|
209
|
+
config.languageModel,
|
|
210
|
+
model,
|
|
211
|
+
systemPrompt,
|
|
212
|
+
mcpTools,
|
|
213
|
+
mcpToolsLoading,
|
|
214
|
+
getApprovalHelpers,
|
|
215
|
+
apiUrl,
|
|
216
|
+
auth.headers,
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const runtime = useChatRuntime({
|
|
221
|
+
transport,
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
226
|
+
<ElementsContext.Provider
|
|
227
|
+
value={{
|
|
228
|
+
config,
|
|
229
|
+
setModel,
|
|
230
|
+
model,
|
|
231
|
+
isExpanded,
|
|
232
|
+
setIsExpanded,
|
|
233
|
+
isOpen: isOpen ?? false,
|
|
234
|
+
setIsOpen,
|
|
235
|
+
plugins,
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
|
|
240
|
+
{/* Doesn't render anything, but is used to register frontend tools */}
|
|
241
|
+
<FrontendTools tools={config.tools?.frontendTools ?? {}} />
|
|
242
|
+
</ElementsContext.Provider>
|
|
243
|
+
</AssistantRuntimeProvider>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const ElementsProvider = (props: ElementsProviderProps) => {
|
|
248
|
+
const queryClient = new QueryClient()
|
|
249
|
+
return (
|
|
250
|
+
<QueryClientProvider client={queryClient}>
|
|
251
|
+
<ToolApprovalProvider>
|
|
252
|
+
<ElementsProviderWithApproval {...props} />
|
|
253
|
+
</ToolApprovalProvider>
|
|
254
|
+
</QueryClientProvider>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useState, useCallback, type ReactNode } from 'react'
|
|
2
|
+
import { ToolApprovalContext } from './contexts'
|
|
3
|
+
|
|
4
|
+
interface PendingApproval {
|
|
5
|
+
toolCallId: string
|
|
6
|
+
toolName: string
|
|
7
|
+
args: unknown
|
|
8
|
+
resolve: (approved: boolean) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ToolApprovalContextType {
|
|
12
|
+
pendingApprovals: Map<string, PendingApproval>
|
|
13
|
+
approvedTools: Set<string>
|
|
14
|
+
requestApproval: (
|
|
15
|
+
toolName: string,
|
|
16
|
+
toolCallId: string,
|
|
17
|
+
args: unknown
|
|
18
|
+
) => Promise<boolean>
|
|
19
|
+
/** Whitelist a tool name so all future calls are auto-approved */
|
|
20
|
+
whitelistTool: (toolName: string) => void
|
|
21
|
+
/** Confirm a specific pending tool call approval */
|
|
22
|
+
confirmPendingApproval: (toolCallId: string) => void
|
|
23
|
+
/** Reject a specific pending tool call approval */
|
|
24
|
+
rejectPendingApproval: (toolCallId: string) => void
|
|
25
|
+
isToolApproved: (toolName: string) => boolean
|
|
26
|
+
getPendingApproval: (toolCallId: string) => PendingApproval | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ToolApprovalProvider({ children }: { children: ReactNode }) {
|
|
30
|
+
const [pendingApprovals, setPendingApprovals] = useState<
|
|
31
|
+
Map<string, PendingApproval>
|
|
32
|
+
>(new Map())
|
|
33
|
+
const [approvedTools, setApprovedTools] = useState<Set<string>>(new Set())
|
|
34
|
+
|
|
35
|
+
const requestApproval = useCallback(
|
|
36
|
+
(toolName: string, toolCallId: string, args: unknown): Promise<boolean> => {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const pending: PendingApproval = {
|
|
39
|
+
toolCallId,
|
|
40
|
+
toolName,
|
|
41
|
+
args,
|
|
42
|
+
resolve,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setPendingApprovals((prev) => {
|
|
46
|
+
const next = new Map(prev)
|
|
47
|
+
next.set(toolCallId, pending)
|
|
48
|
+
return next
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
},
|
|
52
|
+
[]
|
|
53
|
+
)
|
|
54
|
+
const whitelistTool = useCallback((toolName: string) => {
|
|
55
|
+
setApprovedTools((prev) => {
|
|
56
|
+
const next = new Set(prev)
|
|
57
|
+
next.add(toolName)
|
|
58
|
+
return next
|
|
59
|
+
})
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
const confirmPendingApproval = useCallback((toolCallId: string) => {
|
|
63
|
+
setPendingApprovals((prev) => {
|
|
64
|
+
const pending = prev.get(toolCallId)
|
|
65
|
+
if (pending) {
|
|
66
|
+
pending.resolve(true)
|
|
67
|
+
const next = new Map(prev)
|
|
68
|
+
next.delete(toolCallId)
|
|
69
|
+
return next
|
|
70
|
+
}
|
|
71
|
+
return prev
|
|
72
|
+
})
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
75
|
+
const rejectPendingApproval = useCallback((toolCallId: string) => {
|
|
76
|
+
setPendingApprovals((prev) => {
|
|
77
|
+
const pending = prev.get(toolCallId)
|
|
78
|
+
if (pending) {
|
|
79
|
+
pending.resolve(false)
|
|
80
|
+
const next = new Map(prev)
|
|
81
|
+
next.delete(toolCallId)
|
|
82
|
+
return next
|
|
83
|
+
}
|
|
84
|
+
return prev
|
|
85
|
+
})
|
|
86
|
+
}, [])
|
|
87
|
+
|
|
88
|
+
const isToolApproved = useCallback(
|
|
89
|
+
(toolName: string) => {
|
|
90
|
+
return approvedTools.has(toolName)
|
|
91
|
+
},
|
|
92
|
+
[approvedTools]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const getPendingApproval = useCallback(
|
|
96
|
+
(toolCallId: string) => {
|
|
97
|
+
return pendingApprovals.get(toolCallId)
|
|
98
|
+
},
|
|
99
|
+
[pendingApprovals]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<ToolApprovalContext.Provider
|
|
104
|
+
value={{
|
|
105
|
+
pendingApprovals,
|
|
106
|
+
approvedTools,
|
|
107
|
+
requestApproval,
|
|
108
|
+
whitelistTool,
|
|
109
|
+
confirmPendingApproval,
|
|
110
|
+
rejectPendingApproval,
|
|
111
|
+
isToolApproved,
|
|
112
|
+
getPendingApproval,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
</ToolApprovalContext.Provider>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type { ToolApprovalContextType }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from 'react'
|
|
2
|
+
import type { ElementsContextType } from '@/types'
|
|
3
|
+
import { ToolApprovalContextType } from './ToolApprovalContext'
|
|
4
|
+
|
|
5
|
+
export const ElementsContext = createContext<ElementsContextType | undefined>(
|
|
6
|
+
undefined
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
export const ToolApprovalContext =
|
|
10
|
+
createContext<ToolApprovalContextType | null>(null)
|
package/src/global.css
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "tw-shimmer";
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
9
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
10
|
+
--radius-lg: var(--radius);
|
|
11
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
12
|
+
--color-background: var(--background);
|
|
13
|
+
--color-foreground: var(--foreground);
|
|
14
|
+
--color-card: var(--card);
|
|
15
|
+
--color-card-foreground: var(--card-foreground);
|
|
16
|
+
--color-popover: var(--popover);
|
|
17
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
18
|
+
--color-primary: var(--primary);
|
|
19
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
20
|
+
--color-secondary: var(--secondary);
|
|
21
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
22
|
+
--color-muted: var(--muted);
|
|
23
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
24
|
+
--color-accent: var(--accent);
|
|
25
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
26
|
+
--color-destructive: var(--destructive);
|
|
27
|
+
--color-border: var(--border);
|
|
28
|
+
--color-input: var(--input);
|
|
29
|
+
--color-ring: var(--ring);
|
|
30
|
+
--color-chart-1: var(--chart-1);
|
|
31
|
+
--color-chart-2: var(--chart-2);
|
|
32
|
+
--color-chart-3: var(--chart-3);
|
|
33
|
+
--color-chart-4: var(--chart-4);
|
|
34
|
+
--color-chart-5: var(--chart-5);
|
|
35
|
+
--color-sidebar: var(--sidebar);
|
|
36
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
37
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
38
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
39
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
40
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
41
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
42
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:root {
|
|
46
|
+
/* Theme: Radius - set via data-radius attribute */
|
|
47
|
+
--radius: 0.625rem;
|
|
48
|
+
--background: oklch(1 0 0);
|
|
49
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
50
|
+
--card: oklch(1 0 0);
|
|
51
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
52
|
+
--popover: oklch(1 0 0);
|
|
53
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
54
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
55
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
56
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
57
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
58
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
59
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
60
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
61
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
62
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
63
|
+
--border: oklch(0.92 0.004 286.32);
|
|
64
|
+
--input: oklch(0.92 0.004 286.32);
|
|
65
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
66
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
67
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
68
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
69
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
70
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
71
|
+
--sidebar: oklch(0.985 0 0);
|
|
72
|
+
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
73
|
+
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
74
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
75
|
+
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
76
|
+
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
77
|
+
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
78
|
+
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.dark {
|
|
82
|
+
--background: oklch(0.141 0.005 285.823);
|
|
83
|
+
--foreground: oklch(0.985 0 0);
|
|
84
|
+
--card: oklch(0.21 0.006 285.885);
|
|
85
|
+
--card-foreground: oklch(0.985 0 0);
|
|
86
|
+
--popover: oklch(0.21 0.006 285.885);
|
|
87
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
88
|
+
--primary: oklch(0.92 0.004 286.32);
|
|
89
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
90
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
91
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
92
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
93
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
94
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
95
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
96
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
97
|
+
--border: oklch(1 0 0 / 10%);
|
|
98
|
+
--input: oklch(1 0 0 / 15%);
|
|
99
|
+
--ring: oklch(0.552 0.016 285.938);
|
|
100
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
101
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
102
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
103
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
104
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
105
|
+
--sidebar: oklch(0.21 0.006 285.885);
|
|
106
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
107
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
108
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
109
|
+
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
110
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
111
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
112
|
+
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@layer base {
|
|
116
|
+
* {
|
|
117
|
+
@apply border-border outline-ring/50;
|
|
118
|
+
}
|
|
119
|
+
body {
|
|
120
|
+
@apply bg-background text-foreground;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Theme: Radius variants via data attribute */
|
|
125
|
+
[data-radius='sharp'] {
|
|
126
|
+
--radius: 0.25rem;
|
|
127
|
+
}
|
|
128
|
+
[data-radius='soft'] {
|
|
129
|
+
--radius: 0.625rem;
|
|
130
|
+
}
|
|
131
|
+
[data-radius='round'] {
|
|
132
|
+
--radius: 1rem;
|
|
133
|
+
}
|
|
134
|
+
[data-radius='pill'] {
|
|
135
|
+
--radius: 9999px;
|
|
136
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isApiKeyAuth } from '@/lib/auth'
|
|
2
|
+
import { AuthConfig } from '../types'
|
|
3
|
+
import { useSession } from './useSession'
|
|
4
|
+
|
|
5
|
+
export type Auth =
|
|
6
|
+
| {
|
|
7
|
+
headers: Record<string, string>
|
|
8
|
+
isLoading: false
|
|
9
|
+
}
|
|
10
|
+
| {
|
|
11
|
+
headers?: Record<string, string>
|
|
12
|
+
isLoading: true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function defaultGetSession(init: {
|
|
16
|
+
projectSlug: string
|
|
17
|
+
}): Promise<string> {
|
|
18
|
+
const response = await fetch('/chat/session', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Gram-Project': init.projectSlug,
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
const data = await response.json()
|
|
25
|
+
return data.client_token
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook to fetch or retrieve the session token for the chat.
|
|
30
|
+
* @returns The session token string or null
|
|
31
|
+
*/
|
|
32
|
+
export const useAuth = ({
|
|
33
|
+
projectSlug,
|
|
34
|
+
auth,
|
|
35
|
+
}: {
|
|
36
|
+
auth?: AuthConfig
|
|
37
|
+
projectSlug: string
|
|
38
|
+
}): Auth => {
|
|
39
|
+
// The session request is only neccessary if we are not using an API key auth
|
|
40
|
+
// configuration. If a custom session fetcher is provided, we use it,
|
|
41
|
+
// otherwise we fallback to the default session fetcher
|
|
42
|
+
const session = useSession({
|
|
43
|
+
enabled: !isApiKeyAuth(auth),
|
|
44
|
+
getSession: !isApiKeyAuth(auth)
|
|
45
|
+
? (auth?.sessionFn ?? defaultGetSession)
|
|
46
|
+
: null,
|
|
47
|
+
projectSlug,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (isApiKeyAuth(auth)) {
|
|
51
|
+
return {
|
|
52
|
+
headers: {
|
|
53
|
+
'Gram-Project': projectSlug,
|
|
54
|
+
'Gram-Key': auth.UNSAFE_apiKey,
|
|
55
|
+
},
|
|
56
|
+
isLoading: false,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return !session
|
|
61
|
+
? {
|
|
62
|
+
isLoading: true,
|
|
63
|
+
}
|
|
64
|
+
: {
|
|
65
|
+
headers: {
|
|
66
|
+
'Gram-Project': projectSlug,
|
|
67
|
+
'Gram-Chat-Session': session,
|
|
68
|
+
},
|
|
69
|
+
isLoading: false,
|
|
70
|
+
}
|
|
71
|
+
}
|