Merge branch 'main' of github.com:ModelEngine-Group/DataMate

This commit is contained in:
o0Shark0o
2025-12-10 15:07:28 +08:00
18 changed files with 277 additions and 520 deletions

View File

@@ -111,7 +111,9 @@ export default function useFetchData<T>(
// 同时执行主要数据获取和额外的轮询函数
const promises = [
fetchFunc({
...filter,
...Object.fromEntries(
Object.entries(filter).filter(([_, value]) => value != null && value.length > 0)
),
...extraParams,
keyword,
type: getFirstOfArray(filter?.type) || undefined,

View File

@@ -1,12 +1,10 @@
import React, { useState, useEffect } from "react";
import React, { useState } from "react";
import {
Button,
Table,
Space,
Tag,
message,
Input,
Select,
Tooltip,
Popconfirm,
Card,
@@ -16,7 +14,6 @@ import {
EditOutlined,
DeleteOutlined,
EyeOutlined,
FilterOutlined,
} from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table";
import {
@@ -26,23 +23,38 @@ import {
import type { AnnotationTemplate } from "../annotation.model";
import TemplateForm from "./TemplateForm.tsx";
import TemplateDetail from "./TemplateDetail.tsx";
const { Search } = Input;
const { Option } = Select;
import {SearchControls} from "@/components/SearchControls.tsx";
import useFetchData from "@/hooks/useFetchData.ts";
import {
AnnotationTypeMap,
ClassificationMap,
DataTypeMap,
TemplateTypeMap
} from "@/pages/DataAnnotation/annotation.const.tsx";
const TemplateList: React.FC = () => {
const [templates, setTemplates] = useState<AnnotationTemplate[]>([]);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [size, setSize] = useState(10);
// Filters
const [searchText, setSearchText] = useState("");
const [categoryFilter, setCategoryFilter] = useState<string | undefined>();
const [dataTypeFilter, setDataTypeFilter] = useState<string | undefined>();
const [labelingTypeFilter, setLabelingTypeFilter] = useState<string | undefined>();
const [builtInFilter, setBuiltInFilter] = useState<boolean | undefined>();
const filterOptions = [
{
key: "category",
label: "分类",
options: [...Object.values(ClassificationMap)],
},
{
key: "dataType",
label: "数据类型",
options: [...Object.values(DataTypeMap)],
},
{
key: "labelingType",
label: "标注类型",
options: [...Object.values(AnnotationTypeMap)],
},
{
key: "builtIn",
label: "模板类型",
options: [...Object.values(TemplateTypeMap)],
},
];
// Modals
const [isFormVisible, setIsFormVisible] = useState(false);
@@ -50,35 +62,16 @@ const TemplateList: React.FC = () => {
const [selectedTemplate, setSelectedTemplate] = useState<AnnotationTemplate | undefined>();
const [formMode, setFormMode] = useState<"create" | "edit">("create");
useEffect(() => {
fetchTemplates();
}, [page, size, categoryFilter, dataTypeFilter, labelingTypeFilter, builtInFilter]);
const fetchTemplates = async () => {
setLoading(true);
try {
const params: any = {
page,
size,
};
if (categoryFilter) params.category = categoryFilter;
if (dataTypeFilter) params.dataType = dataTypeFilter;
if (labelingTypeFilter) params.labelingType = labelingTypeFilter;
if (builtInFilter !== undefined) params.builtIn = builtInFilter;
const response = await queryAnnotationTemplatesUsingGet(params);
if (response.code === 200 && response.data) {
setTemplates(response.data.content || []);
setTotal(response.data.total || 0);
}
} catch (error) {
message.error("获取模板列表失败");
console.error(error);
} finally {
setLoading(false);
}
};
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
fetchData,
handleFiltersChange,
handleKeywordChange,
} = useFetchData(queryAnnotationTemplatesUsingGet, undefined, undefined, undefined, undefined, 0);
const handleCreate = () => {
setFormMode("create");
@@ -102,7 +95,7 @@ const TemplateList: React.FC = () => {
const response = await deleteAnnotationTemplateByIdUsingDelete(templateId);
if (response.code === 200) {
message.success("模板删除成功");
fetchTemplates();
fetchData();
} else {
message.error(response.message || "删除模板失败");
}
@@ -114,15 +107,7 @@ const TemplateList: React.FC = () => {
const handleFormSuccess = () => {
setIsFormVisible(false);
fetchTemplates();
};
const handleClearFilters = () => {
setCategoryFilter(undefined);
setDataTypeFilter(undefined);
setLabelingTypeFilter(undefined);
setBuiltInFilter(undefined);
setSearchText("");
fetchData();
};
const getCategoryColor = (category: string) => {
@@ -143,7 +128,6 @@ const TemplateList: React.FC = () => {
key: "name",
width: 200,
ellipsis: true,
filteredValue: searchText ? [searchText] : null,
onFilter: (value, record) =>
record.name.toLowerCase().includes(value.toString().toLowerCase()) ||
(record.description?.toLowerCase().includes(value.toString().toLowerCase()) ?? false),
@@ -269,78 +253,22 @@ const TemplateList: React.FC = () => {
},
];
const hasActiveFilters = categoryFilter || dataTypeFilter || labelingTypeFilter || builtInFilter !== undefined;
return (
<div className="flex flex-col gap-4">
{/* Search, Filters and Buttons in one row */}
<div className="flex items-center justify-between gap-2">
{/* Left side: Search and Filters */}
<div className="flex items-center gap-2 flex-wrap">
<Search
placeholder="搜索模板..."
allowClear
style={{ width: 300 }}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={handleKeywordChange}
searchPlaceholder="搜索任务名称、描述"
filters={filterOptions}
onFiltersChange={handleFiltersChange}
showViewToggle={true}
onReload={fetchData}
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}
/>
<Select
placeholder="分类"
allowClear
style={{ width: 140 }}
value={categoryFilter}
onChange={setCategoryFilter}
>
<Option value="computer-vision"></Option>
<Option value="nlp"></Option>
<Option value="audio"></Option>
<Option value="quality-control"></Option>
<Option value="custom"></Option>
</Select>
<Select
placeholder="数据类型"
allowClear
style={{ width: 120 }}
value={dataTypeFilter}
onChange={setDataTypeFilter}
>
<Option value="image"></Option>
<Option value="text"></Option>
<Option value="audio"></Option>
<Option value="video"></Option>
</Select>
<Select
placeholder="标注类型"
allowClear
style={{ width: 140 }}
value={labelingTypeFilter}
onChange={setLabelingTypeFilter}
>
<Option value="classification"></Option>
<Option value="object-detection"></Option>
<Option value="segmentation"></Option>
<Option value="ner"></Option>
</Select>
<Select
placeholder="模板类型"
allowClear
style={{ width: 120 }}
value={builtInFilter}
onChange={setBuiltInFilter}
>
<Option value={true}></Option>
<Option value={false}></Option>
</Select>
{hasActiveFilters && (
<Button icon={<FilterOutlined />} onClick={handleClearFilters}>
</Button>
)}
</div>
{/* Right side: Create button */}
@@ -354,20 +282,10 @@ const TemplateList: React.FC = () => {
<Card>
<Table
columns={columns}
dataSource={templates}
dataSource={tableData}
rowKey="id"
loading={loading}
pagination={{
current: page,
pageSize: size,
total: total,
showSizeChanger: true,
showTotal: (total) => `${total} 个模板`,
onChange: (page, pageSize) => {
setPage(page);
setSize(pageSize);
},
}}
pagination={pagination}
scroll={{ x: 1400, y: "calc(100vh - 24rem)" }}
/>
</Card>

View File

@@ -1,15 +1,10 @@
import { get, post, put, del, download } from "@/utils/request";
import { get, post, put, del } from "@/utils/request";
// 标注任务管理相关接口
export function queryAnnotationTasksUsingGet(params?: any) {
return get("/api/annotation/project", params);
}
// 获取应用配置(包含 Label Studio 基础 URL)
export function getConfigUsingGet() {
return get("/api/annotation/about");
}
export function createAnnotationTaskUsingPost(data: any) {
return post("/api/annotation/project", data);
}
@@ -18,80 +13,11 @@ export function syncAnnotationTaskUsingPost(data: any) {
return post(`/api/annotation/task/sync`, data);
}
export function queryAnnotationTaskByIdUsingGet(mappingId: string | number) {
return get(`/api/annotation/project/${mappingId}`);
}
// 根据源 datasetId 查询映射关系(分页)
export function queryMappingsBySourceUsingGet(datasetId: string, params?: any) {
return get(`/api/annotation/project/by-source/${datasetId}`, params);
}
export function deleteAnnotationTaskByIdUsingDelete(mappingId: string) {
// Backend expects mapping UUID as path parameter
return del(`/api/annotation/project/${mappingId}`);
}
// 智能预标注相关接口
export function preAnnotateUsingPost(data: any) {
return post("/api/v1/annotation/pre-annotate", data);
}
// 标注数据管理接口
export function queryAnnotationDataUsingGet(
taskId: string | number,
params?: any
) {
return get(`/api/v1/annotation/tasks/${taskId}/data`, params);
}
export function submitAnnotationUsingPost(taskId: string | number, data: any) {
return post(`/api/v1/annotation/tasks/${taskId}/annotations`, data);
}
export function updateAnnotationUsingPut(
taskId: string | number,
annotationId: string | number,
data: any
) {
return put(
`/api/v1/annotation/tasks/${taskId}/annotations/${annotationId}`,
data
);
}
export function deleteAnnotationUsingDelete(
taskId: string | number,
annotationId: string | number
) {
return del(`/api/v1/annotation/tasks/${taskId}/annotations/${annotationId}`);
}
// 标注任务执行控制
export function startAnnotationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/annotation/tasks/${taskId}/start`);
}
export function pauseAnnotationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/annotation/tasks/${taskId}/pause`);
}
export function resumeAnnotationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/annotation/tasks/${taskId}/resume`);
}
export function completeAnnotationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/annotation/tasks/${taskId}/complete`);
}
// 标注任务统计信息
export function getAnnotationTaskStatisticsUsingGet(taskId: string | number) {
return get(`/api/v1/annotation/tasks/${taskId}/statistics`);
}
export function getAnnotationStatisticsUsingGet(params?: any) {
return get("/api/v1/annotation/statistics", params);
}
// 标签配置管理
export function getTagConfigUsingGet() {
return get("/api/annotation/tags/config");
@@ -106,173 +32,15 @@ export function createAnnotationTemplateUsingPost(data: any) {
return post("/api/annotation/template", data);
}
export function queryAnnotationTemplateByIdUsingGet(
templateId: string | number
) {
return get(`/api/v1/annotation/templates/${templateId}`);
}
export function updateAnnotationTemplateByIdUsingPut(
templateId: string | number,
data: any
) {
return put(`/api/v1/annotation/templates/${templateId}`, data);
return put(`/api/annotation/template/${templateId}`, data);
}
export function deleteAnnotationTemplateByIdUsingDelete(
templateId: string | number
) {
return del(`/api/v1/annotation/templates/${templateId}`);
}
// 主动学习相关接口
export function queryActiveLearningCandidatesUsingGet(
taskId: string | number,
params?: any
) {
return get(
`/api/v1/annotation/tasks/${taskId}/active-learning/candidates`,
params
);
}
export function submitActiveLearningFeedbackUsingPost(
taskId: string | number,
data: any
) {
return post(
`/api/v1/annotation/tasks/${taskId}/active-learning/feedback`,
data
);
}
export function updateActiveLearningModelUsingPost(
taskId: string | number,
data: any
) {
return post(
`/api/v1/annotation/tasks/${taskId}/active-learning/update-model`,
data
);
}
// 标注质量控制
export function validateAnnotationsUsingPost(
taskId: string | number,
data: any
) {
return post(`/api/v1/annotation/tasks/${taskId}/validate`, data);
}
export function getAnnotationQualityReportUsingGet(taskId: string | number) {
return get(`/api/v1/annotation/tasks/${taskId}/quality-report`);
}
// 标注数据导入导出
export function exportAnnotationsUsingPost(taskId: string | number, data: any) {
return post(`/api/v1/annotation/tasks/${taskId}/export`, data);
}
export function importAnnotationsUsingPost(taskId: string | number, data: any) {
return post(`/api/v1/annotation/tasks/${taskId}/import`, data);
}
export function downloadAnnotationsUsingGet(
taskId: string | number,
filename?: string
) {
return download(
`/api/v1/annotation/tasks/${taskId}/download`,
null,
filename
);
}
// 标注者管理
export function queryAnnotatorsUsingGet(params?: any) {
return get("/api/v1/annotation/annotators", params);
}
export function assignAnnotatorUsingPost(taskId: string | number, data: any) {
return post(`/api/v1/annotation/tasks/${taskId}/assign`, data);
}
export function getAnnotatorStatisticsUsingGet(annotatorId: string | number) {
return get(`/api/v1/annotation/annotators/${annotatorId}/statistics`);
}
// 标注配置管理
export function getAnnotationConfigUsingGet(taskId: string | number) {
return get(`/api/v1/annotation/tasks/${taskId}/config`);
}
export function updateAnnotationConfigUsingPut(
taskId: string | number,
data: any
) {
return put(`/api/v1/annotation/tasks/${taskId}/config`, data);
}
// 标注类型和标签管理
export function queryAnnotationTypesUsingGet() {
return get("/api/v1/annotation/types");
}
export function queryAnnotationLabelsUsingGet(taskId: string | number) {
return get(`/api/v1/annotation/tasks/${taskId}/labels`);
}
export function createAnnotationLabelUsingPost(
taskId: string | number,
data: any
) {
return post(`/api/v1/annotation/tasks/${taskId}/labels`, data);
}
export function updateAnnotationLabelUsingPut(
taskId: string | number,
labelId: string | number,
data: any
) {
return put(`/api/v1/annotation/tasks/${taskId}/labels/${labelId}`, data);
}
export function deleteAnnotationLabelUsingDelete(
taskId: string | number,
labelId: string | number
) {
return del(`/api/v1/annotation/tasks/${taskId}/labels/${labelId}`);
}
// 批量操作
export function batchAssignAnnotatorsUsingPost(data: any) {
return post("/api/v1/annotation/tasks/batch-assign", data);
}
export function batchUpdateTaskStatusUsingPost(data: any) {
return post("/api/v1/annotation/tasks/batch-update-status", data);
}
export function batchDeleteTasksUsingPost(data: { taskIds: string[] }) {
return post("/api/v1/annotation/tasks/batch-delete", data);
}
// 标注进度跟踪
export function getAnnotationProgressUsingGet(taskId: string | number) {
return get(`/api/v1/annotation/tasks/${taskId}/progress`);
}
// 标注审核
export function submitAnnotationReviewUsingPost(
taskId: string | number,
data: any
) {
return post(`/api/v1/annotation/tasks/${taskId}/review`, data);
}
export function getAnnotationReviewResultsUsingGet(
taskId: string | number,
params?: any
) {
return get(`/api/v1/annotation/tasks/${taskId}/reviews`, params);
}
return del(`/api/annotation/template/${templateId}`);
}

View File

@@ -1,13 +1,9 @@
import { StickyNote } from "lucide-react";
import { AnnotationTaskStatus } from "./annotation.model";
import {AnnotationTaskStatus, AnnotationType, Classification, DataType, TemplateType} from "./annotation.model";
import {
CheckCircleOutlined,
ClockCircleOutlined,
CloseCircleOutlined,
CustomerServiceOutlined,
FileTextOutlined,
PictureOutlined,
VideoCameraOutlined,
} from "@ant-design/icons";
export const AnnotationTaskStatusMap = {
@@ -70,3 +66,75 @@ export function mapAnnotationTask(task: any) {
statistics: statsArray,
};
}
export const DataTypeMap = {
[DataType.TEXT]: {
label: "文本",
value: DataType.TEXT
},
[DataType.IMAGE]: {
label: "图片",
value: DataType.IMAGE
},
[DataType.AUDIO]: {
label: "音频",
value: DataType.AUDIO
},
[DataType.VIDEO]: {
label: "视频",
value: DataType.VIDEO
},
}
export const ClassificationMap = {
[Classification.COMPUTER_VERSION]: {
label: "计算机视觉",
value: Classification.COMPUTER_VERSION
},
[Classification.NLP]: {
label: "自然语言处理",
value: Classification.NLP
},
[Classification.AUDIO]: {
label: "音频",
value: Classification.AUDIO
},
[Classification.QUALITY_CONTROL]: {
label: "质量控制",
value: Classification.QUALITY_CONTROL
},
[Classification.CUSTOM]: {
label: "自定义",
value: Classification.CUSTOM
},
}
export const AnnotationTypeMap = {
[AnnotationType.CLASSIFICATION]: {
label: "分类",
value: AnnotationType.CLASSIFICATION
},
[AnnotationType.OBJECT_DETECTION]: {
label: "目标检测",
value: AnnotationType.OBJECT_DETECTION
},
[AnnotationType.SEGMENTATION]: {
label: "分割",
value: AnnotationType.SEGMENTATION
},
[AnnotationType.NER]: {
label: "命名实体识别",
value: AnnotationType.NER
},
}
export const TemplateTypeMap = {
[TemplateType.SYSTEM]: {
label: "系统内置",
value: TemplateType.SYSTEM
},
[TemplateType.CUSTOM]: {
label: "自定义",
value: TemplateType.CUSTOM
},
}

View File

@@ -78,3 +78,30 @@ export interface AnnotationTemplateListResponse {
size: number;
totalPages: number;
}
export enum DataType {
TEXT = "text",
IMAGE = "image",
AUDIO = "audio",
VIDEO = "video",
}
export enum Classification {
COMPUTER_VERSION = "computer-vision",
NLP = "nlp",
AUDIO = "audio",
QUALITY_CONTROL = "quality-control",
CUSTOM = "custom"
}
export enum AnnotationType {
CLASSIFICATION = "classification",
OBJECT_DETECTION = "object-detection",
SEGMENTATION = "segmentation",
NER = "ner"
}
export enum TemplateType {
SYSTEM = "true",
CUSTOM = "false"
}

View File

@@ -1,5 +1,5 @@
import { useState } from "react";
import { Input, Button, Radio, Form, InputNumber, App, Select } from "antd";
import { Input, Button, Radio, Form, App, Select } from "antd";
import { Link, useNavigate } from "react-router";
import { ArrowLeft } from "lucide-react";
import { createTaskUsingPost } from "../collection.apis";
@@ -76,7 +76,7 @@ export default function CollectionTaskCreate() {
createDataset: false,
});
const [scheduleExpression, setScheduleExpression] = useState({
type: SyncMode.SCHEDULED,
type: "once",
time: "00:00",
cronExpression: "0 0 0 * * ?",
});
@@ -388,6 +388,7 @@ export default function CollectionTaskCreate() {
name="createDataset"
required
rules={[{ required: true, message: "请选择是否创建数据集" }]}
tooltip={"支持后续在【数据管理】中手动创建数据集并关联至此任务。"}
>
<Radio.Group
value={isCreateDataset}

View File

@@ -218,6 +218,19 @@ export default function DatasetManagementPage() {
key: "type",
width: 100,
},
{
title: "状态",
dataIndex: "status",
key: "status",
render: (status: any) => {
return (
<Tag icon={status?.icon} color={status?.color}>
{status?.label}
</Tag>
);
},
width: 120,
},
{
title: "大小",
dataIndex: "size",
@@ -230,12 +243,12 @@ export default function DatasetManagementPage() {
key: "fileCount",
width: 100,
},
{
title: "创建者",
dataIndex: "createdBy",
key: "createdBy",
width: 120,
},
// {
// title: "创建者",
// dataIndex: "createdBy",
// key: "createdBy",
// width: 120,
// },
{
title: "存储路径",
dataIndex: "targetLocation",
@@ -255,26 +268,6 @@ export default function DatasetManagementPage() {
key: "updatedAt",
width: 180,
},
{
title: "描述",
dataIndex: "description",
key: "description",
width: 200,
ellipsis: true,
},
{
title: "状态",
dataIndex: "status",
key: "status",
render: (status: any) => {
return (
<Tag icon={status?.icon} color={status?.color}>
{status?.label}
</Tag>
);
},
width: 120,
},
{
title: "操作",
key: "actions",
@@ -372,7 +365,7 @@ export default function DatasetManagementPage() {
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={handleKeywordChange}
searchPlaceholder="搜索数据集名称、描述或标签..."
searchPlaceholder="搜索数据集名称、描述"
filters={filterOptions}
onFiltersChange={handleFiltersChange}
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}

View File

@@ -21,12 +21,13 @@ export default function WelcomePage() {
// 检查接口连通性的函数
const checkDeerFlowDeploy = async (): Promise<boolean> => {
try {
const response = await fetch('/deer-flow-backend/config', { // 替换为你的实际接口地址
const response = await fetch('/deer-flow-backend/config', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
timeout: 5000, // 5秒超时
cache: 'no-store'
});
// 检查 HTTP 状态码在 200-299 范围内

View File

@@ -36,6 +36,8 @@ export default function SettingsPage() {
key: "webhook-config",
icon: <ApiOutlined />,
label: "Webhook",
disabled: true,
title: "暂未开放"
},
]}
selectedKeys={[activeTab]}