You've already forked DataMate
@@ -65,7 +65,15 @@ export default function OperatorPluginCreate() {
|
||||
setParsedInfo({ ...parsedInfo, percent: 100 }); // 上传完成,进度100%
|
||||
// 解析文件过程
|
||||
const res = await uploadOperatorUsingPost({ fileName });
|
||||
setParsedInfo({ ...parsedInfo, ...res.data, fileName });
|
||||
const configs = res.data.settings && typeof res.data.settings === "string"
|
||||
? JSON.parse(res.data.settings)
|
||||
: {};
|
||||
const defaultParams: Record<string, string> = {};
|
||||
Object.keys(configs).forEach((key) => {
|
||||
const { value } = configs[key];
|
||||
defaultParams[key] = value;
|
||||
});
|
||||
setParsedInfo({ ...res.data, fileName, configs, defaultParams});
|
||||
setUploadStep("parsing");
|
||||
} catch (err) {
|
||||
setParseError("文件解析失败," + err.data.message);
|
||||
@@ -91,7 +99,15 @@ export default function OperatorPluginCreate() {
|
||||
const onFetchOperator = async (operatorId: string) => {
|
||||
// 编辑模式,加载已有算子信息逻辑待实现
|
||||
const { data } = await queryOperatorByIdUsingGet(operatorId);
|
||||
setParsedInfo(data);
|
||||
const configs = data.settings && typeof data.settings === "string"
|
||||
? JSON.parse(data.settings)
|
||||
: {};
|
||||
const defaultParams: Record<string, string> = {};
|
||||
Object.keys(configs).forEach((key) => {
|
||||
const { value } = configs[key];
|
||||
defaultParams[key] = value;
|
||||
});
|
||||
setParsedInfo({ ...data, configs, defaultParams});
|
||||
setUploadStep("configure");
|
||||
};
|
||||
|
||||
@@ -127,7 +143,7 @@ export default function OperatorPluginCreate() {
|
||||
icon: <Settings />,
|
||||
},
|
||||
{
|
||||
title: "配置标签",
|
||||
title: "配置信息",
|
||||
icon: <TagIcon />,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Alert, Input, Form } from "antd";
|
||||
import {Alert, Input, Form} from "antd";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { useEffect } from "react";
|
||||
import React, {useEffect} from "react";
|
||||
import ParamConfig from "@/pages/DataCleansing/Create/components/ParamConfig.tsx";
|
||||
|
||||
export default function ConfigureStep({
|
||||
parsedInfo,
|
||||
@@ -13,6 +14,24 @@ export default function ConfigureStep({
|
||||
form.setFieldsValue(parsedInfo);
|
||||
}, [parsedInfo]);
|
||||
|
||||
const handleConfigChange = (
|
||||
operatorId: string,
|
||||
paramKey: string,
|
||||
value: any
|
||||
) => {
|
||||
setParsedInfo((op) =>
|
||||
op.id === operatorId
|
||||
? {
|
||||
...op,
|
||||
overrides: {
|
||||
...(op?.overrides || op?.defaultParams),
|
||||
[paramKey]: value,
|
||||
},
|
||||
}
|
||||
: op
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 解析结果 */}
|
||||
@@ -33,50 +52,54 @@ export default function ConfigureStep({
|
||||
layout="vertical"
|
||||
initialValues={parsedInfo}
|
||||
onValuesChange={(_, allValues) => {
|
||||
setParsedInfo({ ...parsedInfo, ...allValues });
|
||||
setParsedInfo({...parsedInfo, ...allValues});
|
||||
}}
|
||||
>
|
||||
{/* 基本信息 */}
|
||||
<h3 className="text-lg font-semibold text-gray-900">基本信息</h3>
|
||||
<Form.Item label="ID" name="id" rules={[{ required: true }]}>
|
||||
<Input value={parsedInfo.id} readOnly />
|
||||
<Form.Item label="ID" name="id" rules={[{required: true}]}>
|
||||
<Input value={parsedInfo.id} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label="名称" name="name" rules={[{ required: true }]}>
|
||||
<Input value={parsedInfo.name} />
|
||||
<Form.Item label="名称" name="name" rules={[{required: true}]}>
|
||||
<Input value={parsedInfo.name}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="版本" name="version" rules={[{ required: true }]}>
|
||||
<Input value={parsedInfo.version} />
|
||||
<Form.Item label="版本" name="version" rules={[{required: true}]}>
|
||||
<Input value={parsedInfo.version}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="描述"
|
||||
name="description"
|
||||
rules={[{ required: false }]}
|
||||
rules={[{required: false}]}
|
||||
>
|
||||
<TextArea value={parsedInfo.description} />
|
||||
<TextArea value={parsedInfo.description}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="输入类型" name="inputs" rules={[{required: true}]}>
|
||||
<Input value={parsedInfo.inputs}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="输出类型" name="outputs" rules={[{required: true}]}>
|
||||
<Input value={parsedInfo.outputs}/>
|
||||
</Form.Item>
|
||||
|
||||
<h3 className="text-lg font-semibold text-gray-900 mt-10 mb-2">
|
||||
应用示例
|
||||
</h3>
|
||||
<div className="border p-4 rounded-lg flex items-center justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<span className="bg-[#2196f3] border-radius px-4 py-1 rounded-tl-lg rounded-br-lg text-white">
|
||||
输入
|
||||
</span>
|
||||
<pre className="p-4 text-sm overflow-auto">
|
||||
{parsedInfo.inputs}
|
||||
</pre>
|
||||
</div>
|
||||
<h1 className="text-3xl">VS</h1>
|
||||
<div className="flex-1">
|
||||
<span className="bg-[#4caf50] border-radius px-4 py-1 rounded-tl-lg rounded-br-lg text-white">
|
||||
输出
|
||||
</span>
|
||||
<pre className=" p-4 text-sm overflow-auto">
|
||||
{parsedInfo.outputs}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{parsedInfo.configs && (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mt-10 mb-2">
|
||||
高级配置
|
||||
</h3>
|
||||
<div className="border p-4 rounded-lg grid grid-cols-2 gap-4">
|
||||
<Form layout="vertical">
|
||||
{Object.entries(parsedInfo?.configs).map(([key, param]) =>
|
||||
<ParamConfig
|
||||
key={key}
|
||||
operator={parsedInfo}
|
||||
paramKey={key}
|
||||
param={param}
|
||||
onParamChange={handleConfigChange}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* <h3 className="text-lg font-semibold text-gray-900 mt-8">高级配置</h3> */}
|
||||
</Form>
|
||||
|
||||
@@ -3,14 +3,8 @@ import { Upload, FileText } from "lucide-react";
|
||||
|
||||
export default function UploadStep({ isUploading, onUpload }) {
|
||||
const supportedFormats = [
|
||||
{ ext: ".py", desc: "Python 脚本文件" },
|
||||
{ ext: ".zip", desc: "压缩包文件" },
|
||||
{ ext: ".tar.gz", desc: "压缩包文件" },
|
||||
{ ext: ".tar", desc: "压缩包文件" },
|
||||
{ ext: ".whl", desc: "Python Wheel 包" },
|
||||
{ ext: ".yaml", desc: "配置文件" },
|
||||
{ ext: ".yml", desc: "配置文件" },
|
||||
{ ext: ".json", desc: "JSON 配置文件" },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -28,9 +22,9 @@ export default function UploadStep({ isUploading, onUpload }) {
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
支持的文件格式
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="flex gap-4">
|
||||
{supportedFormats.map((format, index) => (
|
||||
<div key={index} className="p-3 border border-gray-200 rounded-lg">
|
||||
<div key={index} className="p-3 border border-gray-200 rounded-lg flex-1">
|
||||
<div className="font-medium text-gray-900">{format.ext}</div>
|
||||
<div className="text-sm text-gray-500">{format.desc}</div>
|
||||
</div>
|
||||
@@ -52,7 +46,7 @@ export default function UploadStep({ isUploading, onUpload }) {
|
||||
onClick={() => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.multiple = false;
|
||||
input.accept = supportedFormats.map((f) => f.ext).join(",");
|
||||
input.onchange = (e) => {
|
||||
const files = (e.target as HTMLInputElement).files;
|
||||
@@ -75,7 +69,7 @@ export default function UploadStep({ isUploading, onUpload }) {
|
||||
拖拽文件到此处或点击选择文件
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
支持单个文件或多个文件同时上传
|
||||
仅支持单个文件上传
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Card, Breadcrumb } from "antd";
|
||||
import {Card, Breadcrumb, message} from "antd";
|
||||
import {
|
||||
FireOutlined,
|
||||
ShareAltOutlined,
|
||||
StarOutlined,
|
||||
DeleteOutlined, StarFilled,
|
||||
StarOutlined, UploadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Download, Clock, User } from "lucide-react";
|
||||
import {Clock, GitBranch} from "lucide-react";
|
||||
import DetailHeader from "@/components/DetailHeader";
|
||||
import { Link, useParams } from "react-router";
|
||||
import {Link, useNavigate, useParams} from "react-router";
|
||||
import Overview from "./components/Overview";
|
||||
import Install from "./components/Install";
|
||||
import Documentation from "./components/Documentation";
|
||||
import Examples from "./components/Examples";
|
||||
import ChangeLog from "./components/ChangeLog";
|
||||
import Reviews from "./components/Reviews";
|
||||
import { queryOperatorByIdUsingGet } from "../operator.api";
|
||||
|
||||
import {deleteOperatorByIdUsingDelete, queryOperatorByIdUsingGet, updateOperatorByIdUsingPut} from "../operator.api";
|
||||
import { OperatorI } from "../operator.model";
|
||||
import { mapOperator } from "../operator.const";
|
||||
|
||||
export default function OperatorPluginDetail() {
|
||||
const { id } = useParams(); // 获取动态路由参数
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const [isFavorited, setIsFavorited] = useState(false);
|
||||
const [isStar, setIsStar] = useState(false);
|
||||
const [operator, setOperator] = useState<OperatorI | null>(null);
|
||||
|
||||
const fetchOperator = async () => {
|
||||
try {
|
||||
const { data } = await queryOperatorByIdUsingGet(id as unknown as number);
|
||||
setOperator(mapOperator(data));
|
||||
setIsStar(data.isStar)
|
||||
} catch (error) {
|
||||
setOperator("error");
|
||||
}
|
||||
@@ -51,216 +49,32 @@ export default function OperatorPluginDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
// 模拟算子数据
|
||||
const mockOperator = {
|
||||
id: 1,
|
||||
name: "图像预处理算子",
|
||||
version: "1.2.0",
|
||||
description:
|
||||
"支持图像缩放、裁剪、旋转、颜色空间转换等常用预处理操作,优化了内存使用和处理速度。这是一个高效、易用的图像预处理工具,适用于各种机器学习和计算机视觉项目。",
|
||||
author: "张三",
|
||||
authorAvatar: "/placeholder-user.jpg",
|
||||
category: "图像处理",
|
||||
modality: ["image"],
|
||||
type: "preprocessing",
|
||||
tags: [
|
||||
"图像处理",
|
||||
"预处理",
|
||||
"缩放",
|
||||
"裁剪",
|
||||
"旋转",
|
||||
"计算机视觉",
|
||||
"深度学习",
|
||||
],
|
||||
createdAt: "2024-01-15",
|
||||
lastModified: "2024-01-23",
|
||||
status: "active",
|
||||
downloads: 1247,
|
||||
usage: 856,
|
||||
stars: 89,
|
||||
framework: "PyTorch",
|
||||
language: "Python",
|
||||
size: "2.3MB",
|
||||
license: "MIT",
|
||||
dependencies: [
|
||||
"opencv-python>=4.5.0",
|
||||
"pillow>=8.0.0",
|
||||
"numpy>=1.20.0",
|
||||
"torch>=1.9.0",
|
||||
"torchvision>=0.10.0",
|
||||
],
|
||||
inputFormat: ["jpg", "png", "bmp", "tiff", "webp"],
|
||||
outputFormat: ["jpg", "png", "tensor", "numpy"],
|
||||
performance: {
|
||||
accuracy: 99.5,
|
||||
speed: "50ms/image",
|
||||
memory: "128MB",
|
||||
throughput: "20 images/sec",
|
||||
},
|
||||
systemRequirements: {
|
||||
python: ">=3.7",
|
||||
memory: ">=2GB RAM",
|
||||
storage: ">=100MB",
|
||||
gpu: "Optional (CUDA support)",
|
||||
},
|
||||
installCommand: "pip install image-preprocessor==1.2.0",
|
||||
documentation: `# 图像预处理算子
|
||||
const handleStar = async () => {
|
||||
const data = {
|
||||
id: operator.id,
|
||||
isStar: !isStar
|
||||
};
|
||||
await updateOperatorByIdUsingPut(operator.id, data)
|
||||
setIsStar(!isStar)
|
||||
}
|
||||
|
||||
## 概述
|
||||
这是一个高效的图像预处理算子,支持多种常用的图像处理操作。
|
||||
|
||||
## 主要功能
|
||||
- 图像缩放和裁剪
|
||||
- 旋转和翻转
|
||||
- 颜色空间转换
|
||||
- 噪声添加和去除
|
||||
- 批量处理支持
|
||||
|
||||
## 性能特点
|
||||
- 内存优化,支持大图像处理
|
||||
- GPU加速支持
|
||||
- 多线程并行处理
|
||||
- 自动批处理优化`,
|
||||
examples: [
|
||||
{
|
||||
title: "基本使用",
|
||||
code: `from image_preprocessor import ImagePreprocessor
|
||||
|
||||
# 初始化预处理器
|
||||
processor = ImagePreprocessor()
|
||||
|
||||
# 加载图像
|
||||
image = processor.load_image("input.jpg")
|
||||
|
||||
# 执行预处理
|
||||
result = processor.process(
|
||||
image,
|
||||
resize=(224, 224),
|
||||
normalize=True,
|
||||
augment=True
|
||||
)
|
||||
|
||||
# 保存结果
|
||||
processor.save_image(result, "output.jpg")`,
|
||||
},
|
||||
{
|
||||
title: "批量处理",
|
||||
code: `from image_preprocessor import ImagePreprocessor
|
||||
import glob
|
||||
|
||||
processor = ImagePreprocessor()
|
||||
|
||||
# 批量处理图像
|
||||
image_paths = glob.glob("images/*.jpg")
|
||||
results = processor.batch_process(
|
||||
image_paths,
|
||||
resize=(256, 256),
|
||||
crop_center=(224, 224),
|
||||
normalize=True
|
||||
)
|
||||
|
||||
# 保存批量结果
|
||||
for i, result in enumerate(results):
|
||||
processor.save_image(result, f"output_{i}.jpg")`,
|
||||
},
|
||||
{
|
||||
title: "高级配置",
|
||||
code: `from image_preprocessor import ImagePreprocessor, Config
|
||||
|
||||
# 自定义配置
|
||||
config = Config(
|
||||
resize_method="bilinear",
|
||||
color_space="RGB",
|
||||
normalize_mean=[0.485, 0.456, 0.406],
|
||||
normalize_std=[0.229, 0.224, 0.225],
|
||||
augmentation={
|
||||
"rotation": (-15, 15),
|
||||
"brightness": (0.8, 1.2),
|
||||
"contrast": (0.8, 1.2)
|
||||
}
|
||||
)
|
||||
|
||||
processor = ImagePreprocessor(config)
|
||||
result = processor.process(image)`,
|
||||
},
|
||||
],
|
||||
changelog: [
|
||||
{
|
||||
version: "1.2.0",
|
||||
date: "2024-01-23",
|
||||
changes: [
|
||||
"新增批量处理功能",
|
||||
"优化内存使用,减少50%内存占用",
|
||||
"添加GPU加速支持",
|
||||
"修复旋转操作的边界问题",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "1.1.0",
|
||||
date: "2024-01-10",
|
||||
changes: [
|
||||
"添加颜色空间转换功能",
|
||||
"支持WebP格式",
|
||||
"改进错误处理机制",
|
||||
"更新文档和示例",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "1.0.0",
|
||||
date: "2024-01-01",
|
||||
changes: [
|
||||
"首次发布",
|
||||
"支持基本图像预处理操作",
|
||||
"包含缩放、裁剪、旋转功能",
|
||||
],
|
||||
},
|
||||
],
|
||||
reviews: [
|
||||
{
|
||||
id: 1,
|
||||
user: "李四",
|
||||
avatar: "/placeholder-user.jpg",
|
||||
rating: 5,
|
||||
date: "2024-01-20",
|
||||
comment:
|
||||
"非常好用的图像预处理工具,性能优秀,文档清晰。在我们的项目中大大提高了数据预处理的效率。",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: "王五",
|
||||
avatar: "/placeholder-user.jpg",
|
||||
rating: 4,
|
||||
date: "2024-01-18",
|
||||
comment:
|
||||
"功能很全面,但是希望能添加更多的数据增强选项。整体来说是个不错的工具。",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: "赵六",
|
||||
avatar: "/placeholder-user.jpg",
|
||||
rating: 5,
|
||||
date: "2024-01-15",
|
||||
comment:
|
||||
"安装简单,使用方便,性能表现超出预期。推荐给所有做图像处理的同学。",
|
||||
},
|
||||
],
|
||||
const handleDelete = async () => {
|
||||
await deleteOperatorByIdUsingDelete(operator.id);
|
||||
navigate("/data/operator-market");
|
||||
message.success("算子删除成功");
|
||||
};
|
||||
|
||||
// 模拟算子数据
|
||||
const statistics = [
|
||||
{
|
||||
icon: <Download className="w-4 h-4" />,
|
||||
icon: <GitBranch className="text-blue-400 w-4 h-4" />,
|
||||
label: "",
|
||||
value: operator?.downloads?.toLocaleString(),
|
||||
value: "v" + operator?.version,
|
||||
},
|
||||
{
|
||||
icon: <User className="w-4 h-4" />,
|
||||
icon: <Clock className="text-blue-400 w-4 h-4" />,
|
||||
label: "",
|
||||
value: operator?.author,
|
||||
},
|
||||
{
|
||||
icon: <Clock className="w-4 h-4" />,
|
||||
label: "",
|
||||
value: operator?.lastModified,
|
||||
value: operator?.updatedAt,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -268,30 +82,33 @@ result = processor.process(image)`,
|
||||
{
|
||||
key: "favorite",
|
||||
label: "收藏",
|
||||
icon: (
|
||||
<StarOutlined
|
||||
className={`w-4 h-4 ${
|
||||
isFavorited ? "fill-yellow-400 text-yellow-400" : ""
|
||||
}`}
|
||||
/>
|
||||
icon: (isStar ? (
|
||||
<StarFilled style={{ color: '#f59e0b' }} />
|
||||
) : (
|
||||
<StarOutlined />
|
||||
)
|
||||
),
|
||||
onClick: () => setIsFavorited(!isFavorited),
|
||||
onClick: handleStar,
|
||||
},
|
||||
{
|
||||
key: "share",
|
||||
label: "分享",
|
||||
icon: <ShareAltOutlined />,
|
||||
onClick: () => {
|
||||
/* 分享逻辑 */
|
||||
},
|
||||
key: "update",
|
||||
label: "更新",
|
||||
icon: <UploadOutlined />,
|
||||
onClick: () => navigate("/data/operator-market/create/" + operator.id),
|
||||
},
|
||||
{
|
||||
key: "report",
|
||||
label: "发布",
|
||||
icon: <FireOutlined />,
|
||||
onClick: () => {
|
||||
/* 发布逻辑 */
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
danger: true,
|
||||
confirm: {
|
||||
title: "确认删除当前算子?",
|
||||
description: "删除后该算子将无法恢复,请谨慎操作。",
|
||||
okText: "删除",
|
||||
cancelText: "取消",
|
||||
okType: "danger"
|
||||
},
|
||||
icon: <DeleteOutlined />,
|
||||
onClick: handleDelete,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -320,36 +137,12 @@ result = processor.process(image)`,
|
||||
key: "overview",
|
||||
label: "概览",
|
||||
},
|
||||
{
|
||||
key: "install",
|
||||
label: "安装",
|
||||
},
|
||||
{
|
||||
key: "documentation",
|
||||
label: "文档",
|
||||
},
|
||||
{
|
||||
key: "examples",
|
||||
label: "示例",
|
||||
},
|
||||
{
|
||||
key: "changelog",
|
||||
label: "更新日志",
|
||||
},
|
||||
{
|
||||
key: "reviews",
|
||||
label: "评价",
|
||||
},
|
||||
]}
|
||||
activeTabKey={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
>
|
||||
{activeTab === "overview" && <Overview operator={operator} />}
|
||||
{activeTab === "install" && <Install operator={operator} />}
|
||||
{activeTab === "documentation" && <Documentation operator={operator} />}
|
||||
{activeTab === "examples" && <Examples operator={operator} />}
|
||||
{activeTab === "changelog" && <ChangeLog operator={operator} />}
|
||||
{activeTab === "reviews" && <Reviews operator={operator} />}
|
||||
{activeTab === "service" && <Install operator={operator} />}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
import { DescriptionsProps, Card, Descriptions, Tag } from "antd";
|
||||
import { FileText, ImageIcon, Music, Video } from "lucide-react";
|
||||
import {DescriptionsProps, Card, Descriptions, Tag} from "antd";
|
||||
|
||||
export default function Overview({ operator }) {
|
||||
const getModalityIcon = (modality: string) => {
|
||||
const iconMap = {
|
||||
text: FileText,
|
||||
image: ImageIcon,
|
||||
audio: Music,
|
||||
video: Video,
|
||||
};
|
||||
const IconComponent = iconMap[modality as keyof typeof iconMap] || FileText;
|
||||
return <IconComponent className="w-4 h-4" />;
|
||||
};
|
||||
|
||||
const descriptionItems: DescriptionsProps["items"] = [
|
||||
{
|
||||
key: "version",
|
||||
@@ -22,59 +10,38 @@ export default function Overview({ operator }) {
|
||||
{
|
||||
key: "category",
|
||||
label: "分类",
|
||||
children: operator.category,
|
||||
},
|
||||
{
|
||||
key: "language",
|
||||
label: "语言",
|
||||
children: operator.language,
|
||||
},
|
||||
{
|
||||
key: "modality",
|
||||
label: "模态",
|
||||
children: (
|
||||
<div className="flex items-center gap-2">
|
||||
{operator.modality.map((mod, index) => (
|
||||
<span
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{operator.categories.map((category, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
className="flex items-center gap-1 px-2 py-1 bg-gray-100 rounded text-sm"
|
||||
className="px-3 py-1 bg-blue-50 text-blue-700 border border-blue-200 rounded-full"
|
||||
>
|
||||
{getModalityIcon(mod)}
|
||||
{mod}
|
||||
</span>
|
||||
{category}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "framework",
|
||||
label: "框架",
|
||||
children: operator.framework,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "类型",
|
||||
children: operator.type,
|
||||
},
|
||||
{
|
||||
key: "size",
|
||||
label: "大小",
|
||||
children: operator.size,
|
||||
},
|
||||
{
|
||||
key: "license",
|
||||
label: "许可证",
|
||||
children: operator.license,
|
||||
key: "inputs",
|
||||
label: "输入类型",
|
||||
children: operator.inputs,
|
||||
},
|
||||
{
|
||||
key: "createdAt",
|
||||
label: "创建时间",
|
||||
children: operator.createdAt,
|
||||
},
|
||||
{
|
||||
key: "outputs",
|
||||
label: "输出类型",
|
||||
children: operator.outputs,
|
||||
},
|
||||
{
|
||||
key: "lastModified",
|
||||
label: "最后修改",
|
||||
children: operator.lastModified,
|
||||
children: operator.updatedAt,
|
||||
},
|
||||
];
|
||||
return (
|
||||
@@ -84,83 +51,8 @@ export default function Overview({ operator }) {
|
||||
<Descriptions column={2} title="基本信息" items={descriptionItems} />
|
||||
</Card>
|
||||
|
||||
{/* 标签 */}
|
||||
<Card>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">标签</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{operator.tags.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
className="px-3 py-1 bg-blue-50 text-blue-700 border border-blue-200 rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 性能指标 */}
|
||||
<Card>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">性能指标</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{operator.performance.accuracy && (
|
||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{operator.performance.accuracy}%
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">准确率</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{operator.performance.speed}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">处理速度</div>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{operator.performance.memory}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">内存使用</div>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{operator.performance.throughput}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">吞吐量</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 输入输出格式 */}
|
||||
<Card>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">支持格式</h3>
|
||||
<Descriptions column={2} bordered size="middle">
|
||||
<Descriptions.Item label="输入格式">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{operator.inputFormat.map((format, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-green-50 text-green-700 border border-green-200 rounded text-sm"
|
||||
>
|
||||
.{format}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="输出格式">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{operator.outputFormat.map((format, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-blue-50 text-blue-700 border border-blue-200 rounded text-sm"
|
||||
>
|
||||
.{format}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Card title="描述" styles={{header: {borderBottom: 'none'}}}>
|
||||
<p>{operator.description}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
FilterOutlined,
|
||||
PlusOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Boxes, Edit } from "lucide-react";
|
||||
import { Boxes } from "lucide-react";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import CardView from "@/components/CardView";
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -186,6 +186,7 @@ export default function OperatorMarketPage() {
|
||||
data={tableData}
|
||||
pagination={pagination}
|
||||
operations={operations}
|
||||
onView={(item) => navigate(`/data/operator-market/plugin-detail/${item.id}`)}
|
||||
/>
|
||||
) : (
|
||||
<ListView
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Code } from "lucide-react";
|
||||
import { OperatorI } from "./operator.model";
|
||||
import {formatDateTime} from "@/utils/unit.ts";
|
||||
|
||||
export const mapOperator = (op: OperatorI) => {
|
||||
return {
|
||||
...op,
|
||||
icon: <Code className="w-full h-full" />,
|
||||
createdAt: formatDateTime(op?.createdAt) || "--",
|
||||
updatedAt: formatDateTime(op?.updatedAt) || formatDateTime(op?.createdAt) || "--",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,6 +25,9 @@ export interface OperatorI {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
version: string;
|
||||
inputs: string;
|
||||
outputs: string;
|
||||
icon: React.ReactNode;
|
||||
description: string;
|
||||
tags: string[];
|
||||
@@ -37,6 +40,8 @@ export interface OperatorI {
|
||||
configs: {
|
||||
[key: string]: ConfigI;
|
||||
};
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CategoryI {
|
||||
|
||||
Reference in New Issue
Block a user