import { untrustedSql } from '@supabase/pg-meta' import dynamic from 'next/dynamic' import Link from 'next/link' import React, { isValidElement, memo, ReactNode, useEffect, useMemo, useRef, type ReactElement, } from 'react' import type { StreamdownProps } from 'streamdown' import { Button, cn, Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogSection, DialogTitle, DialogTrigger, } from 'ui' import { CodeBlock, type CodeBlockLang } from 'ui-patterns/CodeBlock' import { markdownComponents } from 'ui-patterns/Markdown' import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock' import { AssistantSnippetProps } from './AIAssistant.types' import { CollapsibleCodeBlock } from './CollapsibleCodeBlock' import { DisplayBlockRenderer } from './DisplayBlockRenderer' import { defaultUrlTransform, wrapPlaceholderUrls } from './Message.utils' import { ChartConfig } from '@/components/interfaces/SQLEditor/UtilityPanel/ChartConfig' const Streamdown = dynamic( () => import('streamdown').then((mod) => mod.Streamdown), { ssr: false } ) // Streamdown splits ordered lists with complex content (e.g. code blocks) into // separate
    elements. The `start` attribute preserves semantics for screen // readers, while `counterReset` is what actually fixes the visible numbering — // the prose config (tailwind.config.ts) uses a custom CSS counter named "item" // with `listStyleType: 'none'`, so the `start` attribute alone has no visual effect. export const OrderedList = memo(({ children, start }: { children?: ReactNode; start?: number }) => (
      {children}
    )) OrderedList.displayName = 'OrderedList' export const ListItem = memo(({ children }: { children?: ReactNode }) => (
  1. {children}
  2. )) 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 ( {children} Verify the link before navigating

    This link will take you to the following URL:

    {safeUrl}

    Are you sure you want to head there?

    ) }) Hyperlink.displayName = 'Hyperlink' const baseMarkdownComponents = { ol: OrderedList, li: ListItem, h3: Heading3, code: InlineCode, a: Hyperlink, img: ({ src }: React.JSX.IntrinsicElements['img']) => ( [Image: {src?.toString()}] ), } export function MessageMarkdown({ id, isLoading, readOnly, className, children, }: { id: string isLoading: boolean readOnly?: boolean className?: string children: ReactNode }) { const markdownSource = useMemo(() => { if (typeof children === 'string') { return wrapPlaceholderUrls(children) } if (Array.isArray(children)) { return wrapPlaceholderUrls( children.filter((child): child is string => typeof child === 'string').join('') ) } return '' }, [children]) const allMarkdownComponents = useMemo( () => ({ ...markdownComponents, ...baseMarkdownComponents, pre: (props: React.JSX.IntrinsicElements['pre']) => ( {props.children} ), }), [id, isLoading, readOnly] ) return ( {markdownSource} ) } export const MarkdownPre = ({ children, id, isLoading: _isLoading, readOnly, }: { children: any id: string isLoading: boolean readOnly?: boolean }) => { // [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 childArray = Array.isArray(children) ? children : [children] const codeElement = childArray.find( (child): child is ReactElement<{ className?: string; children: ReactNode }> => isValidElement<{ className?: string; children: ReactNode }>(child) ) const codeProps = codeElement?.props || ({} as { className?: string; children: ReactNode }) const language = codeProps.className?.replace('language-', '') || 'sql' const codeChildren = codeProps.children const rawContent = Array.isArray(codeChildren) ? codeChildren.map((node) => (typeof node === 'string' ? node : '')).join('') : typeof codeChildren === 'string' ? codeChildren : '' 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' // Strip props from the content for both SQL and edge functions const cleanContent = rawContent.replace(/(?:--|\/\/)\s*props:\s*\{[^}]+\}/, '').trim() const toolCallId = String(snippetId ?? id) useEffect(() => { chartConfig.current = { ...chartConfig.current, view: isChart ? 'chart' : 'table', xKey: xAxis ?? '', yKey: yAxis ?? '', } // eslint-disable-next-line react-hooks/exhaustive-deps }, [snippetProps]) if (!codeElement) { return
    {children}
    } return (
    {language === 'edge' ? ( ) : language === 'sql' ? ( readOnly ? ( ) : ( {}} showConfirmFooter={false} onChartConfigChange={(config) => { chartConfig.current = { ...config } }} /> ) ) : ( code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap [&>code]:block [&>code>span]:text-foreground' )} /> )}
    ) }