import React from 'react' import { format } from 'sql-formatter' import { useCallback, useEffect, useRef, useState } from 'react' import { Button, CodeBlock, IconAlertTriangle, IconCornerDownLeft, IconUser, Input, Toggle, MessageRole, MessageStatus, useAiChat, Tabs, } from 'ui' import { cn } from '../../../utils/cn' import { AiIcon, AiIconChat } from '../Command.icons' import { CommandItem } from '../Command.utils' import { useCommandMenu } from '../CommandMenuProvider' import { SAMPLE_QUERIES } from '../Command.constants' import SQLOutputActions from './SQLOutputActions' import { generatePrompt } from './GenerateSQL.utils' import { ExcludeSchemaAlert, IncludeSchemaAlert, AiWarning } from '../Command.alerts' const GenerateSQL = () => { // [Joshen] Temp hack to ensure that generatePrompt receives updated value // of includeSchemaMetadata, needs to be fixed const includeSchemaMetadataRef = useRef() 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 { submit, reset, messages, isResponding, hasError } = useAiChat({ messageTemplate: (message) => { // [Joshen] Only pass the schema metadata at the start of the conversation if opted in // Since the prompts are contextualized to the conversation, no need to keep sending it return generatePrompt( message, isOptedInToAI && includeSchemaMetadataRef.current && messages.length === 0 ? definitions : undefined ) }, setIsLoading, }) const handleSubmit = useCallback( (message: string) => { setSearch('') submit(message) }, [submit] ) const handleReset = useCallback(() => { setSearch('') reset() }, [reset]) useEffect(() => { if (search) handleSubmit(search) includeSchemaMetadataRef.current = includeSchemaMetadata }, []) 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) return return handleSubmit(query) default: return } }} forceMount key={query.replace(/\s+/g, '_')} >

{query}

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

Sorry, looks like Clippy 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) => { includeSchemaMetadataRef.current = !prev return !prev }) } />
) : includeSchemaMetadata ? ( ) : ( )}
)} { if (inputElement) { // We need to delay the focus until the end of the call stack // due to order of operations setTimeout(() => { inputElement.focus() }, 0) } }} className="bg-scale-100 rounded mx-3 mb-4" autoFocus placeholder={ isLoading || isResponding ? 'Waiting on an answer...' : 'Describe what you need and Supabase AI will try to generate the relevant SQL statements' } value={search} actions={ <> {!isLoading && !isResponding ? (
Submit message
) : null} } onChange={(e) => { if (!isLoading || !isResponding) { setSearch(e.target.value) } }} onKeyDown={(e) => { switch (e.key) { case 'Enter': if (!search || isLoading || isResponding) return return handleSubmit(search) default: return } }} />
) } export default GenerateSQL