Files
users/app/ai-insight/nlq/page.tsx
v0 b17b488f8e refactor: restructure navigation and module layout
Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2026-01-31 04:32:36 +00:00

252 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { MessageSquare, ArrowLeft, Sparkles, Code, Copy, Play, Clock, CheckCircle2, Table } from "lucide-react"
import Link from "next/link"
const exampleQueries = [
"查询最近7天新增的高价值用户",
"统计各项目的用户流失率",
"找出消费金额前100的用户",
"分析用户活跃时段分布",
]
const queryHistory = [
{
id: 1,
query: "查询最近30天流失风险大于80的用户数量",
sql: "SELECT COUNT(*) FROM users WHERE churn_risk > 80 AND last_active >= DATE_SUB(NOW(), INTERVAL 30 DAY)",
result: "共 2,340 位用户",
time: "10分钟前",
},
{
id: 2,
query: "统计各省份的用户分布",
sql: "SELECT province, COUNT(*) as count FROM users GROUP BY province ORDER BY count DESC",
result: "返回 31 条记录",
time: "1小时前",
},
{
id: 3,
query: "找出RFM评分S级的用户",
sql: "SELECT * FROM users WHERE rfm_level = 'S' ORDER BY total_value DESC LIMIT 100",
result: "返回 100 条记录",
time: "2小时前",
},
]
export default function NLQPage() {
const [query, setQuery] = useState("")
const [isProcessing, setIsProcessing] = useState(false)
const [result, setResult] = useState<{
sql: string
data: { columns: string[]; rows: string[][] }
summary: string
} | null>(null)
const handleSubmit = () => {
if (!query.trim()) return
setIsProcessing(true)
// 模拟AI处理
setTimeout(() => {
setResult({
sql: `SELECT user_id, name, phone, rfm_score, total_value
FROM users
WHERE rfm_level = 'S'
AND last_active >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY total_value DESC
LIMIT 10`,
data: {
columns: ["用户ID", "姓名", "手机号", "RFM评分", "总价值"],
rows: [
["U001", "张伟", "138****8888", "95", "¥125,800"],
["U002", "李娜", "139****6666", "92", "¥98,500"],
["U003", "王芳", "137****5555", "90", "¥87,200"],
["U004", "刘洋", "136****4444", "88", "¥76,800"],
["U005", "陈静", "135****3333", "86", "¥65,400"],
],
},
summary: "找到 5 位符合条件的S级高价值用户总价值 ¥453,700",
})
setIsProcessing(false)
}, 1500)
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-emerald-50/30 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* 页面标题 */}
<div className="flex items-center gap-4">
<Link href="/ai-insight">
<Button variant="ghost" size="icon" className="rounded-full">
<ArrowLeft className="w-5 h-5" />
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-slate-800"></h1>
<p className="text-slate-500 mt-1">AI自动生成SQL并执行</p>
</div>
</div>
{/* 查询输入区 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-6">
<div className="flex items-center gap-3">
<div className="flex-1 relative">
<MessageSquare className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<Input
placeholder="用自然语言描述你想查询的数据例如查询最近7天新增的高价值用户..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
className="pl-12 pr-4 py-6 text-base"
/>
</div>
<Button
onClick={handleSubmit}
disabled={isProcessing || !query.trim()}
className="bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 px-6 py-6"
>
{isProcessing ? (
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : (
<>
<Sparkles className="w-5 h-5 mr-2" />
AI查询
</>
)}
</Button>
</div>
{/* 示例查询 */}
<div className="flex items-center gap-2 mt-4 flex-wrap">
<span className="text-sm text-slate-500"></span>
{exampleQueries.map((eq, i) => (
<Button
key={i}
variant="outline"
size="sm"
className="text-xs bg-transparent"
onClick={() => setQuery(eq)}
>
{eq}
</Button>
))}
</div>
</CardContent>
</Card>
{/* 查询结果 */}
{result && (
<div className="space-y-4">
{/* 生成的SQL */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<Code className="w-4 h-4 text-slate-500" />
SQL
</CardTitle>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<Copy className="w-4 h-4 mr-1" />
</Button>
<Button variant="outline" size="sm">
<Play className="w-4 h-4 mr-1" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<pre className="bg-slate-900 text-slate-100 p-4 rounded-lg text-sm overflow-x-auto">
<code>{result.sql}</code>
</pre>
</CardContent>
</Card>
{/* 查询结果表格 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<Table className="w-4 h-4 text-slate-500" />
</CardTitle>
<Badge className="bg-emerald-100 text-emerald-700">
<CheckCircle2 className="w-3 h-3 mr-1" />
{result.summary}
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-slate-200">
{result.data.columns.map((col, i) => (
<th key={i} className="text-left py-3 px-4 text-xs font-medium text-slate-500">
{col}
</th>
))}
</tr>
</thead>
<tbody>
{result.data.rows.map((row, i) => (
<tr key={i} className="border-b border-slate-100 hover:bg-slate-50/50">
{row.map((cell, j) => (
<td key={j} className="py-3 px-4 text-sm text-slate-700">
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
)}
{/* 查询历史 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Clock className="w-4 h-4 text-slate-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{queryHistory.map((item) => (
<div key={item.id} className="p-4 bg-slate-50/80 rounded-lg border border-slate-200/60">
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-slate-800">{item.query}</p>
<pre className="mt-2 text-xs text-slate-500 bg-slate-100 p-2 rounded overflow-x-auto">
{item.sql}
</pre>
</div>
<div className="text-right ml-4">
<Badge variant="outline" className="text-xs">
{item.result}
</Badge>
<p className="text-xs text-slate-500 mt-1">{item.time}</p>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
)
}