polish UI

This commit is contained in:
qixinbo
2026-03-18 10:57:48 +08:00
parent cc93e0ea5d
commit c75e8dfe84
4 changed files with 265 additions and 14 deletions
+194
View File
@@ -13,6 +13,7 @@
"@tailwindcss/postcss": "^4.2.1",
"@types/dagre": "^0.7.54",
"@types/react-grid-layout": "^1.3.6",
"@types/react-syntax-highlighter": "^15.5.13",
"@xyflow/react": "^12.10.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -24,11 +25,13 @@
"react-grid-layout": "^2.2.2",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.1",
"react-syntax-highlighter": "^16.1.1",
"react-vega": "^8.0.0",
"recharts": "^3.8.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"shadcn": "^4.0.6",
"sql-formatter": "^15.7.2",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"vega": "^6.2.0",
@@ -3129,6 +3132,12 @@
"undici-types": "~7.16.0"
}
},
"node_modules/@types/prismjs": {
"version": "1.26.6",
"resolved": "https://registry.npmmirror.com/@types/prismjs/-/prismjs-1.26.6.tgz",
"integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz",
@@ -3157,6 +3166,15 @@
"@types/react": "*"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmmirror.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/statuses": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/@types/statuses/-/statuses-2.0.6.tgz",
@@ -4868,6 +4886,12 @@
"node": ">=0.3.1"
}
},
"node_modules/discontinuous-range": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
"integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==",
"license": "MIT"
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz",
@@ -5510,6 +5534,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fault": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
@@ -5649,6 +5686,14 @@
"dev": true,
"license": "ISC"
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -6100,6 +6145,21 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/highlightjs-vue": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
"license": "CC0-1.0"
},
"node_modules/hono": {
"version": "4.12.7",
"resolved": "https://registry.npmmirror.com/hono/-/hono-4.12.7.tgz",
@@ -7000,6 +7060,20 @@
"loose-envify": "cli.js"
}
},
"node_modules/lowlight": {
"version": "1.20.0",
"resolved": "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -8020,6 +8094,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/moo": {
"version": "0.5.3",
"resolved": "https://registry.npmmirror.com/moo/-/moo-0.5.3.tgz",
"integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==",
"license": "BSD-3-Clause"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
@@ -8117,6 +8197,34 @@
"dev": true,
"license": "MIT"
},
"node_modules/nearley": {
"version": "2.20.1",
"resolved": "https://registry.npmmirror.com/nearley/-/nearley-2.20.1.tgz",
"integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
"license": "MIT",
"dependencies": {
"commander": "^2.19.0",
"moo": "^0.5.0",
"railroad-diagrams": "^1.0.0",
"randexp": "0.4.6"
},
"bin": {
"nearley-railroad": "bin/nearley-railroad.js",
"nearley-test": "bin/nearley-test.js",
"nearley-unparse": "bin/nearley-unparse.js",
"nearleyc": "bin/nearleyc.js"
},
"funding": {
"type": "individual",
"url": "https://nearley.js.org/#give-to-nearley"
}
},
"node_modules/nearley/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz",
@@ -8612,6 +8720,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz",
@@ -8719,6 +8836,25 @@
],
"license": "MIT"
},
"node_modules/railroad-diagrams": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
"integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==",
"license": "CC0-1.0"
},
"node_modules/randexp": {
"version": "0.4.6",
"resolved": "https://registry.npmmirror.com/randexp/-/randexp-0.4.6.tgz",
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
"license": "MIT",
"dependencies": {
"discontinuous-range": "1.0.0",
"ret": "~0.1.10"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz",
@@ -8997,6 +9133,26 @@
}
}
},
"node_modules/react-syntax-highlighter": {
"version": "16.1.1",
"resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz",
"integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0",
"prismjs": "^1.30.0",
"refractor": "^5.0.0"
},
"engines": {
"node": ">= 16.20.2"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/react-vega": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/react-vega/-/react-vega-8.0.0.tgz",
@@ -9071,6 +9227,22 @@
"redux": "^5.0.0"
}
},
"node_modules/refractor": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/refractor/-/refractor-5.0.0.tgz",
"integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/prismjs": "^1.0.0",
"hastscript": "^9.0.0",
"parse-entities": "^4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/rehype-raw": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz",
@@ -9207,6 +9379,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ret": {
"version": "0.1.15",
"resolved": "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"license": "MIT",
"engines": {
"node": ">=0.12"
}
},
"node_modules/rettime": {
"version": "0.10.1",
"resolved": "https://registry.npmmirror.com/rettime/-/rettime-0.10.1.tgz",
@@ -9613,6 +9794,19 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/sql-formatter": {
"version": "15.7.2",
"resolved": "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-15.7.2.tgz",
"integrity": "sha512-b0BGoM81KFRVSpZFwPpIPU5gng4YD8DI/taLD96NXCFRf5af3FzSE4aSwjKmxcyTmf/MfPu91j75883nRrWDBw==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"nearley": "^2.20.1"
},
"bin": {
"sql-formatter": "bin/sql-formatter-cli.cjs"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
+3
View File
@@ -15,6 +15,7 @@
"@tailwindcss/postcss": "^4.2.1",
"@types/dagre": "^0.7.54",
"@types/react-grid-layout": "^1.3.6",
"@types/react-syntax-highlighter": "^15.5.13",
"@xyflow/react": "^12.10.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -26,11 +27,13 @@
"react-grid-layout": "^2.2.2",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.13.1",
"react-syntax-highlighter": "^16.1.1",
"react-vega": "^8.0.0",
"recharts": "^3.8.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"shadcn": "^4.0.6",
"sql-formatter": "^15.7.2",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"vega": "^6.2.0",
+15 -6
View File
@@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { User, Loader2, Sparkles, ArrowUp, ChevronDown, Paperclip, Check, X, Square, Plus, Database, Wand2, Search, Zap, LayoutGrid, CheckCircle2, Table, XCircle } from "lucide-react";
import { User, Loader2, Sparkles, ArrowUp, ChevronDown, Paperclip, Check, X, Square, Plus, Database, Wand2, Search, Zap, LayoutGrid, CheckCircle2, Table, XCircle, Settings } from "lucide-react";
import { api } from "@/lib/api";
import { type ChartSpec } from "@/store/visualizationStore";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
@@ -848,11 +848,20 @@ export function ChatInterface() {
<span>{msg.awaitingFirstToken ? "正在处理中" : "处理完成"}</span>
</div>
<div className="mt-1.5 space-y-1">
{msg.progressLogs.map((log, idx) => (
<div key={`${msg.id}-log-${idx}`} className="text-[12px] text-zinc-500 leading-5">
{idx + 1}. {log}
</div>
))}
{msg.progressLogs.map((log, idx, arr) => {
const isLast = idx === arr.length - 1;
const isLoading = isLast && msg.awaitingFirstToken;
return (
<div key={`${msg.id}-log-${idx}`} className="flex items-start gap-2 text-[12px] text-zinc-500 leading-5">
{isLoading ? (
<Settings className="mt-0.5 h-3.5 w-3.5 text-amber-500 animate-spin" />
) : (
<CheckCircle2 className="mt-0.5 h-3.5 w-3.5 text-emerald-500" />
)}
<span>{log}</span>
</div>
);
})}
</div>
</div>
) : null}
@@ -3,12 +3,15 @@ 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 } from "lucide-react";
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: {
@@ -24,6 +27,7 @@ interface InlineVisualizationCardProps {
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<Omit<ChartConfig, 'layout'> | null>(null);
const { addChart } = useDashboardStore();
const { currentProject } = useProjectStore();
@@ -68,6 +72,14 @@ export function InlineVisualizationCard({ viz }: InlineVisualizationCardProps) {
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 <div className="text-sm text-red-500">{viz.error}</div>;
}
@@ -76,7 +88,6 @@ export function InlineVisualizationCard({ viz }: InlineVisualizationCardProps) {
<Card className="w-full border border-zinc-100 shadow-none">
<CardHeader className="pb-2">
<CardTitle className="text-base">{viz.chartSpec?.title || "可视化结果"}</CardTitle>
<CardDescription>{viz.reasoning || "根据当前回答生成的可视化"}</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<div className="flex items-center justify-between mb-3">
@@ -106,13 +117,47 @@ export function InlineVisualizationCard({ viz }: InlineVisualizationCardProps) {
SQL
</Button>
} />
<DialogContent className="sm:max-w-[625px]">
<DialogHeader>
<DialogTitle>Generated SQL Query</DialogTitle>
<DialogDescription></DialogDescription>
<DialogContent className="sm:max-w-[700px]">
<DialogHeader className="flex flex-row items-start justify-between pr-8">
<div>
<DialogTitle>Generated SQL Query</DialogTitle>
<DialogDescription className="mt-1"></DialogDescription>
</div>
<Button
variant="outline"
size="sm"
className="h-8 gap-1.5 shrink-0"
onClick={handleCopySql}
>
{copied ? (
<>
<Check className="h-3.5 w-3.5 text-emerald-500" />
<span></span>
</>
) : (
<>
<Copy className="h-3.5 w-3.5" />
<span></span>
</>
)}
</Button>
</DialogHeader>
<div className="bg-slate-950 text-slate-50 p-4 rounded-md overflow-x-auto">
<pre className="text-sm font-mono">{viz.sql || "--"}</pre>
<div className="relative rounded-md overflow-hidden bg-[#1e1e1e] border border-zinc-200 shadow-inner mt-2">
<ScrollArea className="max-h-[500px]">
<SyntaxHighlighter
language="sql"
style={vscDarkPlus}
customStyle={{
margin: 0,
padding: '1.25rem',
fontSize: '0.875rem',
lineHeight: '1.5',
background: 'transparent',
}}
>
{formattedSql}
</SyntaxHighlighter>
</ScrollArea>
</div>
</DialogContent>
</Dialog>