Files
supabase/apps/studio/components/ui/AIAssistantPanel/EdgeFunctionRenderer.tsx
Saxon Fletcher 80da153450 Fix for AI Assistant query and deploy confirmation (#46052)
When Assistant requests confirmation to run a query or deploy an edge
function if the user doesn't skip or run and instead sends a follow-up
message it errors out. This allows follow-up messages and treats them as
"skips" which means adjusting confirmation message state as part of the
follow-up. This also uses toModelOutput to cleanse data based on
permissions.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Enhanced tool approval workflow: pending approvals are now
automatically resolved as denied when submitting new messages
* Improved chat input state management with better handling of approval
states
  * Customizable loading messages for tool operations

* **Bug Fixes**
  * Fixed chat input availability during pending tool approval states
  * Improved tool execution feedback during approval workflows

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46052?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-20 09:09:28 +10:00

127 lines
3.9 KiB
TypeScript

import { useParams } from 'common'
import { useMemo, useState, type PropsWithChildren } from 'react'
import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock'
import { ConfirmFooter } from './ConfirmFooter'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query'
import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
interface EdgeFunctionRendererProps {
label: string
code: string
functionName: string
onApprove?: () => void
onDeny?: () => void
isDeploying?: boolean
initialIsDeployed?: boolean
showConfirmFooter?: boolean
}
export const EdgeFunctionRenderer = ({
label,
code,
functionName,
onApprove,
onDeny,
isDeploying = false,
initialIsDeployed,
showConfirmFooter = true,
}: PropsWithChildren<EdgeFunctionRendererProps>) => {
const { ref } = useParams()
const { data: org } = useSelectedOrganizationQuery()
const { mutate: sendEvent } = useSendEventMutation()
const [showReplaceWarning, setShowReplaceWarning] = useState(false)
const { data: settings } = useProjectSettingsV2Query({ projectRef: ref }, { enabled: !!ref })
const { data: existingFunction } = useEdgeFunctionQuery(
{ projectRef: ref, slug: functionName },
{ enabled: !!ref && !!functionName && !initialIsDeployed }
)
const functionUrl = useMemo(() => {
const endpoint = settings?.app_config?.endpoint
if (!endpoint || !ref || !functionName) return undefined
try {
const url = new URL(`https://${endpoint}`)
const restUrlTld = url.hostname.split('.').pop()
return restUrlTld
? `https://${ref}.supabase.${restUrlTld}/functions/v1/${functionName}`
: undefined
} catch (error) {
return undefined
}
}, [settings?.app_config?.endpoint, ref, functionName])
const deploymentDetailsUrl = useMemo(() => {
if (!ref || !functionName) return undefined
return `/project/${ref}/functions/${functionName}/details`
}, [ref, functionName])
const downloadCommand = useMemo(() => {
if (!functionName) return undefined
return `supabase functions download ${functionName}`
}, [functionName])
const approveDeploy = () => {
if (!code || isDeploying || !ref || !functionName) return
setShowReplaceWarning(false)
sendEvent({
action: 'edge_function_deploy_button_clicked',
properties: { origin: 'functions_ai_assistant' },
groups: {
project: ref ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
onApprove?.()
}
const handleDeploy = () => {
if (!code || isDeploying || !ref || !functionName) return
if (existingFunction) {
setShowReplaceWarning(true)
return
}
approveDeploy()
}
return (
<div className="w-auto overflow-x-hidden my-4">
<EdgeFunctionBlock
label={label}
code={code}
functionName={functionName}
disabled={showConfirmFooter}
isDeploying={isDeploying}
isDeployed={initialIsDeployed}
functionUrl={functionUrl}
deploymentDetailsUrl={deploymentDetailsUrl}
downloadCommand={downloadCommand}
hideDeployButton={showConfirmFooter || initialIsDeployed}
showReplaceWarning={showReplaceWarning}
onCancelReplace={() => setShowReplaceWarning(false)}
onConfirmReplace={approveDeploy}
/>
{showConfirmFooter && (
<div className="mx-4">
<ConfirmFooter
message="Assistant wants to deploy this Edge Function"
cancelLabel="Skip"
confirmLabel="Deploy"
confirmLabelLoading="Deploying..."
isLoading={isDeploying}
onCancel={() => onDeny?.()}
onConfirm={handleDeploy}
/>
</div>
)}
</div>
)
}