生产管理系统 模型服务接入、模型服务管理2个页面开发

This commit is contained in:
2025-11-01 15:32:48 +08:00
parent 3459cae699
commit 624fc38b21
22 changed files with 5635 additions and 15 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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