Files
users/app/data-platform/page.tsx
v0 afc77439bb feat: enhance user profile with detailed tags and asset evaluation
Optimize user detail page for asset assessment and tag info.

#VERCEL_SKIP

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-08-21 05:32:37 +00:00

452 lines
17 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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Database, Plus, RefreshCw, Settings, Brain, Play } from "lucide-react"
import BottomTabs from "@/components/nav/bottom-tabs"
interface DataSource {
id: string
name: string
description: string
type: string
records: string
lastSync: string
status: "connected" | "disconnected" | "syncing"
}
interface AIModel {
id: string
name: string
type: string
accuracy: string
algorithm: string
features: number
lastTrained: string
status: "ready" | "training" | "error"
}
const mockDataSources: DataSource[] = [
{
id: "wechat-db",
name: "微信用户数据库",
description: "存储微信用户基础信息和行为数据",
type: "MySQL",
records: "2.5B",
lastSync: "2024/1/15 18:30:00",
status: "connected",
},
{
id: "traffic-keywords",
name: "流量关键词库",
description: "搜索引擎关键词和流量数据",
type: "PostgreSQL",
records: "150.0K",
lastSync: "2024/1/15 17:45:00",
status: "connected",
},
{
id: "user-behavior",
name: "用户行为日志",
description: "用户操作行为和交互记录",
type: "MongoDB",
records: "1.5B",
lastSync: "2024/1/15 19:00:00",
status: "syncing",
},
]
const mockAIModels: AIModel[] = [
{
id: "user-value-prediction",
name: "用户价值预测模型",
type: "Classification",
accuracy: "92.0%",
algorithm: "RandomForest",
features: 25,
lastTrained: "2024/1/14 23:30:00",
status: "ready",
},
{
id: "traffic-trend-analysis",
name: "流量趋势分析模型",
type: "Regression",
accuracy: "87.0%",
algorithm: "LSTM",
features: 15,
lastTrained: "2024/1/15 16:00:00",
status: "training",
},
{
id: "user-clustering",
name: "用户聚类模型",
type: "Clustering",
accuracy: "89.0%",
algorithm: "KMeans",
features: 20,
lastTrained: "2024/1/13 20:00:00",
status: "ready",
},
]
export default function DataPlatformPage() {
const [activeTab, setActiveTab] = useState("datasource")
const [dataSources, setDataSources] = useState<DataSource[]>(mockDataSources)
const [aiModels, setAIModels] = useState<AIModel[]>(mockAIModels)
const [syncing, setSyncing] = useState<string | null>(null)
const [training, setTraining] = useState<string | null>(null)
const [showAddDialog, setShowAddDialog] = useState(false)
const [newDataSource, setNewDataSource] = useState({
name: "",
description: "",
type: "MySQL",
host: "",
port: "",
database: "",
username: "",
password: "",
})
const handleSync = async (sourceId: string) => {
setSyncing(sourceId)
setTimeout(() => {
setSyncing(null)
setDataSources((prev) =>
prev.map((source) =>
source.id === sourceId ? { ...source, lastSync: new Date().toLocaleString("zh-CN") } : source,
),
)
}, 2000)
}
const handleRetrain = async (modelId: string) => {
setTraining(modelId)
setTimeout(() => {
setTraining(null)
setAIModels((prev) =>
prev.map((model) =>
model.id === modelId ? { ...model, lastTrained: new Date().toLocaleString("zh-CN") } : model,
),
)
}, 3000)
}
const handleAddDataSource = () => {
const newSource: DataSource = {
id: `datasource-${Date.now()}`,
name: newDataSource.name,
description: newDataSource.description,
type: newDataSource.type,
records: "0",
lastSync: "从未同步",
status: "disconnected",
}
setDataSources((prev) => [...prev, newSource])
setNewDataSource({
name: "",
description: "",
type: "MySQL",
host: "",
port: "",
database: "",
username: "",
password: "",
})
setShowAddDialog(false)
}
const getStatusBadge = (status: DataSource["status"]) => {
switch (status) {
case "connected":
return <Badge className="bg-green-100 text-green-800 border-green-200">Connected</Badge>
case "disconnected":
return <Badge variant="destructive">Disconnected</Badge>
case "syncing":
return <Badge className="bg-blue-100 text-blue-800 border-blue-200">Syncing</Badge>
default:
return <Badge variant="secondary">Unknown</Badge>
}
}
const getModelStatusBadge = (status: AIModel["status"]) => {
switch (status) {
case "ready":
return <Badge className="bg-green-100 text-green-800 border-green-200">Ready</Badge>
case "training":
return <Badge className="bg-yellow-100 text-yellow-800 border-yellow-200">Training</Badge>
case "error":
return <Badge variant="destructive">Error</Badge>
default:
return <Badge variant="secondary">Unknown</Badge>
}
}
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-white border-b">
<div className="container mx-auto px-4 py-6">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-2xl font-bold text-gray-900"></h1>
<p className="text-gray-600 mt-1">AI模型训练平台</p>
</div>
{activeTab === "datasource" ? (
<Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
<DialogTrigger asChild>
<Button className="flex items-center gap-2">
<Plus className="w-4 h-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
</Label>
<Input
id="name"
value={newDataSource.name}
onChange={(e) => setNewDataSource({ ...newDataSource, name: e.target.value })}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="description" className="text-right">
</Label>
<Input
id="description"
value={newDataSource.description}
onChange={(e) => setNewDataSource({ ...newDataSource, description: e.target.value })}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="type" className="text-right">
</Label>
<Select
value={newDataSource.type}
onValueChange={(value) => setNewDataSource({ ...newDataSource, type: value })}
>
<SelectTrigger className="col-span-3">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="MySQL">MySQL</SelectItem>
<SelectItem value="PostgreSQL">PostgreSQL</SelectItem>
<SelectItem value="MongoDB">MongoDB</SelectItem>
<SelectItem value="Redis">Redis</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="host" className="text-right">
</Label>
<Input
id="host"
value={newDataSource.host}
onChange={(e) => setNewDataSource({ ...newDataSource, host: e.target.value })}
className="col-span-3"
placeholder="localhost"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="port" className="text-right">
</Label>
<Input
id="port"
value={newDataSource.port}
onChange={(e) => setNewDataSource({ ...newDataSource, port: e.target.value })}
className="col-span-3"
placeholder="3306"
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setShowAddDialog(false)}>
</Button>
<Button onClick={handleAddDataSource} disabled={!newDataSource.name || !newDataSource.type}>
</Button>
</div>
</DialogContent>
</Dialog>
) : (
<Button className="flex items-center gap-2" onClick={() => handleRetrain("all")}>
<RefreshCw className="w-4 h-4" />
</Button>
)}
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="datasource" className="flex items-center gap-2">
<Database className="w-4 h-4" />
</TabsTrigger>
<TabsTrigger value="aimodel" className="flex items-center gap-2">
<Brain className="w-4 h-4" />
AI模型
</TabsTrigger>
</TabsList>
</Tabs>
</div>
</div>
<div className="container mx-auto px-4 py-6">
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsContent value="datasource" className="space-y-4">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold"></h2>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{dataSources.map((source) => (
<Card key={source.id} className="bg-white">
<CardHeader className="pb-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<CardTitle className="text-lg font-semibold mb-2">{source.name}</CardTitle>
<p className="text-gray-600 text-sm mb-3">{source.description}</p>
<div className="space-y-1 text-sm text-gray-500">
<div>
<span className="font-medium">{source.type}</span>
</div>
<div>
<span className="font-medium">{source.records}</span>
</div>
</div>
</div>
<div className="flex items-center gap-2">
{getStatusBadge(syncing === source.id ? "syncing" : source.status)}
</div>
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="space-y-3">
<div className="text-sm text-gray-500">{source.lastSync}</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleSync(source.id)}
disabled={syncing === source.id}
className="flex items-center gap-2 flex-1"
>
<RefreshCw className={`w-4 h-4 ${syncing === source.id ? "animate-spin" : ""}`} />
{syncing === source.id ? "同步中" : "同步数据"}
</Button>
<Button variant="ghost" size="sm">
<Settings className="w-4 h-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
<TabsContent value="aimodel" className="space-y-4">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold">AI模型管理</h2>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{aiModels.map((model) => (
<Card key={model.id} className="bg-white">
<CardHeader className="pb-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<CardTitle className="text-lg font-semibold mb-2">{model.name}</CardTitle>
<p className="text-gray-600 text-sm mb-3">{model.type} </p>
<div className="mb-3">
<div className="flex justify-between text-sm mb-1">
<span></span>
<span className="font-medium">{model.accuracy}</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div className="bg-gray-900 h-2 rounded-full" style={{ width: model.accuracy }}></div>
</div>
</div>
<div className="space-y-1 text-sm text-gray-500">
<div>
<span className="font-medium">{model.algorithm}</span>
</div>
<div>
<span className="font-medium">{model.features}</span>
</div>
</div>
</div>
<div className="flex items-center gap-2">
{getModelStatusBadge(training === model.id ? "training" : model.status)}
</div>
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="space-y-3">
<div className="text-sm text-gray-500">{model.lastTrained}</div>
<div className="flex items-center gap-2">
{model.status === "ready" ? (
<Button variant="outline" size="sm" className="flex items-center gap-2 flex-1 bg-transparent">
<Play className="w-4 h-4" />
</Button>
) : model.status === "training" ? (
<Button
variant="outline"
size="sm"
disabled
className="flex items-center gap-2 flex-1 bg-transparent"
>
<RefreshCw className="w-4 h-4 animate-spin" />
</Button>
) : (
<Button
variant="outline"
size="sm"
onClick={() => handleRetrain(model.id)}
disabled={training === model.id}
className="flex items-center gap-2 flex-1"
>
<RefreshCw className={`w-4 h-4 ${training === model.id ? "animate-spin" : ""}`} />
</Button>
)}
<Button variant="ghost" size="sm">
<Settings className="w-4 h-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
</Tabs>
</div>
<BottomTabs />
</div>
)
}