import React, { useState, useRef, useCallback } from 'react'; import { apiCall } from '../utils/api'; import { streamText } from '../utils/streamText'; import { FALLBACK_NL2SQL } from '../constants/fallbacks'; const EXAMPLE_QUERIES = [ 'Angry customers who still kept buying', "Products similar to 'wireless headphones' + bad reviews", 'Transactions with fraud risk > 0.7 this week', 'Which customer cohort churns fastest?', 'Find customers semantically similar to our top 10 buyers', ]; const LOADING_STEPS = [ 'Classifying intent...', 'Generating SQL...', 'Running AST validation...', 'Executing on read-only Postgres...', 'Done', ]; function highlightSQL(sql) { if (!sql) return ''; const keywords = /\b(WITH|SELECT|FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|FULL JOIN|ORDER BY|GROUP BY|HAVING|LIMIT|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|INDEX|ON|USING|AS|AND|OR|NOT|IN|EXISTS|BETWEEN|LIKE|IS|NULL|TRUE|FALSE|CASE|WHEN|THEN|ELSE|END|DISTINCT|UNION|ALL|DESC|ASC|OVER|PARTITION BY|OFFSET|RETURNING|DEFAULT|REFERENCES|PRIMARY KEY|UNIQUE|CASCADE|CONCURRENTLY|MATERIALIZED VIEW|REFRESH)\b/gi; const functions = /\b(AVG|COUNT|SUM|MIN|MAX|RANK|DENSE_RANK|ROW_NUMBER|LAG|LEAD|PERCENT_RANK|ROUND|COALESCE|NOW|EXTRACT|DATE_TRUNC|to_tsvector|to_tsquery|ts_rank|gen_random_uuid|ARRAY_AGG|STRING_AGG)\b/gi; const operators = /(<->|<=>|@@|->>'?|::vector|::DATE|::TIMESTAMPTZ|@>)/g; const comments = /(--.*$)/gm; const strings = /('(?:[^']|'')*')/g; const numbers = /\b(\d+\.?\d*)\b/g; let highlighted = sql; highlighted = highlighted.replace(comments, '$1'); highlighted = highlighted.replace(strings, '$1'); highlighted = highlighted.replace(keywords, '$1'); highlighted = highlighted.replace(functions, '$1'); highlighted = highlighted.replace(operators, '$1'); return highlighted; } export default function NLtoSQL() { const [query, setQuery] = useState("Show me customers who left angry reviews but still kept buying — ranked by lifetime value"); const [loading, setLoading] = useState(false); const [currentStep, setCurrentStep] = useState(-1); const [sqlOutput, setSqlOutput] = useState(''); const [explanation, setExplanation] = useState(''); const [error, setError] = useState(null); const [usedFallback, setUsedFallback] = useState(false); const [validation, setValidation] = useState(null); const [execution, setExecution] = useState(null); const [intentClass, setIntentClass] = useState(null); const [retryCount, setRetryCount] = useState(0); const [copied, setCopied] = useState(null); const abortRef = useRef(null); const handleGenerate = useCallback(async () => { setLoading(true); setCurrentStep(0); setSqlOutput(''); setExplanation(''); setError(null); setUsedFallback(false); setValidation(null); setExecution(null); setIntentClass(null); setRetryCount(0); // Animate loading steps for (let i = 0; i < LOADING_STEPS.length - 1; i++) { await new Promise((r) => setTimeout(r, 600)); setCurrentStep(i + 1); } try { const result = await apiCall('/api/demo/nl2sql', { query }, { timeout: 45000 }); if (result.ok) { setCurrentStep(LOADING_STEPS.length - 1); // Stream the SQL output abortRef.current = new AbortController(); await streamText(result.sql || '', (text) => setSqlOutput(text), 6, abortRef.current.signal); setExplanation(result.explanation || ''); setValidation(result.validation || null); setExecution(result.execution || null); setIntentClass(result.intent_class || null); setRetryCount(result.retry_count || 0); } else { throw new Error(result.error || 'API returned an error'); } } catch (err) { setError(err.message || 'Request failed'); setUsedFallback(true); setCurrentStep(LOADING_STEPS.length - 1); abortRef.current = new AbortController(); await streamText(FALLBACK_NL2SQL.sql, (text) => setSqlOutput(text), 4, abortRef.current.signal); setExplanation(FALLBACK_NL2SQL.explanation); } finally { setLoading(false); } }, [query]); const copyCsv = () => { const rows = execution?.rows || []; const columns = execution?.columns || []; if (!rows.length || !columns.length) return; const escape = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`; const csv = [columns.join(','), ...rows.map((row) => columns.map((column) => escape(row[column])).join(','))].join('\n'); handleCopy(csv, 'csv'); }; const handleCopy = (text, type) => { navigator.clipboard.writeText(text); setCopied(type); setTimeout(() => setCopied(null), 2000); }; return (
{/* Left Panel - Input */}

Ask in Plain English