import React from 'react' import { format } from 'sql-formatter' import { useCallback, useEffect, useRef, useState } from 'react' import { Button, CodeBlock, IconAlertTriangle, IconCornerDownLeft, IconUser, Input, Toggle, Tabs, } from 'ui' import { MessageRole, MessageStatus, useAiChat, UseAiChatOptions } from './../AiCommand' import { cn } from 'ui/src/lib/utils' import { AiIcon, AiIconChat } from '../Command.icons' import { CommandItem, useAutoInputFocus, useHistoryKeys } from '../Command.utils' import { useCommandMenu } from '../CommandMenuContext' import { SAMPLE_QUERIES } from '../Command.constants' import SQLOutputActions from './SQLOutputActions' import { generatePrompt } from './GenerateSQL.utils' import { ExcludeSchemaAlert, IncludeSchemaAlert, AiWarning } from '../Command.alerts' import { StatusIcon } from 'ui' const GenerateSQL = () => { const [includeSchemaMetadata, setIncludeSchemaMetadata] = useState(false) const [selectedCategory, setSelectedCategory] = useState(SAMPLE_QUERIES[0].category) const { isLoading, setIsLoading, search, setSearch, isOptedInToAI, metadata, project } = useCommandMenu() const { flags, definitions } = metadata || {} const allowSendingSchemaMetadata = project?.ref !== undefined && flags?.allowCMDKDataOptIn && isOptedInToAI const messageTemplate = useCallback>( (message) => generatePrompt(message, isOptedInToAI && includeSchemaMetadata ? definitions : undefined), [isOptedInToAI, includeSchemaMetadata, definitions] ) const { submit, reset, messages, isResponding, hasError } = useAiChat({ messageTemplate, setIsLoading, }) const inputRef = useAutoInputFocus() useHistoryKeys({ enable: !isResponding, messages: messages .filter(({ role }) => role === MessageRole.User) .map(({ content }) => content), setPrompt: setSearch, }) const handleSubmit = useCallback( (message: string) => { setSearch('') submit(message) }, [submit] ) const handleReset = useCallback(() => { setSearch('') reset() }, [reset]) useEffect(() => { if (search) handleSubmit(search) }, []) // Detect an IME composition (so that we can ignore Enter keypress) const [isImeComposing, setIsImeComposing] = useState(false) const formatAnswer = (answer: string) => { try { return format(answer, { language: 'postgresql', keywordCase: 'lower', }) } catch (error: any) { return answer } } return (
e.stopPropagation()}>
{messages.map((message, i) => { switch (message.role) { case MessageRole.User: return (
{message.content}
) case MessageRole.Assistant: const unformattedAnswer = message.content .replace(/```sql/g, '') .replace(/```.*/gs, '') .replace(/-- End of SQL query\.*/g, '') .trim() const answer = message.status === MessageStatus.Complete ? formatAnswer(unformattedAnswer) : unformattedAnswer const cantHelp = answer.replace(/^-- /, '') === "Sorry, I don't know how to help with that." return (
<> {message.status === MessageStatus.Pending ? (
) : cantHelp ? (

Sorry, I don't know how to help with that.

) : (
{answer}
{message.status === MessageStatus.Complete && ( )}
)}
) } })} {messages.length === 0 && !hasError && (

Describe what you need and Supabase AI will try to generate the relevant SQL statements

Here are some example prompts to try out:

{SAMPLE_QUERIES.map((sample) => (
{SAMPLE_QUERIES.find( (item) => item.category === sample.category )?.queries.map((query) => ( { if (!search) { handleSubmit(query) } }} onKeyDown={(e) => { switch (e.key) { case 'Enter': if (!search || isLoading || isResponding || isImeComposing) { return } return handleSubmit(query) default: return } }} key={query.replace(/\s+/g, '_')} >

{query}

))}
))}
)} {hasError && (

Sorry, looks like Supabase AI is having a hard time!

Please try again in a bit.

)}
{/* {messages.length > 0 && !hasError && } */} {allowSendingSchemaMetadata && (
{messages.length === 0 ? (

Include table names, column names and their corresponding data types in conversation

This will generate answers that are more relevant to your project during the current conversation

setIncludeSchemaMetadata((prev) => !prev)} />
) : includeSchemaMetadata ? ( ) : ( )}
)} {!isLoading && !isResponding ? (
Submit message
) : null} } onChange={(e) => { if (!isLoading || !isResponding) { setSearch(e.target.value) } }} onCompositionStart={() => setIsImeComposing(true)} onCompositionEnd={() => setIsImeComposing(false)} onKeyDown={(e) => { switch (e.key) { case 'Enter': if (!search || isLoading || isResponding || isImeComposing) { return } return handleSubmit(search) default: return } }} />
) } export default GenerateSQL