import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Code, Table as TableIcon, BarChart as ChartIcon, LayoutDashboard, Copy, Check } from "lucide-react"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useDashboardStore, type ChartConfig } from "@/store/dashboardStore"; import { useProjectStore } from "@/store/projectStore"; import type { ChartSpec } from "@/store/visualizationStore"; import { VegaChart } from "./VegaChart"; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { format } from 'sql-formatter'; interface InlineVisualizationCardProps { viz: { sql: string; rows: unknown[]; chartSpec: ChartSpec | null; canVisualize: boolean; reasoning?: string; error?: string | null; }; } export function InlineVisualizationCard({ viz }: InlineVisualizationCardProps) { const [view, setView] = useState<'table' | 'chart'>('chart'); const [confirmOpen, setConfirmOpen] = useState(false); const [copied, setCopied] = useState(false); const [pendingChart, setPendingChart] = useState | null>(null); const { addChart } = useDashboardStore(); const { currentProject } = useProjectStore(); const objectRows = viz.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row)) as Record[]; const columns = objectRows.length > 0 ? Object.keys(objectRows[0]) : []; const buildPendingChart = (): Omit => { if (view === "table") { return { id: Date.now().toString(), title: viz.chartSpec?.title || "Generated Analysis", type: "table", data: objectRows, sql: viz.sql, chartSpec: null, }; } const mark = viz.chartSpec?.mark; const markType = typeof mark === "string" ? mark : mark?.type; const dashboardType = markType === "line" ? "line" : "bar"; return { id: Date.now().toString(), title: viz.chartSpec?.title || "Generated Analysis", type: dashboardType, data: objectRows, sql: viz.sql, chartSpec: viz.chartSpec, }; }; const handleAddToDashboard = () => { if (!currentProject) return; const chart = buildPendingChart(); setPendingChart(chart); setConfirmOpen(true); }; const handleConfirmAdd = () => { if (!pendingChart || !currentProject) return; addChart(pendingChart, currentProject.id); setConfirmOpen(false); setPendingChart(null); }; const handleCopySql = () => { navigator.clipboard.writeText(viz.sql || ""); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const formattedSql = viz.sql ? format(viz.sql, { language: 'postgresql' }) : "--"; if (viz.error) { return
{viz.error}
; } return ( {viz.chartSpec?.title || "可视化结果"}
SQL } />
Generated SQL Query 用于生成当前图表的数据查询语句。
{formattedSql}
{view === "chart" ? ( viz.canVisualize && viz.chartSpec && objectRows.length > 0 ? (
) : (
本次结果不适合图表展示。
) ) : objectRows.length > 0 ? ( {columns.map((col) => {col})} {objectRows.map((row, i) => ( {columns.map((col) => ( {String(row[col] ?? "")} ))} ))}
) : (
当前结果没有可渲染的结构化数据。
)}
确认加入 Dashboard 将当前图表添加到 Dashboard,是否继续?
); }