import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useRouter } from 'next/router'
import {
DragEvent,
memo,
ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
} from 'react'
import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { useProfile } from 'lib/profile'
import Link from 'next/link'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { Dashboards } from 'types'
import {
Badge,
Button,
cn,
CodeBlock,
CodeBlockLang,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogTitle,
DialogTrigger,
} from 'ui'
import { DebouncedComponent } from '../DebouncedComponent'
import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock'
import { QueryBlock } from '../QueryBlock/QueryBlock'
import { AssistantSnippetProps } from './AIAssistant.types'
import { identifyQueryType } from './AIAssistant.utils'
import { CollapsibleCodeBlock } from './CollapsibleCodeBlock'
import { MessageContext } from './Message'
import { defaultUrlTransform } from './Message.utils'
export const OrderedList = memo(({ children }: { children: ReactNode }) => (
{children}
))
OrderedList.displayName = 'OrderedList'
export const ListItem = memo(({ children }: { children: ReactNode }) => (
{children}
))
ListItem.displayName = 'ListItem'
export const Heading3 = memo(({ children }: { children: ReactNode }) => (
{children}
))
Heading3.displayName = 'Heading3'
export const InlineCode = memo(
({ className, children }: { className?: string; children: ReactNode }) => (
{children}
)
)
InlineCode.displayName = 'InlineCode'
export const Hyperlink = memo(({ href, children }: { href?: string; children: ReactNode }) => {
const isExternalURL = !href?.startsWith('https://supabase.com/dashboard')
const safeUrl = defaultUrlTransform(href ?? '')
const isSafeUrl = safeUrl.length > 0
if (!isSafeUrl) {
return {children}
}
return (
)
})
Hyperlink.displayName = 'Hyperlink'
const MemoizedQueryBlock = memo(
({
sql,
title,
xAxis,
yAxis,
isChart,
isLoading,
isDraggable,
runQuery,
results,
onRunQuery,
onResults,
onDragStart,
onUpdateChartConfig,
}: {
sql: string
title: string
xAxis?: string
yAxis?: string
isChart: boolean
isLoading: boolean
isDraggable: boolean
runQuery: boolean
results?: any[]
onRunQuery: (queryType: 'select' | 'mutation') => void
onResults: (results: any[]) => void
onDragStart: (e: DragEvent) => void
onUpdateChartConfig?: ({
chart,
chartConfig,
}: {
chart?: Partial
chartConfig: Partial
}) => void
}) => (
Writing SQL...
}
>
NEW
Drag to add this chart into your custom report
) : undefined
}
showSql={!isChart}
isChart={isChart}
isLoading={isLoading}
draggable={isDraggable}
runQuery={runQuery}
results={results}
onRunQuery={onRunQuery}
onResults={onResults}
onDragStart={onDragStart}
onUpdateChartConfig={onUpdateChartConfig}
/>
)
)
MemoizedQueryBlock.displayName = 'MemoizedQueryBlock'
export const MarkdownPre = ({
children,
id,
onResults,
}: {
children: any
id: string
onResults: ({
messageId,
resultId,
results,
}: {
messageId: string
resultId?: string
results: any[]
}) => void
}) => {
const router = useRouter()
const { profile } = useProfile()
const { isLoading, readOnly } = useContext(MessageContext)
const { mutate: sendEvent } = useSendEventMutation()
const snap = useAiAssistantStateSnapshot()
const project = useSelectedProject()
const org = useSelectedOrganization()
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
// [Joshen] Using a ref as this data doesn't need to trigger a re-render
const chartConfig = useRef({
view: 'table',
type: 'bar',
xKey: '',
yKey: '',
cumulative: false,
})
const language = children[0].props.className?.replace('language-', '') || 'sql'
const rawContent = children[0].props.children[0]
const propsMatch = rawContent.match(/(?:--|\/\/)\s*props:\s*(\{[^}]+\})/)
const snippetProps: AssistantSnippetProps = useMemo(() => {
try {
if (propsMatch) {
return JSON.parse(propsMatch[1])
}
} catch {}
return {}
}, [propsMatch])
const { xAxis, yAxis } = snippetProps
const snippetId = snippetProps.id
const title = snippetProps.title || (language === 'edge' ? 'Edge Function' : 'SQL Query')
const isChart = snippetProps.isChart === 'true'
const runQuery = snippetProps.runQuery === 'true'
const results = snap.getCachedSQLResults({ messageId: id, snippetId })
// Strip props from the content for both SQL and edge functions
const cleanContent = rawContent.replace(/(?:--|\/\/)\s*props:\s*\{[^}]+\}/, '').trim()
const isDraggableToReports = canCreateSQLSnippet && router.pathname.endsWith('/reports/[id]')
useEffect(() => {
chartConfig.current = {
...chartConfig.current,
view: isChart ? 'chart' : 'table',
xKey: xAxis ?? '',
yKey: yAxis ?? '',
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [snippetProps])
const onResultsReturned = useCallback(
(results: any[]) => {
onResults({ messageId: id, resultId: snippetProps.id, results })
},
[onResults, snippetProps.id]
)
const onRunQuery = async (queryType: 'select' | 'mutation') => {
sendEvent({
action: 'assistant_suggestion_run_query_clicked',
properties: {
queryType,
...(queryType === 'mutation'
? { category: identifyQueryType(cleanContent) ?? 'unknown' }
: {}),
},
groups: {
project: project?.ref ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
}
return (
{language === 'edge' ? (
) : language === 'sql' ? (
readOnly ? (
) : (
{
chartConfig.current = { ...chartConfig.current, ...config }
}}
onDragStart={(e: DragEvent) => {
e.dataTransfer.setData(
'application/json',
JSON.stringify({ label: title, sql: cleanContent, config: chartConfig.current })
)
}}
/>
)
) : (
code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap [&>code]:block [&>code>span]:text-foreground'
)}
/>
)}
)
}