You've already forked DataMate
add operator create page (#38)
* feat: Update site name to DataMate and refine text for AI data processing * feat: Refactor settings page and implement model access functionality - Created a new ModelAccess component for managing model configurations. - Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig. - Added SystemConfig component for managing system settings. - Implemented WebhookConfig component for managing webhook configurations. - Updated API functions for model management in settings.apis.ts. - Adjusted routing to point to the new SettingsPage component. * feat: Implement Data Collection Page with Task Management and Execution Log - Created DataCollectionPage component to manage data collection tasks. - Added TaskManagement and ExecutionLog components for task handling and logging. - Integrated task operations including start, stop, edit, and delete functionalities. - Implemented filtering and searching capabilities in task management. - Introduced SimpleCronScheduler for scheduling tasks with cron expressions. - Updated CreateTask component to utilize new scheduling and template features. - Enhanced BasicInformation component to conditionally render fields based on visibility settings. - Refactored ImportConfiguration component to remove NAS import section. * feat: Update task creation API endpoint and enhance task creation form with new fields and validation * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Button, Steps } from "antd";
|
||||
import { Button, App, Steps } from "antd";
|
||||
import {
|
||||
ArrowLeft,
|
||||
CheckCircle,
|
||||
@@ -6,122 +6,102 @@ import {
|
||||
TagIcon,
|
||||
Upload,
|
||||
} from "lucide-react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import UploadStep from "./components/UploadStep";
|
||||
import ParsingStep from "./components/ParsingStep";
|
||||
import ConfigureStep from "./components/ConfigureStep";
|
||||
import PreviewStep from "./components/PreviewStep";
|
||||
|
||||
interface ParsedOperatorInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
description: string;
|
||||
author: string;
|
||||
category: string;
|
||||
modality: string[];
|
||||
type: "preprocessing" | "training" | "inference" | "postprocessing";
|
||||
framework: string;
|
||||
language: string;
|
||||
size: string;
|
||||
dependencies: string[];
|
||||
inputFormat: string[];
|
||||
outputFormat: string[];
|
||||
performance: {
|
||||
accuracy?: number;
|
||||
speed: string;
|
||||
memory: string;
|
||||
};
|
||||
documentation?: string;
|
||||
examples?: string[];
|
||||
}
|
||||
import { useFileSliceUpload } from "@/hooks/useSliceUpload";
|
||||
import {
|
||||
createOperatorUsingPost,
|
||||
preUploadOperatorUsingPost,
|
||||
queryOperatorByIdUsingGet,
|
||||
updateOperatorByIdUsingPut,
|
||||
uploadOperatorChunkUsingPost,
|
||||
uploadOperatorUsingPost,
|
||||
} from "../operator.api";
|
||||
import { sliceFile } from "@/utils/file.util";
|
||||
|
||||
export default function OperatorPluginCreate() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { message } = App.useApp();
|
||||
const [uploadStep, setUploadStep] = useState<
|
||||
"upload" | "parsing" | "configure" | "preview"
|
||||
>("upload");
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
|
||||
const [parseProgress, setParseProgress] = useState(0);
|
||||
const [parsedInfo, setParsedInfo] = useState<ParsedOperatorInfo | null>(null);
|
||||
const [parsedInfo, setParsedInfo] = useState({});
|
||||
const [parseError, setParseError] = useState<string | null>(null);
|
||||
|
||||
const { handleUpload, createTask, taskList } = useFileSliceUpload(
|
||||
{
|
||||
preUpload: preUploadOperatorUsingPost,
|
||||
uploadChunk: uploadOperatorChunkUsingPost,
|
||||
cancelUpload: null,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// 模拟文件上传
|
||||
const handleFileUpload = useCallback((files: FileList) => {
|
||||
const handleFileUpload = async (files: FileList) => {
|
||||
setIsUploading(true);
|
||||
setParseError(null);
|
||||
|
||||
// 模拟文件上传过程
|
||||
setTimeout(() => {
|
||||
const fileArray = Array.from(files).map((file) => ({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
}));
|
||||
setUploadedFiles(fileArray);
|
||||
setIsUploading(false);
|
||||
setUploadStep("parsing");
|
||||
startParsing();
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
// 模拟解析过程
|
||||
const startParsing = useCallback(() => {
|
||||
setParseProgress(0);
|
||||
const interval = setInterval(() => {
|
||||
setParseProgress((prev) => {
|
||||
if (prev >= 100) {
|
||||
clearInterval(interval);
|
||||
// 模拟解析完成
|
||||
setTimeout(() => {
|
||||
setParsedInfo({
|
||||
name: "图像预处理算子",
|
||||
version: "1.2.0",
|
||||
description:
|
||||
"支持图像缩放、裁剪、旋转、颜色空间转换等常用预处理操作,优化了内存使用和处理速度",
|
||||
author: "当前用户",
|
||||
category: "图像处理",
|
||||
modality: ["image"],
|
||||
type: "preprocessing",
|
||||
framework: "PyTorch",
|
||||
language: "Python",
|
||||
size: "2.3MB",
|
||||
dependencies: [
|
||||
"opencv-python>=4.5.0",
|
||||
"pillow>=8.0.0",
|
||||
"numpy>=1.20.0",
|
||||
],
|
||||
inputFormat: ["jpg", "png", "bmp", "tiff"],
|
||||
outputFormat: ["jpg", "png", "tensor"],
|
||||
performance: {
|
||||
accuracy: 99.5,
|
||||
speed: "50ms/image",
|
||||
memory: "128MB",
|
||||
},
|
||||
documentation:
|
||||
"# 图像预处理算子\n\n这是一个高效的图像预处理算子...",
|
||||
examples: [
|
||||
"from operator import ImagePreprocessor\nprocessor = ImagePreprocessor()\nresult = processor.process(image)",
|
||||
],
|
||||
});
|
||||
setUploadStep("configure");
|
||||
}, 500);
|
||||
return 100;
|
||||
}
|
||||
return prev + 10;
|
||||
setUploadStep("parsing");
|
||||
try {
|
||||
const fileName = files[0].name;
|
||||
await handleUpload({
|
||||
task: createTask({
|
||||
dataset: { id: "operator-upload", name: "上传算子" },
|
||||
}),
|
||||
files: [
|
||||
{
|
||||
originFile: files[0],
|
||||
slices: sliceFile(files[0]),
|
||||
name: fileName,
|
||||
size: files[0].size,
|
||||
},
|
||||
], // 假设只上传一个文件
|
||||
});
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
const handlePublish = () => {
|
||||
// 模拟发布过程
|
||||
setUploadStep("preview");
|
||||
setTimeout(() => {
|
||||
alert("算子发布成功!");
|
||||
// 这里可以重置状态或跳转到其他页面
|
||||
}, 2000);
|
||||
setParsedInfo({ ...parsedInfo, fileName, percent: 100 }); // 上传完成,进度100%
|
||||
// 解析文件过程
|
||||
const res = await uploadOperatorUsingPost({ fileName });
|
||||
setParsedInfo({ ...parsedInfo, ...res.data });
|
||||
} catch (err) {
|
||||
setParseError("文件解析失败," + err.data.message);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
setUploadStep("configure");
|
||||
}
|
||||
};
|
||||
|
||||
const handlePublish = async () => {
|
||||
try {
|
||||
if (id) {
|
||||
await updateOperatorByIdUsingPut(id, parsedInfo!);
|
||||
} else {
|
||||
await createOperatorUsingPost(parsedInfo);
|
||||
}
|
||||
setUploadStep("preview");
|
||||
} catch (err) {
|
||||
message.error("算子发布失败," + err.data.message);
|
||||
}
|
||||
};
|
||||
|
||||
const onFetchOperator = async (operatorId: string) => {
|
||||
// 编辑模式,加载已有算子信息逻辑待实现
|
||||
const { data } = await queryOperatorByIdUsingGet(operatorId);
|
||||
setParsedInfo(data);
|
||||
setUploadStep("configure");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
// 编辑模式,加载已有算子信息逻辑待实现
|
||||
onFetchOperator(id);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div className="flex-overflow-auto bg-gray-50">
|
||||
{/* Header */}
|
||||
@@ -174,13 +154,13 @@ export default function OperatorPluginCreate() {
|
||||
)}
|
||||
{uploadStep === "parsing" && (
|
||||
<ParsingStep
|
||||
parseProgress={parseProgress}
|
||||
uploadedFiles={uploadedFiles}
|
||||
parseProgress={taskList[0]?.percent || parsedInfo.percent || 0}
|
||||
uploadedFiles={taskList}
|
||||
/>
|
||||
)}
|
||||
{uploadStep === "configure" && (
|
||||
<ConfigureStep
|
||||
setUploadStep={setUploadStep}
|
||||
setParsedInfo={setParsedInfo}
|
||||
parseError={parseError}
|
||||
parsedInfo={parsedInfo}
|
||||
/>
|
||||
@@ -192,7 +172,6 @@ export default function OperatorPluginCreate() {
|
||||
{uploadStep === "configure" && (
|
||||
<div className="flex justify-end gap-3 mt-8">
|
||||
<Button onClick={() => setUploadStep("upload")}>重新上传</Button>
|
||||
<Button onClick={() => setUploadStep("preview")}>预览</Button>
|
||||
<Button type="primary" onClick={handlePublish}>
|
||||
发布算子
|
||||
</Button>
|
||||
|
||||
@@ -1,274 +1,75 @@
|
||||
import { Alert, Input, Button } from "antd";
|
||||
import { CheckCircle, Plus, TagIcon, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ConfigureStep({ parsedInfo, parseError }) {
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const [customTag, setCustomTag] = useState("");
|
||||
const availableTags = [
|
||||
"图像处理",
|
||||
"预处理",
|
||||
"缩放",
|
||||
"裁剪",
|
||||
"旋转",
|
||||
"文本处理",
|
||||
"分词",
|
||||
"中文",
|
||||
"NLP",
|
||||
"医学",
|
||||
"音频处理",
|
||||
"特征提取",
|
||||
"MFCC",
|
||||
"频谱分析",
|
||||
"视频处理",
|
||||
"帧提取",
|
||||
"关键帧",
|
||||
"采样",
|
||||
"多模态",
|
||||
"融合",
|
||||
"深度学习",
|
||||
"注意力机制",
|
||||
"推理加速",
|
||||
"TensorRT",
|
||||
"优化",
|
||||
"GPU",
|
||||
"数据增强",
|
||||
"几何变换",
|
||||
"颜色变换",
|
||||
"噪声",
|
||||
];
|
||||
|
||||
const handleAddCustomTag = () => {
|
||||
if (customTag.trim() && !selectedTags.includes(customTag.trim())) {
|
||||
setSelectedTags([...selectedTags, customTag.trim()]);
|
||||
setCustomTag("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (tagToRemove: string) => {
|
||||
setSelectedTags(selectedTags.filter((tag) => tag !== tagToRemove));
|
||||
};
|
||||
import { Alert, Input, Form } from "antd";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
|
||||
export default function ConfigureStep({
|
||||
parsedInfo,
|
||||
parseError,
|
||||
setParsedInfo,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* 解析结果 */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<CheckCircle className="w-6 h-6 text-green-500" />
|
||||
<h2 className="text-xl font-bold text-gray-900">解析完成</h2>
|
||||
</div>
|
||||
|
||||
{parseError && (
|
||||
<Alert
|
||||
message="解析过程中发现问题"
|
||||
description={parseError}
|
||||
type="warning"
|
||||
type="error"
|
||||
showIcon
|
||||
className="mb-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
{parsedInfo && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Form
|
||||
layout="vertical"
|
||||
initialValues={parsedInfo}
|
||||
onValuesChange={(_, allValues) => {
|
||||
setParsedInfo({ ...parsedInfo, ...allValues });
|
||||
}}
|
||||
>
|
||||
{/* 基本信息 */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">基本信息</h3>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
算子名称
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.name}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
版本
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.version}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
作者
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.author}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
分类
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.category}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
<Form.Item
|
||||
label="描述"
|
||||
name="description"
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<TextArea value={parsedInfo.description} />
|
||||
</Form.Item>
|
||||
|
||||
{/* 技术规格 */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">技术规格</h3>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
框架
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.framework}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
语言
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.language}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
类型
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.type}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
模态
|
||||
</label>
|
||||
<div className="p-2 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.modality.join(", ")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 描述 */}
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
描述
|
||||
</label>
|
||||
<div className="p-3 bg-gray-50 rounded border text-gray-900">
|
||||
{parsedInfo.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 依赖项 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
依赖项
|
||||
</label>
|
||||
<div className="p-3 bg-gray-50 rounded border">
|
||||
<div className="space-y-1">
|
||||
{parsedInfo.dependencies.map((dep, index) => (
|
||||
<div key={index} className="text-sm text-gray-900 font-mono">
|
||||
{dep}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 性能指标 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
性能指标
|
||||
</label>
|
||||
<div className="p-3 bg-gray-50 rounded border space-y-2">
|
||||
{parsedInfo.performance.accuracy && (
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">准确率:</span>{" "}
|
||||
{parsedInfo.performance.accuracy}%
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">速度:</span>{" "}
|
||||
{parsedInfo.performance.speed}
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">内存:</span>{" "}
|
||||
{parsedInfo.performance.memory}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 标签配置 */}
|
||||
{/* 预定义标签 */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">推荐标签</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{availableTags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={() => {
|
||||
if (selectedTags.includes(tag)) {
|
||||
handleRemoveTag(tag);
|
||||
} else {
|
||||
setSelectedTags([...selectedTags, tag]);
|
||||
}
|
||||
}}
|
||||
className={`px-3 py-1 rounded-full text-sm font-medium border transition-colors ${
|
||||
selectedTags.includes(tag)
|
||||
? "bg-blue-100 text-blue-800 border-blue-200"
|
||||
: "bg-gray-50 text-gray-700 border-gray-200 hover:bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 自定义标签 */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
添加自定义标签
|
||||
</h3>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="输入自定义标签..."
|
||||
value={customTag}
|
||||
onChange={(e) => setCustomTag(e.target.value)}
|
||||
onPressEnter={handleAddCustomTag}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button onClick={handleAddCustomTag} disabled={!customTag.trim()}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 已选标签 */}
|
||||
{selectedTags.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
已选标签 ({selectedTags.length})
|
||||
<h3 className="text-lg font-semibold text-gray-900 mt-10 mb-2">
|
||||
应用示例
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedTags.map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
className="flex items-center gap-1 px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"
|
||||
>
|
||||
<TagIcon className="w-3 h-3" />
|
||||
<span>{tag}</span>
|
||||
<button
|
||||
onClick={() => handleRemoveTag(tag)}
|
||||
className="ml-1 hover:text-blue-600"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-gray-900 mt-8">高级配置</h3>
|
||||
</Form>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Button } from "antd";
|
||||
import { CheckCircle, Plus, Eye } from "lucide-react";
|
||||
import { CheckCircle, Plus } from "lucide-react";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export default function PreviewStep({ setUploadStep }) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className="text-center py-2">
|
||||
<div className="w-24 h-24 mx-auto mb-6 bg-green-50 rounded-full flex items-center justify-center">
|
||||
@@ -15,9 +17,8 @@ export default function PreviewStep({ setUploadStep }) {
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
继续上传
|
||||
</Button>
|
||||
<Button type="primary">
|
||||
<Eye className="w-4 h-4 mr-2" />
|
||||
查看算子
|
||||
<Button type="primary" onClick={() => navigate("/data/operator-market")}>
|
||||
返回主页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ export default function UploadStep({ isUploading, onUpload }) {
|
||||
{ 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: "配置文件" },
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "antd";
|
||||
import { FilterOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { Boxes } from "lucide-react";
|
||||
import { Button, message } from "antd";
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
FilterOutlined,
|
||||
PlusOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Boxes, Edit } from "lucide-react";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import CardView from "@/components/CardView";
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -14,6 +19,7 @@ import TagManagement from "@/components/TagManagement";
|
||||
import { ListView } from "./components/List";
|
||||
import useFetchData from "@/hooks/useFetchData";
|
||||
import {
|
||||
deleteOperatorByIdUsingDelete,
|
||||
queryCategoryTreeUsingGet,
|
||||
queryOperatorsUsingPost,
|
||||
} from "../operator.api";
|
||||
@@ -23,8 +29,6 @@ export default function OperatorMarketPage() {
|
||||
const navigate = useNavigate();
|
||||
const [viewMode, setViewMode] = useState<"card" | "list">("card");
|
||||
|
||||
const filterOptions = [];
|
||||
|
||||
const [selectedFilters, setSelectedFilters] = useState<
|
||||
Record<string, string[]>
|
||||
>({});
|
||||
@@ -50,33 +54,44 @@ export default function OperatorMarketPage() {
|
||||
handleFiltersChange,
|
||||
} = useFetchData(queryOperatorsUsingPost, mapOperator);
|
||||
|
||||
const handleViewOperator = (operator: OperatorI) => {
|
||||
navigate(`/data/operator-market/plugin-detail/${operator.id}`);
|
||||
};
|
||||
|
||||
const handleUploadOperator = () => {
|
||||
navigate(`/data/operator-market/create`);
|
||||
};
|
||||
|
||||
const handleUpdateOperator = (operator: OperatorI) => {
|
||||
navigate(`/data/operator-market/edit/${operator.id}`);
|
||||
navigate(`/data/operator-market/create/${operator.id}`);
|
||||
};
|
||||
|
||||
const handleDeleteTag = (operator: OperatorI) => {
|
||||
// 删除算子逻辑
|
||||
console.log("删除算子", operator);
|
||||
const handleDeleteOperator = async (operator: OperatorI) => {
|
||||
try {
|
||||
await deleteOperatorByIdUsingDelete(operator.id);
|
||||
message.success("算子删除成功");
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
message.error("算子删除失败");
|
||||
}
|
||||
};
|
||||
|
||||
const operations = [
|
||||
{
|
||||
key: "edit",
|
||||
label: "更新算子",
|
||||
label: "更新",
|
||||
icon: <EditOutlined />,
|
||||
onClick: handleUpdateOperator,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除算子",
|
||||
onClick: handleDeleteTag,
|
||||
label: "删除",
|
||||
danger: true,
|
||||
icon: <DeleteOutlined />,
|
||||
confirm: {
|
||||
title: "确认删除",
|
||||
description: "此操作不可撤销,是否继续?",
|
||||
okText: "删除",
|
||||
okType: "danger",
|
||||
cancelText: "取消",
|
||||
},
|
||||
onClick: handleDeleteOperator,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -87,14 +102,14 @@ export default function OperatorMarketPage() {
|
||||
const filteredIds = Object.values(selectedFilters).reduce(
|
||||
(acc, filter: string[]) => {
|
||||
if (filter.length) {
|
||||
acc.push(...filter.map(Number));
|
||||
acc.push(...filter);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
fetchData({ categories: filteredIds?.length ? filteredIds : undefined });
|
||||
}, [selectedFilters]);
|
||||
|
||||
@@ -103,7 +118,7 @@ export default function OperatorMarketPage() {
|
||||
{/* Header */}
|
||||
<div className="flex justify-between">
|
||||
<h1 className="text-xl font-bold text-gray-900">算子市场</h1>
|
||||
{/* <div className="flex gap-2">
|
||||
<div className="flex gap-2">
|
||||
<TagManagement />
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -112,7 +127,7 @@ export default function OperatorMarketPage() {
|
||||
>
|
||||
上传算子
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
{/* Main Content */}
|
||||
<div className="flex-overflow-auto flex-row border-card">
|
||||
@@ -146,7 +161,7 @@ export default function OperatorMarketPage() {
|
||||
setSearchParams({ ...searchParams, keyword })
|
||||
}
|
||||
searchPlaceholder="搜索算子名称、描述..."
|
||||
filters={filterOptions}
|
||||
filters={[]}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
@@ -167,9 +182,17 @@ export default function OperatorMarketPage() {
|
||||
) : (
|
||||
<>
|
||||
{viewMode === "card" ? (
|
||||
<CardView data={tableData} pagination={pagination} />
|
||||
<CardView
|
||||
data={tableData}
|
||||
pagination={pagination}
|
||||
operations={operations}
|
||||
/>
|
||||
) : (
|
||||
<ListView operators={tableData} pagination={pagination} />
|
||||
<ListView
|
||||
operators={tableData}
|
||||
operations={operations}
|
||||
pagination={pagination}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -122,8 +122,6 @@ const Filters: React.FC<FiltersProps> = ({
|
||||
setSelectedFilters(newFilters);
|
||||
};
|
||||
|
||||
console.log(categoriesTree);
|
||||
|
||||
const hasActiveFilters = Object.values(selectedFilters).some(
|
||||
(filters) => Array.isArray(filters) && filters.length > 0
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Button, List, Tag, Badge } from "antd";
|
||||
import { DeleteOutlined, EditOutlined, StarFilled } from "@ant-design/icons";
|
||||
import { StarFilled } from "@ant-design/icons";
|
||||
import { Zap, Settings, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Operator } from "../../operator.model";
|
||||
|
||||
export function ListView({ operators, pagination }) {
|
||||
export function ListView({ operators = [], pagination, operations }) {
|
||||
const navigate = useNavigate();
|
||||
const [favoriteOperators, setFavoriteOperators] = useState<Set<number>>(
|
||||
new Set([1, 3, 6])
|
||||
@@ -59,46 +59,39 @@ export function ListView({ operators, pagination }) {
|
||||
<List.Item
|
||||
className="hover:bg-gray-50 transition-colors px-6 py-4"
|
||||
actions={[
|
||||
<Button
|
||||
key="edit"
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() => handleUpdateOperator(operator)}
|
||||
icon={<EditOutlined className="w-4 h-4" />}
|
||||
title="更新算子"
|
||||
/>,
|
||||
<Button
|
||||
key="favorite"
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() => handleToggleFavorite(operator.id)}
|
||||
className={
|
||||
favoriteOperators.has(operator.id)
|
||||
? "text-yellow-500 hover:text-yellow-600"
|
||||
: "text-gray-400 hover:text-yellow-500"
|
||||
}
|
||||
icon={
|
||||
<StarFilled
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
color: favoriteOperators.has(operator.id)
|
||||
? "#ffcc00ff"
|
||||
: "#d1d5db",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => handleToggleFavorite(operator.id)}
|
||||
/>
|
||||
}
|
||||
title="收藏"
|
||||
/>,
|
||||
<Button
|
||||
key="delete"
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined className="w-4 h-4" />}
|
||||
title="删除算子"
|
||||
/>,
|
||||
// <Button
|
||||
// key="favorite"
|
||||
// type="text"
|
||||
// size="small"
|
||||
// onClick={() => handleToggleFavorite(operator.id)}
|
||||
// className={
|
||||
// favoriteOperators.has(operator.id)
|
||||
// ? "text-yellow-500 hover:text-yellow-600"
|
||||
// : "text-gray-400 hover:text-yellow-500"
|
||||
// }
|
||||
// icon={
|
||||
// <StarFilled
|
||||
// style={{
|
||||
// fontSize: "16px",
|
||||
// color: favoriteOperators.has(operator.id)
|
||||
// ? "#ffcc00ff"
|
||||
// : "#d1d5db",
|
||||
// cursor: "pointer",
|
||||
// }}
|
||||
// onClick={() => handleToggleFavorite(operator.id)}
|
||||
// />
|
||||
// }
|
||||
// title="收藏"
|
||||
// />,
|
||||
...operations.map((operation) => (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
title={operation.label}
|
||||
icon={operation.icon}
|
||||
onClick={() => operation.onClick(operator)}
|
||||
/>
|
||||
)),
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
@@ -124,12 +117,12 @@ export function ListView({ operators, pagination }) {
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<div className="text-gray-600 ">{operator.description}</div>
|
||||
{/* <div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<span>作者: {operator.author}</span>
|
||||
<span>类型: {operator.type}</span>
|
||||
<span>框架: {operator.framework}</span>
|
||||
<span>使用次数: {operator?.usage?.toLocaleString()}</span>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,10 @@ export function createOperatorUsingPost(data: any) {
|
||||
}
|
||||
|
||||
// 更新算子
|
||||
export function updateOperatorByIdUsingPut(operatorId: string | number, data: any) {
|
||||
export function updateOperatorByIdUsingPut(
|
||||
operatorId: string | number,
|
||||
data: any
|
||||
) {
|
||||
return put(`/api/operators/${operatorId}`, data);
|
||||
}
|
||||
|
||||
@@ -35,6 +38,16 @@ export function uploadOperatorUsingPost(data: any) {
|
||||
return post("/api/operators/upload", data);
|
||||
}
|
||||
|
||||
export function preUploadOperatorUsingPost(_, data: any) {
|
||||
return post("/api/operators/upload/pre-upload", data);
|
||||
}
|
||||
|
||||
export function uploadOperatorChunkUsingPost(_, data: FormData, config?: any) {
|
||||
return post("/api/operators/upload/chunk", data, {
|
||||
showLoading: false,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
// 发布算子
|
||||
export function publishOperatorUsingPost(operatorId: string | number) {
|
||||
return post(`/api/operators/${operatorId}/publish`);
|
||||
@@ -75,169 +88,3 @@ export function deleteCategoryUsingDelete(data: { id: string | number }) {
|
||||
return del("/api/category", data);
|
||||
}
|
||||
|
||||
// 扩展功能接口(基于常见需求)
|
||||
|
||||
// 收藏/取消收藏算子
|
||||
export function starOperatorUsingPost(operatorId: string | number) {
|
||||
return post(`/api/operators/${operatorId}/star`);
|
||||
}
|
||||
|
||||
// 下载算子
|
||||
export function downloadOperatorUsingGet(operatorId: string | number, filename?: string) {
|
||||
return download(`/api/operators/${operatorId}/download`, null, filename);
|
||||
}
|
||||
|
||||
// 算子评分
|
||||
export function rateOperatorUsingPost(operatorId: string | number, data: { rating: number; comment?: string }) {
|
||||
return post(`/api/operators/${operatorId}/rating`, data);
|
||||
}
|
||||
|
||||
// 获取算子统计信息
|
||||
export function getOperatorStatisticsUsingGet(params?: any) {
|
||||
return get("/api/operators/statistics", params);
|
||||
}
|
||||
|
||||
// 获取我的算子列表
|
||||
export function queryMyOperatorsUsingPost(data: any) {
|
||||
return post("/api/operators/my-operators", data);
|
||||
}
|
||||
|
||||
// 获取收藏的算子列表
|
||||
export function queryFavoriteOperatorsUsingPost(data: any) {
|
||||
return post("/api/operators/favorites", data);
|
||||
}
|
||||
|
||||
// 算子使用统计
|
||||
export function getOperatorUsageStatsUsingGet(operatorId: string | number) {
|
||||
return get(`/api/operators/${operatorId}/usage-stats`);
|
||||
}
|
||||
|
||||
// 算子依赖检查
|
||||
export function checkOperatorDependenciesUsingPost(operatorId: string | number) {
|
||||
return post(`/api/operators/${operatorId}/check-dependencies`);
|
||||
}
|
||||
|
||||
// 算子兼容性检查
|
||||
export function checkOperatorCompatibilityUsingPost(operatorId: string | number, data: any) {
|
||||
return post(`/api/operators/${operatorId}/check-compatibility`, data);
|
||||
}
|
||||
|
||||
// 克隆算子
|
||||
export function cloneOperatorUsingPost(operatorId: string | number, data: any) {
|
||||
return post(`/api/operators/${operatorId}/clone`, data);
|
||||
}
|
||||
|
||||
// 获取算子版本列表
|
||||
export function queryOperatorVersionsUsingGet(operatorId: string | number, params?: any) {
|
||||
return get(`/api/operators/${operatorId}/versions`, params);
|
||||
}
|
||||
|
||||
// 创建算子版本
|
||||
export function createOperatorVersionUsingPost(operatorId: string | number, data: any) {
|
||||
return post(`/api/operators/${operatorId}/versions`, data);
|
||||
}
|
||||
|
||||
// 切换算子版本
|
||||
export function switchOperatorVersionUsingPut(operatorId: string | number, versionId: string | number) {
|
||||
return put(`/api/operators/${operatorId}/versions/${versionId}/switch`);
|
||||
}
|
||||
|
||||
// 删除算子版本
|
||||
export function deleteOperatorVersionUsingDelete(operatorId: string | number, versionId: string | number) {
|
||||
return del(`/api/operators/${operatorId}/versions/${versionId}`);
|
||||
}
|
||||
|
||||
// 算子测试
|
||||
export function testOperatorUsingPost(operatorId: string | number, data: any) {
|
||||
return post(`/api/operators/${operatorId}/test`, data);
|
||||
}
|
||||
|
||||
// 获取算子测试结果
|
||||
export function getOperatorTestResultUsingGet(operatorId: string | number, testId: string | number) {
|
||||
return get(`/api/operators/${operatorId}/test/${testId}/result`);
|
||||
}
|
||||
|
||||
// 算子审核相关
|
||||
export function submitOperatorForReviewUsingPost(operatorId: string | number) {
|
||||
return post(`/api/operators/${operatorId}/submit-review`);
|
||||
}
|
||||
|
||||
export function approveOperatorUsingPost(operatorId: string | number, data?: any) {
|
||||
return post(`/api/operators/${operatorId}/approve`, data);
|
||||
}
|
||||
|
||||
export function rejectOperatorUsingPost(operatorId: string | number, data: { reason: string }) {
|
||||
return post(`/api/operators/${operatorId}/reject`, data);
|
||||
}
|
||||
|
||||
// 获取算子评论列表
|
||||
export function queryOperatorCommentsUsingGet(operatorId: string | number, params?: any) {
|
||||
return get(`/api/operators/${operatorId}/comments`, params);
|
||||
}
|
||||
|
||||
// 添加算子评论
|
||||
export function addOperatorCommentUsingPost(operatorId: string | number, data: any) {
|
||||
return post(`/api/operators/${operatorId}/comments`, data);
|
||||
}
|
||||
|
||||
// 删除算子评论
|
||||
export function deleteOperatorCommentUsingDelete(operatorId: string | number, commentId: string | number) {
|
||||
return del(`/api/operators/${operatorId}/comments/${commentId}`);
|
||||
}
|
||||
|
||||
// 搜索算子
|
||||
export function searchOperatorsUsingPost(data: any) {
|
||||
return post("/api/operators/search", data);
|
||||
}
|
||||
|
||||
// 获取热门算子
|
||||
export function queryPopularOperatorsUsingGet(params?: any) {
|
||||
return get("/api/operators/popular", params);
|
||||
}
|
||||
|
||||
// 获取推荐算子
|
||||
export function queryRecommendedOperatorsUsingGet(params?: any) {
|
||||
return get("/api/operators/recommended", params);
|
||||
}
|
||||
|
||||
// 获取最新算子
|
||||
export function queryLatestOperatorsUsingGet(params?: any) {
|
||||
return get("/api/operators/latest", params);
|
||||
}
|
||||
|
||||
// 算子使用示例
|
||||
export function getOperatorExamplesUsingGet(operatorId: string | number) {
|
||||
return get(`/api/operators/${operatorId}/examples`);
|
||||
}
|
||||
|
||||
// 创建算子使用示例
|
||||
export function createOperatorExampleUsingPost(operatorId: string | number, data: any) {
|
||||
return post(`/api/operators/${operatorId}/examples`, data);
|
||||
}
|
||||
|
||||
// 算子文档
|
||||
export function getOperatorDocumentationUsingGet(operatorId: string | number) {
|
||||
return get(`/api/operators/${operatorId}/documentation`);
|
||||
}
|
||||
|
||||
// 更新算子文档
|
||||
export function updateOperatorDocumentationUsingPut(operatorId: string | number, data: any) {
|
||||
return put(`/api/operators/${operatorId}/documentation`, data);
|
||||
}
|
||||
|
||||
// 批量操作
|
||||
export function batchDeleteOperatorsUsingPost(data: { operatorIds: string[] }) {
|
||||
return post("/api/operators/batch-delete", data);
|
||||
}
|
||||
|
||||
export function batchUpdateOperatorsUsingPost(data: any) {
|
||||
return post("/api/operators/batch-update", data);
|
||||
}
|
||||
|
||||
export function batchPublishOperatorsUsingPost(data: { operatorIds: string[] }) {
|
||||
return post("/api/operators/batch-publish", data);
|
||||
}
|
||||
|
||||
export function batchUnpublishOperatorsUsingPost(data: { operatorIds: string[] }) {
|
||||
return post("/api/operators/batch-unpublish", data);
|
||||
}
|
||||
Reference in New Issue
Block a user