diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.prompts.ts b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.prompts.ts new file mode 100644 index 00000000000..8274e777d8e --- /dev/null +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.prompts.ts @@ -0,0 +1,42 @@ +export const defaultPrompts = [ + { + title: 'Create a back-end', + prompt: + 'Create a messaging app with users, messages, and an edge function that uses OpenAI to summarize message threads.', + }, + { + title: 'Health check', + prompt: 'Can you check if my database and edge functions are healthy?', + }, + { + title: 'Query your data', + prompt: 'Give me a list of new users from the auth.users table who signed up in the past week', + }, + { + title: 'Set up RLS policies', + prompt: 'Create RLS policies to ensure users can only access their own data', + }, + { + title: 'Create a function', + prompt: 'Create an edge function that summarises the contents of a table row using OpenAI', + }, + { + title: 'Generate sample data', + prompt: 'Generate sample data for a blog with users, posts, and comments tables', + }, +] + +export const codeSnippetPrompts = [ + { + title: 'Explain code', + prompt: 'Explain what this code does and how it works', + }, + { + title: 'Improve code', + prompt: 'How can I improve this code for better performance and readability?', + }, + { + title: 'Debug issues', + prompt: 'Help me debug any potential issues with this code', + }, +] diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index 55889cafcbe..04f9fde6376 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -22,14 +22,14 @@ import uuidv4 from 'lib/uuid' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' import { AiIconAnimation, Button, cn } from 'ui' -import { Admonition, AssistantChatForm, GenericSkeletonLoader } from 'ui-patterns' +import { Admonition, GenericSkeletonLoader } from 'ui-patterns' import { ButtonTooltip } from '../ButtonTooltip' import { ErrorBoundary } from '../ErrorBoundary' import { onErrorChat } from './AIAssistant.utils' import { AIAssistantChatSelector } from './AIAssistantChatSelector' import { AIOnboarding } from './AIOnboarding' import { AIOptInModal } from './AIOptInModal' -import { CollapsibleCodeBlock } from './CollapsibleCodeBlock' +import { AssistantChatForm } from './AssistantChatForm' import { Message } from './Message' import { useAutoScroll } from './hooks' import type { AssistantMessageType } from 'state/ai-assistant-state' @@ -246,34 +246,20 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { const hasMessages = chatMessages.length > 0 const isShowingOnboarding = !hasMessages && isApiKeySet - const sendMessageToAssistant = (content: string) => { - let finalContent = content - - // Handle SQL snippets based on opt-in level - if (aiOptInLevel !== 'disabled') { - const sqlSnippetsString = - snap.sqlSnippets?.map((snippet: string) => '```sql\n' + snippet + '\n```').join('\n') || '' - finalContent = [content, sqlSnippetsString].filter(Boolean).join('\n\n') - } else { - snap.setSqlSnippets([]) - } - + const sendMessageToAssistant = (finalContent: string) => { const payload = { role: 'user', createdAt: new Date(), content: finalContent, id: uuidv4(), } as MessageType + snap.clearSqlSnippets() - - // Store the user message in the ref before appending lastUserMessageRef.current = payload - append(payload) - setValue('') - if (content.includes('Help me to debug')) { + if (finalContent.includes('Help me to debug')) { sendEvent({ action: 'assistant_debug_submitted', groups: { @@ -552,23 +538,6 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { {!isShowingOnboarding && (
- {snap.sqlSnippets && snap.sqlSnippets.length > 0 && ( -
- {snap.sqlSnippets.map((snippet: string, index: number) => ( - { - const newSnippets = [...(snap.sqlSnippets ?? [])] - newSnippets.splice(index, 1) - snap.setSqlSnippets(newSnippets) - }} - className="text-xs rounded-b-none border-b-0" - /> - ))} -
- )} {disablePrompts && ( { textarea]:text-base [&>textarea]:md:text-sm [&>textarea]:border-1 [&>textarea]:rounded-md [&>textarea]:!outline-none [&>textarea]:!ring-offset-0 [&>textarea]:!ring-0' + 'z-20 [&>form>textarea]:text-base [&>form>textarea]:md:text-sm [&>form>textarea]:border-1 [&>form>textarea]:rounded-md [&>form>textarea]:!outline-none [&>form>textarea]:!ring-offset-0 [&>form>textarea]:!ring-0' )} loading={isChatLoading} disabled={!isApiKeySet || disablePrompts || isChatLoading} @@ -607,13 +576,18 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { : 'Chat to Postgres...' } value={value} - autoFocus onValueChange={(e) => setValue(e.target.value)} - onSubmit={(event) => { - event.preventDefault() - sendMessageToAssistant(value) + onSubmit={(finalMessage) => { + sendMessageToAssistant(finalMessage) scrollToEnd() }} + sqlSnippets={snap.sqlSnippets as string[] | undefined} + onRemoveSnippet={(index) => { + const newSnippets = [...(snap.sqlSnippets ?? [])] + newSnippets.splice(index, 1) + snap.setSqlSnippets(newSnippets) + }} + includeSnippetsInMessage={aiOptInLevel !== 'disabled'} />
)} diff --git a/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx b/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx index d6b7aa3617d..a8b6a505c18 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx @@ -1,10 +1,10 @@ import { motion } from 'framer-motion' -import { Code, FileText, Heart, MessageCircleMore, Shield, WandSparkles } from 'lucide-react' +import { FileText } from 'lucide-react' import { useRef } from 'react' import { Button, cn } from 'ui' -import { AssistantChatForm } from 'ui-patterns' -import { CollapsibleCodeBlock } from './CollapsibleCodeBlock' +import { AssistantChatForm } from './AssistantChatForm' +import { codeSnippetPrompts, defaultPrompts } from './AIAssistant.prompts' interface AIOnboardingProps { onMessageSend: (message: string) => void @@ -28,60 +28,6 @@ export const AIOnboarding = ({ }: AIOnboardingProps) => { const inputRef = useRef(null) - const defaultPrompts = [ - { - title: 'Create a back-end', - prompt: - 'Create a messaging app with users, messages, and an edge function that uses OpenAI to summarize message threads.', - icon: , - }, - { - title: 'Health check', - prompt: 'Can you check if my database and edge functions are healthy?', - icon: , - }, - { - title: 'Query your data', - prompt: - 'Give me a list of new users from the auth.users table who signed up in the past week', - icon: , - }, - { - title: 'Set up RLS policies', - prompt: 'Create RLS policies to ensure users can only access their own data', - icon: , - }, - { - title: 'Create a function', - prompt: 'Create an edge function that summarises the contents of a table row using OpenAI', - icon: , - }, - { - title: 'Generate sample data', - prompt: 'Generate sample data for a blog with users, posts, and comments tables', - icon: , - }, - ] - - const codeSnippetPrompts = [ - { - title: 'Explain code', - prompt: 'Explain what this code does and how it works', - icon: , - }, - { - title: 'Improve code', - prompt: 'How can I improve this code for better performance and readability?', - icon: , - }, - { - title: 'Debug issues', - prompt: 'Help me debug any potential issues with this code', - icon: , - }, - ] - - // Use suggestions if available, otherwise use code-specific prompts if snippets exist, or default prompts const prompts = suggestions?.prompts ? suggestions.prompts.map((suggestion) => ({ title: suggestion.label, @@ -105,37 +51,26 @@ export const AIOnboarding = ({

How can I assist you?

- {sqlSnippets && sqlSnippets.length > 0 && ( -
- {sqlSnippets.map((snippet: string, index: number) => ( - onRemoveSnippet?.(index)} - className="text-xs rounded-b-none border-b-0 text-left" - /> - ))} -
- )} textarea]:text-base [&>textarea]:md:text-sm [&>textarea]:border-1 [&>textarea]:rounded-md [&>textarea]:!outline-none [&>textarea]:!ring-offset-0 [&>textarea]:!ring-0' + 'z-20 [&>form>textarea]:text-base [&>form>textarea]:md:text-sm [&>form>textarea]:border-1 [&>form>textarea]:rounded-md [&>form>textarea]:!outline-none [&>form>textarea]:!ring-offset-0 [&>form>textarea]:!ring-0' )} loading={false} disabled={false} placeholder="Ask me anything..." value={value} onValueChange={(e) => onValueChange(e.target.value)} - onSubmit={(event) => { - event.preventDefault() - if (value.trim()) { - onMessageSend(value) + onSubmit={(finalMessage) => { + if (finalMessage.trim()) { + onMessageSend(finalMessage) onValueChange('') } }} - autoFocus + sqlSnippets={sqlSnippets} + onRemoveSnippet={onRemoveSnippet} + snippetsClassName="text-left" + includeSnippetsInMessage={true} />
@@ -149,7 +84,6 @@ export const AIOnboarding = ({ >