子仓库提交
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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地址或CIDR,如:192.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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
759
src/app/(app)/ai-crop-model/model-integration/access/page.tsx
Normal file
759
src/app/(app)/ai-crop-model/model-integration/access/page.tsx
Normal 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>: 支持ONNX、TensorFlow、PyTorch等主流框架</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user