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:
336
app/ai-agent/nlq/page.tsx
Normal file
336
app/ai-agent/nlq/page.tsx
Normal file
@@ -0,0 +1,336 @@
|
||||
"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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
Search,
|
||||
Play,
|
||||
Download,
|
||||
Copy,
|
||||
History,
|
||||
Star,
|
||||
StarOff,
|
||||
Clock,
|
||||
Database,
|
||||
Loader2,
|
||||
ChevronRight,
|
||||
Table,
|
||||
BarChart3,
|
||||
} from "lucide-react"
|
||||
|
||||
// 第四部分:AI Agent - 自然语言查询 (NLQ)
|
||||
export default function NLQPage() {
|
||||
const [query, setQuery] = useState("")
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState("query")
|
||||
const [showResult, setShowResult] = useState(false)
|
||||
const [queryResult, setQueryResult] = useState<any>(null)
|
||||
|
||||
// 示例查询
|
||||
const exampleQueries = [
|
||||
{ label: "高价值用户", query: "查找最近30天消费超过1万元的用户" },
|
||||
{ label: "流失用户分析", query: "统计最近7天未登录的活跃用户数量" },
|
||||
{ label: "地域分布", query: "按省份统计用户数量,按数量降序排列" },
|
||||
{ label: "消费趋势", query: "统计每天的交易金额,最近30天" },
|
||||
]
|
||||
|
||||
// 历史查询
|
||||
const historyQueries = [
|
||||
{ id: "1", query: "查找最近30天消费超过1万元的用户", time: "2分钟前", starred: true },
|
||||
{ id: "2", query: "统计各渠道用户转化率", time: "1小时前", starred: false },
|
||||
{ id: "3", query: "获取VIP用户的平均消费金额", time: "2小时前", starred: true },
|
||||
{ id: "4", query: "按年龄段统计用户数量", time: "昨天", starred: false },
|
||||
{ id: "5", query: "查询高活跃度但低消费的用户群体", time: "昨天", starred: false },
|
||||
]
|
||||
|
||||
const handleExecuteQuery = () => {
|
||||
if (!query.trim()) return
|
||||
setIsLoading(true)
|
||||
setShowResult(false)
|
||||
|
||||
// 模拟AI解析和查询
|
||||
setTimeout(() => {
|
||||
setQueryResult({
|
||||
sql: `SELECT u.id, u.name, u.phone, SUM(t.amount) as total_spending
|
||||
FROM users u
|
||||
JOIN transactions t ON u.id = t.user_id
|
||||
WHERE t.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY u.id, u.name, u.phone
|
||||
HAVING total_spending > 10000
|
||||
ORDER BY total_spending DESC
|
||||
LIMIT 100`,
|
||||
data: [
|
||||
{ id: "U001", name: "张三", phone: "138****1234", total_spending: 25680 },
|
||||
{ id: "U002", name: "李四", phone: "139****5678", total_spending: 18920 },
|
||||
{ id: "U003", name: "王五", phone: "137****9012", total_spending: 15340 },
|
||||
{ id: "U004", name: "赵六", phone: "136****3456", total_spending: 13280 },
|
||||
{ id: "U005", name: "钱七", phone: "135****7890", total_spending: 12150 },
|
||||
],
|
||||
totalCount: 823,
|
||||
executionTime: "0.23s",
|
||||
explanation: "我将您的查询解析为:从用户表和交易表中联合查询,筛选最近30天内消费总额超过10000元的用户,按消费金额降序排列。",
|
||||
})
|
||||
setShowResult(true)
|
||||
setIsLoading(false)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
const handleExampleClick = (exampleQuery: string) => {
|
||||
setQuery(exampleQuery)
|
||||
}
|
||||
|
||||
const handleHistoryClick = (historyQuery: string) => {
|
||||
setQuery(historyQuery)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-purple-50/20">
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 顶部标题 */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">自然语言查询</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">用自然语言描述您的数据需求,AI自动转换为SQL并执行查询</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
{/* 左侧:历史记录 */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="bg-white/80 backdrop-blur border-0 shadow-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base font-semibold flex items-center gap-2">
|
||||
<History className="h-4 w-4" />
|
||||
历史查询
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{historyQueries.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => handleHistoryClick(item.query)}
|
||||
className="w-full text-left p-3 rounded-lg hover:bg-gray-50 transition-colors group"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<p className="text-sm text-gray-700 line-clamp-2 group-hover:text-blue-600">
|
||||
{item.query}
|
||||
</p>
|
||||
{item.starred ? (
|
||||
<Star className="h-4 w-4 text-yellow-500 shrink-0" />
|
||||
) : (
|
||||
<StarOff className="h-4 w-4 text-gray-300 shrink-0 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 mt-1 text-xs text-gray-400">
|
||||
<Clock className="h-3 w-3" />
|
||||
{item.time}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 右侧:查询区域 */}
|
||||
<div className="lg:col-span-3 space-y-6">
|
||||
{/* 查询输入 */}
|
||||
<Card className="bg-white/80 backdrop-blur border-0 shadow-sm">
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleExecuteQuery()}
|
||||
placeholder="输入您的查询,例如:查找最近30天消费超过1万元的用户"
|
||||
className="pl-12 py-6 text-lg bg-white"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500">示例查询:</span>
|
||||
{exampleQueries.map((example, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleExampleClick(example.query)}
|
||||
className="text-sm text-blue-600 hover:text-blue-700 hover:underline"
|
||||
>
|
||||
{example.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Button onClick={handleExecuteQuery} disabled={isLoading || !query.trim()}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
解析中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
执行查询
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 查询结果 */}
|
||||
{showResult && queryResult && (
|
||||
<div className="space-y-4">
|
||||
{/* AI解释 */}
|
||||
<Card className="bg-blue-50 border-blue-200">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="p-2 rounded-lg bg-blue-100">
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-blue-800">{queryResult.explanation}</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-blue-600">
|
||||
<span>执行时间:{queryResult.executionTime}</span>
|
||||
<span>结果数量:{queryResult.totalCount} 条</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* SQL和结果 */}
|
||||
<Card className="bg-white/80 backdrop-blur border-0 shadow-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="bg-gray-100">
|
||||
<TabsTrigger value="query">
|
||||
<Table className="h-4 w-4 mr-1" />
|
||||
查询结果
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sql">
|
||||
<Database className="h-4 w-4 mr-1" />
|
||||
SQL语句
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="chart">
|
||||
<BarChart3 className="h-4 w-4 mr-1" />
|
||||
图表
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
复制
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4 mr-1" />
|
||||
导出
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TabsContent value="query" className="mt-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b bg-gray-50">
|
||||
<th className="text-left py-3 px-4 font-medium">用户ID</th>
|
||||
<th className="text-left py-3 px-4 font-medium">姓名</th>
|
||||
<th className="text-left py-3 px-4 font-medium">手机号</th>
|
||||
<th className="text-right py-3 px-4 font-medium">消费总额</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{queryResult.data.map((row: any, index: number) => (
|
||||
<tr key={index} className="border-b border-gray-100 hover:bg-gray-50">
|
||||
<td className="py-3 px-4 font-mono text-gray-600">{row.id}</td>
|
||||
<td className="py-3 px-4">{row.name}</td>
|
||||
<td className="py-3 px-4 text-gray-500">{row.phone}</td>
|
||||
<td className="py-3 px-4 text-right text-green-600 font-semibold">
|
||||
¥{row.total_spending.toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-4 pt-4 border-t">
|
||||
<span className="text-sm text-gray-500">
|
||||
显示 1-{queryResult.data.length} 条,共 {queryResult.totalCount} 条
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" disabled>
|
||||
上一页
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
下一页
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="sql" className="mt-0">
|
||||
<div className="bg-gray-900 rounded-lg p-4 font-mono text-sm text-gray-100 overflow-x-auto">
|
||||
<pre className="whitespace-pre-wrap">{queryResult.sql}</pre>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Button variant="outline" size="sm">
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
复制SQL
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
在SQL编辑器中打开
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="chart" className="mt-0">
|
||||
<div className="h-64 flex items-center justify-center bg-gray-50 rounded-lg">
|
||||
<div className="text-center text-gray-500">
|
||||
<BarChart3 className="h-12 w-12 mx-auto mb-2 text-gray-300" />
|
||||
<p>图表视图即将支持</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 无结果时的提示 */}
|
||||
{!showResult && !isLoading && (
|
||||
<Card className="bg-white/80 backdrop-blur border-0 shadow-sm">
|
||||
<CardContent className="p-12 text-center">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-gray-300" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">开始您的数据探索</h3>
|
||||
<p className="text-sm text-gray-500 mb-6">
|
||||
输入自然语言描述您想要查询的数据,AI将自动转换为SQL并执行
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{exampleQueries.map((example, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleExampleClick(example.query)}
|
||||
className="px-4 py-2 rounded-full bg-gray-100 text-sm text-gray-600 hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
{example.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user