mirror of
https://github.com/supabase/supabase.git
synced 2026-09-10 03:51:51 +08:00
<img width="2337" height="1005" alt="image" src="https://github.com/user-attachments/assets/08298850-715e-4b31-866d-186d73266305" /> ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Assistant execution feedback improvement. ## Stack context Builds on #49351. ## What is the current behavior? When an Assistant query, notebook, Edge Function deployment, or log query completes or fails, the preview can be replaced by a terse text result. ## What is the new behavior? - Retains the original query, log-query, notebook, and Edge Function preview after the tool resolves. - Replaces confirmation actions with a success, error, or skipped footer state. - Keeps the Open notebook action available after a successful notebook creation or update. ## To test 1. Ask the Assistant to run a valid SQL query, approve it, and confirm the query cell remains visible with a Query executed footer. 2. Trigger a failed SQL or log query and confirm the original preview remains visible with an error footer and error result. 3. Ask the Assistant to create or update a notebook, approve it, and confirm the preview remains visible with a completed footer and Open notebook action. 4. Skip any approval and confirm the preview remains visible with a skipped footer instead of being replaced by plain text. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Assistant actions now show clear success, error, or denied-status messages. * Completed actions retain relevant previews and provide follow-up actions, such as opening a created notebook. * SQL, log-query, Edge Function, and notebook errors appear within their respective result views. * Status updates are announced more clearly as actions progress and complete. * **Bug Fixes** * Preserved submitted tool details when execution fails or original input is unavailable. * Improved handling of failed and denied operations across assistant workflows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
187 lines
5.7 KiB
TypeScript
187 lines
5.7 KiB
TypeScript
import { useRef, useState } from 'react'
|
|
|
|
import { identifyQueryType } from './AIAssistant.utils'
|
|
import {
|
|
changeAssistantQuerySource,
|
|
createAssistantQueryModel,
|
|
DEFAULT_ASSISTANT_LOGS_QUERY_TITLE,
|
|
DEFAULT_ASSISTANT_QUERY_TITLE,
|
|
getAssistantQueryDisplay,
|
|
setAssistantQuerySql,
|
|
shouldClearAssistantQueryResult,
|
|
} from './AssistantQueryCell.utils'
|
|
import { Confirm } from './Confirm'
|
|
import { type ConfirmFooterApprovalState } from './Confirm.utils'
|
|
import { QueryEditor } from '@/components/interfaces/Explorer/QueryEditor'
|
|
import { type QueryDisplay, type QueryResult } from '@/components/interfaces/Explorer/types'
|
|
import {
|
|
type QuerySourceBinding,
|
|
type QuerySourceTag,
|
|
} from '@/data/query-sources/query-source-registry'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
import { useLocalRoleImpersonationState } from '@/state/role-impersonation-state'
|
|
|
|
interface AssistantQueryCellProps {
|
|
id: string
|
|
sql: string
|
|
title?: string
|
|
initialResult?: QueryResult
|
|
source?: QuerySourceBinding
|
|
view?: 'table' | 'chart'
|
|
xAxis?: string
|
|
yAxis?: string
|
|
/** Follow incoming SQL while the assistant is still streaming the query text. */
|
|
isStreaming?: boolean
|
|
confirmState?: ConfirmFooterApprovalState
|
|
onApprove?: () => void
|
|
onDeny?: () => void
|
|
}
|
|
|
|
const DEFAULT_SOURCE: QuerySourceBinding = { _tag: 'database' }
|
|
|
|
const QUERY_OUTCOME_MESSAGES: Record<
|
|
QuerySourceTag,
|
|
{ success: string; error: string; denied: string }
|
|
> = {
|
|
database: {
|
|
success: 'Query executed',
|
|
error: 'Failed to execute SQL',
|
|
denied: 'Skipped query',
|
|
},
|
|
logs: {
|
|
success: 'Query executed',
|
|
error: 'Failed to query logs',
|
|
denied: 'Skipped query',
|
|
},
|
|
}
|
|
|
|
/** Assistant adapter around the shared QueryEditor. Local state only — nothing is persisted. */
|
|
export const AssistantQueryCell = ({
|
|
id,
|
|
sql: initialSql,
|
|
title: initialTitle,
|
|
initialResult,
|
|
source = DEFAULT_SOURCE,
|
|
view,
|
|
xAxis,
|
|
yAxis,
|
|
isStreaming = false,
|
|
confirmState,
|
|
onApprove,
|
|
onDeny,
|
|
}: AssistantQueryCellProps) => {
|
|
const track = useTrack()
|
|
const roleImpersonationState = useLocalRoleImpersonationState()
|
|
|
|
const fallbackTitle =
|
|
initialTitle?.trim() ||
|
|
(source._tag === 'logs' ? DEFAULT_ASSISTANT_LOGS_QUERY_TITLE : DEFAULT_ASSISTANT_QUERY_TITLE)
|
|
|
|
const hasExplicitAxes = Boolean(xAxis || yAxis)
|
|
|
|
const [title, setTitle] = useState(fallbackTitle)
|
|
const [query, setQuery] = useState(() => createAssistantQueryModel(initialSql, source))
|
|
const [showQuery, setShowQuery] = useState(true)
|
|
// undefined uses the tool output; null intentionally clears it after changing source.
|
|
const [resultOverride, setResultOverride] = useState<QueryResult | null>()
|
|
const [localDisplay, setLocalDisplay] = useState<QueryDisplay | undefined>(undefined)
|
|
const previousId = useRef(id)
|
|
|
|
if (previousId.current !== id) {
|
|
previousId.current = id
|
|
setTitle(fallbackTitle)
|
|
setQuery(createAssistantQueryModel(initialSql, source))
|
|
setShowQuery(true)
|
|
setResultOverride(undefined)
|
|
setLocalDisplay(undefined)
|
|
}
|
|
|
|
if (isStreaming && query.uncheckedSql !== initialSql) {
|
|
setQuery((current) => setAssistantQuerySql(current, initialSql))
|
|
}
|
|
|
|
const result = resultOverride === undefined ? initialResult : (resultOverride ?? undefined)
|
|
const display =
|
|
localDisplay ??
|
|
getAssistantQueryDisplay({
|
|
view,
|
|
xAxis,
|
|
yAxis,
|
|
sql: query.uncheckedSql,
|
|
rows: result?.rows,
|
|
})
|
|
|
|
const handleTitleChange = (value: string) => {
|
|
const nextTitle = value.trim()
|
|
if (!nextTitle) return
|
|
setTitle(nextTitle)
|
|
}
|
|
|
|
const handleSourceChange = (nextSource: QuerySourceBinding) => {
|
|
const isBackendChange = nextSource._tag !== query._tag
|
|
if (shouldClearAssistantQueryResult(query, nextSource)) setResultOverride(null)
|
|
if (isBackendChange && !hasExplicitAxes) setLocalDisplay(undefined)
|
|
setQuery((current) => changeAssistantQuerySource(current, nextSource))
|
|
}
|
|
|
|
const handleDisplayChange = (nextDisplay: QueryDisplay) => {
|
|
setLocalDisplay(nextDisplay)
|
|
}
|
|
|
|
const handleResultChange = (nextResult: QueryResult) => {
|
|
setResultOverride(nextResult)
|
|
}
|
|
|
|
const handleRun = () => {
|
|
const sql = query.uncheckedSql
|
|
const mutationType = identifyQueryType(sql)
|
|
track('assistant_suggestion_run_query_clicked', {
|
|
queryType: mutationType ? 'mutation' : 'select',
|
|
...(mutationType ? { mutationType } : {}),
|
|
})
|
|
}
|
|
|
|
const isConfirming = confirmState !== undefined
|
|
const outcomeMessages = QUERY_OUTCOME_MESSAGES[source._tag]
|
|
|
|
return (
|
|
<Confirm
|
|
fill
|
|
className="h-96 w-full max-w-6xl mx-auto"
|
|
state={confirmState}
|
|
message="Assistant wants to run this query"
|
|
cancelLabel="Skip"
|
|
confirmLabel="Run query"
|
|
confirmLabelLoading="Running..."
|
|
successMessage={outcomeMessages.success}
|
|
errorMessage={outcomeMessages.error}
|
|
deniedMessage={outcomeMessages.denied}
|
|
onCancel={onDeny}
|
|
onConfirm={onApprove}
|
|
>
|
|
<QueryEditor
|
|
isReadOnly
|
|
id={id}
|
|
variant="viewport"
|
|
title={title}
|
|
query={query}
|
|
result={result}
|
|
showQuery={showQuery}
|
|
onShowQueryChange={setShowQuery}
|
|
roleImpersonationState={roleImpersonationState}
|
|
display={display}
|
|
isRunDisabled={isConfirming}
|
|
onTitleChange={handleTitleChange}
|
|
onSqlChange={(sql) => setQuery((current) => setAssistantQuerySql(current, sql))}
|
|
onSourceChange={handleSourceChange}
|
|
onResultChange={handleResultChange}
|
|
onRowLimitChange={(rowLimit) =>
|
|
setQuery((current) => (current._tag === 'database' ? { ...current, rowLimit } : current))
|
|
}
|
|
onDisplayChange={handleDisplayChange}
|
|
onRun={handleRun}
|
|
/>
|
|
</Confirm>
|
|
)
|
|
}
|