子仓库提交

This commit is contained in:
2025-11-10 09:19:56 +08:00
parent 62f92213f7
commit 5feb24e4e2
733 changed files with 141413 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { toast } from 'sonner';
import {
Package,
Plus,
CheckCircle,
AlertCircle,
RefreshCw,
Trash2,
Clock,
Settings,
Download,
} from 'lucide-react';
interface ModelService {
id: string;
name: string;
dependencies: string[];
}
interface DependencyManageDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
model: ModelService | null;
}
export function DependencyManageDialog({ open, onOpenChange, model }: DependencyManageDialogProps) {
const handleUpdateDependency = () => {
toast.success('依赖已更新');
};
const handleRemoveDependency = (dep: string) => {
toast.success(`依赖 ${dep} 已移除`);
};
const handleAddDependency = () => {
toast.success('新依赖已添加');
};
if (!model) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> - {model.name}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 当前依赖 */}
<Card className="p-4">
<div className="flex items-center justify-between mb-4">
<h4 className="flex items-center gap-2">
<Package className="w-4 h-4 text-blue-600 dark:text-blue-400" />
({model.dependencies.length})
</h4>
<Button size="sm" variant="outline" onClick={handleAddDependency}>
<Plus className="w-3 h-3 mr-1" />
</Button>
</div>
<div className="space-y-2">
{model.dependencies.map((dep, idx) => (
<div key={idx} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900 rounded-lg group">
<div className="flex items-center gap-3 flex-1">
<Package className="w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0" />
<code className="font-mono text-sm flex-1">{dep}</code>
<Badge variant="outline" className="text-xs font-light"></Badge>
</div>
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<Button size="sm" variant="ghost" onClick={handleUpdateDependency} title="更新版本">
<RefreshCw className="w-3 h-3" />
</Button>
<Button size="sm" variant="ghost" onClick={() => handleRemoveDependency(dep)} title="移除">
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
))}
</div>
</Card>
{/* 依赖检查 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400" />
</h4>
<div className="space-y-3">
<div className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-950 rounded-lg">
<div className="flex items-center gap-3">
<CheckCircle className="w-5 h-5 text-green-600 dark:text-green-400" />
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground"></div>
</div>
</div>
<Badge className="bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"></Badge>
</div>
<Button variant="outline" className="w-full">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</div>
</Card>
{/* 依赖冲突检测 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<AlertCircle className="w-4 h-4 text-yellow-600 dark:text-yellow-400" />
</h4>
<div className="p-4 bg-yellow-50 dark:bg-yellow-950 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<div className="font-medium text-yellow-900 dark:text-yellow-100 mb-2"></div>
<div className="text-sm text-yellow-800 dark:text-yellow-200 space-y-1">
<div> numpy==1.24.0 tensorflow==2.13.0 </div>
<div> numpy 1.24.3 </div>
</div>
<Button size="sm" className="mt-3" variant="outline">
</Button>
</div>
</div>
</div>
</Card>
{/* 环境配置 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Settings className="w-4 h-4 text-purple-600 dark:text-purple-400" />
</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Python版本</Label>
<Select defaultValue="3.9">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="3.8">Python 3.8</SelectItem>
<SelectItem value="3.9">Python 3.9</SelectItem>
<SelectItem value="3.10">Python 3.10</SelectItem>
<SelectItem value="3.11">Python 3.11</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>CUDA版本</Label>
<Select defaultValue="11.8">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">使CUDA</SelectItem>
<SelectItem value="11.7">CUDA 11.7</SelectItem>
<SelectItem value="11.8">CUDA 11.8</SelectItem>
<SelectItem value="12.0">CUDA 12.0</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</Card>
{/* 依赖更新日志 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Clock className="w-4 h-4 text-gray-600 dark:text-gray-400" />
</h4>
<div className="space-y-2 max-h-[150px] overflow-y-auto">
{[
{ date: '2024-10-20', action: '更新 tensorflow 2.12.0 → 2.13.0', user: '张三' },
{ date: '2024-10-15', action: '添加 opencv-python==4.8.0', user: '李四' },
{ date: '2024-10-10', action: '更新 numpy 1.23.0 → 1.24.0', user: '王五' },
].map((log, idx) => (
<div key={idx} className="flex items-start gap-3 p-2 text-sm">
<Clock className="w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" />
<div className="flex-1">
<div className="text-xs text-muted-foreground">{log.date}</div>
<div>{log.action}</div>
<div className="text-xs text-muted-foreground">by {log.user}</div>
</div>
</div>
))}
</div>
</Card>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600">
<Download className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,226 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Card } from '@/components/ui/card';
import { toast } from 'sonner';
import {
Server,
CheckCircle,
Eye,
} from 'lucide-react';
interface DeployConfigDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function DeployConfigDialog({ open, onOpenChange }: DeployConfigDialogProps) {
const handleDeploy = () => {
toast.success('模型部署已启动预计3-5分钟完成');
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 部署环境 */}
<Card className="p-4">
<h4 className="mb-4"></h4>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Select defaultValue="production">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="development"></SelectItem>
<SelectItem value="staging"></SelectItem>
<SelectItem value="production"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Select defaultValue="cn-east">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="cn-east"></SelectItem>
<SelectItem value="cn-north"></SelectItem>
<SelectItem value="cn-south"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
</Card>
{/* 资源配置 */}
<Card className="p-4">
<h4 className="mb-4"></h4>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>CPU配置</Label>
<Select defaultValue="2">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1</SelectItem>
<SelectItem value="2">2</SelectItem>
<SelectItem value="4">4</SelectItem>
<SelectItem value="8">8</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Select defaultValue="4">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2">2GB</SelectItem>
<SelectItem value="4">4GB</SelectItem>
<SelectItem value="8">8GB</SelectItem>
<SelectItem value="16">16GB</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>GPU配置</Label>
<Select defaultValue="none">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">使GPU</SelectItem>
<SelectItem value="t4">NVIDIA T4</SelectItem>
<SelectItem value="v100">NVIDIA V100</SelectItem>
<SelectItem value="a100">NVIDIA A100</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="3" min="1" max="10" />
</div>
</div>
</Card>
{/* 自动伸缩 */}
<Card className="p-4">
<h4 className="mb-4"></h4>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-950 rounded-lg">
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground"></div>
</div>
<Switch defaultChecked />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" defaultValue="2" min="1" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="10" max="50" />
</div>
<div>
<Label>CPU</Label>
<Input type="number" defaultValue="70" min="0" max="100" />
</div>
<div>
<Label>CPU</Label>
<Input type="number" defaultValue="30" min="0" max="100" />
</div>
</div>
</div>
</Card>
{/* 健康检查 */}
<Card className="p-4">
<h4 className="mb-4"></h4>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" defaultValue="30" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="10" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="3" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="1" />
</div>
</div>
</Card>
{/* 部署进度预估 */}
<Card className="p-4 bg-gradient-to-r from-green-50 to-teal-50 dark:from-green-950 dark:to-teal-950">
<div className="flex items-start gap-3">
<Server className="w-5 h-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h4 className="text-green-900 dark:text-green-100 mb-2"></h4>
<div className="space-y-2 text-xs text-green-800 dark:text-green-200">
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
<span>1. (~1)</span>
</div>
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
<span>2. (~2)</span>
</div>
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
<span>3. (~1)</span>
</div>
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
<span>4. (~1)</span>
</div>
<p className="mt-2 text-green-600 dark:text-green-400">预计总时间: 3-5</p>
</div>
</div>
</div>
</Card>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button variant="outline">
<Eye className="w-4 h-4 mr-2" />
</Button>
<Button className="bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600" onClick={handleDeploy}>
<Server className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,257 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Card } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { toast } from 'sonner';
import {
Brain,
BarChart3,
Link,
Package,
Terminal,
CheckCircle,
GitBranch,
Copy,
Eye,
} from 'lucide-react';
interface ModelService {
id: string;
name: string;
version: string;
type: string;
format: string;
description: string;
author: string;
createTime: string;
lastUpdateTime: string;
status: string;
endpoint: string;
accessLevel: string;
tags: string[];
accuracy?: number;
inferenceTime?: number;
requestCount: number;
successRate: number;
dependencies: string[];
}
interface ModelDetailDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
model: ModelService | null;
}
export function ModelDetailDialog({ open, onOpenChange, model }: ModelDetailDialogProps) {
const handleCopyEndpoint = async (endpoint: string) => {
try {
await navigator.clipboard.writeText(endpoint);
toast.success('端点已复制到剪贴板');
} catch (error) {
toast.error('复制失败,请重试');
}
};
const handleTestModel = () => {
toast.success('模型测试成功,推理正常');
};
const getAccessLevelIcon = (level: string) => {
switch (level) {
case '公开': return '🌐';
case '私有': return '🔒';
case '团队共享': return '👥';
default: return '🔒';
}
};
if (!model) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-6xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> - {model.name}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* 基本信息 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Brain className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</h4>
<div className="space-y-4">
{/* 模型名称 - 大字体显示 */}
<div className="text-center">
<h3 className="text-2xl font-bold text-foreground mb-2">{model.name}</h3>
<Badge variant="outline" className="text-sm">
<GitBranch className="w-3 h-3 mr-1" />
{model.version}
</Badge>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-xs"></Label>
<p className="mt-1">
<Badge variant="outline" className="font-light">{model.type}</Badge>
</p>
</div>
<div>
<Label className="text-xs"></Label>
<p className="mt-1">{model.format}</p>
</div>
</div>
<div>
<Label className="text-xs"></Label>
<p className="mt-1 text-sm text-muted-foreground">{model.description}</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-xs">访</Label>
<p className="mt-1 flex items-center gap-2">
<span>{getAccessLevelIcon(model.accessLevel)}</span>
<span className="text-sm">{model.accessLevel}</span>
</p>
</div>
<div>
<Label className="text-xs"></Label>
<div className="mt-1 flex flex-wrap gap-2">
{model.tags.map((tag, idx) => (
<Badge key={idx} variant="outline" className="text-xs font-light">{tag}</Badge>
))}
</div>
</div>
</div>
</div>
</Card>
{/* 性能指标 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<BarChart3 className="w-4 h-4 text-green-600 dark:text-green-400" />
</h4>
<div className="grid grid-cols-4 gap-4">
<div className="text-center p-4 bg-green-50 dark:bg-green-950 rounded-lg">
<p className="text-xs text-muted-foreground"></p>
<p className="text-2xl text-green-600 dark:text-green-400 mt-1">{model.accuracy}%</p>
</div>
<div className="text-center p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
<p className="text-xs text-muted-foreground"></p>
<p className="text-2xl text-blue-600 dark:text-blue-400 mt-1">{model.inferenceTime}ms</p>
<p className="text-xs text-blue-600 dark:text-blue-400 mt-1"></p>
</div>
<div className="text-center p-4 bg-purple-50 dark:bg-purple-950 rounded-lg">
<p className="text-xs text-muted-foreground"></p>
<p className="text-2xl text-purple-600 dark:text-purple-400 mt-1">{model.requestCount.toLocaleString()}</p>
<p className="text-xs text-purple-600 dark:text-purple-400 mt-1"></p>
</div>
<div className="text-center p-4 bg-orange-50 dark:bg-orange-950 rounded-lg">
<p className="text-xs text-muted-foreground"></p>
<p className="text-2xl text-orange-600 dark:text-orange-400 mt-1">{model.successRate}%</p>
</div>
</div>
</Card>
{/* API端点信息 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Link className="w-4 h-4 text-blue-600 dark:text-blue-400" />
API端点
</h4>
<div className="space-y-3">
<div>
<Label className="text-xs"></Label>
<div className="mt-2 flex items-center gap-2">
<code className="flex-1 bg-gray-900 dark:bg-gray-950 text-green-400 px-4 py-2 rounded text-sm font-mono">
{model.endpoint}
</code>
<Button size="sm" variant="outline" onClick={() => handleCopyEndpoint(model.endpoint)}>
</Button>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-xs"></Label>
<p className="mt-1 text-sm">POST</p>
</div>
<div>
<Label className="text-xs">Content-Type</Label>
<p className="mt-1 text-sm">application/json</p>
</div>
</div>
</div>
</Card>
{/* 依赖包列表 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Package className="w-4 h-4 text-purple-600 dark:text-purple-400" />
({model.dependencies.length})
</h4>
<div className="space-y-2">
{model.dependencies.map((dep, idx) => (
<div key={idx} className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-900 rounded text-sm">
<CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0" />
<code className="font-mono">{dep}</code>
</div>
))}
</div>
</Card>
{/* 调用示例 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Terminal className="w-4 h-4 text-green-600 dark:text-green-400" />
API调用示例
</h4>
<div className="bg-gray-900 dark:bg-gray-950 text-green-400 p-4 rounded-lg font-mono text-sm overflow-x-auto">
<pre>{`# Python调用示例
import requests
url = "${model.endpoint}"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY"
}
payload = {
"data": [
[25.3, 65.2, 45820, 3.2, 1013.2, 18.5, 45.3, 2.3]
]
}
response = requests.post(url, json=payload, headers=headers)
result = response.json()
print(f"预测结果: {result['prediction']}")
print(f"置信度: {result['confidence']}%")`}</pre>
</div>
</Card>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button variant="outline" onClick={handleTestModel}>
<CheckCircle className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,148 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { toast } from 'sonner';
import { CheckCircle, Upload } from 'lucide-react';
interface ModelService {
id: string;
name: string;
version: string;
type: string;
format: string;
description: string;
accessLevel: string;
tags: string[];
dependencies: string[];
}
interface ModelEditDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
model: ModelService | null;
}
export function ModelEditDialog({ open, onOpenChange, model }: ModelEditDialogProps) {
const handleSaveEdit = () => {
toast.success('模型信息已更新');
onOpenChange(false);
};
if (!model) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> - {model.name}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input defaultValue={model.name} />
</div>
<div>
<Label></Label>
<Input defaultValue={model.version} />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Select defaultValue={model.type}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="作物生长预测"></SelectItem>
<SelectItem value="病虫害识别"></SelectItem>
<SelectItem value="产量预估"></SelectItem>
<SelectItem value="土壤分析"></SelectItem>
<SelectItem value="灌溉优化"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Select defaultValue={model.format}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ONNX">ONNX</SelectItem>
<SelectItem value="TensorFlow">TensorFlow</SelectItem>
<SelectItem value="PyTorch">PyTorch</SelectItem>
<SelectItem value="Scikit-learn">Scikit-learn</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label></Label>
<Textarea defaultValue={model.description} rows={3} placeholder="描述模型的功能、适用场景等..." />
</div>
<div>
<Label></Label>
<div className="border-2 border-dashed rounded-lg p-6 text-center">
<Upload className="w-8 h-8 mx-auto text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground mb-1">
</p>
<p className="text-xs text-muted-foreground">
.onnx, .h5, .pb, .pt
</p>
</div>
</div>
<div>
<Label></Label>
<Textarea defaultValue={model.dependencies.join('\n')} rows={3} placeholder="每行一个依赖tensorflow==2.13.0" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>访</Label>
<Select defaultValue={model.accessLevel}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="公开"></SelectItem>
<SelectItem value="私有"></SelectItem>
<SelectItem value="团队共享"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input defaultValue={model.tags.join(', ')} placeholder="用逗号分隔,如:深度学习,CNN" />
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600" onClick={handleSaveEdit}>
<CheckCircle className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,270 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Card } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { toast } from 'sonner';
import {
Activity,
Zap,
Server,
Cpu,
BarChart3,
Eye,
} from 'lucide-react';
interface PerformanceMetrics {
avgResponseTime: number;
p95ResponseTime: number;
p99ResponseTime: number;
qps: number;
errorRate: number;
cpuUsage: number;
memoryUsage: number;
}
interface ModelService {
id: string;
name: string;
}
interface PerformanceTuneDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
model: ModelService | null;
performanceMetrics: PerformanceMetrics;
}
export function PerformanceTuneDialog({ open, onOpenChange, model, performanceMetrics }: PerformanceTuneDialogProps) {
const handleApplyTuning = () => {
toast.success('性能优化配置已应用');
onOpenChange(false);
};
if (!model) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-5xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> - {model.name}</DialogTitle>
<DialogDescription>
使
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 当前性能指标 */}
<Card className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-950 dark:to-purple-950">
<h4 className="mb-4 flex items-center gap-2">
<Activity className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</h4>
<div className="grid grid-cols-4 gap-4">
<div className="p-3 bg-white dark:bg-gray-900 rounded-lg">
<p className="text-xs text-muted-foreground"></p>
<p className="text-2xl text-blue-600 dark:text-blue-400 mt-1">{performanceMetrics.avgResponseTime}ms</p>
</div>
<div className="p-3 bg-white dark:bg-gray-900 rounded-lg">
<p className="text-xs text-muted-foreground">QPS</p>
<p className="text-2xl text-green-600 dark:text-green-400 mt-1">{performanceMetrics.qps}</p>
</div>
<div className="p-3 bg-white dark:bg-gray-900 rounded-lg">
<p className="text-xs text-muted-foreground">CPU使用率</p>
<p className="text-2xl text-orange-600 dark:text-orange-400 mt-1">{performanceMetrics.cpuUsage}%</p>
</div>
<div className="p-3 bg-white dark:bg-gray-900 rounded-lg">
<p className="text-xs text-muted-foreground">使</p>
<p className="text-2xl text-purple-600 dark:text-purple-400 mt-1">{performanceMetrics.memoryUsage}%</p>
</div>
</div>
</Card>
{/* 负载均衡配置 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Zap className="w-4 h-4 text-orange-600 dark:text-orange-400" />
</h4>
<div className="space-y-4">
<div>
<Label></Label>
<Select defaultValue="round-robin">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="round-robin"> (Round Robin)</SelectItem>
<SelectItem value="least-connections"> (Least Connections)</SelectItem>
<SelectItem value="ip-hash">IP哈希 (IP Hash)</SelectItem>
<SelectItem value="weighted"> (Weighted)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" defaultValue="300" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="30" />
</div>
</div>
<div className="flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-950 rounded-lg">
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground"></div>
</div>
<Switch defaultChecked />
</div>
</div>
</Card>
{/* 缓存配置 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Server className="w-4 h-4 text-purple-600 dark:text-purple-400" />
</h4>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-purple-50 dark:bg-purple-950 rounded-lg">
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground"></div>
</div>
<Switch defaultChecked />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Select defaultValue="lru">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="lru">LRU (使)</SelectItem>
<SelectItem value="lfu">LFU (使)</SelectItem>
<SelectItem value="fifo">FIFO ()</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label> (MB)</Label>
<Input type="number" defaultValue="1024" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="3600" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="10000" />
</div>
</div>
</div>
</Card>
{/* 并发控制 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Cpu className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" defaultValue="100" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="10" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="1000" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="60" />
</div>
</div>
</Card>
{/* 资源限制 */}
<Card className="p-4">
<h4 className="mb-4"></h4>
<div className="grid grid-cols-2 gap-6">
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm"></span>
<span className="font-medium">8GB</span>
</div>
<Progress value={performanceMetrics.memoryUsage} className="h-2" />
<p className="text-xs text-muted-foreground mt-1">使: {performanceMetrics.memoryUsage}%</p>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm">CPU限制</span>
<span className="font-medium">4</span>
</div>
<Progress value={performanceMetrics.cpuUsage} className="h-2" />
<p className="text-xs text-muted-foreground mt-1">使: {performanceMetrics.cpuUsage}%</p>
</div>
</div>
</Card>
{/* 性能测试 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<BarChart3 className="w-4 h-4 text-green-600 dark:text-green-400" />
</h4>
<div className="space-y-3">
<p className="text-sm text-muted-foreground">
</p>
<div className="grid grid-cols-3 gap-4">
<div>
<Label></Label>
<Input type="number" defaultValue="50" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="5" />
</div>
<div>
<Label>QPS</Label>
<Input type="number" defaultValue="100" />
</div>
</div>
<Button variant="outline" className="w-full">
<Activity className="w-4 h-4 mr-2" />
</Button>
</div>
</Card>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button variant="outline">
<Eye className="w-4 h-4 mr-2" />
</Button>
<Button className="bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600" onClick={handleApplyTuning}>
<Zap className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,194 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import { Card } from '@/components/ui/card';
import { Textarea } from '@/components/ui/textarea';
import { toast } from 'sonner';
import {
Shield,
Unlock,
Users,
Lock,
Gauge,
} from 'lucide-react';
interface ModelService {
id: string;
name: string;
accessLevel: string;
}
interface PermissionManageDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
model: ModelService | null;
}
export function PermissionManageDialog({ open, onOpenChange, model }: PermissionManageDialogProps) {
const handleSavePermission = () => {
toast.success('权限设置已保存');
onOpenChange(false);
};
if (!model) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> - {model.name}</DialogTitle>
<DialogDescription>
访使
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 访问级别 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Shield className="w-4 h-4 text-blue-600 dark:text-blue-400" />
访
</h4>
<div className="space-y-3">
<div className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer">
<div className="flex items-center gap-3">
<Unlock className="w-5 h-5 text-green-600 dark:text-green-400" />
<div>
<div className="font-medium">访</div>
<div className="text-xs text-muted-foreground">访</div>
</div>
</div>
<input
type="radio"
name="access"
defaultChecked={model.accessLevel === '公开'}
/>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer">
<div className="flex items-center gap-3">
<Users className="w-5 h-5 text-blue-600 dark:text-blue-400" />
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground">访</div>
</div>
</div>
<input
type="radio"
name="access"
defaultChecked={model.accessLevel === '团队共享'}
/>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer">
<div className="flex items-center gap-3">
<Lock className="w-5 h-5 text-red-600 dark:text-red-400" />
<div>
<div className="font-medium">访</div>
<div className="text-xs text-muted-foreground">访</div>
</div>
</div>
<input
type="radio"
name="access"
defaultChecked={model.accessLevel === '私有'}
/>
</div>
</div>
</Card>
{/* API限流 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Gauge className="w-4 h-4 text-orange-600 dark:text-orange-400" />
API限流配置
</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" defaultValue="100" placeholder="0表示无限制" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="10000" placeholder="0表示无限制" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="32" />
</div>
<div>
<Label></Label>
<Input type="number" defaultValue="10" />
</div>
</div>
</Card>
{/* IP白名单 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<Shield className="w-4 h-4 text-green-600 dark:text-green-400" />
IP白名单
</h4>
<div className="space-y-3">
<div className="flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-950 rounded-lg">
<div>
<div className="font-medium">IP白名单</div>
<div className="text-xs text-muted-foreground">IP访问</div>
</div>
<Switch />
</div>
<div>
<Label>IP地址列表</Label>
<Textarea
placeholder="每行一个IP地址或CIDR192.168.1.1 或 10.0.0.0/8"
rows={4}
/>
</div>
</div>
</Card>
{/* 访问令牌管理 */}
<Card className="p-4">
<h4 className="mb-4">访</h4>
<div className="space-y-3">
<div className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-950 rounded-lg">
<div>
<div className="font-medium">API密钥认证</div>
<div className="text-xs text-muted-foreground">API调用需要有效的密钥</div>
</div>
<Switch defaultChecked />
</div>
<div className="space-y-2">
<Label>API密钥</Label>
<div className="space-y-2">
<div className="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-900 rounded text-sm">
<code className="font-mono text-xs">sk-1234567890abcdef...</code>
<div className="flex gap-1">
<Button size="sm" variant="ghost"></Button>
<Button size="sm" variant="ghost"></Button>
</div>
</div>
</div>
<Button size="sm" variant="outline" className="w-full">
</Button>
</div>
</div>
</Card>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button className="bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600" onClick={handleSavePermission}>
<Shield className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,89 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner';
import { Server } from 'lucide-react';
interface ServiceConfigDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function ServiceConfigDialog({ open, onOpenChange }: ServiceConfigDialogProps) {
const handleSaveConfig = () => {
toast.success('配置已保存');
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" placeholder="10" />
</div>
<div>
<Label></Label>
<Input type="number" placeholder="30" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" placeholder="3" />
</div>
<div>
<Label></Label>
<Input type="number" placeholder="1" />
</div>
</div>
<div className="flex items-center justify-between p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground"></div>
</div>
<Switch defaultChecked />
</div>
<div className="flex items-center justify-between p-4 bg-green-50 dark:bg-green-950 rounded-lg">
<div>
<div className="font-medium"></div>
<div className="text-xs text-muted-foreground"></div>
</div>
<Switch defaultChecked />
</div>
<div>
<Label></Label>
<Textarea placeholder="KEY=VALUE每行一个" rows={3} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button className="bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600" onClick={handleSaveConfig}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,192 @@
'use client';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { toast } from 'sonner';
import {
GitBranch,
Plus,
CheckCircle,
Download,
RefreshCw,
BarChart3,
} from 'lucide-react';
import {
LineChart as ReLineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip as RechartsTooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
interface ModelService {
id: string;
name: string;
version: string;
lastUpdateTime: string;
}
interface VersionManageDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
model: ModelService | null;
}
export function VersionManageDialog({ open, onOpenChange, model }: VersionManageDialogProps) {
const handleSwitchVersion = (version: string) => {
toast.success(`已切换到版本 ${version}`);
};
const handleDownloadVersion = (version: string) => {
toast.success(`开始下载版本 ${version}`);
};
if (!model) return null;
// 模拟版本数据
const versions = [
{ version: 'v2.3.1', date: '2024-10-10', accuracy: 94.5, inference: 120, status: '当前', desc: '优化推理性能,提升准确率' },
{ version: 'v2.3.0', date: '2024-09-15', accuracy: 93.8, inference: 135, status: '已归档', desc: '增加新特征,改进模型结构' },
{ version: 'v2.2.0', date: '2024-08-20', accuracy: 92.5, inference: 145, status: '已归档', desc: '数据集扩充,重新训练' },
{ version: 'v2.1.0', date: '2024-07-10', accuracy: 91.2, inference: 150, status: '已归档', desc: '修复已知问题,提升稳定性' },
];
// 版本性能对比数据
const performanceData = [
{ version: 'v2.1.0', 准确率: 91.2, 推理时间: 150 },
{ version: 'v2.2.0', 准确率: 92.5, 推理时间: 145 },
{ version: 'v2.3.0', 准确率: 93.8, 推理时间: 135 },
{ version: 'v2.3.1', 准确率: 94.5, 推理时间: 120 },
];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> - {model.name}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 当前版本 */}
<Card className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 border-blue-200 dark:from-blue-950 dark:to-purple-950 dark:border-blue-800">
<div className="flex items-center justify-between">
<div>
<h4 className="flex items-center gap-2">
<GitBranch className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</h4>
<p className="text-2xl mt-2 font-mono">{model.version}</p>
<p className="text-xs text-muted-foreground mt-1">
: {model.lastUpdateTime}
</p>
</div>
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300"></Badge>
</div>
</Card>
{/* 版本列表 */}
<Card>
<div className="p-4 border-b">
<div className="flex items-center justify-between">
<h4></h4>
<Button size="sm" className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600">
<Plus className="w-3 h-3 mr-1" />
</Button>
</div>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{versions.map((ver) => (
<TableRow key={ver.version}>
<TableCell>
<div className="flex items-center gap-2">
{ver.status === '当前' && <CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400" />}
<code className="font-mono">{ver.version}</code>
</div>
</TableCell>
<TableCell className="text-sm">{ver.date}</TableCell>
<TableCell>
<span className="text-green-600 dark:text-green-400">{ver.accuracy}%</span>
</TableCell>
<TableCell>{ver.inference}ms</TableCell>
<TableCell>
<Badge className={ver.status === '当前' ? 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300' : 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'}>
{ver.status}
</Badge>
</TableCell>
<TableCell className="text-xs text-muted-foreground">
{ver.desc}
</TableCell>
<TableCell>
<div className="flex gap-2">
{ver.status !== '当前' && (
<Button size="sm" variant="outline" onClick={() => handleSwitchVersion(ver.version)}>
<RefreshCw className="w-3 h-3" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => handleDownloadVersion(ver.version)}>
<Download className="w-3 h-3" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
{/* 版本对比 */}
<Card className="p-4">
<h4 className="mb-4 flex items-center gap-2">
<BarChart3 className="w-4 h-4 text-purple-600 dark:text-purple-400" />
</h4>
<ResponsiveContainer width="100%" height={250}>
<ReLineChart data={performanceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="version" />
<YAxis yAxisId="left" />
<YAxis yAxisId="right" orientation="right" />
<RechartsTooltip />
<Legend />
<Line yAxisId="left" type="monotone" dataKey="准确率" stroke="#10b981" strokeWidth={2} />
<Line yAxisId="right" type="monotone" dataKey="推理时间" stroke="#3b82f6" strokeWidth={2} />
</ReLineChart>
</ResponsiveContainer>
</Card>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button className="bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600">
<Download className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,759 @@
/**
* filekorolheader: 模型接入页面 - AI模型统一接入管理平台
* 功能:模型服务注册、接入步骤说明、格式支持展示、快速接入示例、模型管理
* 路径:/ai-crop-model/model-integration/access
* 规范遵循crop-x/docs/开发项目规范.md使用useReducer状态管理shadcn语义化样式支持暗色主题
*/
'use client';
import { useState, useReducer } from 'react';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Progress } from '@/components/ui/progress';
import { Switch } from '@/components/ui/switch';
import { copyToClipboard } from '@/lib/clipboard';
import {
Brain,
Plus,
Search,
Download,
Upload,
RefreshCw,
CheckCircle,
XCircle,
AlertCircle,
PlayCircle,
PauseCircle,
Settings,
Eye,
Edit,
Trash2,
Link,
Cpu,
Zap,
Shield,
BarChart3,
Activity,
Clock,
Package,
Server,
Gauge,
TrendingUp,
Users,
Lock,
Unlock,
Copy,
GitBranch,
Code,
Terminal,
RotateCw,
} from 'lucide-react';
import { toast } from 'sonner';
// 导入弹窗组件
import { ServiceConfigDialog } from './components/ServiceConfigDialog';
import { ModelDetailDialog } from './components/ModelDetailDialog';
import { VersionManageDialog } from './components/VersionManageDialog';
import { DeployConfigDialog } from './components/DeployConfigDialog';
import { ModelEditDialog } from './components/ModelEditDialog';
import { PermissionManageDialog } from './components/PermissionManageDialog';
import { DependencyManageDialog } from './components/DependencyManageDialog';
import { PerformanceTuneDialog } from './components/PerformanceTuneDialog';
// 类型定义
type ModelType = '作物生长预测' | '病虫害识别' | '产量预估' | '土壤分析' | '灌溉优化' | '其他';
type ModelFormat = 'ONNX' | 'TensorFlow' | 'PyTorch' | 'Scikit-learn' | 'H5' | 'SavedModel';
type ModelStatus = '运行中' | '已停止' | '部署中' | '故障' | '维护中';
type AccessLevel = '公开' | '私有' | '团队共享';
interface ModelService {
id: string;
name: string;
version: string;
type: ModelType;
format: ModelFormat;
description: string;
author: string;
createTime: string;
lastUpdateTime: string;
status: ModelStatus;
endpoint: string;
accessLevel: AccessLevel;
tags: string[];
accuracy?: number;
inferenceTime?: number;
requestCount: number;
successRate: number;
dependencies: string[];
}
interface PerformanceMetrics {
avgResponseTime: number;
p95ResponseTime: number;
p99ResponseTime: number;
qps: number;
errorRate: number;
cpuUsage: number;
memoryUsage: number;
}
interface ModelState {
modelServices: ModelService[];
selectedModel: ModelService | null;
showModelDialog: boolean;
showConfigDialog: boolean;
showDetailDialog: boolean;
showVersionDialog: boolean;
showDeployDialog: boolean;
showEditDialog: boolean;
showPermissionDialog: boolean;
showDependencyDialog: boolean;
showPerformanceDialog: boolean;
performanceMetrics: PerformanceMetrics;
}
type ModelAction =
| { type: 'SET_MODEL_SERVICES'; payload: ModelService[] }
| { type: 'SET_SELECTED_MODEL'; payload: ModelService | null }
| { type: 'TOGGLE_DIALOG'; payload: keyof Pick<ModelState, 'showModelDialog' | 'showConfigDialog' | 'showDetailDialog' | 'showVersionDialog' | 'showDeployDialog' | 'showEditDialog' | 'showPermissionDialog' | 'showDependencyDialog' | 'showPerformanceDialog'> }
| { type: 'UPDATE_MODEL_STATUS'; payload: { id: string; status: ModelStatus } };
// 初始状态
const initialState: ModelState = {
modelServices: [
{
id: 'model-1',
name: '番茄生长预测模型',
version: 'v2.3.1',
type: '作物生长预测',
format: 'TensorFlow',
description: '基于深度学习的番茄生长周期预测模型,综合温湿度、光照等因素',
author: '农业AI研究院',
createTime: '2024-03-15',
lastUpdateTime: '2024-10-10',
status: '运行中',
endpoint: 'https://api.farm-ai.com/v1/tomato-growth',
accessLevel: '团队共享',
tags: ['作物生长', '番茄', '深度学习'],
accuracy: 94.5,
inferenceTime: 120,
requestCount: 15680,
successRate: 99.2,
dependencies: ['tensorflow==2.13.0', 'numpy==1.24.0', 'pandas==2.0.0'],
},
{
id: 'model-2',
name: '病虫害智能识别',
version: 'v1.8.0',
type: '病虫害识别',
format: 'PyTorch',
description: '基于卷积神经网络的作物病虫害图像识别模型支持20+种常见病虫害',
author: '植保技术团队',
createTime: '2024-05-20',
lastUpdateTime: '2024-10-12',
status: '运行中',
endpoint: 'https://api.farm-ai.com/v1/pest-detection',
accessLevel: '公开',
tags: ['病虫害', '图像识别', 'CNN'],
accuracy: 96.8,
inferenceTime: 85,
requestCount: 28950,
successRate: 98.7,
dependencies: ['torch==2.0.1', 'torchvision==0.15.0', 'opencv-python==4.8.0'],
},
{
id: 'model-3',
name: '产量预估分析',
version: 'v3.1.2',
type: '产量预估',
format: 'ONNX',
description: '综合历史数据和实时监测的作物产量预估模型',
author: '数据分析中心',
createTime: '2024-01-10',
lastUpdateTime: '2024-09-28',
status: '运行中',
endpoint: 'https://api.farm-ai.com/v1/yield-prediction',
accessLevel: '私有',
tags: ['产量预估', 'LSTM', '时间序列'],
accuracy: 92.3,
inferenceTime: 150,
requestCount: 8520,
successRate: 99.5,
dependencies: ['onnxruntime==1.15.0', 'scikit-learn==1.3.0'],
},
{
id: 'model-4',
name: '土壤养分分析',
version: 'v2.0.0',
type: '土壤分析',
format: 'Scikit-learn',
description: '基于机器学习的土壤养分含量预测与分析模型',
author: '土壤实验室',
createTime: '2024-06-01',
lastUpdateTime: '2024-10-05',
status: '已停止',
endpoint: 'https://api.farm-ai.com/v1/soil-analysis',
accessLevel: '团队共享',
tags: ['土壤分析', '机器学习'],
accuracy: 89.7,
inferenceTime: 95,
requestCount: 4250,
successRate: 97.8,
dependencies: ['scikit-learn==1.3.0', 'xgboost==2.0.0'],
},
],
selectedModel: null,
showModelDialog: false,
showConfigDialog: false,
showDetailDialog: false,
showVersionDialog: false,
showDeployDialog: false,
showEditDialog: false,
showPermissionDialog: false,
showDependencyDialog: false,
showPerformanceDialog: false,
performanceMetrics: {
avgResponseTime: 115,
p95ResponseTime: 280,
p99ResponseTime: 450,
qps: 45.6,
errorRate: 0.8,
cpuUsage: 42.5,
memoryUsage: 68.3,
},
};
// Reducer
function modelReducer(state: ModelState, action: ModelAction): ModelState {
switch (action.type) {
case 'SET_MODEL_SERVICES':
return { ...state, modelServices: action.payload };
case 'SET_SELECTED_MODEL':
return { ...state, selectedModel: action.payload };
case 'TOGGLE_DIALOG':
return { ...state, [action.payload]: !state[action.payload] };
case 'UPDATE_MODEL_STATUS':
return {
...state,
modelServices: state.modelServices.map(model =>
model.id === action.payload.id
? { ...model, status: action.payload.status }
: model
),
};
default:
return state;
}
}
export default function ModelAccessPage() {
const [state, dispatch] = useReducer(modelReducer, initialState);
// 计算统计数据
const totalModels = state.modelServices.length;
const runningModels = state.modelServices.filter(m => m.status === '运行中').length;
const stoppedModels = state.modelServices.filter(m => m.status === '已停止').length;
const avgAccuracy = state.modelServices.reduce((sum, m) => sum + (m.accuracy || 0), 0) / totalModels;
// 模型类型分布数据
const modelTypeDistribution = [
{ name: '作物生长预测', value: 3, color: '#10b981' },
{ name: '病虫害识别', value: 2, color: '#3b82f6' },
{ name: '产量预估', value: 2, color: '#f59e0b' },
{ name: '土壤分析', value: 1, color: '#8b5cf6' },
{ name: '灌溉优化', value: 1, color: '#ec4899' },
];
// 模型调用趋势数据
const modelCallTrend = [
{ time: '10:00', 调用次数: 120, 成功率: 98.5 },
{ time: '11:00', 调用次数: 156, 成功率: 99.1 },
{ time: '12:00', 调用次数: 142, 成功率: 98.8 },
{ time: '13:00', 调用次数: 178, 成功率: 99.3 },
{ time: '14:00', 调用次数: 195, 成功率: 99.0 },
];
// 工具函数
const getStatusColor = (status: ModelStatus) => {
switch (status) {
case '运行中': return 'bg-success-muted text-success-muted-foreground';
case '已停止': return 'bg-muted text-muted-foreground';
case '部署中': return 'bg-info-muted text-info-muted-foreground';
case '故障': return 'bg-error-muted text-error-muted-foreground';
case '维护中': return 'bg-warning-muted text-warning-muted-foreground';
default: return 'bg-muted text-muted-foreground';
}
};
const getStatusIcon = (status: ModelStatus) => {
switch (status) {
case '运行中': return <PlayCircle className="w-4 h-4 text-success" />;
case '已停止': return <PauseCircle className="w-4 h-4 text-muted-foreground" />;
case '部署中': return <RefreshCw className="w-4 h-4 text-info animate-spin" />;
case '故障': return <XCircle className="w-4 h-4 text-error" />;
case '维护中': return <AlertCircle className="w-4 h-4 text-warning" />;
default: return <AlertCircle className="w-4 h-4 text-muted-foreground" />;
}
};
const getAccessLevelIcon = (level: AccessLevel) => {
switch (level) {
case '公开': return <Unlock className="w-4 h-4 text-green-600" />;
case '私有': return <Lock className="w-4 h-4 text-red-600" />;
case '团队共享': return <Users className="w-4 h-4 text-blue-600" />;
default: return <Lock className="w-4 h-4 text-gray-600" />;
}
};
// 事件处理函数
const handleToggleDialog = (dialog: keyof Pick<ModelState, 'showModelDialog' | 'showConfigDialog' | 'showDetailDialog' | 'showVersionDialog' | 'showDeployDialog' | 'showEditDialog' | 'showPermissionDialog' | 'showDependencyDialog' | 'showPerformanceDialog'>) => {
dispatch({ type: 'TOGGLE_DIALOG', payload: dialog });
};
const handleSelectModel = (model: ModelService) => {
dispatch({ type: 'SET_SELECTED_MODEL', payload: model });
};
const handleStartModel = (modelName: string) => {
toast.success(`模型"${modelName}"已启动`);
};
const handleStopModel = (modelName: string) => {
toast.success(`模型"${modelName}"已停止`);
};
const handleTestModel = () => {
toast.success('模型测试成功,推理正常');
};
const handleSaveModel = () => {
toast.success('模型服务注册成功');
handleToggleDialog('showModelDialog');
};
const handleCopyEndpoint = async (endpoint: string) => {
const success = await copyToClipboard(endpoint);
if (success) {
toast.success('端点已复制到剪贴板');
} else {
toast.error('复制失败,请重试');
}
};
const handleEditModel = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showEditDialog');
};
const handleViewDetail = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showDetailDialog');
};
const handleViewVersions = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showVersionDialog');
};
const handlePermissionManage = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showPermissionDialog');
};
const handleDependencyManage = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showDependencyDialog');
};
const handlePerformanceTune = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showPerformanceDialog');
};
const handleRedeploy = (model: ModelService) => {
toast.success(`模型"${model.name}"重新部署已启动预计3-5分钟完成`);
};
const handleConfigModel = (model: ModelService) => {
handleSelectModel(model);
handleToggleDialog('showConfigDialog');
};
return (
<div className="space-y-6">
{/* 页面标题 */}
<div>
<h2 className="text-3xl font-bold tracking-tight"></h2>
<p className="text-muted-foreground mt-1">
AI模型统一接入
</p>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground"></p>
<p className="mt-2 text-3xl text-blue-600 dark:text-blue-400">{totalModels}</p>
<p className="text-xs text-blue-600 dark:text-blue-400 mt-1"></p>
</div>
<Brain className="w-12 h-12 text-blue-600 dark:text-blue-400 opacity-50" />
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground"></p>
<p className="mt-2 text-3xl text-green-600 dark:text-green-400">{runningModels}</p>
<p className="text-xs text-green-600 dark:text-green-400 mt-1"></p>
</div>
<PlayCircle className="w-12 h-12 text-green-600 dark:text-green-400 opacity-50" />
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground"></p>
<p className="mt-2 text-3xl text-purple-600 dark:text-purple-400">{avgAccuracy.toFixed(1)}%</p>
<p className="text-xs text-purple-600 dark:text-purple-400 mt-1"></p>
</div>
<BarChart3 className="w-12 h-12 text-purple-600 dark:text-purple-400 opacity-50" />
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">QPS</p>
<p className="mt-2 text-3xl text-orange-600 dark:text-orange-400">{state.performanceMetrics.qps}</p>
<p className="text-xs text-orange-600 dark:text-orange-400 mt-1"></p>
</div>
<Zap className="w-12 h-12 text-orange-600 dark:text-orange-400 opacity-50" />
</div>
</Card>
</div>
{/* 功能说明卡片 */}
<Card className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 border-blue-200 dark:from-blue-950 dark:to-purple-950 dark:border-blue-800">
<div className="flex items-start gap-2">
<Brain className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="text-sm text-blue-900 dark:text-blue-100">
<p className="mb-2"></p>
<ul className="space-y-1 text-xs">
<li> <strong></strong>: </li>
<li> <strong></strong>: AI模型</li>
<li> <strong></strong>: ONNXTensorFlowPyTorch等主流框架</li>
<li> <strong></strong>: </li>
<li> <strong></strong>: </li>
</ul>
</div>
</div>
</Card>
{/* 操作按钮 */}
<div className="flex gap-4">
<Button
className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600"
onClick={() => {
dispatch({ type: 'SET_SELECTED_MODEL', payload: null });
handleToggleDialog('showModelDialog');
}}
>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 接入步骤说明 */}
<Card className="p-6">
<h4 className="mb-4 flex items-center gap-2">
<Code className="w-5 h-5 text-blue-600 dark:text-blue-400" />
</h4>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="text-center p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center mx-auto mb-3">
<Upload className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<h5 className="mb-2">1. </h5>
<p className="text-xs text-muted-foreground">
</p>
</div>
<div className="text-center p-4 bg-purple-50 dark:bg-purple-950 rounded-lg">
<div className="w-12 h-12 bg-purple-100 dark:bg-purple-900 rounded-full flex items-center justify-center mx-auto mb-3">
<Settings className="w-6 h-6 text-purple-600 dark:text-purple-400" />
</div>
<h5 className="mb-2">2. </h5>
<p className="text-xs text-muted-foreground">
</p>
</div>
<div className="text-center p-4 bg-green-50 dark:bg-green-950 rounded-lg">
<div className="w-12 h-12 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mx-auto mb-3">
<CheckCircle className="w-6 h-6 text-green-600 dark:text-green-400" />
</div>
<h5 className="mb-2">3. </h5>
<p className="text-xs text-muted-foreground">
</p>
</div>
<div className="text-center p-4 bg-orange-50 dark:bg-orange-950 rounded-lg">
<div className="w-12 h-12 bg-orange-100 dark:bg-orange-900 rounded-full flex items-center justify-center mx-auto mb-3">
<Server className="w-6 h-6 text-orange-600 dark:text-orange-400" />
</div>
<h5 className="mb-2">4. </h5>
<p className="text-xs text-muted-foreground">
API服务接口
</p>
</div>
</div>
</Card>
{/* 模型格式支持 */}
<Card className="p-6">
<h4 className="mb-4 flex items-center gap-2">
<Package className="w-5 h-5 text-purple-600 dark:text-purple-400" />
</h4>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{['ONNX', 'TensorFlow', 'PyTorch', 'Scikit-learn', 'H5', 'SavedModel'].map((format) => (
<div key={format} className="flex items-center gap-3 p-3 bg-muted dark:bg-muted rounded-lg">
<CheckCircle className="w-5 h-5 text-green-600 dark:text-green-400" />
<div>
<div className="font-medium">{format}</div>
<div className="text-xs text-muted-foreground"></div>
</div>
</div>
))}
</div>
</Card>
{/* 快速接入示例 */}
<Card className="p-6">
<h4 className="mb-4 flex items-center gap-2">
<Terminal className="w-5 h-5 text-green-600 dark:text-green-400" />
</h4>
<div className="bg-foreground text-green-400 p-4 rounded-lg font-mono text-sm overflow-x-auto">
<pre>{`# 使用Python SDK快速注册模型
from farm_ai_sdk import ModelRegistry
# 1. 初始化注册器
registry = ModelRegistry(api_key="your_api_key")
# 2. 注册模型
model = registry.register(
name="番茄生长预测模型",
model_path="./tomato_growth_v2.onnx",
model_type="作物生长预测",
version="v2.3.1",
metadata={
"input_shape": "(batch, 10, 8)",
"output_shape": "(batch, 1)",
"framework": "ONNX",
}
)
# 3. 部署模型
deployment = registry.deploy(
model_id=model.id,
replicas=3,
enable_autoscaling=True
)
print(f"模型已部署: {deployment.endpoint}")`}</pre>
</div>
</Card>
{/* 模型注册对话框 */}
<Dialog open={state.showModelDialog} onOpenChange={() => handleToggleDialog('showModelDialog')}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>AI模型</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入模型名称" />
</div>
<div>
<Label></Label>
<Input placeholder="v1.0.0" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="选择模型类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="作物生长预测"></SelectItem>
<SelectItem value="病虫害识别"></SelectItem>
<SelectItem value="产量预估"></SelectItem>
<SelectItem value="土壤分析"></SelectItem>
<SelectItem value="灌溉优化"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="选择模型格式" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ONNX">ONNX</SelectItem>
<SelectItem value="TensorFlow">TensorFlow</SelectItem>
<SelectItem value="PyTorch">PyTorch</SelectItem>
<SelectItem value="Scikit-learn">Scikit-learn</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label></Label>
<Textarea placeholder="描述模型的功能、适用场景等..." rows={3} />
</div>
<div>
<Label></Label>
<div className="border-2 border-dashed rounded-lg p-6 text-center">
<Upload className="w-8 h-8 mx-auto text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground mb-1">
</p>
<p className="text-xs text-muted-foreground">
.onnx, .h5, .pb, .pt
</p>
</div>
</div>
<div>
<Label></Label>
<Textarea placeholder="每行一个依赖tensorflow==2.13.0" rows={3} />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>访</Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="选择访问权限" />
</SelectTrigger>
<SelectContent>
<SelectItem value="公开"></SelectItem>
<SelectItem value="私有"></SelectItem>
<SelectItem value="团队共享"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input placeholder="用逗号分隔,如:深度学习,CNN" />
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => handleToggleDialog('showModelDialog')}>
</Button>
<Button variant="outline" onClick={handleTestModel}>
<CheckCircle className="w-4 h-4 mr-2" />
</Button>
<Button className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600" onClick={handleSaveModel}>
<Server className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 所有弹窗组件 */}
<ServiceConfigDialog
open={state.showConfigDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showConfigDialog');
}}
/>
<ModelDetailDialog
open={state.showDetailDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showDetailDialog');
}}
model={state.selectedModel}
/>
<VersionManageDialog
open={state.showVersionDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showVersionDialog');
}}
model={state.selectedModel}
/>
<DeployConfigDialog
open={state.showDeployDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showDeployDialog');
}}
/>
<ModelEditDialog
open={state.showEditDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showEditDialog');
}}
model={state.selectedModel}
/>
<PermissionManageDialog
open={state.showPermissionDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showPermissionDialog');
}}
model={state.selectedModel}
/>
<DependencyManageDialog
open={state.showDependencyDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showDependencyDialog');
}}
model={state.selectedModel}
/>
<PerformanceTuneDialog
open={state.showPerformanceDialog}
onOpenChange={(open) => {
if (!open) handleToggleDialog('showPerformanceDialog');
}}
model={state.selectedModel}
performanceMetrics={state.performanceMetrics}
/>
</div>
);
}