import { useState, useRef, useEffect, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Send, CheckCircle2, XCircle, ChevronDown, ChevronUp, Loader2, MessageSquare, Zap, RefreshCw, Trash2, } from 'lucide-react' import { useStore } from '../store/useStore' import { streamExecuteQuery, submitFeedback, fetchPromptHistory } from '../lib/api' import { ResultsTable } from './ResultsTable' import type { ChatMessage, AttemptStep } from '../lib/types' // ─── SQL Syntax Highlighter ─────────────────────────────────────── const SQL_KEYWORDS = /\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|FULL|ON|GROUP\s+BY|ORDER\s+BY|HAVING|LIMIT|OFFSET|UNION|ALL|DISTINCT|AS|AND|OR|NOT|IN|IS|NULL|LIKE|BETWEEN|CASE|WHEN|THEN|ELSE|END|WITH|CTE|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|TABLE|INDEX|VIEW|SET|VALUES|INTO|EXISTS|COUNT|SUM|AVG|MIN|MAX|COALESCE|NULLIF|CAST|OVER|PARTITION\s+BY|ROW_NUMBER|RANK|DENSE_RANK|LAG|LEAD|DATE|STRFTIME|JULIANDAY|ROUND|ABS|LENGTH|SUBSTR|UPPER|LOWER|TRIM|REPLACE|IFNULL)\b/gi function SqlBlock({ sql, streaming }: { sql: string; streaming?: boolean }) { const parts: React.ReactNode[] = [] let last = 0 let match: RegExpExecArray | null const re = new RegExp(SQL_KEYWORDS.source, 'gi') while ((match = re.exec(sql)) !== null) { if (match.index > last) { parts.push({sql.slice(last, match.index)}) } parts.push( {match[0]} ) last = match.index + match[0].length } if (last < sql.length) { parts.push({sql.slice(last)}) } return (
      {parts}
      {streaming && }
    
) } // ─── Attempt badge ──────────────────────────────────────────────── function AttemptBadge({ attempt, total }: { attempt: number; total: number }) { const colors = attempt === 1 ? 'text-gray-400 bg-white/5 border-white/10' : attempt === 2 ? 'text-amber-400 bg-amber-500/10 border-amber-500/20' : attempt === 3 ? 'text-orange-400 bg-orange-500/10 border-orange-500/20' : 'text-red-400 bg-red-500/10 border-red-500/20' return ( Attempt {attempt}/{total} ) } // ─── RL Action badge ────────────────────────────────────────────── function RLActionBadge({ action, score }: { action: string; score?: number }) { return ( {action} {score !== undefined && ( {score.toFixed(2)} )} ) } // ─── Reward display ────────────────────────────────────────────── function RewardBadge({ reward }: { reward: number }) { const positive = reward >= 0 return ( {positive ? '+' : ''}{reward.toFixed(2)} ) } // ─── Attempt steps collapsible ──────────────────────────────────── function AttemptSteps({ steps }: { steps: AttemptStep[] }) { const [open, setOpen] = useState(false) if (steps.length <= 1) return null return (
{open && (
{steps.map((step) => (
{step.action && ( )} {step.reward !== undefined && }
{step.error && (
{step.error}
)}
))}
)}
) } // ─── Suggested query chips ──────────────────────────────────────── const SUGGESTED: Record = { easy: ['Show all products', 'List users from USA', 'What categories exist?'], medium: ['Top 5 sellers by revenue', 'Average order value by country', 'Products with low stock'], hard: ['Rolling 7-day revenue', 'Seller ranking with rank change', 'Cohort retention analysis'], } function SuggestionSkeleton() { return (
{[90, 110, 80].map((w, i) => (
))}
) } function EmptyState({ onSelect }: { onSelect: (q: string) => void }) { const { taskDifficulty, isCustomDb, customDbSuggestions, suggestionsLoading } = useStore() const suggestions = isCustomDb ? customDbSuggestions : (SUGGESTED[taskDifficulty] ?? SUGGESTED.easy) return (

Ask about your data

Type a question in natural language. The agent will generate SQL, execute it, and self-repair on errors using reinforcement learning.

{isCustomDb && suggestionsLoading ? 'Generating suggestions…' : 'Try these queries'}
{isCustomDb && suggestionsLoading ? ( ) : suggestions.length > 0 ? ( suggestions.map((q) => ( )) ) : null}
) } // ─── Message Card ───────────────────────────────────────────────── function MessageCard({ msg, onFeedback, onRetry, }: { msg: ChatMessage onFeedback: (id: string, correct: boolean, remark?: string) => Promise onRetry: (q: string, previousSql?: string) => void }) { const [sqlOpen, setSqlOpen] = useState(true) const [wrongOpen, setWrongOpen] = useState(false) const [remark, setRemark] = useState('') return (
{/* User question bubble */}

{msg.question}

{/* Agent response */}
{/* Streaming thinking */} {msg.status === 'streaming' && !msg.sql && (
Generating SQL...
)} {/* Multiple attempts */} {/* Final SQL block */} {msg.sql && (
{sqlOpen && ( )}
)} {/* Executing indicator */} {msg.status === 'streaming' && msg.sql && msg.rows.length === 0 && !msg.errorMsg && (
Executing...
)} {/* RL badges row */} {(msg.rlAction || msg.reward !== undefined) && (
{msg.rlAction && ( )} {msg.reward !== undefined && }
)} {/* Result table */} {msg.status === 'done' && msg.attempts > 0 && (
Success · {msg.rowCount} row{msg.rowCount !== 1 ? 's' : ''} {msg.attempts > 1 && ( {msg.attempts} attempts )}
)} {/* Error */} {msg.status === 'error' && (

Query failed

{msg.errorMsg ?? 'Agent exhausted all repair attempts'}

)} {/* Feedback */} {msg.status === 'done' && msg.attempts > 0 && (
{msg.feedback ? (
{msg.feedback === 'correct' ? : } Marked as {msg.feedback}
) : ( <> Was this correct? )} {(msg.status === 'done' || msg.status === 'error') && ( )}
{/* Remarks dropdown for Wrong */} {wrongOpen && !msg.feedback && (

What was wrong? (optional — helps improve the prompt)