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>
This commit is contained in:
251
app/ai-insight/nlq/page.tsx
Normal file
251
app/ai-insight/nlq/page.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user