diff --git a/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/dto/CollectionTaskPagingQuery.java b/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/dto/CollectionTaskPagingQuery.java index 024d96d..3005042 100644 --- a/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/dto/CollectionTaskPagingQuery.java +++ b/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/dto/CollectionTaskPagingQuery.java @@ -21,5 +21,5 @@ public class CollectionTaskPagingQuery extends PagingQuery { /** * 任务名称 */ - private String name; + private String keyword; } diff --git a/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/rest/CollectionTaskController.java b/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/rest/CollectionTaskController.java index 8278e37..0f20964 100644 --- a/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/rest/CollectionTaskController.java +++ b/backend/services/data-collection-service/src/main/java/com/datamate/collection/interfaces/rest/CollectionTaskController.java @@ -72,7 +72,8 @@ public class CollectionTaskController{ Page page = new Page<>(query.getPage(), query.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(query.getStatus() != null, CollectionTask::getStatus, query.getStatus()) - .like(StringUtils.isNotBlank(query.getName()), CollectionTask::getName, query.getName()); + .like(StringUtils.isNotBlank(query.getKeyword()), CollectionTask::getName, query.getKeyword()) + .orderByDesc(CollectionTask::getCreatedAt); return ResponseEntity.ok(CollectionTaskConverter.INSTANCE.toResponse(taskService.getTasks(page, wrapper))); } } diff --git a/deployment/docker/milvus/docker-compose.yml b/deployment/docker/milvus/docker-compose.yml index 6d17b2e..f2f9ceb 100644 --- a/deployment/docker/milvus/docker-compose.yml +++ b/deployment/docker/milvus/docker-compose.yml @@ -41,7 +41,7 @@ services: milvus: container_name: milvus-standalone - image: milvusdb/milvus:v2.6.2 + image: milvusdb/milvus:v2.6.5 command: ["milvus", "run", "standalone"] security_opt: - seccomp:unconfined diff --git a/frontend/src/hooks/useFetchData.ts b/frontend/src/hooks/useFetchData.ts index a4c29ca..7df7738 100644 --- a/frontend/src/hooks/useFetchData.ts +++ b/frontend/src/hooks/useFetchData.ts @@ -111,7 +111,9 @@ export default function useFetchData( // 同时执行主要数据获取和额外的轮询函数 const promises = [ fetchFunc({ - ...filter, + ...Object.fromEntries( + Object.entries(filter).filter(([_, value]) => value != null && value.length > 0) + ), ...extraParams, keyword, type: getFirstOfArray(filter?.type) || undefined, diff --git a/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx b/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx index 3bd32b4..2a4370b 100644 --- a/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx +++ b/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx @@ -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([]); - 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(); - const [dataTypeFilter, setDataTypeFilter] = useState(); - const [labelingTypeFilter, setLabelingTypeFilter] = useState(); - const [builtInFilter, setBuiltInFilter] = useState(); + 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(); 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 (
{/* Search, Filters and Buttons in one row */}
{/* Left side: Search and Filters */}
- setSearchText(e.target.value)} + setSearchParams({ ...searchParams, filter: {} })} /> - - - - - - - - - - {hasActiveFilters && ( - - )}
{/* Right side: Create button */} @@ -354,20 +282,10 @@ const TemplateList: React.FC = () => { `共 ${total} 个模板`, - onChange: (page, pageSize) => { - setPage(page); - setSize(pageSize); - }, - }} + pagination={pagination} scroll={{ x: 1400, y: "calc(100vh - 24rem)" }} /> diff --git a/frontend/src/pages/DataAnnotation/annotation.api.ts b/frontend/src/pages/DataAnnotation/annotation.api.ts index ffef0f0..eb05233 100644 --- a/frontend/src/pages/DataAnnotation/annotation.api.ts +++ b/frontend/src/pages/DataAnnotation/annotation.api.ts @@ -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}`); +} \ No newline at end of file diff --git a/frontend/src/pages/DataAnnotation/annotation.const.tsx b/frontend/src/pages/DataAnnotation/annotation.const.tsx index b939333..a60b055 100644 --- a/frontend/src/pages/DataAnnotation/annotation.const.tsx +++ b/frontend/src/pages/DataAnnotation/annotation.const.tsx @@ -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 + }, +} \ No newline at end of file diff --git a/frontend/src/pages/DataAnnotation/annotation.model.ts b/frontend/src/pages/DataAnnotation/annotation.model.ts index 02b942d..3a751d6 100644 --- a/frontend/src/pages/DataAnnotation/annotation.model.ts +++ b/frontend/src/pages/DataAnnotation/annotation.model.ts @@ -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" +} diff --git a/frontend/src/pages/DataCollection/Create/CreateTask.tsx b/frontend/src/pages/DataCollection/Create/CreateTask.tsx index d13655e..c094e58 100644 --- a/frontend/src/pages/DataCollection/Create/CreateTask.tsx +++ b/frontend/src/pages/DataCollection/Create/CreateTask.tsx @@ -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={"支持后续在【数据管理】中手动创建数据集并关联至此任务。"} > { + return ( + + {status?.label} + + ); + }, + 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 ( - - {status?.label} - - ); - }, - width: 120, - }, { title: "操作", key: "actions", @@ -372,7 +365,7 @@ export default function DatasetManagementPage() { setSearchParams({ ...searchParams, filter: {} })} diff --git a/frontend/src/pages/Home/Home.tsx b/frontend/src/pages/Home/Home.tsx index 6fbf51a..f324181 100644 --- a/frontend/src/pages/Home/Home.tsx +++ b/frontend/src/pages/Home/Home.tsx @@ -21,12 +21,13 @@ export default function WelcomePage() { // 检查接口连通性的函数 const checkDeerFlowDeploy = async (): Promise => { 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 范围内 diff --git a/frontend/src/pages/SettingsPage/SettingsPage.tsx b/frontend/src/pages/SettingsPage/SettingsPage.tsx index 4be1004..4c938a1 100644 --- a/frontend/src/pages/SettingsPage/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage/SettingsPage.tsx @@ -36,6 +36,8 @@ export default function SettingsPage() { key: "webhook-config", icon: , label: "Webhook", + disabled: true, + title: "暂未开放" }, ]} selectedKeys={[activeTab]} diff --git a/scripts/images/frontend/Dockerfile b/scripts/images/frontend/Dockerfile index d29fa59..80968d0 100644 --- a/scripts/images/frontend/Dockerfile +++ b/scripts/images/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine AS builder +FROM node:20-alpine AS builder WORKDIR /app