@gram-ai/elements 1.18.5 → 1.18.7

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 (86) hide show
  1. package/dist/components/Chat/stories/Sidecar.stories.d.ts +6 -0
  2. package/dist/elements.cjs +29 -28
  3. package/dist/elements.cjs.map +1 -0
  4. package/dist/elements.css +1 -1
  5. package/dist/elements.js +1945 -1918
  6. package/dist/elements.js.map +1 -0
  7. package/dist/index-Bj7jPiuy.cjs +1 -0
  8. package/dist/index-Bj7jPiuy.cjs.map +1 -0
  9. package/dist/index-CJRypLIa.js +1 -0
  10. package/dist/index-CJRypLIa.js.map +1 -0
  11. package/dist/plugins.cjs +1 -0
  12. package/dist/plugins.cjs.map +1 -0
  13. package/dist/plugins.js +1 -0
  14. package/dist/plugins.js.map +1 -0
  15. package/dist/server.cjs +1 -0
  16. package/dist/server.cjs.map +1 -0
  17. package/dist/server.js +1 -0
  18. package/dist/server.js.map +1 -0
  19. package/package.json +3 -2
  20. package/src/components/Chat/index.tsx +21 -0
  21. package/src/components/Chat/stories/ColorScheme.stories.tsx +52 -0
  22. package/src/components/Chat/stories/Composer.stories.tsx +42 -0
  23. package/src/components/Chat/stories/Customization.stories.tsx +88 -0
  24. package/src/components/Chat/stories/Density.stories.tsx +52 -0
  25. package/src/components/Chat/stories/FrontendTools.stories.tsx +145 -0
  26. package/src/components/Chat/stories/Modal.stories.tsx +84 -0
  27. package/src/components/Chat/stories/Model.stories.tsx +32 -0
  28. package/src/components/Chat/stories/Plugins.stories.tsx +50 -0
  29. package/src/components/Chat/stories/Radius.stories.tsx +52 -0
  30. package/src/components/Chat/stories/Sidecar.stories.tsx +27 -0
  31. package/src/components/Chat/stories/ToolApproval.stories.tsx +110 -0
  32. package/src/components/Chat/stories/Tools.stories.tsx +175 -0
  33. package/src/components/Chat/stories/Variants.stories.tsx +46 -0
  34. package/src/components/Chat/stories/Welcome.stories.tsx +42 -0
  35. package/src/components/FrontendTools/index.tsx +9 -0
  36. package/src/components/assistant-ui/assistant-modal.tsx +255 -0
  37. package/src/components/assistant-ui/assistant-sidecar.tsx +88 -0
  38. package/src/components/assistant-ui/attachment.tsx +233 -0
  39. package/src/components/assistant-ui/markdown-text.tsx +240 -0
  40. package/src/components/assistant-ui/reasoning.tsx +261 -0
  41. package/src/components/assistant-ui/thread-list.tsx +97 -0
  42. package/src/components/assistant-ui/thread.tsx +632 -0
  43. package/src/components/assistant-ui/tool-fallback.tsx +111 -0
  44. package/src/components/assistant-ui/tool-group.tsx +59 -0
  45. package/src/components/assistant-ui/tooltip-icon-button.tsx +57 -0
  46. package/src/components/ui/avatar.tsx +51 -0
  47. package/src/components/ui/button.tsx +27 -0
  48. package/src/components/ui/buttonVariants.ts +33 -0
  49. package/src/components/ui/collapsible.tsx +31 -0
  50. package/src/components/ui/dialog.tsx +141 -0
  51. package/src/components/ui/popover.tsx +46 -0
  52. package/src/components/ui/skeleton.tsx +13 -0
  53. package/src/components/ui/tool-ui.stories.tsx +146 -0
  54. package/src/components/ui/tool-ui.tsx +676 -0
  55. package/src/components/ui/tooltip.tsx +61 -0
  56. package/src/contexts/ElementsProvider.tsx +287 -0
  57. package/src/contexts/ToolApprovalContext.tsx +120 -0
  58. package/src/contexts/contexts.ts +10 -0
  59. package/src/global.css +136 -0
  60. package/src/hooks/useAuth.ts +71 -0
  61. package/src/hooks/useDensity.ts +110 -0
  62. package/src/hooks/useElements.ts +14 -0
  63. package/src/hooks/useExpanded.ts +20 -0
  64. package/src/hooks/useMCPTools.ts +73 -0
  65. package/src/hooks/usePluginComponents.ts +34 -0
  66. package/src/hooks/useRadius.ts +42 -0
  67. package/src/hooks/useSession.ts +38 -0
  68. package/src/hooks/useThemeProps.ts +24 -0
  69. package/src/hooks/useToolApproval.ts +16 -0
  70. package/src/index.ts +45 -0
  71. package/src/lib/api.test.ts +90 -0
  72. package/src/lib/api.ts +8 -0
  73. package/src/lib/auth.ts +10 -0
  74. package/src/lib/easing.ts +1 -0
  75. package/src/lib/humanize.ts +14 -0
  76. package/src/lib/models.ts +22 -0
  77. package/src/lib/tools.ts +210 -0
  78. package/src/lib/utils.ts +16 -0
  79. package/src/plugins/README.md +49 -0
  80. package/src/plugins/chart/component.tsx +102 -0
  81. package/src/plugins/chart/index.ts +27 -0
  82. package/src/plugins/index.ts +7 -0
  83. package/src/server.ts +89 -0
  84. package/src/types/index.ts +726 -0
  85. package/src/types/plugins.ts +65 -0
  86. 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,287 @@
