From 9568c86a3230d1756d3f7e86975db5e446b8f709 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sun, 15 Mar 2026 11:13:40 +0800 Subject: [PATCH] viz widget opt --- frontend/src/components/ChatInterface.tsx | 18 +-- .../components/InlineVisualizationCard.tsx | 132 ++++++++++++++++++ 2 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 frontend/src/components/InlineVisualizationCard.tsx diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx index 0afd25c..fa4a093 100644 --- a/frontend/src/components/ChatInterface.tsx +++ b/frontend/src/components/ChatInterface.tsx @@ -12,7 +12,7 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import { useLocation } from "react-router-dom"; -import { VegaChart } from "./VegaChart"; +import { InlineVisualizationCard } from "./InlineVisualizationCard"; interface Message { id: string; @@ -581,21 +581,7 @@ export function ChatInterface() { {msg.viz ? (
- {msg.viz.error ? ( -
{msg.viz.error}
- ) : msg.viz.canVisualize && msg.viz.chartSpec ? ( - (() => { - const objectRows = msg.viz?.rows?.filter((row) => row && typeof row === "object" && !Array.isArray(row)) || []; - if (objectRows.length === 0) { - return
当前结果没有可渲染的结构化数据。
; - } - return ( -
- -
- ); - })() - ) : null} +
) : null} diff --git a/frontend/src/components/InlineVisualizationCard.tsx b/frontend/src/components/InlineVisualizationCard.tsx new file mode 100644 index 0000000..ee6f829 --- /dev/null +++ b/frontend/src/components/InlineVisualizationCard.tsx @@ -0,0 +1,132 @@ +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 } from "@/components/ui/dialog"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Code, Table as TableIcon, BarChart as ChartIcon, LayoutDashboard } from "lucide-react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { useDashboardStore } from "@/store/dashboardStore"; +import type { ChartSpec } from "@/store/visualizationStore"; +import { VegaChart } from "./VegaChart"; + +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 { addChart } = useDashboardStore(); + 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 handleAddToDashboard = () => { + const mark = viz.chartSpec?.mark; + const markType = typeof mark === "string" ? mark : mark?.type; + const dashboardType = markType === "line" ? "line" : "bar"; + addChart({ + id: Date.now().toString(), + title: viz.chartSpec?.title || "Generated Analysis", + type: dashboardType, + data: objectRows, + sql: viz.sql, + }); + }; + + if (viz.error) { + return
{viz.error}
; + } + + return ( + + + {viz.chartSpec?.title || "可视化结果"} + {viz.reasoning || "根据当前回答生成的可视化"} + + +
+
+ + + + + + SQL + + } /> + + + Generated SQL Query + 用于生成当前图表的数据查询语句。 + +
+
{viz.sql || "--"}
+
+
+
+
+
+ +
+
+ + {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] ?? "")} + ))} + + ))} + +
+
+ ) : ( +
当前结果没有可渲染的结构化数据。
+ )} +
+
+ ); +}