生产管理系统 模型服务接入、模型服务管理2个页面开发
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user