1
+ import { FrontendTools } from '@/components/FrontendTools'
2
+ import { useMCPTools } from '@/hooks/useMCPTools'
3
+ import { useToolApproval } from '@/hooks/useToolApproval'
4
+ import { getApiUrl } from '@/lib/api'
5
+ import { MODELS } from '@/lib/models'
6
+ import {
7
+ clearFrontendToolApprovalConfig,
8
+ getEnabledTools,
9
+ setFrontendToolApprovalConfig,
10
+ toAISDKTools,
11
+ wrapToolsWithApproval,
12
+ type ApprovalHelpers,
13
+ } from '@/lib/tools'
14
+ import { recommended } from '@/plugins'
15
+ import { ElementsConfig, Model } from '@/types'
16
+ import { Plugin } from '@/types/plugins'
17
+ import { AssistantRuntimeProvider } from '@assistant-ui/react'
18
+ import {
19
+ frontendTools as convertFrontendToolsToAISDKTools,
20
+ useChatRuntime,
21
+ } from '@assistant-ui/react-ai-sdk'
22
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider'
23
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
24
+ import {
25
+ convertToModelMessages,
26
+ smoothStream,
27
+ stepCountIs,
28
+ streamText,
29
+ ToolSet,
30
+ type ChatTransport,
31
+ type UIMessage,
32
+ } from 'ai'
33
+ import {
34
+ ReactNode,
35
+ useCallback,
36
+ useEffect,
37
+ useMemo,
38
+ useRef,
39
+ useState,
40
+ } from 'react'
41
+ import { useAuth } from '../hooks/useAuth'
42
+ import { ElementsContext } from './contexts'
43
+ import { ToolApprovalProvider } from './ToolApprovalContext'
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
+ /**
67
+ * Cleans messages before sending to the model to work around an AI SDK bug.
68
+ * Strips callProviderMetadata from all parts (AI SDK bug #9731)
69
+ */
70
+ function cleanMessagesForModel(messages: UIMessage[]): UIMessage[] {
71
+ return messages.map((message) => {
72
+ const partsArray = message.parts
73
+ if (!Array.isArray(partsArray)) {
74
+ return message
75
+ }
76
+
77
+ // Process each part: strip providerOptions/providerMetadata and filter reasoning
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ const cleanedParts = partsArray.map((part: any) => {
80
+ // Strip providerOptions and providerMetadata from all remaining parts
81
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
82
+ const { callProviderMetadata, ...cleanPart } = part
83
+ return cleanPart
84
+ })
85
+
86
+ return {
87
+ ...message,
88
+ parts: cleanedParts,
89
+ }
90
+ })
91
+ }
92
+
93
+ const ElementsProviderWithApproval = ({
94
+ children,
95
+ config,
96
+ }: ElementsProviderProps) => {
97
+ const apiUrl = getApiUrl(config)
98
+ const auth = useAuth({
99
+ auth: config.api,
100
+ projectSlug: config.projectSlug,
101
+ })
102
+ const toolApproval = useToolApproval()
103
+
104
+ const [model, setModel] = useState<Model>(
105
+ config.model?.defaultModel ?? MODELS[0]
106
+ )
107
+ const [isExpanded, setIsExpanded] = useState(
108
+ config.modal?.defaultExpanded ?? false
109
+ )
110
+ const [isOpen, setIsOpen] = useState(config.modal?.defaultOpen)
111
+
112
+ // If there are any user provided plugins, use them, otherwise use the recommended plugins
113
+ const plugins = config.plugins ?? recommended
114
+
115
+ const systemPrompt = mergeInternalSystemPromptWith(
116
+ config.systemPrompt,
117
+ plugins
118
+ )
119
+
120
+ const { data: mcpTools, isLoading: mcpToolsLoading } = useMCPTools({
121
+ auth,
122
+ mcp: config.mcp,
123
+ environment: config.environment ?? {},
124
+ })
125
+
126
+ // Store approval helpers in ref so they can be used in async contexts
127
+ const approvalHelpersRef = useRef<ApprovalHelpers>({
128
+ requestApproval: toolApproval.requestApproval,
129
+ isToolApproved: toolApproval.isToolApproved,
130
+ whitelistTool: toolApproval.whitelistTool,
131
+ })
132
+
133
+ approvalHelpersRef.current = {
134
+ requestApproval: toolApproval.requestApproval,
135
+ isToolApproved: toolApproval.isToolApproved,
136
+ whitelistTool: toolApproval.whitelistTool,
137
+ }
138
+
139
+ const getApprovalHelpers = useCallback((): ApprovalHelpers => {
140
+ return {
141
+ requestApproval: (...args) =>
142
+ approvalHelpersRef.current.requestApproval(...args),
143
+ isToolApproved: (...args) =>
144
+ approvalHelpersRef.current.isToolApproved(...args),
145
+ whitelistTool: (...args) =>
146
+ approvalHelpersRef.current.whitelistTool(...args),
147
+ }
148
+ }, [])
149
+
150
+ // Set up frontend tool approval config for runtime checking
151
+ useEffect(() => {
152
+ if (config.tools?.toolsRequiringApproval?.length) {
153
+ setFrontendToolApprovalConfig(
154
+ getApprovalHelpers(),
155
+ config.tools.toolsRequiringApproval
156
+ )
157
+ }
158
+ return () => {
159
+ clearFrontendToolApprovalConfig()
160
+ }
161
+ }, [config.tools?.toolsRequiringApproval, getApprovalHelpers])
162
+
163
+ // Create custom transport
164
+ const transport = useMemo<ChatTransport<UIMessage> | undefined>(
165
+ () => ({
166
+ sendMessages: async ({ messages, abortSignal }) => {
167
+ const usingCustomModel = !!config.languageModel
168
+
169
+ if (auth.isLoading) {
170
+ throw new Error('Session is loading')
171
+ }
172
+
173
+ const context = runtime.thread.getModelContext()
174
+ const frontendTools = toAISDKTools(
175
+ getEnabledTools(context?.tools ?? {})
176
+ )
177
+
178
+ // Create OpenRouter model (only needed when not using custom model)
179
+ const openRouterModel = usingCustomModel
180
+ ? null
181
+ : createOpenRouter({
182
+ baseURL: apiUrl,
183
+ apiKey: 'unused, but must be set',
184
+ headers: auth.headers,
185
+ })
186
+
187
+ if (config.languageModel) {
188
+ console.log('Using custom language model', config.languageModel)
189
+ }
190
+
191
+ // Combine tools - MCP tools only available when not using custom model
192
+ const combinedTools: ToolSet = {
193
+ ...mcpTools,
194
+ ...convertFrontendToolsToAISDKTools(frontendTools),
195
+ } as ToolSet
196
+
197
+ // Wrap tools that require approval
198
+ const tools = wrapToolsWithApproval(
199
+ combinedTools,
200
+ config.tools?.toolsRequiringApproval,
201
+ getApprovalHelpers()
202
+ )
203
+
204
+ // Stream the response
205
+ const modelToUse = config.languageModel
206
+ ? config.languageModel
207
+ : openRouterModel!.chat(model)
208
+
209
+ try {
210
+ // This works around AI SDK bug where these fields cause validation failures
211
+ const cleanedMessages = cleanMessagesForModel(messages)
212
+ const modelMessages = convertToModelMessages(cleanedMessages)
213
+
214
+ const result = streamText({
215
+ system: systemPrompt,
216
+ model: modelToUse,
217
+ messages: modelMessages,
218
+ tools,
219
+ stopWhen: stepCountIs(10),
220
+ experimental_transform: smoothStream({ delayInMs: 15 }),
221
+ abortSignal,
222
+ onError: ({ error }) => {
223
+ console.error('Stream error in onError callback:', error)
224
+ },
225
+ })
226
+
227
+ return result.toUIMessageStream()
228
+ } catch (error) {
229
+ console.error('Error creating stream:', error)
230
+ throw error
231
+ }
232
+ },
233
+ reconnectToStream: async () => {
234
+ // Not implemented for client-side streaming
235
+ throw new Error('Stream reconnection not supported')
236
+ },
237
+ }),
238
+ [
239
+ config,
240
+ config.languageModel,
241
+ model,
242
+ systemPrompt,
243
+ mcpTools,
244
+ mcpToolsLoading,
245
+ getApprovalHelpers,
246
+ apiUrl,
247
+ auth.headers,
248
+ ]
249
+ )
250
+
251
+ const runtime = useChatRuntime({
252
+ transport,
253
+ })
254
+
255
+ return (
256
+ <AssistantRuntimeProvider runtime={runtime}>
257
+ <ElementsContext.Provider
258
+ value={{
259
+ config,
260
+ setModel,
261
+ model,
262
+ isExpanded,
263
+ setIsExpanded,
264
+ isOpen: isOpen ?? false,
265
+ setIsOpen,
266
+ plugins,
267
+ }}
268
+ >
269
+ {children}
270
+
271
+ {/* Doesn't render anything, but is used to register frontend tools */}
272
+ <FrontendTools tools={config.tools?.frontendTools ?? {}} />
273
+ </ElementsContext.Provider>
274
+ </AssistantRuntimeProvider>
275
+ )
276
+ }
277
+
278
+ export const ElementsProvider = (props: ElementsProviderProps) => {
279
+ const queryClient = new QueryClient()
280
+ return (
281
+ <QueryClientProvider client={queryClient}>
282
+ <ToolApprovalProvider>
283
+ <ElementsProviderWithApproval {...props} />
284
+ </ToolApprovalProvider>
285
+ </QueryClientProvider>
286
+ )
287
+ }
@@ -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
+ }