diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.types.ts b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.types.ts index c587434a402..9f847ae2c8c 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.types.ts +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.types.ts @@ -1,10 +1,11 @@ export type SupportedAssistantEntities = 'rls-policies' | 'functions' export type SupportedAssistantQuickPromptTypes = 'suggest' | 'examples' | 'ask' -export type AssistantSnippetProps = { - title: string - runQuery: 'true' | 'false' - isChart?: 'true' | 'false' +export interface AssistantSnippetProps { + title?: string + isChart?: string + runQuery?: string xAxis?: string yAxis?: string + name?: string } diff --git a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx index 56777e53466..d7a38e9c464 100644 --- a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx @@ -16,6 +16,7 @@ import { AssistantSnippetProps } from './AIAssistant.types' import { identifyQueryType } from './AIAssistant.utils' import CollapsibleCodeBlock from './CollapsibleCodeBlock' import { MessageContext } from './Message' +import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock' export const OrderedList = memo(({ children }: { children: ReactNode }) => (
    {children}
@@ -149,19 +150,22 @@ export const MarkdownPre = ({ children }: { children: any }) => { }) const language = children[0].props.className?.replace('language-', '') || 'sql' - const rawSql = language === 'sql' ? children[0].props.children : undefined - const formatted = (rawSql || [''])[0] - const propsMatch = formatted.match(/--\s*props:\s*(\{[^}]+\})/) + const rawContent = children[0].props.children[0] + const propsMatch = rawContent.match(/--\s*props:\s*(\{[^}]+\})/) const snippetProps: AssistantSnippetProps = useMemo( () => (propsMatch ? JSON.parse(propsMatch[1]) : {}), [propsMatch] ) + const { xAxis, yAxis } = snippetProps - const title = snippetProps.title || 'SQL Query' + const title = snippetProps.title || (language === 'edge' ? 'Edge Function' : 'SQL Query') const isChart = snippetProps.isChart === 'true' const runQuery = snippetProps.runQuery === 'true' - const sql = formatted?.replace(/--\s*props:\s*\{[^}]+\}/, '').trim() + + // Strip props from the content for both SQL and edge functions + const cleanContent = rawContent.replace(/--\s*props:\s*\{[^}]+\}/, '').trim() + const isDraggableToReports = supportSQLBlocks && canCreateSQLSnippet && router.pathname.endsWith('/reports/[id]') @@ -180,23 +184,28 @@ export const MarkdownPre = ({ children }: { children: any }) => { action: TelemetryActions.ASSISTANT_SUGGESTION_RUN_QUERY_CLICKED, properties: { queryType, - ...(queryType === 'mutation' ? { category: identifyQueryType(sql) ?? 'unknown' } : {}), + ...(queryType === 'mutation' + ? { category: identifyQueryType(cleanContent) ?? 'unknown' } + : {}), }, }) } return (
- {language === 'sql' ? ( + {language === 'edge' ? ( + + ) : language === 'sql' ? ( readOnly ? ( - + ) : ( { onDragStart={(e: DragEvent) => { e.dataTransfer.setData( 'application/json', - JSON.stringify({ label: title, sql, config: chartConfig.current }) + JSON.stringify({ label: title, sql: cleanContent, config: chartConfig.current }) ) }} /> @@ -219,7 +228,7 @@ export const MarkdownPre = ({ children }: { children: any }) => { ) : ( ) => void +} + +export const EdgeFunctionBlock = ({ + label, + code, + functionName, + actions, + showCode: _showCode = false, + tooltip, +}: EdgeFunctionBlockProps) => { + const { ref } = useParams() + const [isDeployed, setIsDeployed] = useState(false) + + const { mutateAsync: deployFunction, isLoading: isDeploying } = useEdgeFunctionDeployMutation({ + onSuccess: () => { + setIsDeployed(true) + toast.success('Successfully deployed edge function') + }, + }) + + const handleDeploy = async () => { + if (!code || isDeploying || !ref) return + + try { + const deployResult = await deployFunction({ + projectRef: ref, + metadata: { + entrypoint_path: 'index.ts', + name: functionName, + verify_jwt: false, + }, + files: [{ name: 'index.ts', content: code }], + }) + } catch (error) { + toast.error( + `Failed to deploy function: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + } + + const functionUrl = ref ? `https://${ref}.supabase.co/functions/v1/${functionName}` : '' + + return ( + + } + label={label} + actions={ + <> + + + {actions} + + } + > +
+ code]:m-0 [&>code>span]:text-foreground' + )} + /> +
+ + {(isDeploying || isDeployed) && ( +
+ {isDeploying ? ( +

Deploying function...

+ ) : ( + <> +

+ Function deployed successfully! You can now invoke it at: +

+ +

+ To download and work on this function locally, use the CLI command: +

+ + + )} +
+ )} +
+ ) +} diff --git a/apps/studio/pages/api/ai/sql/generate-v3.ts b/apps/studio/pages/api/ai/sql/generate-v3.ts index 4843862c3c7..e0f229a1708 100644 --- a/apps/studio/pages/api/ai/sql/generate-v3.ts +++ b/apps/studio/pages/api/ai/sql/generate-v3.ts @@ -121,7 +121,12 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) { - First, always use the getEdgeFunctionKnowledge tool to get knowledge about how to write edge functions for Supabase - When writing edge functions, always ensure that they are written in TypeScript and Deno JavaScript runtime. - When writing edge functions, always ensure that they are written in a way that is compatible with the database schema. - - When suggesting edge functions, dont suggest generic edge functions as shown in the getEdgeFunctionKnowledge tool, create personalised edge functions based on the database schema. + - When suggesting edge functions, follow the guidelines in getEdgeFunctionKnowledge tool. Always create personalised edge functions based on the database schema + - When outputting edge functions, always include a props comment in the first line of the code block: + -- props: {"name": "function-name", "title": "Human readable title"} + - The function name in the props must be URL-friendly (use hyphens instead of spaces or underscores) + - Always wrap the edge function code in a markdown code block with the language set to 'edge' + - The props comment must be the first line inside the code block, followed by the actual function code # You convert sql to supabase-js client code Use the convertSqlToSupabaseJs tool to convert select sql to supabase-js client code. Only provide js code snippets if explicitly asked. If conversion isn't supported, build a postgres function instead and suggest using supabase-js to call it via "const { data, error } = await supabase.rpc('echo', { say: '👋'})"