diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index e8e00f70abe..78e1b2d1a5e 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -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' @@ -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 }) => { @@ -89,8 +90,7 @@ export const AIAssistant = ({ const { open, initialInput, sqlSnippets, suggestions } = aiAssistantPanel const inputRef = useRef(null) - const bottomRef = useRef(null) - const scrollContainerRef = useRef(null) + const { ref: scrollContainerRef, isSticky, scrollToEnd } = useAutoScroll() const [value, setValue] = useState(initialInput) const [assistantError, setAssistantError] = useState() @@ -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) - - return () => { - clearTimeout(timeoutId) - const container = scrollContainerRef.current - if (container) { - container.removeEventListener('scroll', handleScroll) - } + if (!isChatLoading) { + if (inputRef.current) inputRef.current.focus() } - }, []) + + if (isSticky) { + setTimeout(scrollToEnd, 0) + } + }, [isChatLoading, isSticky, scrollToEnd, messages]) useEffect(() => { setValue(initialInput) @@ -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) { @@ -310,11 +265,7 @@ export const AIAssistant = ({ return ( <>
-
+
@@ -401,7 +352,7 @@ export const AIAssistant = ({
)} -
+
) : suggestions ? (
@@ -490,16 +441,41 @@ export const AIAssistant = ({
)}
+ - {showFade && ( - -
- + {!isSticky && ( + <> + +
+ + + + + )} @@ -567,6 +543,7 @@ export const AIAssistant = ({ .join('\n') || '' const valueWithSnippets = [value, sqlSnippetsString].filter(Boolean).join('\n\n') sendMessageToAssistant(valueWithSnippets) + scrollToEnd() } else { sendMessageToAssistant(value) } diff --git a/apps/studio/components/ui/AIAssistantPanel/SqlSnippet.tsx b/apps/studio/components/ui/AIAssistantPanel/SqlSnippet.tsx index ff9f68ca873..a948822e79e 100644 --- a/apps/studio/components/ui/AIAssistantPanel/SqlSnippet.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/SqlSnippet.tsx @@ -183,6 +183,9 @@ export const SqlCard = ({ : 'This query involves running a function.'}{' '} Are you sure you want to execute it?

+

+ Make sure you are not accidentally removing something important. +