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 (
Type a question in natural language. The agent will generate SQL, execute it, and self-repair on errors using reinforcement learning.
{msg.question}
Query failed
{msg.errorMsg ?? 'Agent exhausted all repair attempts'}
What was wrong? (optional — helps improve the prompt)
Enter to send · Shift+Enter for newline · Agent uses LinUCB + GEPA