Files
users/app/data-platform/page.tsx
v0 4eed69520c feat: refactor data asset center for enhanced search and analytics
Refactor homepage for focused search and data display; streamline data platform; enhance user and tag management; focus AI assistant on data analysis and report generation.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-07-25 06:42:34 +00:00

695 lines
27 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, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Progress } from "@/components/ui/progress"
import { Switch } from "@/components/ui/switch"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import {
Database,
Activity,
AlertCircle,
CheckCircle,
Clock,
Brain,
Search,
Filter,
Download,
Settings,
RefreshCw,
Shield,
BarChart3,
Plus,
RotateCcw,
Target,
} from "lucide-react"
import { TooltipHelp, TooltipProvider } from "@/components/ui/tooltip-help"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog"
export default function DataPlatformPage() {
const [selectedSource, setSelectedSource] = useState("all")
const [searchQuery, setSearchQuery] = useState("")
const [isAddingSource, setIsAddingSource] = useState(false)
const [isTrainingModel, setIsTrainingModel] = useState(false)
const [selectedModel, setSelectedModel] = useState<string | null>(null)
// 数据源管理
const [dataSources, setDataSources] = useState([
{
id: "1",
name: "用户行为数据库",
type: "MySQL",
status: "connected",
lastSync: "2分钟前",
records: 1200000,
description: "用户行为轨迹和交互数据",
connectionString: "mysql://localhost:3306/user_behavior",
},
{
id: "2",
name: "触客宝API",
type: "REST API",
status: "connected",
lastSync: "5分钟前",
records: 856000,
description: "客户通话记录和服务数据",
connectionString: "https://api.chukebao.com/v1",
},
{
id: "3",
name: "抖音数据接口",
type: "API",
status: "syncing",
lastSync: "10分钟前",
records: 324000,
description: "抖音平台内容和用户数据",
connectionString: "https://api.douyin.com/data",
},
{
id: "4",
name: "小红书数据源",
type: "API",
status: "connected",
lastSync: "3分钟前",
records: 567000,
description: "小红书用户画像和内容数据",
connectionString: "https://api.xiaohongshu.com/user",
},
{
id: "5",
name: "微信生态数据",
type: "Database",
status: "connected",
lastSync: "1分钟前",
records: 2340000,
description: "微信用户关系和群组数据",
connectionString: "mongodb://localhost:27017/wechat_data",
},
])
// AI模型管理
const [aiModels, setAiModels] = useState([
{
id: "1",
name: "用户画像模型",
type: "classification",
status: "active",
accuracy: 94.2,
lastTrained: "2024-01-10",
predictions: 15420,
trainingData: "用户行为数据",
parameters: {
algorithm: "Random Forest",
features: 156,
epochs: 100,
learningRate: 0.001,
},
},
{
id: "2",
name: "流失预测模型",
type: "prediction",
status: "active",
accuracy: 88.7,
lastTrained: "2024-01-12",
predictions: 8765,
trainingData: "用户活跃度数据",
parameters: {
algorithm: "XGBoost",
features: 89,
epochs: 150,
learningRate: 0.01,
},
},
{
id: "3",
name: "价值评估模型",
type: "regression",
status: "training",
accuracy: 91.3,
lastTrained: "2024-01-08",
predictions: 12340,
trainingData: "交易和行为数据",
parameters: {
algorithm: "Neural Network",
features: 234,
epochs: 200,
learningRate: 0.005,
},
},
{
id: "4",
name: "行为预测模型",
type: "prediction",
status: "active",
accuracy: 86.5,
lastTrained: "2024-01-14",
predictions: 9876,
trainingData: "用户行为序列",
parameters: {
algorithm: "LSTM",
features: 67,
epochs: 80,
learningRate: 0.002,
},
},
])
const getStatusIcon = (status: string) => {
switch (status) {
case "connected":
return <CheckCircle className="h-4 w-4 text-green-500" />
case "syncing":
return <RefreshCw className="h-4 w-4 text-blue-500 animate-spin" />
case "error":
return <AlertCircle className="h-4 w-4 text-red-500" />
case "active":
return <Activity className="h-4 w-4 text-green-500" />
case "training":
return <Brain className="h-4 w-4 text-blue-500 animate-pulse" />
default:
return <Clock className="h-4 w-4 text-gray-500" />
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case "connected":
return <Badge className="bg-green-100 text-green-800"></Badge>
case "syncing":
return <Badge className="bg-blue-100 text-blue-800"></Badge>
case "error":
return <Badge className="bg-red-100 text-red-800"></Badge>
case "active":
return <Badge className="bg-green-100 text-green-800"></Badge>
case "training":
return <Badge className="bg-blue-100 text-blue-800"></Badge>
default:
return <Badge className="bg-gray-100 text-gray-800"></Badge>
}
}
// 添加数据源
const handleAddDataSource = () => {
setIsAddingSource(true)
}
// 导入数据源数据
const handleImportData = (sourceId: string) => {
const source = dataSources.find((s) => s.id === sourceId)
if (source) {
// 模拟数据导入过程
setDataSources((prev) =>
prev.map((s) => (s.id === sourceId ? { ...s, status: "syncing", lastSync: "正在同步..." } : s)),
)
setTimeout(() => {
setDataSources((prev) =>
prev.map((s) =>
s.id === sourceId
? {
...s,
status: "connected",
lastSync: "刚刚",
records: s.records + Math.floor(Math.random() * 10000),
}
: s,
),
)
}, 3000)
}
}
// 重新训练模型
const handleRetrainModel = (modelId: string) => {
setAiModels((prev) =>
prev.map((m) => (m.id === modelId ? { ...m, status: "training", lastTrained: "训练中..." } : m)),
)
setTimeout(() => {
setAiModels((prev) =>
prev.map((m) =>
m.id === modelId
? {
...m,
status: "active",
lastTrained: new Date().toISOString().split("T")[0],
accuracy: Math.min(95, m.accuracy + Math.random() * 2),
}
: m,
),
)
}, 5000)
}
return (
<TooltipProvider>
<div className="container mx-auto p-6 space-y-6">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold flex items-center gap-2">
<Database className="h-8 w-8 text-blue-600" />
</h1>
<p className="text-muted-foreground mt-2">AI模型训练平台</p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<Download className="h-4 w-4 mr-2" />
</Button>
<Button size="sm">
<Settings className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
{/* 核心指标概览 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-1">
<TooltipHelp content="已接入的数据源总数,支持实时同步" />
</CardTitle>
<Database className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{dataSources.length}</div>
<p className="text-xs text-muted-foreground">
<span className="text-green-600">{dataSources.filter((s) => s.status === "connected").length}</span>{" "}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-1">
<TooltipHelp content="所有数据源中的记录总数" />
</CardTitle>
<BarChart3 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{(dataSources.reduce((sum, source) => sum + source.records, 0) / 1000000).toFixed(1)}M
</div>
<p className="text-xs text-muted-foreground"> +2.3%</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-1">
AI模型
<TooltipHelp content="运行中的AI模型数量和平均准确率" />
</CardTitle>
<Brain className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{aiModels.filter((m) => m.status === "active").length}</div>
<p className="text-xs text-muted-foreground">
{(aiModels.reduce((sum, m) => sum + m.accuracy, 0) / aiModels.length).toFixed(1)}%
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-1">
<TooltipHelp content="整体系统健康状态和可用性" />
</CardTitle>
<Shield className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">99.8%</div>
<p className="text-xs text-muted-foreground"></p>
</CardContent>
</Card>
</div>
{/* 主要功能区域 */}
<Tabs defaultValue="sources" className="space-y-6">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="sources"></TabsTrigger>
<TabsTrigger value="ai-models">AI模型</TabsTrigger>
</TabsList>
{/* 数据源管理 */}
<TabsContent value="sources" className="space-y-6">
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
<TooltipHelp content="管理所有数据源的连接、同步和数据导入" />
</CardTitle>
<CardDescription></CardDescription>
</div>
<div className="flex space-x-2">
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="搜索数据源..."
className="pl-8 w-64"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Button variant="outline" size="sm">
<Filter className="mr-2 h-4 w-4" />
</Button>
<Button size="sm" onClick={handleAddDataSource}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{dataSources.map((source) => (
<TableRow key={source.id}>
<TableCell>
<div>
<div className="font-medium">{source.name}</div>
<div className="text-sm text-gray-500">{source.description}</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline">{source.type}</Badge>
</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
{getStatusIcon(source.status)}
{getStatusBadge(source.status)}
</div>
</TableCell>
<TableCell>{source.records.toLocaleString()}</TableCell>
<TableCell>{source.lastSync}</TableCell>
<TableCell>
<div className="flex space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() => handleImportData(source.id)}
disabled={source.status === "syncing"}
>
<Download className="h-4 w-4 mr-1" />
</Button>
<Button variant="ghost" size="sm">
<Settings className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm">
<RefreshCw className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
{/* AI模型管理 */}
<TabsContent value="ai-models" className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{aiModels.map((model) => (
<Card key={model.id} className="relative">
<CardHeader>
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-base flex items-center gap-2">
<Brain className="h-5 w-5 text-purple-600" />
{model.name}
</CardTitle>
<CardDescription>: {model.type}</CardDescription>
</div>
{getStatusBadge(model.status)}
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span className="text-sm font-medium"></span>
<span className="text-sm font-bold">{model.accuracy}%</span>
</div>
<Progress value={model.accuracy} className="h-2" />
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-muted-foreground"></span>
<div className="font-medium">{model.lastTrained}</div>
</div>
<div>
<span className="text-muted-foreground"></span>
<div className="font-medium">{model.predictions.toLocaleString()}</div>
</div>
</div>
<div className="text-sm">
<span className="text-muted-foreground">: </span>
<span className="font-medium">{model.trainingData}</span>
</div>
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
className="flex-1 bg-transparent"
onClick={() => handleRetrainModel(model.id)}
disabled={model.status === "training"}
>
<RotateCcw className="h-4 w-4 mr-2" />
{model.status === "training" ? "训练中..." : "重新训练"}
</Button>
<Button
variant="outline"
size="sm"
className="flex-1 bg-transparent"
onClick={() => setSelectedModel(model.id)}
>
<Settings className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
{/* 模型性能监控 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Target className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 text-center">
<div>
<div className="text-2xl font-bold">
{aiModels.reduce((sum, m) => sum + m.predictions, 0).toLocaleString()}
</div>
<div className="text-sm text-muted-foreground"></div>
</div>
<div>
<div className="text-2xl font-bold">
{(aiModels.reduce((sum, m) => sum + m.accuracy, 0) / aiModels.length).toFixed(1)}%
</div>
<div className="text-sm text-muted-foreground"></div>
</div>
<div>
<div className="text-2xl font-bold">156ms</div>
<div className="text-sm text-muted-foreground"></div>
</div>
<div>
<div className="text-2xl font-bold">99.8%</div>
<div className="text-sm text-muted-foreground"></div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 添加数据源对话框 */}
<Dialog open={isAddingSource} onOpenChange={setIsAddingSource}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="source-name" className="text-right">
</Label>
<Input id="source-name" className="col-span-3" placeholder="例如:用户行为数据库" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="source-type" className="text-right">
</Label>
<Select>
<SelectTrigger id="source-type" className="col-span-3">
<SelectValue placeholder="选择数据源类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="mysql">MySQL</SelectItem>
<SelectItem value="postgresql">PostgreSQL</SelectItem>
<SelectItem value="mongodb">MongoDB</SelectItem>
<SelectItem value="api">REST API</SelectItem>
<SelectItem value="file"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="connection-string" className="text-right">
</Label>
<Input
id="connection-string"
className="col-span-3"
placeholder="例如mysql://user:pass@host:port/db"
/>
</div>
<div className="grid grid-cols-4 items-start gap-4">
<Label htmlFor="description" className="text-right pt-2">
</Label>
<Textarea id="description" className="col-span-3" placeholder="数据源描述信息" rows={3} />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<div className="text-right">
<Label htmlFor="auto-import"></Label>
</div>
<div className="flex items-center space-x-2 col-span-3">
<Switch id="auto-import" defaultChecked />
<Label htmlFor="auto-import"></Label>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddingSource(false)}>
</Button>
<Button onClick={() => setIsAddingSource(false)}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 模型设置对话框 */}
<Dialog open={!!selectedModel} onOpenChange={(open) => !open && setSelectedModel(null)}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>AI模型的训练参数和设置</DialogDescription>
</DialogHeader>
{selectedModel && (
<div className="grid gap-4 py-4">
{(() => {
const model = aiModels.find((m) => m.id === selectedModel)
if (!model) return null
return (
<>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right"></Label>
<Input className="col-span-3" defaultValue={model.name} />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right"></Label>
<Select defaultValue={model.parameters.algorithm}>
<SelectTrigger className="col-span-3">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Random Forest">Random Forest</SelectItem>
<SelectItem value="XGBoost">XGBoost</SelectItem>
<SelectItem value="Neural Network">Neural Network</SelectItem>
<SelectItem value="LSTM">LSTM</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right"></Label>
<Input className="col-span-3" type="number" defaultValue={model.parameters.features} />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right"></Label>
<Input className="col-span-3" type="number" defaultValue={model.parameters.epochs} />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right"></Label>
<Input
className="col-span-3"
type="number"
step="0.001"
defaultValue={model.parameters.learningRate}
/>
</div>
<div className="grid grid-cols-4 items-start gap-4">
<Label className="text-right pt-2"></Label>
<Textarea className="col-span-3" defaultValue={model.trainingData} rows={3} />
</div>
</>
)
})()}
</div>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setSelectedModel(null)}>
</Button>
<Button onClick={() => setSelectedModel(null)}>
<Settings className="mr-2 h-4 w-4" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</TooltipProvider>
)
}