import React, { useState, useEffect } from "react"; import ChartView from "./ChartView"; import DataTable from "./DataTable"; // Manual SQL Syntax Highlighter using regex rules that avoids matching inside HTML tags function highlightSQL(sql) { if (!sql) return ""; // Escape HTML tags to prevent XSS/rendering issues let escaped = sql .replace(/&/g, "&") .replace(//g, ">"); // Syntax highlighting rules const strings = /(["'])(.*?)\1/g; // Stash string literals to prevent nested highlighting const savedStrings = []; escaped = escaped.replace(strings, (match) => { const placeholder = `__STRING_PLACEHOLDER_${savedStrings.length}__`; savedStrings.push(match); return placeholder; }); // Highlight keywords - ignoring anything inside html tags by matching tags first and returning them as-is escaped = escaped.replace(/(<[^>]+>)|(\b(SELECT|FROM|WHERE|GROUP BY|ORDER BY|LIMIT|JOIN|ON|AS|AND|OR|IN|SUM|AVG|COUNT|MIN|MAX|HAVING|DESC|ASC|LEFT|RIGHT|INNER|IS|NOT|NULL|LIKE)\b)/gi, (match, tag, kw) => { if (tag) return tag; return `${kw.toUpperCase()}`; }); // Highlight numbers - ignoring anything inside html tags escaped = escaped.replace(/(<[^>]+>)|(\b\d+(?:\.\d+)?\b)/g, (match, tag, num) => { if (tag) return tag; return `${num}`; }); // Highlight table aliases - c., o., p., oi., r. in pink #f472b6 escaped = escaped.replace(/(<[^>]+>)|(\b(c|o|p|oi|r)\.)/gi, (match, tag, aliasWithDot, alias) => { if (tag) return tag; return `${alias.toLowerCase()}.`; }); // Restore string literals savedStrings.forEach((str, idx) => { escaped = escaped.replace(`__STRING_PLACEHOLDER_${idx}__`, `${str}`); }); return escaped; } export default function QueryResult({ result }) { const [sqlExpanded, setSqlExpanded] = useState(false); const [copied, setCopied] = useState(false); const [activeTab, setActiveTab] = useState("chart"); // chart | table const [relativeTime, setRelativeTime] = useState("just now"); // Update relative timestamp periodic updates useEffect(() => { if (!result.timestamp) return; const updateTime = () => { const elapsedMs = new Date() - new Date(result.timestamp); const elapsedMins = Math.floor(elapsedMs / 60000); if (elapsedMins < 1) { setRelativeTime("just now"); } else if (elapsedMins === 1) { setRelativeTime("1 min ago"); } else { setRelativeTime(`${elapsedMins} min ago`); } }; updateTime(); const interval = setInterval(updateTime, 30000); // update every 30s return () => clearInterval(interval); }, [result.timestamp]); const handleCopySQL = () => { if (!result.sql) return; navigator.clipboard.writeText(result.sql); setCopied(true); setTimeout(() => setCopied(false), 1500); }; const handleExportCSV = () => { if (!result.results || !result.results.columns || !result.results.rows) return; const cols = result.results.columns; const rows = result.results.rows; // Build CSV content const headerRow = cols.map(c => '"' + c.replace(/"/g, '""') + '"').join(","); const dataRows = rows.map(row => row.map(val => { if (val === null || val === undefined) return '""'; return '"' + String(val).replace(/"/g, '""') + '"'; }).join(",") ); const csvContent = [headerRow, ...dataRows].join("\n"); const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); // File name: "{db_name}_{question_first_4_words}.csv" const words = result.question .split(/\s+/) .slice(0, 4) .join("_") .toLowerCase() .replace(/[^a-z0-9_]/g, ""); // Clean filename const filename = `${result.db_name || "dataset"}_${words || "export"}.csv`; const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return (
{/* a. Question Header */}
Q: {result.question}
{relativeTime}
{/* Tables Joined Banner (Schema Mode Flow) */} {result.mode === "schema" && result.tables_used && result.tables_used.length > 0 && (
🔗 Tables Joined: {result.tables_used.map((tbl, idx) => ( {tbl} {idx < result.tables_used.length - 1 && ( )} ))}
)} {/* b. SQL block */}
{sqlExpanded && (
{/* Copy Button */}
          
)} {/* Retry Warnings */} {result.retries > 0 && ( ⚠️ Required {result.retries} retries to generate valid SQL. )}
{/* c. Insight Box */} {result.insight && (
💡 Insight

{result.insight}

)} {/* d. Results Tabs */}
{/* Dynamic content rendering based on activeTab */}
{activeTab === "chart" ? ( ) : ( )}
{/* g. Footer statistics & Export */}
{result.results.row_count} rows returned | Executed in {result.results.execution_time_ms}ms
); }