mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 21:05:06 +03:00
all the things
This commit is contained in:
1 parent
0e3b4e02b6
commit
97aeb76b30
15 files changed
+869
-610
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Assistant
|
||||
description: AI assistant widget that streams database-aware answers through Supabase Edge Functions and the Model Context Protocol (MCP).
|
||||
description: Add an AI Assistant to your product that can read and write to your database using user defined tools that respect your RLS policies.
|
||||
---
|
||||
|
||||
<BlockPreview name="assistant-demo" wide />
|
||||
@@ -36,13 +36,13 @@ The bundle contains four key pieces:
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Key | Where it is used | Notes |
|
||||
| --- | ---------------- | ----- |
|
||||
| `NEXT_PUBLIC_SUPABASE_URL` | Next.js client + Edge Functions | Base URL of your Supabase project. |
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Next.js client | Anonymous key used by the browser client. |
|
||||
| `NEXT_PUBLIC_ASSISTANT_CHAT_URL` | Next.js client | Optional override for the chat Edge Function endpoint. Defaults to `${NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`. |
|
||||
| `OPENAI_API_KEY` | `supabase/functions/chat` | Injected into the Edge Function to talk to your LLM provider. |
|
||||
| `MCP_SERVER_URL` | `supabase/functions/chat` and `supabase/functions/mcp-server` | Used by the chat handler to find the MCP server endpoint. |
|
||||
| Key | Where it is used | Notes |
|
||||
| -------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `NEXT_PUBLIC_SUPABASE_URL` | Next.js client + Edge Functions | Base URL of your Supabase project. |
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Next.js client | Anonymous key used by the browser client. |
|
||||
| `NEXT_PUBLIC_ASSISTANT_CHAT_URL` | Next.js client | Optional override for the chat Edge Function endpoint. Defaults to `${NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`. |
|
||||
| `OPENAI_API_KEY` | `supabase/functions/chat` | Injected into the Edge Function to talk to your LLM provider. |
|
||||
| `MCP_SERVER_URL` | `supabase/functions/chat` and `supabase/functions/mcp-server` | Used by the chat handler to find the MCP server endpoint. |
|
||||
|
||||
Store these values in `.env.local` for the app and in a `.env` file that you pass to `supabase functions serve`.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "2.0.45",
|
||||
"@ai-sdk/react": "^2.0.68",
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-accordion": "*",
|
||||
@@ -54,7 +54,7 @@
|
||||
"@supabase/vue-blocks": "workspace:*",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@xyflow/react": "^12.9.2",
|
||||
"ai": "^5.0.90",
|
||||
"ai": "^5.0.93",
|
||||
"axios": "^1.12.0",
|
||||
"class-variance-authority": "*",
|
||||
"cmdk": "^1.0.0",
|
||||
|
||||
+187
-390
@@ -1,6 +1,4 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { createClient } from '@/registry/default/blocks/assistant/lib/supabase/client'
|
||||
import {
|
||||
Conversation,
|
||||
@@ -9,12 +7,6 @@ import {
|
||||
} from '@/registry/default/components/ai-elements/conversation'
|
||||
import {
|
||||
Message,
|
||||
MessageBranch,
|
||||
MessageBranchContent,
|
||||
MessageBranchNext,
|
||||
MessageBranchPage,
|
||||
MessageBranchPrevious,
|
||||
MessageBranchSelector,
|
||||
MessageContent,
|
||||
MessageResponse,
|
||||
} from '@/registry/default/components/ai-elements/message'
|
||||
@@ -27,52 +19,29 @@ import {
|
||||
ModelSelectorItem,
|
||||
ModelSelectorList,
|
||||
ModelSelectorLogo,
|
||||
ModelSelectorLogoGroup,
|
||||
ModelSelectorName,
|
||||
ModelSelectorTrigger,
|
||||
} from '@/registry/default/components/ai-elements/model-selector'
|
||||
import {
|
||||
PromptInput,
|
||||
PromptInputActionAddAttachments,
|
||||
PromptInputActionMenu,
|
||||
PromptInputActionMenuContent,
|
||||
PromptInputActionMenuTrigger,
|
||||
PromptInputAttachment,
|
||||
PromptInputAttachments,
|
||||
PromptInputBody,
|
||||
PromptInputButton,
|
||||
PromptInputFooter,
|
||||
PromptInputHeader,
|
||||
type PromptInputMessage,
|
||||
PromptInputSubmit,
|
||||
PromptInputTextarea,
|
||||
PromptInputTools,
|
||||
} from '@/registry/default/components/ai-elements/prompt-input'
|
||||
import {
|
||||
Reasoning,
|
||||
ReasoningContent,
|
||||
ReasoningTrigger,
|
||||
} from '@/registry/default/components/ai-elements/reasoning'
|
||||
import {
|
||||
Source,
|
||||
Sources,
|
||||
SourcesContent,
|
||||
SourcesTrigger,
|
||||
} from '@/registry/default/components/ai-elements/sources'
|
||||
import { Suggestion, Suggestions } from '@/registry/default/components/ai-elements/suggestion'
|
||||
import { Button } from '@/registry/default/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover'
|
||||
import { useChat } from '@ai-sdk/react'
|
||||
import { DefaultChatTransport } from 'ai'
|
||||
import { GlobeIcon, MessageSquare, MicIcon, Minimize2, X } from 'lucide-react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { CheckIcon, MessageCircle } from 'lucide-react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const suggestions = [
|
||||
'Show me my recent orders',
|
||||
'How many active users do we have?',
|
||||
'List all tasks due this week',
|
||||
"Summarize today's signups",
|
||||
]
|
||||
const CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL
|
||||
|
||||
const models = [
|
||||
{
|
||||
@@ -89,6 +58,13 @@ const models = [
|
||||
chefSlug: 'openai',
|
||||
providers: ['openai', 'azure'],
|
||||
},
|
||||
{
|
||||
id: 'claude-opus-4-20250514',
|
||||
name: 'Claude 4 Opus',
|
||||
chef: 'Anthropic',
|
||||
chefSlug: 'anthropic',
|
||||
providers: ['anthropic', 'azure', 'google', 'amazon-bedrock'],
|
||||
},
|
||||
{
|
||||
id: 'claude-sonnet-4-20250514',
|
||||
name: 'Claude 4 Sonnet',
|
||||
@@ -105,397 +81,218 @@ const models = [
|
||||
},
|
||||
]
|
||||
|
||||
type TextPart = {
|
||||
type: 'text'
|
||||
text: string
|
||||
const suggestions = [
|
||||
'What are the latest trends in AI?',
|
||||
'How does machine learning work?',
|
||||
'Explain quantum computing',
|
||||
'Best practices for React development',
|
||||
'Tell me about TypeScript benefits',
|
||||
'How to optimize database queries?',
|
||||
'What is the difference between SQL and NoSQL?',
|
||||
'Explain cloud computing basics',
|
||||
]
|
||||
|
||||
interface AssistantWidgetProps {
|
||||
/**
|
||||
* Override the trigger positioning if you need to embed the widget in a scoped container.
|
||||
*/
|
||||
triggerClassName?: string
|
||||
}
|
||||
|
||||
type SourcePart = {
|
||||
type: 'source-url'
|
||||
url: string
|
||||
title?: string
|
||||
}
|
||||
export const AssistantWidget = ({
|
||||
triggerClassName = 'fixed bottom-6 right-6',
|
||||
}: AssistantWidgetProps = {}) => {
|
||||
const [model, setModel] = useState<string>(models[0].id)
|
||||
const [modelSelectorOpen, setModelSelectorOpen] = useState(false)
|
||||
const [text, setText] = useState<string>('')
|
||||
const [useWebSearch, setUseWebSearch] = useState<boolean>(false)
|
||||
const [useMicrophone, setUseMicrophone] = useState<boolean>(false)
|
||||
|
||||
type ReasoningPart = {
|
||||
type: 'reasoning'
|
||||
text: string
|
||||
}
|
||||
|
||||
type FormattedMessage = {
|
||||
key: string
|
||||
from: 'user' | 'assistant'
|
||||
sources?: { href: string; title: string }[]
|
||||
versions: { id: string; content: string }[]
|
||||
reasoning?: { content: string; duration?: number }
|
||||
}
|
||||
|
||||
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '')
|
||||
const DEFAULT_CHAT_API = SUPABASE_URL ? `${SUPABASE_URL}/functions/v1/chat` : '/functions/v1/chat'
|
||||
const CHAT_API_URL = 'https://idglwaxxhycmeyjvbbbr.supabase.co/functions/v1/chat'
|
||||
|
||||
export function AssistantWidget() {
|
||||
const supabase = useMemo(() => createClient(), [])
|
||||
const getAuthHeaders = useCallback(async () => {
|
||||
let {
|
||||
const supabase = createClient()
|
||||
const {
|
||||
data: { session },
|
||||
error: sessionError,
|
||||
error,
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (sessionError) {
|
||||
throw sessionError
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
const { data, error } = await supabase.auth.signInAnonymously()
|
||||
|
||||
if (error || !data.session) {
|
||||
throw error ?? new Error('Unable to authenticate anonymously.')
|
||||
}
|
||||
|
||||
session = data.session
|
||||
throw new Error('No active session found. Please sign in.')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
}
|
||||
}, [supabase])
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isMinimized, setIsMinimized] = useState(false)
|
||||
const [text, setText] = useState('')
|
||||
const [model, setModel] = useState(models[0].id)
|
||||
const [modelSelectorOpen, setModelSelectorOpen] = useState(false)
|
||||
const [useWebSearch, setUseWebSearch] = useState(false)
|
||||
const [useMicrophone, setUseMicrophone] = useState(false)
|
||||
}, [])
|
||||
|
||||
const { messages, sendMessage, status, error } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: CHAT_API_URL,
|
||||
headers: async () => {
|
||||
try {
|
||||
return await getAuthHeaders()
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error)
|
||||
toast.error('Failed to authenticate')
|
||||
return {}
|
||||
}
|
||||
},
|
||||
body: () => ({
|
||||
model,
|
||||
useWebSearch,
|
||||
}),
|
||||
}),
|
||||
onError: (chatError) => {
|
||||
onError: (error) => {
|
||||
console.error('Chat error:', error)
|
||||
toast.error('Failed to send message', {
|
||||
description: chatError.message,
|
||||
description: error.message,
|
||||
})
|
||||
},
|
||||
onFinish: () => {
|
||||
// Successfully finished streaming
|
||||
},
|
||||
})
|
||||
|
||||
const selectedModelData = models.find((entry) => entry.id === model)
|
||||
const selectedModelData = models.find((m) => m.id === model)
|
||||
|
||||
const formattedMessages = useMemo<FormattedMessage[]>(() => {
|
||||
return messages.map((message) => {
|
||||
const parts = message.parts ?? []
|
||||
const handleSubmit = useCallback(
|
||||
(message: PromptInputMessage) => {
|
||||
const hasText = Boolean(message.text)
|
||||
const hasAttachments = Boolean(message.files?.length)
|
||||
|
||||
const textParts =
|
||||
parts.filter((part): part is TextPart => part?.type === 'text') ??
|
||||
(Array.isArray(message.content)
|
||||
? message.content.filter(
|
||||
(part): part is TextPart => typeof part === 'object' && part.type === 'text'
|
||||
)
|
||||
: [])
|
||||
|
||||
const versions =
|
||||
textParts.length > 0
|
||||
? textParts.map((part, index) => ({
|
||||
id: `${message.id}-${index}`,
|
||||
content: part.text,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
id: `${message.id}-content`,
|
||||
content:
|
||||
typeof message.content === 'string'
|
||||
? message.content
|
||||
: message.content
|
||||
?.map((part) =>
|
||||
typeof part === 'string' ? part : part.type === 'text' ? part.text : ''
|
||||
)
|
||||
.join('')
|
||||
.trim() ?? '',
|
||||
},
|
||||
]
|
||||
|
||||
const sources = parts
|
||||
.filter((part): part is SourcePart => part?.type === 'source-url')
|
||||
.map((part) => ({
|
||||
href: part.url,
|
||||
title: part.title ?? part.url,
|
||||
}))
|
||||
|
||||
const reasoningPart = parts.find((part): part is ReasoningPart => part?.type === 'reasoning')
|
||||
|
||||
return {
|
||||
key: message.id,
|
||||
from: message.role === 'assistant' ? 'assistant' : 'user',
|
||||
sources,
|
||||
versions,
|
||||
reasoning: reasoningPart
|
||||
? {
|
||||
content: reasoningPart.text,
|
||||
}
|
||||
: undefined,
|
||||
if (!(hasText || hasAttachments)) {
|
||||
return
|
||||
}
|
||||
})
|
||||
}, [messages])
|
||||
|
||||
const handleSubmit = async (message: PromptInputMessage) => {
|
||||
const hasText = Boolean(message.text?.trim())
|
||||
const hasAttachments = Boolean(message.files?.length)
|
||||
if (message.files?.length) {
|
||||
toast.success('Files attached', {
|
||||
description: `${message.files.length} file(s) attached to message`,
|
||||
})
|
||||
}
|
||||
|
||||
if (!(hasText || hasAttachments) || status === 'streaming') {
|
||||
return
|
||||
}
|
||||
|
||||
if (message.files?.length) {
|
||||
toast.success('Files attached', {
|
||||
description: `${message.files.length} file(s) attached to message`,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = await getAuthHeaders()
|
||||
await sendMessage(
|
||||
{
|
||||
text: message.text?.trim() ?? 'Sent with attachments',
|
||||
files: message.files,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
body: {
|
||||
model,
|
||||
webSearch: useWebSearch,
|
||||
},
|
||||
}
|
||||
)
|
||||
sendMessage({ text: message.text || 'Sent with attachments' })
|
||||
setText('')
|
||||
} catch (sendError) {
|
||||
toast.error('Failed to send message', {
|
||||
description: sendError instanceof Error ? sendError.message : 'Unknown error occurred.',
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
[sendMessage]
|
||||
)
|
||||
|
||||
const handleSuggestionClick = async (suggestion: string) => {
|
||||
if (status === 'streaming' || status === 'submitted') return
|
||||
try {
|
||||
const headers = await getAuthHeaders()
|
||||
await sendMessage(
|
||||
{ text: suggestion },
|
||||
{
|
||||
headers,
|
||||
body: { model, webSearch: useWebSearch },
|
||||
}
|
||||
)
|
||||
} catch (sendError) {
|
||||
toast.error('Failed to send message', {
|
||||
description: sendError instanceof Error ? sendError.message : 'Unknown error occurred.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOpen) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-all hover:bg-primary/90"
|
||||
aria-label="Open AI Assistant"
|
||||
>
|
||||
<MessageSquare className="h-6 w-6" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const submitDisabled = (!text.trim() && status !== 'streaming') || status === 'streaming'
|
||||
const handleSuggestionClick = useCallback(
|
||||
(suggestion: string) => {
|
||||
sendMessage({ text: suggestion })
|
||||
},
|
||||
[sendMessage]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'fixed bottom-6 right-6 z-50 flex flex-col rounded-lg border bg-background shadow-2xl transition-all',
|
||||
isMinimized ? 'h-14 w-80' : 'h-[640px] w-[420px]'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b bg-muted/60 px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
<div>
|
||||
<p className="text-sm font-semibold">AI Assistant</p>
|
||||
<p className="text-xs text-muted-foreground">Powered by MCP</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => setIsMinimized((prev) => !prev)}
|
||||
>
|
||||
<Minimize2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setIsOpen(false)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
aria-label="Open assistant"
|
||||
className={`${triggerClassName} h-12 w-12 rounded-full shadow-lg`}
|
||||
size="icon"
|
||||
>
|
||||
<MessageCircle className="h-5 w-5" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
{!isMinimized && (
|
||||
<div className="flex h-full flex-col divide-y overflow-hidden">
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Conversation className="flex h-full flex-col">
|
||||
<ConversationContent>
|
||||
{formattedMessages.map(({ key, versions, sources, reasoning, from }) => (
|
||||
<MessageBranch defaultBranch={0} key={key}>
|
||||
<MessageBranchContent>
|
||||
{versions.map((version) => (
|
||||
<Message from={from} key={`${key}-${version.id}`}>
|
||||
<div>
|
||||
{sources && sources.length > 0 && (
|
||||
<Sources>
|
||||
<SourcesTrigger count={sources.length} />
|
||||
<SourcesContent>
|
||||
{sources.map((source) => (
|
||||
<Source
|
||||
key={source.href}
|
||||
href={source.href}
|
||||
title={source.title}
|
||||
/>
|
||||
))}
|
||||
</SourcesContent>
|
||||
</Sources>
|
||||
)}
|
||||
|
||||
{reasoning && (
|
||||
<Reasoning>
|
||||
<ReasoningTrigger />
|
||||
<ReasoningContent>{reasoning.content}</ReasoningContent>
|
||||
</Reasoning>
|
||||
)}
|
||||
|
||||
<MessageContent>
|
||||
<MessageResponse>{version.content}</MessageResponse>
|
||||
</MessageContent>
|
||||
</div>
|
||||
</Message>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="h-[480px] w-[480px] p-0 flex flex-col gap-0"
|
||||
side="top"
|
||||
sideOffset={16}
|
||||
>
|
||||
<Conversation className="flex-1 border-b">
|
||||
<ConversationContent>
|
||||
{messages.map(({ role, parts }, index) => (
|
||||
<Message from={role} key={index}>
|
||||
<MessageContent>
|
||||
{parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case 'text':
|
||||
return <MessageResponse key={`${role}-${i}`}>{part.text}</MessageResponse>
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
<Suggestions className="p-3">
|
||||
{suggestions.map((suggestion) => (
|
||||
<Suggestion
|
||||
key={suggestion}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
suggestion={suggestion}
|
||||
/>
|
||||
))}
|
||||
</Suggestions>
|
||||
<div className="w-full px-4 pb-4">
|
||||
<PromptInput globalDrop multiple onSubmit={handleSubmit}>
|
||||
<PromptInputBody>
|
||||
<PromptInputTextarea onChange={(event) => setText(event.target.value)} value={text} />
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter>
|
||||
<PromptInputTools>
|
||||
<ModelSelector onOpenChange={setModelSelectorOpen} open={modelSelectorOpen}>
|
||||
<ModelSelectorTrigger asChild>
|
||||
<PromptInputButton>
|
||||
{selectedModelData?.chefSlug && (
|
||||
<ModelSelectorLogo
|
||||
className="dark:invert"
|
||||
provider={selectedModelData.chefSlug}
|
||||
/>
|
||||
)}
|
||||
{selectedModelData?.name && (
|
||||
<ModelSelectorName>{selectedModelData.name}</ModelSelectorName>
|
||||
)}
|
||||
</PromptInputButton>
|
||||
</ModelSelectorTrigger>
|
||||
<ModelSelectorContent>
|
||||
<ModelSelectorInput placeholder="Search models..." />
|
||||
<ModelSelectorList>
|
||||
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
|
||||
{['OpenAI', 'Anthropic', 'Google'].map((chef) => (
|
||||
<ModelSelectorGroup key={chef} heading={chef}>
|
||||
{models
|
||||
.filter((m) => m.chef === chef)
|
||||
.map((m) => (
|
||||
<ModelSelectorItem
|
||||
key={m.id}
|
||||
onSelect={() => {
|
||||
setModel(m.id)
|
||||
setModelSelectorOpen(false)
|
||||
}}
|
||||
value={m.id}
|
||||
>
|
||||
<ModelSelectorLogo className="dark:invert" provider={m.chefSlug} />
|
||||
<ModelSelectorName>{m.name}</ModelSelectorName>
|
||||
{model === m.id ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</ModelSelectorItem>
|
||||
))}
|
||||
</ModelSelectorGroup>
|
||||
))}
|
||||
</MessageBranchContent>
|
||||
{versions.length > 1 && (
|
||||
<MessageBranchSelector from={from}>
|
||||
<MessageBranchPrevious />
|
||||
<MessageBranchPage />
|
||||
<MessageBranchNext />
|
||||
</MessageBranchSelector>
|
||||
)}
|
||||
</MessageBranch>
|
||||
))}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
</div>
|
||||
|
||||
<div className="grid shrink-0 gap-4 pt-4">
|
||||
<Suggestions className="px-4">
|
||||
{suggestions.map((suggestion) => (
|
||||
<Suggestion
|
||||
key={suggestion}
|
||||
suggestion={suggestion}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
/>
|
||||
))}
|
||||
</Suggestions>
|
||||
<div className="w-full px-4 pb-4">
|
||||
<PromptInput globalDrop multiple onSubmit={handleSubmit}>
|
||||
<PromptInputHeader>
|
||||
<PromptInputAttachments>
|
||||
{(attachment) => <PromptInputAttachment data={attachment} />}
|
||||
</PromptInputAttachments>
|
||||
</PromptInputHeader>
|
||||
<PromptInputBody>
|
||||
<PromptInputTextarea
|
||||
value={text}
|
||||
onChange={(event) => setText(event.target.value)}
|
||||
placeholder="Ask me anything about your Supabase data..."
|
||||
/>
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter>
|
||||
<PromptInputTools>
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuTrigger />
|
||||
<PromptInputActionMenuContent>
|
||||
<PromptInputActionAddAttachments />
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu>
|
||||
<PromptInputButton
|
||||
onClick={() => setUseMicrophone((prev) => !prev)}
|
||||
variant={useMicrophone ? 'default' : 'ghost'}
|
||||
type="button"
|
||||
>
|
||||
<MicIcon size={16} />
|
||||
<span className="sr-only">Microphone</span>
|
||||
</PromptInputButton>
|
||||
<PromptInputButton
|
||||
onClick={() => setUseWebSearch((prev) => !prev)}
|
||||
variant={useWebSearch ? 'default' : 'ghost'}
|
||||
type="button"
|
||||
>
|
||||
<GlobeIcon size={16} />
|
||||
<span>Search</span>
|
||||
</PromptInputButton>
|
||||
<ModelSelector open={modelSelectorOpen} onOpenChange={setModelSelectorOpen}>
|
||||
<ModelSelectorTrigger asChild>
|
||||
<PromptInputButton type="button">
|
||||
{selectedModelData?.chefSlug && (
|
||||
<ModelSelectorLogo provider={selectedModelData.chefSlug} />
|
||||
)}
|
||||
{selectedModelData?.name && (
|
||||
<ModelSelectorName>{selectedModelData.name}</ModelSelectorName>
|
||||
)}
|
||||
</PromptInputButton>
|
||||
</ModelSelectorTrigger>
|
||||
<ModelSelectorContent>
|
||||
<ModelSelectorInput placeholder="Search models..." />
|
||||
<ModelSelectorList>
|
||||
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
|
||||
{['OpenAI', 'Anthropic', 'Google'].map((chef) => (
|
||||
<ModelSelectorGroup key={chef} heading={chef}>
|
||||
{models
|
||||
.filter((entry) => entry.chef === chef)
|
||||
.map((entry) => (
|
||||
<ModelSelectorItem
|
||||
key={entry.id}
|
||||
value={entry.id}
|
||||
onSelect={() => {
|
||||
setModel(entry.id)
|
||||
setModelSelectorOpen(false)
|
||||
}}
|
||||
>
|
||||
<ModelSelectorLogo provider={entry.chefSlug} />
|
||||
<ModelSelectorName>{entry.name}</ModelSelectorName>
|
||||
<ModelSelectorLogoGroup>
|
||||
{entry.providers.map((provider) => (
|
||||
<ModelSelectorLogo key={provider} provider={provider} />
|
||||
))}
|
||||
</ModelSelectorLogoGroup>
|
||||
{model === entry.id ? (
|
||||
<span className="ml-auto text-xs text-foreground">
|
||||
Selected
|
||||
</span>
|
||||
) : (
|
||||
<div className="ml-auto h-4 w-4" />
|
||||
)}
|
||||
</ModelSelectorItem>
|
||||
))}
|
||||
</ModelSelectorGroup>
|
||||
))}
|
||||
</ModelSelectorList>
|
||||
</ModelSelectorContent>
|
||||
</ModelSelector>
|
||||
</PromptInputTools>
|
||||
<PromptInputSubmit status={status} disabled={submitDisabled} />
|
||||
</PromptInputFooter>
|
||||
</PromptInput>
|
||||
{error && <p className="mt-2 text-xs text-destructive">{error.message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</ModelSelectorList>
|
||||
</ModelSelectorContent>
|
||||
</ModelSelector>
|
||||
</PromptInputTools>
|
||||
<PromptInputSubmit
|
||||
disabled={!(text.trim() || status) || status === 'streaming'}
|
||||
status={status}
|
||||
/>
|
||||
</PromptInputFooter>
|
||||
</PromptInput>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssistantWidget
|
||||
@@ -6,6 +6,7 @@
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"input",
|
||||
"popover",
|
||||
"scroll-area",
|
||||
"@ai-elements/conversation",
|
||||
"@ai-elements/message",
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
"strict": true
|
||||
},
|
||||
"imports": {
|
||||
"hono": "npm:hono@^4.6.14",
|
||||
"hono": "npm:hono@4.10.6",
|
||||
"zod": "npm:zod@^4.1.12",
|
||||
"ai": "npm:ai@^5.0.90",
|
||||
"@ai-sdk/openai": "npm:@ai-sdk/openai@^2.0.32",
|
||||
"@ai-sdk/mcp": "npm:@ai-sdk/mcp@latest"
|
||||
"ai": "npm:ai@^5.0.93",
|
||||
"@ai-sdk/openai": "npm:@ai-sdk/openai@^2.0.68",
|
||||
"@ai-sdk/mcp": "npm:@ai-sdk/mcp@latest",
|
||||
"@modelcontextprotocol/sdk/client/streamableHttp.js": "npm:@modelcontextprotocol/sdk@^1.22.0/client/streamableHttp.js"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { experimental_createMCPClient as createMCPClient } from '@ai-sdk/mcp'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { streamText, type CoreMessage } from 'ai'
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
import { convertToModelMessages, stepCountIs, streamText } from 'ai'
|
||||
import { corsHeaders } from '../_shared/cors.ts'
|
||||
|
||||
type ChatRequestBody = {
|
||||
messages?: CoreMessage[]
|
||||
messages?: unknown[]
|
||||
model?: string
|
||||
}
|
||||
|
||||
@@ -39,16 +41,77 @@ Deno.serve(async (req) => {
|
||||
|
||||
const modelId = body.model && typeof body.model === 'string' ? body.model : 'gpt-4o'
|
||||
|
||||
let normalizedMessages
|
||||
try {
|
||||
normalizedMessages = convertToModelMessages(body.messages as any)
|
||||
} catch (conversionError) {
|
||||
console.error('Assistant chat message normalization error:', conversionError)
|
||||
return new Response(JSON.stringify({ error: 'Invalid message format' }), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
// Create MCP client to connect to our MCP server
|
||||
let mcpClient
|
||||
let tools = {}
|
||||
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')
|
||||
const mcpServerUrl = `${supabaseUrl}/functions/v1/mcp-server/mcp`
|
||||
|
||||
// Use StreamableHTTPClientTransport for compatibility with mcp-lite's StreamableHttpTransport
|
||||
const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl), {
|
||||
requestInit: {
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
mcpClient = await createMCPClient({
|
||||
transport,
|
||||
})
|
||||
|
||||
// Get available tools from the MCP server
|
||||
tools = await mcpClient.tools()
|
||||
} catch (mcpError) {
|
||||
console.error('MCP client initialization error:', mcpError)
|
||||
// Continue without MCP tools if initialization fails
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await streamText({
|
||||
model: openai(modelId),
|
||||
messages: body.messages,
|
||||
messages: normalizedMessages,
|
||||
stopWhen: stepCountIs(5),
|
||||
system: 'You are a helpful AI assistant.',
|
||||
tools,
|
||||
onFinish: async () => {
|
||||
// Close the MCP client when the stream is complete
|
||||
if (mcpClient) {
|
||||
await mcpClient.close()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return result.toTextStreamResponse({ headers: corsHeaders })
|
||||
return result.toUIMessageStreamResponse({
|
||||
headers: corsHeaders,
|
||||
sendReasoning: true,
|
||||
sendSources: true,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Assistant chat error:', error)
|
||||
|
||||
// Ensure MCP client is closed even on error
|
||||
if (mcpClient) {
|
||||
try {
|
||||
await mcpClient.close()
|
||||
} catch (closeError) {
|
||||
console.error('Error closing MCP client:', closeError)
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Failed to process chat request',
|
||||
|
||||
+4
-3
@@ -4,9 +4,10 @@
|
||||
"strict": true
|
||||
},
|
||||
"imports": {
|
||||
"hono": "npm:hono@^4.6.14",
|
||||
"mcp-lite": "npm:mcp-lite@0.8.2",
|
||||
"hono": "npm:hono@4.10.6",
|
||||
"mcp-lite": "npm:mcp-lite@0.10.0",
|
||||
"zod": "npm:zod@^4.1.12",
|
||||
"@supabase/supabase-js": "npm:@supabase/supabase-js@^2"
|
||||
}
|
||||
},
|
||||
"verify_jwt": false
|
||||
}
|
||||
+272
-68
@@ -1,13 +1,8 @@
|
||||
// Follow this setup guide to integrate the Deno language server with your editor:
|
||||
// https://deno.land/manual/getting_started/setup_your_environment
|
||||
// This enables autocomplete, go to definition, etc.
|
||||
//
|
||||
// Setup type definitions for built-in Supabase Runtime APIs
|
||||
/// <reference types="https://esm.sh/@supabase/functions-js/src/edge-runtime.d.ts" />
|
||||
|
||||
import { Hono } from "hono";
|
||||
import { McpServer, StreamableHttpTransport } from "mcp-lite";
|
||||
import { z } from "zod";
|
||||
import { Hono } from 'hono'
|
||||
import { McpServer, StreamableHttpTransport } from 'mcp-lite'
|
||||
import { z } from 'zod'
|
||||
import { corsHeaders } from '../_shared/cors.ts'
|
||||
import { createClient } from 'jsr:@supabase/supabase-js@2'
|
||||
|
||||
// We create two Hono instances:
|
||||
// 1. `app` is the root handler for the Supabase Edge Function (must match the function name, e.g. /mcp-server)
|
||||
@@ -15,77 +10,286 @@ import { z } from "zod";
|
||||
// This pattern is required because Supabase Edge Functions route all requests to /<function-name>/*
|
||||
|
||||
const mcp = new McpServer({
|
||||
name: "supabase-assistant-mcp-server",
|
||||
version: "1.0.0",
|
||||
name: 'supabase-assistant-mcp-server',
|
||||
version: '1.0.0',
|
||||
schemaAdapter: (schema: unknown) => z.toJSONSchema(schema as z.ZodType),
|
||||
});
|
||||
})
|
||||
|
||||
// Temporary placeholder tool while wiring up the rest of the MCP server.
|
||||
mcp.tool("queryDatabase", {
|
||||
description:
|
||||
"Example placeholder tool that just echoes a string back to the caller.",
|
||||
inputSchema: z.object({
|
||||
message: z
|
||||
.string()
|
||||
.default("Hello from the Supabase MCP server!")
|
||||
.describe("Optional message you would like the tool to return"),
|
||||
}),
|
||||
handler: async ({ message }: { message: string }) => {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Example tool response: ${message}`,
|
||||
// Auth Header Management
|
||||
// StreamableHttpTransport doesn't expose HTTP request headers to the MCP context (ctx.request.headers is undefined).
|
||||
// We use a module-level variable to pass the auth header from the Hono handler to MCP middleware.
|
||||
// This is safe because Supabase Edge Functions process requests sequentially, not concurrently.
|
||||
// The header is set before processing and cleared in a finally block to prevent leaks.
|
||||
// Alternative: Use InMemorySessionAdapter with custom session context, but adds unnecessary complexity.
|
||||
let currentAuthHeader: string | null = null
|
||||
|
||||
// Helper function to create authenticated Supabase client
|
||||
function createAuthenticatedClient(authHeader: string) {
|
||||
return createClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
Deno.env.get('SUPABASE_ANON_KEY')!,
|
||||
{
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Middleware to inject auth header into MCP context state
|
||||
mcp.use(async (ctx: any, next: any) => {
|
||||
// Try to get auth header from ctx.request.headers (doesn't work with StreamableHttpTransport)
|
||||
// Fall back to the stored currentAuthHeader set by the Hono handler
|
||||
const authHeader = ctx.request?.headers?.get?.('Authorization') || currentAuthHeader
|
||||
|
||||
if (authHeader) {
|
||||
// Make auth header available to all tool handlers via ctx.state
|
||||
ctx.state.authHeader = authHeader
|
||||
}
|
||||
|
||||
await next()
|
||||
})
|
||||
|
||||
mcp.tool('getUserTasks', {
|
||||
description: 'Fetch tasks for the authenticated user with optional filtering by due date and title search.',
|
||||
inputSchema: z.object({
|
||||
due_date: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional due date filter in YYYY-MM-DD format. Returns tasks with this exact due date.'),
|
||||
title_search: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional title search term. Returns tasks where the title contains this text (case-insensitive).'),
|
||||
}),
|
||||
handler: async ({ due_date, title_search }: { due_date?: string; title_search?: string }, ctx: any) => {
|
||||
const authHeader = ctx.state?.authHeader
|
||||
|
||||
if (!authHeader) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Error: Authentication required to fetch tasks.',
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const supabase = createAuthenticatedClient(authHeader)
|
||||
|
||||
let query = supabase
|
||||
.from('tasks')
|
||||
.select('id, created_at, title, assignee_id, due_at')
|
||||
.order('due_at', { ascending: true, nullsFirst: false })
|
||||
|
||||
// Apply due_date filter if provided
|
||||
if (due_date) {
|
||||
query = query.eq('due_at', due_date)
|
||||
}
|
||||
|
||||
// Apply title_search filter if provided
|
||||
if (title_search) {
|
||||
query = query.ilike('title', `%${title_search}%`)
|
||||
}
|
||||
|
||||
const { data, error } = await query
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error fetching tasks: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const taskCount = data?.length || 0
|
||||
const tasksText = data && taskCount > 0
|
||||
? data.map((task) => {
|
||||
const dueDate = task.due_at ? `Due: ${task.due_at}` : 'No due date'
|
||||
return `- [${task.id}] ${task.title || 'Untitled'} (${dueDate})`
|
||||
}).join('\n')
|
||||
: 'No tasks found.'
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Found ${taskCount} task(s):\n\n${tasksText}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const transport = new StreamableHttpTransport();
|
||||
const httpHandler = transport.bind(mcp);
|
||||
mcp.tool('createTask', {
|
||||
description: 'Create a task for the authenticated user. Optionally provide a due date (YYYY-MM-DD).',
|
||||
inputSchema: z.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(1, 'Title must not be empty')
|
||||
.describe('Task title to display in the list.'),
|
||||
due_date: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional due date in YYYY-MM-DD format.'),
|
||||
}),
|
||||
handler: async ({ title, due_date }: { title: string; due_date?: string }, ctx: any) => {
|
||||
const authHeader = ctx.state?.authHeader
|
||||
|
||||
const app = new Hono();
|
||||
const mcpApp = new Hono();
|
||||
if (!authHeader) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Error: Authentication required to create tasks.',
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
mcpApp.get("/", (c) => {
|
||||
try {
|
||||
const supabase = createAuthenticatedClient(authHeader)
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
error: userError,
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (userError || !user) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error retrieving authenticated user: ${userError?.message ?? 'Not found'}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('tasks')
|
||||
.insert({
|
||||
title,
|
||||
due_at: due_date ?? null,
|
||||
assignee_id: user.id,
|
||||
})
|
||||
.select('id, title, due_at, assignee_id')
|
||||
.single()
|
||||
|
||||
if (error || !data) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error creating task: ${error?.message ?? 'Unknown error'}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const dueDateText = data.due_at ? `Due ${data.due_at}` : 'No due date set'
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Created task [${data.id}] "${data.title}" for user ${data.assignee_id}. ${dueDateText}.`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const transport = new StreamableHttpTransport()
|
||||
const httpHandler = transport.bind(mcp)
|
||||
|
||||
const app = new Hono()
|
||||
const mcpApp = new Hono()
|
||||
|
||||
// Handle OPTIONS requests for CORS
|
||||
mcpApp.use('*', async (c, next) => {
|
||||
if (c.req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
await next()
|
||||
})
|
||||
|
||||
mcpApp.get('/', (c) => {
|
||||
return c.json({
|
||||
message: "MCP Server on Supabase Edge Functions",
|
||||
message: 'MCP Server on Supabase Edge Functions',
|
||||
endpoints: {
|
||||
mcp: "/mcp",
|
||||
health: "/health",
|
||||
mcp: '/mcp',
|
||||
health: '/health',
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
mcpApp.get("/health", (c) => {
|
||||
mcpApp.get('/health', (c) => {
|
||||
return c.json({
|
||||
message: "Service is up and running",
|
||||
});
|
||||
});
|
||||
message: 'Service is up and running',
|
||||
})
|
||||
})
|
||||
|
||||
mcpApp.all("/mcp", async (c) => {
|
||||
const response = await httpHandler(c.req.raw);
|
||||
return response;
|
||||
});
|
||||
// Handle both POST (for regular MCP requests) and GET (for SSE connections)
|
||||
const handleMcpRequest = async (c: any) => {
|
||||
// Validate Authorization header at the Hono level
|
||||
// (StreamableHttpTransport doesn't expose HTTP headers to MCP context)
|
||||
const authHeader = c.req.header('Authorization')
|
||||
|
||||
if (!authHeader) {
|
||||
return c.json({ error: 'Unauthorized' }, { status: 401, headers: corsHeaders })
|
||||
}
|
||||
|
||||
// Store auth header so MCP middleware can inject it into ctx.state for tool handlers
|
||||
currentAuthHeader = authHeader
|
||||
|
||||
try {
|
||||
const response = await httpHandler(c.req.raw)
|
||||
return response
|
||||
} finally {
|
||||
// Clean up auth header after request completes to prevent leaks
|
||||
currentAuthHeader = null
|
||||
}
|
||||
}
|
||||
|
||||
mcpApp.post('/mcp', handleMcpRequest)
|
||||
mcpApp.get('/mcp', handleMcpRequest)
|
||||
|
||||
// Mount the MCP app at /mcp-server (matches the function name)
|
||||
app.route("/mcp-server", mcpApp);
|
||||
app.route('/mcp-server', mcpApp)
|
||||
|
||||
export default app;
|
||||
|
||||
/* To invoke locally:
|
||||
|
||||
1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start)
|
||||
2. Make an HTTP request:
|
||||
|
||||
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/mcp-server/mcp' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","method":"tools/list","id":1}'
|
||||
|
||||
3. Test the health endpoint:
|
||||
|
||||
curl 'http://127.0.0.1:54321/functions/v1/mcp-server/health'
|
||||
|
||||
*/
|
||||
export default app
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { createClient } from 'jsr:@supabase/supabase-js@2'
|
||||
|
||||
export function createAuthenticatedClient(authHeader: string) {
|
||||
return createClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
Deno.env.get('SUPABASE_ANON_KEY')!,
|
||||
{
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import { z } from 'zod'
|
||||
import { createAuthenticatedClient } from '../lib/supabase.ts'
|
||||
|
||||
export const createTaskTool = {
|
||||
name: 'createTask',
|
||||
options: {
|
||||
description: 'Create a task for the authenticated user. Optionally provide a due date (YYYY-MM-DD).',
|
||||
inputSchema: z.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(1, 'Title must not be empty')
|
||||
.describe('Task title to display in the list.'),
|
||||
due_date: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional due date in YYYY-MM-DD format.'),
|
||||
}),
|
||||
handler: async ({ title, due_date }: { title: string; due_date?: string }, ctx: any) => {
|
||||
const authHeader = ctx.state?.authHeader
|
||||
|
||||
if (!authHeader) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Error: Authentication required to create tasks.',
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const supabase = createAuthenticatedClient(authHeader)
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
error: userError,
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (userError || !user) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error retrieving authenticated user: ${userError?.message ?? 'Not found'}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('tasks')
|
||||
.insert({
|
||||
title,
|
||||
due_at: due_date ?? null,
|
||||
assignee_id: user.id,
|
||||
})
|
||||
.select('id, title, due_at, assignee_id')
|
||||
.single()
|
||||
|
||||
if (error || !data) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error creating task: ${error?.message ?? 'Unknown error'}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const dueDateText = data.due_at ? `Due ${data.due_at}` : 'No due date set'
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Created task [${data.id}] "${data.title}" for user ${data.assignee_id}. ${dueDateText}.`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import { z } from 'zod'
|
||||
import { createAuthenticatedClient } from '../lib/supabase.ts'
|
||||
|
||||
export const getUserTasksTool = {
|
||||
name: 'getUserTasks',
|
||||
options: {
|
||||
description:
|
||||
'Fetch tasks for the authenticated user with optional filtering by due date and title search.',
|
||||
inputSchema: z.object({
|
||||
due_date: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional due date filter in YYYY-MM-DD format. Returns tasks with this exact due date.'),
|
||||
title_search: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional title search term. Returns tasks where the title contains this text (case-insensitive).'),
|
||||
}),
|
||||
handler: async (
|
||||
{ due_date, title_search }: { due_date?: string; title_search?: string },
|
||||
ctx: any
|
||||
) => {
|
||||
const authHeader = ctx.state?.authHeader
|
||||
|
||||
if (!authHeader) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Error: Authentication required to fetch tasks.',
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const supabase = createAuthenticatedClient(authHeader)
|
||||
|
||||
let query = supabase
|
||||
.from('tasks')
|
||||
.select('id, created_at, title, assignee_id, due_at')
|
||||
.order('due_at', { ascending: true, nullsFirst: false })
|
||||
|
||||
if (due_date) {
|
||||
query = query.eq('due_at', due_date)
|
||||
}
|
||||
|
||||
if (title_search) {
|
||||
query = query.ilike('title', `%${title_search}%`)
|
||||
}
|
||||
|
||||
const { data, error } = await query
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error fetching tasks: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const taskCount = data?.length || 0
|
||||
const tasksText =
|
||||
data && taskCount > 0
|
||||
? data
|
||||
.map((task) => {
|
||||
const dueDate = task.due_at ? `Due: ${task.due_at}` : 'No due date'
|
||||
return `- [${task.id}] ${task.title || 'Untitled'} (${dueDate})`
|
||||
})
|
||||
.join('\n')
|
||||
: 'No tasks found.'
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Found ${taskCount} task(s):\n\n${tasksText}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export type ToolDefinition = typeof getUserTasksTool
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { createTaskTool } from './create-task.ts'
|
||||
import { getUserTasksTool } from './get-user-tasks.ts'
|
||||
|
||||
export const toolRegistry = [getUserTasksTool, createTaskTool]
|
||||
|
||||
export type ToolDefinition = (typeof toolRegistry)[number]
|
||||
@@ -39,4 +39,8 @@ function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitiv
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
function PopoverClose({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Close>) {
|
||||
return <PopoverPrimitive.Close data-slot="popover-close" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose }
|
||||
@@ -1,97 +1,45 @@
|
||||
'use client'
|
||||
|
||||
import { AssistantWidget } from '@/registry/default/blocks/assistant/components/assistant-widget'
|
||||
import { Badge } from '@/registry/default/components/ui/badge'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/default/components/ui/card'
|
||||
import { Separator } from '@/registry/default/components/ui/separator'
|
||||
import { Check, Database, ShieldCheck, Sparkles } from 'lucide-react'
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: 'Query with plain English',
|
||||
description: 'Natural language requests turn into SQL via MCP tools.',
|
||||
icon: Sparkles,
|
||||
},
|
||||
{
|
||||
title: 'Database aware',
|
||||
description: 'Assistant can run validated SQL against your Supabase project.',
|
||||
icon: Database,
|
||||
},
|
||||
{
|
||||
title: 'Secure by default',
|
||||
description: 'Supabase auth token is forwarded for RLS protected data.',
|
||||
icon: ShieldCheck,
|
||||
},
|
||||
]
|
||||
|
||||
const setupSteps = [
|
||||
'Serve the `chat` and `mcp-server` Edge Functions.',
|
||||
'Expose `NEXT_PUBLIC_ASSISTANT_CHAT_URL` and `MCP_SERVER_URL`.',
|
||||
'Mount `<AssistantWidget />` near the root layout of your app.',
|
||||
]
|
||||
|
||||
const Feature = ({ title, description, icon: Icon }: (typeof features)[number]) => (
|
||||
<div className="flex gap-3 rounded-lg border bg-muted/20 p-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<Icon className="h-5 w-5" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{title}</p>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const SetupChecklist = () => (
|
||||
<div className="space-y-3 rounded-lg border bg-muted/20 p-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-foreground/70">
|
||||
Quick setup
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{setupSteps.map((step) => (
|
||||
<li key={step} className="flex items-start gap-2 text-left">
|
||||
<Check className="mt-0.5 h-4 w-4 text-primary" strokeWidth={3 / 2} />
|
||||
<span>{step}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
import { createClient } from '@/registry/default/blocks/assistant/lib/supabase/client'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const AssistantDemoContent = () => (
|
||||
<Card className="relative z-10 w-full max-w-3xl border bg-background/95 shadow-2xl backdrop-blur">
|
||||
<CardHeader>
|
||||
<Badge variant="outline" className="w-fit text-xs font-medium tracking-tight">
|
||||
AI Assistant
|
||||
</Badge>
|
||||
<CardTitle className="text-2xl font-semibold">Ask your data anything</CardTitle>
|
||||
<CardDescription>
|
||||
The Assistant widget pairs MCP tooling with Supabase authentication so you can run secure,
|
||||
natural-language queries against your database without leaving the page.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{features.map((feature) => (
|
||||
<Feature key={feature.title} {...feature} />
|
||||
))}
|
||||
</div>
|
||||
<Separator />
|
||||
<SetupChecklist />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Tip: Click the bubble in the bottom-right corner to open the live assistant powered by the
|
||||
same block you install via the registry.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const AssistantDemo = () => (
|
||||
<div className="relative flex h-[600px] items-center justify-center bg-gradient-to-b from-background via-muted/40 to-background">
|
||||
<div className="absolute inset-0 opacity-50 [background-image:radial-gradient(circle_at_top,_rgba(15,118,110,0.15),_transparent_50%)]" />
|
||||
<AssistantDemoContent />
|
||||
<AssistantWidget />
|
||||
<div className="max-w-2xl mx-auto p-8">
|
||||
<p>
|
||||
Click the Assistant widget and ask it to create, read, update, or delete tasks in your
|
||||
database. Tasks will be scoped to your current session.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
const AssistantDemo = () => {
|
||||
useEffect(() => {
|
||||
const ensureSession = async () => {
|
||||
const supabase = createClient()
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session) {
|
||||
const { error } = await supabase.auth.signInAnonymously()
|
||||
if (error) {
|
||||
console.error('Anonymous sign-in failed', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ensureSession().catch((error) => {
|
||||
console.error('Failed to check session', error)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="relative h-screen w-screen flex flex-col items-center justify-center">
|
||||
<AssistantDemoContent />
|
||||
<AssistantWidget triggerClassName="absolute bottom-6 right-6" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssistantDemo
|
||||
Generated
+62
-42
@@ -400,7 +400,7 @@ importers:
|
||||
version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.3.0
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
'@supabase/supabase-js':
|
||||
specifier: 'catalog:'
|
||||
version: 2.78.0
|
||||
@@ -815,7 +815,7 @@ importers:
|
||||
version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.3.0
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
'@std/path':
|
||||
specifier: npm:@jsr/std__path@^1.0.8
|
||||
version: '@jsr/std__path@1.0.8'
|
||||
@@ -1250,8 +1250,8 @@ importers:
|
||||
apps/ui-library:
|
||||
dependencies:
|
||||
'@ai-sdk/react':
|
||||
specifier: 2.0.45
|
||||
version: 2.0.45(react@18.3.1)(zod@3.25.76)
|
||||
specifier: ^2.0.68
|
||||
version: 2.0.93(react@18.3.1)(zod@3.25.76)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.1.1
|
||||
version: 3.3.1(react-hook-form@7.47.0(react@18.3.1))
|
||||
@@ -1361,8 +1361,8 @@ importers:
|
||||
specifier: ^12.9.2
|
||||
version: 12.9.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
ai:
|
||||
specifier: ^5.0.90
|
||||
version: 5.0.90(zod@3.25.76)
|
||||
specifier: ^5.0.93
|
||||
version: 5.0.93(zod@3.25.76)
|
||||
axios:
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.2
|
||||
@@ -1504,7 +1504,7 @@ importers:
|
||||
version: 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tanstack/react-start':
|
||||
specifier: ^1.114.25
|
||||
version: 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
|
||||
version: 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
|
||||
'@types/common-tags':
|
||||
specifier: ^1.8.4
|
||||
version: 1.8.4
|
||||
@@ -1606,7 +1606,7 @@ importers:
|
||||
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
'@supabase/supabase-js':
|
||||
specifier: 'catalog:'
|
||||
version: 2.78.0
|
||||
@@ -1693,7 +1693,7 @@ importers:
|
||||
version: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)
|
||||
next-seo:
|
||||
specifier: ^6.5.0
|
||||
version: 6.5.0(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 6.5.0(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -2644,8 +2644,8 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/gateway@2.0.7':
|
||||
resolution: {integrity: sha512-/AI5AKi4vOK9SEb8Z1dfXkhsJ5NAfWsoJQc96B/mzn2KIrjw5occOjIwD06scuhV9xWlghCoXJT1sQD9QH/tyg==}
|
||||
'@ai-sdk/gateway@2.0.9':
|
||||
resolution: {integrity: sha512-E6x4h5CPPPJ0za1r5HsLtHbeI+Tp3H+YFtcH8G3dSSPFE6w+PZINzB4NxLZmg1QqSeA5HTP3ZEzzsohp0o2GEw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
@@ -2668,8 +2668,8 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.16':
|
||||
resolution: {integrity: sha512-lsWQY9aDXHitw7C1QRYIbVGmgwyT98TF3MfM8alNIXKpdJdi+W782Rzd9f1RyOfgRmZ08gJ2EYNDhWNK7RqpEA==}
|
||||
'@ai-sdk/provider-utils@3.0.17':
|
||||
resolution: {integrity: sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
@@ -2694,6 +2694,16 @@ packages:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@ai-sdk/react@2.0.93':
|
||||
resolution: {integrity: sha512-2TzhpQr10HuWxpqyHpSAUMRUqD1G2O73J2sAaJChomVDbjr7BwpM0mdR3aRamCXNtuLiJmTFQhbNzw8fXMBdYw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
peerDependenciesMeta:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -10105,8 +10115,8 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
ai@5.0.90:
|
||||
resolution: {integrity: sha512-bawNN10N2cXzFedbDdNUZo8KkcGp12VX1b+mCL5dfllh6WmLsIYYME7GVxsRJvHvPP7xRhuds5fn0jtLyxGnZw==}
|
||||
ai@5.0.93:
|
||||
resolution: {integrity: sha512-9eGcu+1PJgPg4pRNV4L7tLjRR3wdJC9CXQoNMvtqvYNOLZHFCzjHtVIOr2SIkoJJeu2+sOy3hyiSuTmy2MA40g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
@@ -19919,10 +19929,10 @@ snapshots:
|
||||
'@ai-sdk/provider-utils': 3.0.9(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/gateway@2.0.7(zod@3.25.76)':
|
||||
'@ai-sdk/gateway@2.0.9(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@ai-sdk/provider-utils': 3.0.16(zod@3.25.76)
|
||||
'@ai-sdk/provider-utils': 3.0.17(zod@3.25.76)
|
||||
'@vercel/oidc': 3.0.3
|
||||
zod: 3.25.76
|
||||
|
||||
@@ -19946,7 +19956,7 @@ snapshots:
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.5(zod@3.25.76)
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.16(zod@3.25.76)':
|
||||
'@ai-sdk/provider-utils@3.0.17(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@standard-schema/spec': 1.0.0
|
||||
@@ -19974,6 +19984,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/react@2.0.93(react@18.3.1)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider-utils': 3.0.17(zod@3.25.76)
|
||||
ai: 5.0.93(zod@3.25.76)
|
||||
react: 18.3.1
|
||||
swr: 2.2.5(react@18.3.1)
|
||||
throttleit: 2.1.0
|
||||
optionalDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
@@ -27518,7 +27538,7 @@ snapshots:
|
||||
|
||||
'@sentry/core@10.3.0': {}
|
||||
|
||||
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
|
||||
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.36.0
|
||||
@@ -28587,7 +28607,7 @@ snapshots:
|
||||
tiny-invariant: 1.3.3
|
||||
tiny-warning: 1.0.3
|
||||
|
||||
'@tanstack/react-start-client@1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
|
||||
'@tanstack/react-start-client@1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
|
||||
dependencies:
|
||||
'@tanstack/react-router': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tanstack/router-core': 1.114.25
|
||||
@@ -28598,7 +28618,7 @@ snapshots:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
tiny-invariant: 1.3.3
|
||||
tiny-warning: 1.0.3
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -28642,7 +28662,7 @@ snapshots:
|
||||
- xml2js
|
||||
- yaml
|
||||
|
||||
'@tanstack/react-start-config@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
|
||||
'@tanstack/react-start-config@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
|
||||
dependencies:
|
||||
'@tanstack/react-start-plugin': 1.114.12(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
|
||||
'@tanstack/router-core': 1.114.25
|
||||
@@ -28652,11 +28672,11 @@ snapshots:
|
||||
'@tanstack/start-server-functions-handler': 1.114.25
|
||||
'@vitejs/plugin-react': 4.3.4(supports-color@8.1.1)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
|
||||
import-meta-resolve: 4.1.0
|
||||
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2)
|
||||
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2)
|
||||
ofetch: 1.4.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
vite: 7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
@@ -28734,11 +28754,11 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@tanstack/react-start-router-manifest@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
|
||||
'@tanstack/react-start-router-manifest@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
|
||||
dependencies:
|
||||
'@tanstack/router-core': 1.114.25
|
||||
tiny-invariant: 1.3.3
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -28797,13 +28817,13 @@ snapshots:
|
||||
tiny-warning: 1.0.3
|
||||
unctx: 2.4.1
|
||||
|
||||
'@tanstack/react-start@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
|
||||
'@tanstack/react-start@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
|
||||
dependencies:
|
||||
'@tanstack/react-start-client': 1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
'@tanstack/react-start-config': 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
|
||||
'@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
'@tanstack/react-start-client': 1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
'@tanstack/react-start-config': 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
|
||||
'@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
'@tanstack/react-start-server': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
'@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
'@tanstack/start-server-functions-client': 1.114.25(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
|
||||
'@tanstack/start-server-functions-handler': 1.114.25
|
||||
'@tanstack/start-server-functions-server': 1.114.12(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
|
||||
@@ -28954,11 +28974,11 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@tanstack/start-api-routes@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
|
||||
'@tanstack/start-api-routes@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
|
||||
dependencies:
|
||||
'@tanstack/router-core': 1.114.25
|
||||
'@tanstack/start-server-core': 1.114.25
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -29866,7 +29886,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@aws-sdk/credential-provider-web-identity': 3.830.0
|
||||
|
||||
'@vercel/nft@0.29.2(rollup@4.50.2)(supports-color@8.1.1)':
|
||||
'@vercel/nft@0.29.2(encoding@0.1.13)(rollup@4.50.2)(supports-color@8.1.1)':
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)(supports-color@8.1.1)
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.50.2)
|
||||
@@ -30401,11 +30421,11 @@ snapshots:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 3.25.76
|
||||
|
||||
ai@5.0.90(zod@3.25.76):
|
||||
ai@5.0.93(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 2.0.7(zod@3.25.76)
|
||||
'@ai-sdk/gateway': 2.0.9(zod@3.25.76)
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@ai-sdk/provider-utils': 3.0.16(zod@3.25.76)
|
||||
'@ai-sdk/provider-utils': 3.0.17(zod@3.25.76)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 3.25.76
|
||||
|
||||
@@ -36709,7 +36729,7 @@ snapshots:
|
||||
next: 15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
react: 18.3.1
|
||||
|
||||
next-seo@6.5.0(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next-seo@6.5.0(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
next: 15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
react: 18.3.1
|
||||
@@ -36750,7 +36770,7 @@ snapshots:
|
||||
|
||||
nice-try@1.0.5: {}
|
||||
|
||||
nitropack@2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2):
|
||||
nitropack@2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.4.0
|
||||
'@netlify/functions': 3.0.4
|
||||
@@ -36761,7 +36781,7 @@ snapshots:
|
||||
'@rollup/plugin-node-resolve': 16.0.1(rollup@4.50.2)
|
||||
'@rollup/plugin-replace': 6.0.2(rollup@4.50.2)
|
||||
'@rollup/plugin-terser': 0.4.4(rollup@4.50.2)
|
||||
'@vercel/nft': 0.29.2(rollup@4.50.2)(supports-color@8.1.1)
|
||||
'@vercel/nft': 0.29.2(encoding@0.1.13)(rollup@4.50.2)(supports-color@8.1.1)
|
||||
archiver: 7.0.1
|
||||
c12: 3.0.2(magicast@0.3.5)
|
||||
chokidar: 4.0.3
|
||||
@@ -41708,7 +41728,7 @@ snapshots:
|
||||
d3-time: 3.1.0
|
||||
d3-timer: 3.0.1
|
||||
|
||||
vinxi@0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1):
|
||||
vinxi@0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.4(supports-color@8.1.1)
|
||||
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4(supports-color@8.1.1))
|
||||
@@ -41730,7 +41750,7 @@ snapshots:
|
||||
hookable: 5.5.3
|
||||
http-proxy: 1.18.1
|
||||
micromatch: 4.0.8
|
||||
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2)
|
||||
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2)
|
||||
node-fetch-native: 1.6.6
|
||||
path-to-regexp: 6.3.0
|
||||
pathe: 1.1.2
|
||||
|
||||
Reference in new issue
Block a user