Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/docs/content/guides/auth/social-login/auth-google.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ To use Google's pre-built signin buttons:

### Configuration [#ios-configuration]

1. Follow the integration instructions on the [get started with Google Sign-In](https://developers.google.com/identity/sign-in/ios/start-integrating) for iOS guide.
1. Follow the integration instructions on the [get started with Google Sign-In](https://developers.google.com/identity/sign-in/ios/start-integrating) for the iOS guide.
2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
3. Add only the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. Enable the `Skip nonce check` option.
3. Add web client ID and iOS client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_, separated by a comma. Enable the `Skip nonce check` option.

</TabPanel>

Expand Down
3 changes: 1 addition & 2 deletions apps/docs/content/guides/database/connection-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,10 @@ The usename can be used to identify the source:

| Role | API/Tool |
| ---------------------------- | ------------------------------------------------------------------------- |
| supabase_admin | Used by Supabase to configure projects and for monitoring |
| supabase_admin | Used by Supabase for monitoring and by Realtime |
| authenticator | Data API (PostgREST) |
| supabase_auth_admin | Auth |
| supabase_storage_admin | Storage |
| supabase_realtime_admin | Realtime |
| supabase_replication_admin | Synchronizes Read Replicas |
| postgres | Supabase Dashboard and External Tools (e.g., Prisma, SQLAlchemy, PSQL...) |
| Custom roles defined by user | External Tools (e.g., Prisma, SQLAlchemy, PSQL...) |
119 changes: 48 additions & 71 deletions apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { AnimatePresence, motion } from 'framer-motion'
import { last } from 'lodash'
import { FileText, Info, X } from 'lucide-react'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { FileText, Info, X, ArrowDown } from 'lucide-react'
import { memo, useEffect, useMemo, useRef, useState, useCallback } from 'react'
import { toast } from 'sonner'

import type { Message as MessageType } from 'ai/react'
Expand Down Expand Up @@ -44,6 +44,7 @@ import DotGrid from '../DotGrid'
import AIOnboarding from './AIOnboarding'
import CollapsibleCodeBlock from './CollapsibleCodeBlock'
import { Message } from './Message'
import { useAutoScroll } from './hooks'

const MemoizedMessage = memo(
({ message, isLoading }: { message: MessageType; isLoading: boolean }) => {
Expand Down Expand Up @@ -89,8 +90,7 @@ export const AIAssistant = ({
const { open, initialInput, sqlSnippets, suggestions } = aiAssistantPanel

const inputRef = useRef<HTMLTextAreaElement>(null)
const bottomRef = useRef<HTMLDivElement>(null)
const scrollContainerRef = useRef<HTMLDivElement>(null)
const { ref: scrollContainerRef, isSticky, scrollToEnd } = useAutoScroll()

const [value, setValue] = useState<string>(initialInput)
const [assistantError, setAssistantError] = useState<string>()
Expand Down Expand Up @@ -229,37 +229,16 @@ export const AIAssistant = ({
)
}

const handleScroll = () => {
const container = scrollContainerRef.current
if (container) {
const scrollPercentage =
(container.scrollTop / (container.scrollHeight - container.clientHeight)) * 100
const isScrollable = container.scrollHeight > container.clientHeight
const isAtBottom = scrollPercentage >= 100

setShowFade(isScrollable && !isAtBottom)
}
}

// Add useEffect to set up scroll listener
// Update scroll behavior for new messages
useEffect(() => {
// Use a small delay to ensure container is mounted and has content
const timeoutId = setTimeout(() => {
const container = scrollContainerRef.current
if (container) {
container.addEventListener('scroll', handleScroll)
handleScroll()
}
}, 100)
if (!isChatLoading) {
if (inputRef.current) inputRef.current.focus()
}

return () => {
clearTimeout(timeoutId)
const container = scrollContainerRef.current
if (container) {
container.removeEventListener('scroll', handleScroll)
}
if (isSticky) {
setTimeout(scrollToEnd, 0)
}
}, [])
}, [isChatLoading, isSticky, scrollToEnd, messages])

useEffect(() => {
setValue(initialInput)
Expand All @@ -269,30 +248,6 @@ export const AIAssistant = ({
}
}, [initialInput])

useEffect(() => {
if (!isChatLoading) {
if (inputRef.current) inputRef.current.focus()
}

setTimeout(
() => {
if (bottomRef.current) bottomRef.current.scrollIntoView({ behavior: 'smooth' })
},
isChatLoading ? 100 : 500
)
}, [isChatLoading])

useEffect(() => {
if (bottomRef.current) bottomRef.current.scrollIntoView({ behavior: 'smooth' })
handleScroll()
// Load messages into state
if (!isChatLoading) {
setAiAssistantPanel({
messages,
})
}
}, [messages, isChatLoading, setAiAssistantPanel])

// Remove suggestions if sqlSnippets were removed
useEffect(() => {
if (!sqlSnippets || sqlSnippets.length === 0) {
Expand All @@ -310,11 +265,7 @@ export const AIAssistant = ({
return (
<>
<div className={cn('flex flex-col h-full', className)}>
<div
ref={scrollContainerRef}
className={cn('flex-grow overflow-auto flex flex-col')}
onScroll={handleScroll}
>
<div ref={scrollContainerRef} className={cn('flex-grow overflow-auto flex flex-col')}>
<div className="z-30 sticky top-0">
<div className="border-b flex items-center bg gap-x-3 px-5 h-[46px]">
<AiIconAnimation allowHoverEffect />
Expand Down Expand Up @@ -401,7 +352,7 @@ export const AIAssistant = ({
</motion.div>
</div>
)}
<div ref={bottomRef} className="h-1" />
<div className="h-1" />
</motion.div>
) : suggestions ? (
<div className="w-full h-full px-8 py-0 flex flex-col flex-1 justify-end">
Expand Down Expand Up @@ -490,16 +441,41 @@ export const AIAssistant = ({
</div>
)}
</div>

<AnimatePresence>
{showFade && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="pointer-events-none z-10 -mt-24"
>
<div className="h-24 w-full bg-gradient-to-t from-background muted to-transparent" />
</motion.div>
{!isSticky && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="pointer-events-none z-10 -mt-24"
>
<div className="h-24 w-full bg-gradient-to-t from-background to-transparent" />
</motion.div>
<motion.div
className="absolute bottom-20 left-1/2 -translate-x-1/2"
variants={{
hidden: { y: 5, opacity: 0 },
show: { y: 0, opacity: 1 },
}}
transition={{ duration: 0.1 }}
initial="hidden"
animate="show"
exit="hidden"
>
<Button
type="default"
className="rounded-full w-8 h-8 p-1.5"
onClick={() => {
scrollToEnd()
if (inputRef.current) inputRef.current.focus()
}}
>
<ArrowDown size={16} />
</Button>
</motion.div>
</>
)}
</AnimatePresence>

Expand Down Expand Up @@ -567,6 +543,7 @@ export const AIAssistant = ({
.join('\n') || ''
const valueWithSnippets = [value, sqlSnippetsString].filter(Boolean).join('\n\n')
sendMessageToAssistant(valueWithSnippets)
scrollToEnd()
} else {
sendMessageToAssistant(value)
}
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/components/ui/AIAssistantPanel/SqlSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ export const SqlCard = ({
: 'This query involves running a function.'}{' '}
Are you sure you want to execute it?
</p>
<p className="text-foreground-light">
Make sure you are not accidentally removing something important.
</p>
<div className="flex justify-stretch mt-2 gap-2">
<Button
type="outline"
Expand All @@ -193,7 +196,7 @@ export const SqlCard = ({
Cancel
</Button>
<Button
type="outline"
type="danger"
size="tiny"
className="w-full flex-1"
onClick={() => {
Expand Down
68 changes: 68 additions & 0 deletions apps/studio/components/ui/AIAssistantPanel/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useCallback, useEffect, useRef, useState } from 'react'

interface UseAutoScrollProps {
enabled?: boolean
}

export function useAutoScroll({ enabled = true }: UseAutoScrollProps = {}) {
const [container, setContainer] = useState<HTMLDivElement | null>(null)
const [isSticky, setIsSticky] = useState(true)
const isStickyRef = useRef(true)
const lastScrollHeightRef = useRef<number>()

const ref = useCallback((element: HTMLDivElement | null) => {
if (element) {
setContainer(element)
}
}, [])

const scrollToEnd = useCallback(() => {
if (container) {
isStickyRef.current = true
setIsSticky(true)
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth',
})
}
}, [container])

useEffect(() => {
if (!container || !enabled) return

const resizeObserver = new ResizeObserver(() => {
// Prevent duplicate scroll events from phantom height changes
if (
lastScrollHeightRef.current !== undefined &&
container.scrollHeight !== lastScrollHeightRef.current
) {
lastScrollHeightRef.current = container.scrollHeight
if (isStickyRef.current) {
scrollToEnd()
}
}
})

const handleScroll = () => {
const isAtBottom =
Math.abs(container.scrollHeight - container.scrollTop - container.clientHeight) < 10

isStickyRef.current = isAtBottom
setIsSticky(isAtBottom)
}

// Observe all children of the container
Array.from(container.children).forEach((child) => {
resizeObserver.observe(child)
})

container.addEventListener('scroll', handleScroll)

return () => {
resizeObserver.disconnect()
container.removeEventListener('scroll', handleScroll)
}
}, [container, enabled, scrollToEnd])

return { ref, isSticky, scrollToEnd }
}
3 changes: 2 additions & 1 deletion apps/studio/pages/api/ai/sql/generate-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {

# For all your abilities, follow these instructions:
- First look at the list of provided schemas and if needed, get more information about a schema. You will almost always need to retrieve information about the public schema before answering a question.
- If the question is about users or involves creating a users table, also retrieve the auth schema.
- If the question is about users or involves creating a users table, also retrieve the auth schema.
- If it a query is a destructive query e.g. table drop, ask for confirmation before writing the query. The user will still have to run the query once you create it


Here are the existing database schema names you can retrieve: ${schemas}
Expand Down
Loading
Loading