'use client' import { useState, useEffect, useCallback, useMemo } from 'react' import { Editor } from '@monaco-editor/react' import { Button } from '@/components/ui/button' import { ResultsTable } from '@/components/results-table' import { Label } from '@/components/ui/label' import { useRunQuery } from '@/hooks/use-run-query' import { ArrowUp, Loader2, BarChart as BarChartIcon, Wand, FileText, AlertTriangle, } from 'lucide-react' import { Input } from '@/components/ui/input' import { Skeleton } from '@/components/ui/skeleton' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import { Switch } from '@/components/ui/switch' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' interface SqlEditorProps { projectRef: string initialSql?: string queryKey?: any label?: string onResults?: (data: any[] | undefined) => void onRowClick?: (row: any, queryKey?: any) => void hideSql?: boolean readOnly?: boolean runAutomatically?: boolean refetch?: number initialNaturalLanguageMode?: boolean hideChartOption?: boolean } export function SqlEditor({ projectRef, initialSql, queryKey, onResults, onRowClick, label = 'Query your data', hideSql = false, readOnly = false, runAutomatically = false, refetch, initialNaturalLanguageMode = false, hideChartOption = false, }: SqlEditorProps) { const [sql, setSql] = useState(initialSql || '') const [isSqlVisible, setIsSqlVisible] = useState(!hideSql) const [isNaturalLanguageMode, setIsNaturalLanguageMode] = useState( process.env.NEXT_PUBLIC_ENABLE_AI_QUERIES === 'true' && initialNaturalLanguageMode ) const [naturalLanguageQuery, setNaturalLanguageQuery] = useState('') const { mutate: runQuery, data, isPending, error } = useRunQuery() const [isGeneratingSql, setIsGeneratingSql] = useState(false) const [aiError, setAiError] = useState(null) const [isChartVisible, setIsChartVisible] = useState(false) const [xAxisColumn, setXAxisColumn] = useState(null) const [yAxisColumn, setYAxisColumn] = useState(null) const columns = useMemo(() => { if (!data || data.length === 0) return [] return Object.keys(data[0]) }, [data]) useEffect(() => { if (initialSql) { setSql(initialSql) } }, [initialSql]) const handleRunNaturalLanguageQuery = async () => { if (!naturalLanguageQuery) return setIsGeneratingSql(true) setAiError(null) try { const response = await fetch('/api/ai/sql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: naturalLanguageQuery, projectRef, }), }) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.message || 'Failed to generate SQL') } const { sql: generatedSql } = await response.json() setSql(generatedSql) // Allow write operations in natural language mode (DDL, DML) runQuery({ projectRef, query: generatedSql, readOnly: false }) } catch (error: any) { console.error(error) setAiError(error.message) } finally { setIsGeneratingSql(false) } } const handleRunQuery = useCallback(() => { if (sql) { // Use the readOnly prop to determine if queries should be read-only runQuery({ projectRef, query: sql, readOnly: readOnly }) } }, [sql, projectRef, runQuery, readOnly]) useEffect(() => { setIsSqlVisible(!hideSql) }, [hideSql]) useEffect(() => { if (runAutomatically && initialSql) { runQuery({ projectRef, query: initialSql, readOnly: readOnly }) } }, [runAutomatically, initialSql, projectRef, runQuery, readOnly]) useEffect(() => { if (refetch && refetch > 0) { handleRunQuery() } }, [refetch, handleRunQuery]) useEffect(() => { if (onResults) { onResults(data) } }, [data, onResults]) useEffect(() => { const noResults = !data || (Array.isArray(data) && data.length === 0) if (noResults && !isSqlVisible && !isNaturalLanguageMode && !readOnly && !isPending) { setIsSqlVisible(true) } }, [data, isSqlVisible, isNaturalLanguageMode]) const serverErrorMessage = (error as any)?.response?.data?.message || '' const isReadOnlyError = serverErrorMessage.includes('permission denied') || serverErrorMessage.includes('42501') const customReadOnlyError = "You can't directly alter your database schema, use chat instead" // Build the toggle-group selection based on current UI state const toggleValues = useMemo(() => { const values: string[] = [] if (isNaturalLanguageMode) values.push('chat') if (isSqlVisible) values.push('sql') if (!hideChartOption && isChartVisible) values.push('chart') return values }, [isNaturalLanguageMode, isSqlVisible, isChartVisible, hideChartOption]) const handleToggleGroupChange = (values: string[]) => { setIsNaturalLanguageMode(values.includes('chat')) setIsSqlVisible(values.includes('sql')) if (!hideChartOption) { setIsChartVisible(values.includes('chart')) } } return (

{label}

{process.env.NEXT_PUBLIC_ENABLE_AI_QUERIES === 'true' && ( )} {!hideChartOption && (

Visualize your data with a chart.

{isChartVisible && (
)}
)}
{isNaturalLanguageMode && (
setNaturalLanguageQuery(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleRunNaturalLanguageQuery() } }} className="w-full px-10" />
)} {isSqlVisible && (
setSql(value || '')} theme="vs-dark" className="bg-transparent" options={{ minimap: { enabled: false }, fontSize: 14, readOnly, padding: { top: 24, bottom: 24, }, }} />
)}
{isPending && (
)} {aiError && (
Error generating SQL {aiError}
)} {error && (
Query Error {isReadOnlyError ? customReadOnlyError : serverErrorMessage || (error as Error)?.message || 'An unexpected error occurred'}
)} {!hideChartOption && data && isChartVisible && xAxisColumn && yAxisColumn && (
)} {data && (
onRowClick?.(row, queryKey)} />
)}
) } function QueryResultChart({ data, xAxis, yAxis }: { data: any[]; xAxis: string; yAxis: string }) { const chartConfig = { [yAxis]: { label: yAxis, color: 'var(--chart-1)', }, } satisfies ChartConfig return ( } /> ) }