1438 lines
44 KiB
TypeScript
1438 lines
44 KiB
TypeScript
"use client"
|
||
|
||
import {
|
||
useCallback,
|
||
useEffect,
|
||
useMemo,
|
||
useReducer,
|
||
useRef,
|
||
useState,
|
||
} from "react"
|
||
import ScaleComponent from "./components/ScaleComponent"
|
||
|
||
import { processVideo } from "@/app/component/label/video2image/processVideo"
|
||
import { Box, Flex, LoadingOverlay, Stack } from "@mantine/core"
|
||
import { showNotification } from "@mantine/notifications"
|
||
import { getLabelResult, getServerImage } from "./api/label"
|
||
import {
|
||
Comment,
|
||
ImageObjects,
|
||
LabelResult,
|
||
WorkLoad,
|
||
} from "./api/label/typing"
|
||
import { getProjectDetail } from "./api/project"
|
||
import { Project } from "./api/project/typing"
|
||
import { getTaskList, getTaskWorkFlow } from "./api/task"
|
||
import { Task } from "./api/task/typing"
|
||
import BottomTools from "./components/BottomTools"
|
||
import ConfirmModal from "./components/ConfirmModal"
|
||
import { splitWord } from "./components/EditorContainer"
|
||
import PaperContainer from "./components/PaperContainer"
|
||
import RightDescTools from "./components/RightDescTools"
|
||
import RightGroupTools from "./components/RightGroupTools"
|
||
import RightObjectTools from "./components/RightObjectTools"
|
||
import RightQATools from "./components/RightQATools"
|
||
import RightTaskTools from "./components/RightTaskTools"
|
||
import ScaleToolContainer from "./components/ScaleToolContainer"
|
||
import TopTools from "./components/TopTools"
|
||
import {
|
||
useImagesStore,
|
||
useKeyEventStore,
|
||
useLabelStore,
|
||
useObjectStore,
|
||
useVideoFrameStore,
|
||
} from "./store"
|
||
import { usePermissionStore } from "./store/auth"
|
||
import { useBottomToolsStore } from "./useBottomToolsStore"
|
||
import { useDescToolsStore } from "./useDescToolsStore"
|
||
import { useKeyboardStore } from "./useKeyBoardStore"
|
||
import { usePaperStore } from "./usePaperStore"
|
||
import { useRightToolsStore } from "./useRightToolsStore"
|
||
import { useIntervalStore, useTimerStore } from "./useTimerStore"
|
||
import { useTopToolsStore } from "./useTopToolsStore"
|
||
import { findGroupKey } from "./util"
|
||
import { labelTypeMap } from "./utils/constants"
|
||
|
||
// Splitter component - will need custom implementation or alternative
|
||
|
||
// 定义状态的类型
|
||
export type LabelState = Map<string, string[]>
|
||
|
||
// 定义操作的类型
|
||
type Action =
|
||
| {
|
||
type: "INIT_IMAGES"
|
||
imageIds: string[]
|
||
objectOperations: Project.LabelSchemaList[]
|
||
}
|
||
| { type: "REMOVE_IMAGE"; imageId: string }
|
||
|
||
// 定义初始状态
|
||
const initialState: LabelState = new Map()
|
||
|
||
const operationTypeList = [101, 102, 103, 104]
|
||
|
||
// utils
|
||
const getRectPoints = ([sx, sy, w, h]: number[]) => {
|
||
return [
|
||
[sx, sy + h],
|
||
[sx, sy],
|
||
[sx + w, sy],
|
||
[sx + w, sy + h],
|
||
]
|
||
}
|
||
|
||
const getSegmentPoints = (arr: Array<number[]>) => {
|
||
return arr.map((item) => [item[0], item[1]])
|
||
}
|
||
|
||
const videoExtPattern = /\.(mp4|mov|avi|mkv|webm|h264|264|ts|m4v)$/i
|
||
|
||
const normalizeNamePrefix = (name: string) => {
|
||
const value = name
|
||
.replace(/[^a-zA-Z0-9_-]+/g, "_")
|
||
.replace(/_+/g, "_")
|
||
.replace(/^_|_$/g, "")
|
||
return value || "video"
|
||
}
|
||
|
||
interface ResultImageEntry {
|
||
frameName: string
|
||
imageObjects: ImageObjects
|
||
}
|
||
|
||
const expandLabelResultImages = (item: LabelResult): ResultImageEntry[] => {
|
||
if (item.image_objects) {
|
||
return [
|
||
{
|
||
frameName: item.data_name,
|
||
imageObjects: item.image_objects,
|
||
},
|
||
]
|
||
}
|
||
if (!item.video_objects) return []
|
||
return Object.entries(item.video_objects).map(([frameName, imageObjects]) => {
|
||
return {
|
||
frameName,
|
||
imageObjects,
|
||
}
|
||
})
|
||
}
|
||
|
||
interface LabelProps {
|
||
headerHeight: number
|
||
leftWidth: number
|
||
project_id: number
|
||
task_id: number
|
||
}
|
||
|
||
const LabelPage = ({
|
||
headerHeight = 0,
|
||
leftWidth = 0,
|
||
project_id = -1,
|
||
task_id = -1,
|
||
}: LabelProps) => {
|
||
const projectId = useMemo(() => {
|
||
return project_id
|
||
}, [project_id])
|
||
const taskId = useMemo(() => {
|
||
return task_id
|
||
}, [task_id])
|
||
// const searchParams = useSearchParams()
|
||
// const id1 = searchParams.get("project_id")
|
||
// const projectId =
|
||
// typeof id1 === "string" && !isNaN(Number(id1)) ? Number(id1) : 1
|
||
// const id2 = searchParams.get("task_id")
|
||
// const taskId =
|
||
// typeof id2 === "string" && !isNaN(Number(id2)) ? Number(id2) : 2
|
||
|
||
const user_id = usePermissionStore.getState().user_id
|
||
const [projectDetail, setProjectDetail] = useState<Project.DetailResponse>()
|
||
const [taskDetail, setTaskDetail] = useState<Task.DataProps>()
|
||
const [oldWorkLoad, setOldWorkLoad] = useState<WorkLoad | null>(null)
|
||
// const [objectOperations, setObjectOperations] = useState<
|
||
// Project.LabelSchemaList[]
|
||
// >([]);
|
||
const topRef = useRef<any>(null)
|
||
const paperContainerRef = useRef<any>(null)
|
||
const qaToolContainerRef = useRef<any>(null)
|
||
const [isConfirmSave, setIsConfirmSave] = useState(false)
|
||
const [loading, setLoading] = useState(false)
|
||
const [sizes, setSizes] = useState<number[]>([])
|
||
|
||
const {
|
||
label: labelData,
|
||
setLabel,
|
||
setStateStack,
|
||
setLabelTime,
|
||
} = useLabelStore()
|
||
const { isAutoSave, autoSaveGap } = useIntervalStore()
|
||
|
||
const {
|
||
editMode,
|
||
setEditMode,
|
||
setPressA,
|
||
activeOperation,
|
||
setActiveOperation,
|
||
showObjectList,
|
||
showGroupList,
|
||
showTaskList,
|
||
showDescList,
|
||
showMultiFrame,
|
||
setShowMultiFrame,
|
||
isView,
|
||
setIsView,
|
||
scale,
|
||
setScale,
|
||
objectOperations,
|
||
setObjectOperations,
|
||
} = useTopToolsStore()
|
||
const { activeImage } = useBottomToolsStore()
|
||
const {
|
||
setDescOperations,
|
||
metaOperation,
|
||
setMetaOperation,
|
||
setQaOperations,
|
||
setQaData,
|
||
resetData,
|
||
} = useDescToolsStore()
|
||
|
||
const asyncGetProjectDetail = useCallback(async () => {
|
||
if (projectId) {
|
||
try {
|
||
const res = await getProjectDetail(projectId)
|
||
setProjectDetail(res)
|
||
let operations: Project.LabelSchemaList[] = []
|
||
let textOperations: Project.LabelSchemaList[] = []
|
||
let metaOperation: Project.LabelSchemaList | null = null
|
||
res.label_schema_list?.forEach((schema) => {
|
||
if (operationTypeList.includes(schema.label_type)) {
|
||
operations.push(schema)
|
||
} else if ([201].includes(schema.label_type)) {
|
||
if (schema.label_class === "元操作序列") metaOperation = schema
|
||
else textOperations.push(schema)
|
||
}
|
||
})
|
||
setObjectOperations(operations)
|
||
if (res.label_type === 5) {
|
||
setDescOperations(textOperations)
|
||
if (metaOperation) setMetaOperation(metaOperation)
|
||
} else {
|
||
setDescOperations([])
|
||
setMetaOperation(null)
|
||
}
|
||
if (res.label_type === 6) {
|
||
let obj: any = {}
|
||
textOperations.forEach((item) => {
|
||
let arr = item.sub_attributes_describe.map((sub) => {
|
||
return {
|
||
[sub.chinese_name]: {
|
||
value: "",
|
||
id: sub.round_id,
|
||
is_pre: true,
|
||
tag: 0,
|
||
uid: 0,
|
||
create_timestamp: 0,
|
||
modify_uid: 0,
|
||
modify_timestamp: 0,
|
||
comment: "",
|
||
flag: 0,
|
||
},
|
||
}
|
||
})
|
||
obj[item.label_class] = arr
|
||
})
|
||
let map = new Map()
|
||
map.set("init", obj)
|
||
console.log(map)
|
||
setQaData(map)
|
||
setQaOperations(textOperations)
|
||
} else {
|
||
setQaOperations([])
|
||
}
|
||
resetData()
|
||
} catch {}
|
||
}
|
||
}, [
|
||
projectId,
|
||
resetData,
|
||
setDescOperations,
|
||
setMetaOperation,
|
||
setObjectOperations,
|
||
setQaData,
|
||
setQaOperations,
|
||
])
|
||
|
||
const normalizeVideoTaskData = useCallback(
|
||
async (detail: Task.DataProps) => {
|
||
const sourceNames = detail.data_name
|
||
.map((item) => item?.name?.[0] || "")
|
||
.filter((name) => !!name)
|
||
if (!sourceNames.length || !projectId || !taskId) return detail
|
||
|
||
const taskDataType = Number(
|
||
(detail as { [key: string]: any })?.data_type ??
|
||
(detail as { [key: string]: any })?.task_data_type ??
|
||
-1
|
||
)
|
||
const isVideoTask =
|
||
taskDataType === 2 ||
|
||
taskDataType === 7 ||
|
||
sourceNames.some((name) => videoExtPattern.test(name))
|
||
if (!isVideoTask) return detail
|
||
|
||
const imagesMap = new Map(useImagesStore.getState().images)
|
||
const frameMapUpdates = new Map<string, string[]>()
|
||
let imageMapUpdated = false
|
||
const normalizedDataName: Task.DataProps["data_name"] = []
|
||
|
||
for (const videoName of sourceNames) {
|
||
const cacheKey = `${projectId}:${taskId}:${videoName}`
|
||
let frameNames =
|
||
useVideoFrameStore.getState().videoFrames.get(cacheKey) ?? []
|
||
|
||
const hasAllCachedFrames =
|
||
frameNames.length > 0 &&
|
||
frameNames.every((frameName) => {
|
||
const cache = imagesMap.get(frameName)
|
||
return typeof cache === "string" && cache.length > 0
|
||
})
|
||
|
||
if (!hasAllCachedFrames) {
|
||
let frameData: Awaited<ReturnType<typeof processVideo>> = []
|
||
try {
|
||
const base64Video = await getServerImage({
|
||
data_names: [videoName],
|
||
data_type: 2,
|
||
project_id: projectId,
|
||
})
|
||
frameData = await processVideo({
|
||
base64Data: String(base64Video || ""),
|
||
fileName: videoName,
|
||
namePrefix: normalizeNamePrefix(
|
||
`${projectId}_${taskId}_${videoName.replace(/\.[^.]+$/, "")}`
|
||
),
|
||
})
|
||
} catch (error) {
|
||
const message = error instanceof Error ? error.message : "未知错误"
|
||
throw new Error(`视频帧生成失败:${videoName}(${message})`)
|
||
}
|
||
frameNames = frameData.map((frame) => frame.name)
|
||
if (!frameNames.length) {
|
||
throw new Error(`视频帧生成失败:${videoName}`)
|
||
}
|
||
frameData.forEach((frame) => {
|
||
imagesMap.set(frame.name, frame.dataUrl)
|
||
})
|
||
imageMapUpdated = true
|
||
frameMapUpdates.set(cacheKey, frameNames)
|
||
}
|
||
|
||
if (!frameNames.length) {
|
||
throw new Error(`视频帧映射为空:${videoName}`)
|
||
}
|
||
|
||
frameNames.forEach((frameName) => {
|
||
normalizedDataName.push({
|
||
name: [frameName] as [string],
|
||
related_images: [],
|
||
})
|
||
})
|
||
}
|
||
|
||
if (!normalizedDataName.length) {
|
||
throw new Error("未生成可用视频帧")
|
||
}
|
||
|
||
if (imageMapUpdated) {
|
||
useImagesStore.getState().setImages(imagesMap)
|
||
}
|
||
frameMapUpdates.forEach((frameNames, cacheKey) => {
|
||
useVideoFrameStore.getState().setVideoFrames(cacheKey, frameNames)
|
||
})
|
||
|
||
return {
|
||
...detail,
|
||
data_name: normalizedDataName,
|
||
}
|
||
},
|
||
[projectId, taskId]
|
||
)
|
||
|
||
const asyncGetTaskDetail = useCallback(async () => {
|
||
if (taskId) {
|
||
try {
|
||
usePaperStore.getState().setLoadingData(true)
|
||
// 获取当前任务全量数据
|
||
const res = await getTaskList({
|
||
id: [taskId],
|
||
get_data: true,
|
||
page_number: 1,
|
||
page_size: 1,
|
||
})
|
||
let detail = res.task_list[0]
|
||
detail = await normalizeVideoTaskData(detail)
|
||
setTaskDetail(detail)
|
||
// 帧列表
|
||
useBottomToolsStore
|
||
.getState()
|
||
.setAllItems(detail.data_name.map((item) => item.name[0] || ""))
|
||
// 获取当前任务流转记录
|
||
let flowCommentArr: Comment[] = []
|
||
let turn1time = 1
|
||
let turn2time = 1
|
||
const flowRes = await getTaskWorkFlow(taskId)
|
||
flowRes.forEach((item) => {
|
||
if (item.task_status_src !== 4 && item.task_status_dst === 4) {
|
||
flowCommentArr.push({
|
||
uid: item.new_uid,
|
||
turn: 1,
|
||
times: turn1time,
|
||
check_box_comments: [],
|
||
text_comments: [],
|
||
comment_type: 0,
|
||
date: 0,
|
||
scene_info: {
|
||
type: 0,
|
||
},
|
||
})
|
||
turn1time++
|
||
} else if (item.task_status_src !== 6 && item.task_status_dst === 6) {
|
||
flowCommentArr.push({
|
||
uid: item.new_uid,
|
||
turn: 2,
|
||
times: turn2time,
|
||
check_box_comments: [],
|
||
text_comments: [],
|
||
comment_type: 0,
|
||
date: 0,
|
||
scene_info: {
|
||
type: 0,
|
||
},
|
||
})
|
||
turn2time++
|
||
}
|
||
})
|
||
console.log(flowCommentArr)
|
||
useLabelStore.getState().setLabelDefaultComments(flowCommentArr)
|
||
const { work_time, label_status } = detail
|
||
setLabelTime({
|
||
label: work_time.label_work_time,
|
||
review1: work_time.first_review_work_time,
|
||
review2: work_time.second_review_work_time,
|
||
})
|
||
// 获取并处理当前任务标注结果数据
|
||
const dataRes = await getLabelResult(taskId)
|
||
const { results } = dataRes
|
||
const resultImageEntries = results.flatMap((item) =>
|
||
expandLabelResultImages(item)
|
||
)
|
||
|
||
let currentWorkTime = 0
|
||
if (label_status === 2) {
|
||
currentWorkTime = work_time.label_work_time
|
||
} else if (label_status === 4) {
|
||
currentWorkTime = work_time.first_review_work_time
|
||
} else if (label_status === 6) {
|
||
currentWorkTime = work_time.second_review_work_time
|
||
}
|
||
|
||
// 生成当前workload
|
||
const workLoad: WorkLoad = {
|
||
object_size: {},
|
||
prelabel_modify_size: {},
|
||
review_size: 0,
|
||
comment_size: 0,
|
||
work_time: currentWorkTime,
|
||
dynamic_size: {},
|
||
static_size: {},
|
||
review_dynamic_size: {},
|
||
review_static_size: {},
|
||
review_question_size: {},
|
||
key_frame_size: {},
|
||
review_key_frame_size: {},
|
||
question_size: {},
|
||
}
|
||
resultImageEntries.forEach(({ frameName, imageObjects }) => {
|
||
const { annotations, images, key_frame, desc } = imageObjects
|
||
const rasterName = frameName || images.file_name
|
||
if (rasterName) {
|
||
usePaperStore
|
||
.getState()
|
||
.setRasterSize(rasterName, [images.width, images.height])
|
||
}
|
||
annotations.forEach((annotation) => {
|
||
let reviewed = false
|
||
const { comment, prelabel, uid } = annotation
|
||
if (prelabel)
|
||
workLoad.prelabel_modify_size[uid]
|
||
? workLoad.prelabel_modify_size[uid]++
|
||
: (workLoad.prelabel_modify_size[uid] = 1)
|
||
else
|
||
workLoad.object_size[uid]
|
||
? workLoad.object_size[uid]++
|
||
: (workLoad.object_size[uid] = 1)
|
||
if (comment) {
|
||
comment.forEach((c) => {
|
||
if (c.comment_type > 0 && c.uid === user_id) reviewed = true
|
||
if (c.comment_type === 2 && c.uid === user_id)
|
||
workLoad.comment_size++
|
||
})
|
||
}
|
||
if (reviewed) workLoad.review_size++
|
||
})
|
||
if (key_frame) {
|
||
if (label_status === 2) {
|
||
let id = imageObjects.label1_uid!
|
||
if (id)
|
||
workLoad.key_frame_size[id]
|
||
? workLoad.key_frame_size[id]++
|
||
: (workLoad.key_frame_size[id] = 1)
|
||
} else if (label_status === 4) {
|
||
let id = imageObjects.review1_uid!
|
||
if (id)
|
||
workLoad.review_key_frame_size[id]
|
||
? workLoad.review_key_frame_size[id]++
|
||
: (workLoad.review_key_frame_size[id] = 1)
|
||
} else if (label_status === 6) {
|
||
let id = imageObjects.review2_uid!
|
||
if (id)
|
||
workLoad.review_key_frame_size[id]
|
||
? workLoad.review_key_frame_size[id]++
|
||
: (workLoad.review_key_frame_size[id] = 1)
|
||
}
|
||
}
|
||
if (useDescToolsStore.getState().qaOperations.length) {
|
||
if (desc) {
|
||
const { other_desc } = desc
|
||
const descData = other_desc ? JSON.parse(other_desc) : {}
|
||
Object.values(descData).forEach((questionArr: any) => {
|
||
questionArr.forEach((q: any) => {
|
||
const v: any = Object.values(q)[0]
|
||
if (v.uid) {
|
||
workLoad.question_size[v.uid]
|
||
? workLoad.question_size[v.uid]++
|
||
: (workLoad.question_size[v.uid] = 1)
|
||
}
|
||
if (v.tag) {
|
||
const uid = usePermissionStore.getState().user_id
|
||
workLoad.review_question_size[uid]
|
||
? workLoad.review_question_size[uid]++
|
||
: (workLoad.review_question_size[uid] = 1)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
}
|
||
})
|
||
console.log("获取时计算的工作量统计", workLoad)
|
||
setOldWorkLoad(workLoad)
|
||
const taskLabelData = new Map()
|
||
const pathGroupData = new Map()
|
||
// 重置 PathIds
|
||
useRightToolsStore.getState().setPathIds([])
|
||
// 重置 PathGroupIds
|
||
useRightToolsStore.getState().setPathGroupIds([])
|
||
// 生成标注结果
|
||
resultImageEntries.forEach(({ frameName, imageObjects }) => {
|
||
const key = frameName
|
||
if (!key) return
|
||
const categoryMap = new Map()
|
||
const { annotations, image_groups, desc } = imageObjects
|
||
// 赋值用过的 id
|
||
annotations.map((annotation) => {
|
||
useRightToolsStore.getState().pushPathId(annotation.id)
|
||
})
|
||
// 赋值用过的 groupId
|
||
image_groups &&
|
||
Object.keys(image_groups)?.map((groupId) => {
|
||
useRightToolsStore.getState().pushPathGroupId(+groupId)
|
||
})
|
||
annotations.forEach((annotation) => {
|
||
const { id, category_id, comment } = annotation
|
||
let current_comment: Comment[] = []
|
||
if (comment) {
|
||
current_comment = flowCommentArr.map((c) => {
|
||
const { turn, times } = c
|
||
let modified_comment = null
|
||
comment.forEach((item) => {
|
||
if (item.turn === turn && item.times === times)
|
||
modified_comment = item
|
||
})
|
||
return modified_comment ? modified_comment : c
|
||
})
|
||
} else {
|
||
current_comment = [...flowCommentArr]
|
||
}
|
||
let detail: any = {
|
||
...annotation,
|
||
comment: current_comment,
|
||
}
|
||
// group info
|
||
if (image_groups) {
|
||
const key = findGroupKey(image_groups as any, id)
|
||
if (key !== -1) detail.parentGroupId = key
|
||
}
|
||
const categoryKey = `${category_id}`
|
||
let arr = categoryMap.has(categoryKey)
|
||
? categoryMap.get(categoryKey)
|
||
: []
|
||
if (annotation.rect) {
|
||
// arr.push([id, getRectPoints(annotation.rect), detail, []]);
|
||
arr.push([id, [getRectPoints(annotation.rect)], detail, []])
|
||
}
|
||
|
||
if (annotation.line_segment) {
|
||
arr.push([
|
||
id,
|
||
annotation.line_segment.map((item) => getSegmentPoints(item)),
|
||
detail,
|
||
[],
|
||
])
|
||
}
|
||
|
||
if (annotation.segmentation) {
|
||
arr.push([
|
||
id,
|
||
// getSegmentPoints(annotation.segmentation[0]),
|
||
annotation.segmentation.map((item) => getSegmentPoints(item)),
|
||
detail,
|
||
annotation.hollow_segmentation?.map((item) =>
|
||
getSegmentPoints(item)
|
||
) || [],
|
||
])
|
||
}
|
||
if (annotation.key_points) {
|
||
arr.push([
|
||
id,
|
||
// getSegmentPoints(annotation.segmentation[0]),
|
||
[
|
||
getSegmentPoints(
|
||
annotation.key_points.map((item) => item.point)
|
||
),
|
||
],
|
||
Object.assign(detail, {
|
||
circles: annotation.key_points,
|
||
}),
|
||
[],
|
||
])
|
||
}
|
||
|
||
categoryMap.set(categoryKey, arr)
|
||
})
|
||
taskLabelData.set(key, categoryMap)
|
||
// 关键帧信息
|
||
const keyFrame = {
|
||
key_frame: imageObjects.key_frame || false,
|
||
label1_ts: imageObjects.label1_ts || 0,
|
||
label1_uid: imageObjects.label1_uid || 0,
|
||
review1_ts: imageObjects.review1_ts || 0,
|
||
review1_uid: imageObjects.review1_uid || 0,
|
||
review2_ts: imageObjects.review2_ts || 0,
|
||
review2_uid: imageObjects.review2_uid || 0,
|
||
}
|
||
useBottomToolsStore.getState().setKeyFrameData(key, keyFrame)
|
||
// 大语言模型 两种数据情况
|
||
if (useDescToolsStore.getState().qaOperations.length) {
|
||
if (desc) {
|
||
const { other_desc } = desc
|
||
let current_data: any = {}
|
||
const descData = other_desc ? JSON.parse(other_desc) : {}
|
||
Object.entries(descData).forEach(
|
||
([label_class, questionArr]: any) => {
|
||
let arr = questionArr.map((question: any) => {
|
||
let [name, val]: any = Object.entries(question)[0]
|
||
return {
|
||
[name]: {
|
||
...val,
|
||
value: val.value
|
||
? `${val.value}${splitWord}${val.value}`
|
||
: undefined,
|
||
comment: val.comment
|
||
? `${val.comment}${splitWord}${val.comment}`
|
||
: undefined,
|
||
},
|
||
}
|
||
})
|
||
current_data[label_class] = arr
|
||
}
|
||
)
|
||
const qaData = useDescToolsStore.getState().qaData
|
||
// 无文本描述时 赋予预设值
|
||
if (JSON.stringify(current_data) === "{}") {
|
||
current_data = qaData.get("init")
|
||
}
|
||
const qaMap = structuredClone(qaData)
|
||
qaMap.set(key, current_data)
|
||
useDescToolsStore.getState().setQaData(qaMap)
|
||
}
|
||
} else if (useDescToolsStore.getState().descOperations.length) {
|
||
if (desc) {
|
||
const { meta_operation, other_desc } = desc
|
||
const currentMetaMap = structuredClone(
|
||
useDescToolsStore.getState().metaData
|
||
)
|
||
currentMetaMap.set(key, meta_operation || [])
|
||
useDescToolsStore.getState().setMetaData(currentMetaMap)
|
||
const currentDescMap = structuredClone(
|
||
useDescToolsStore.getState().descData
|
||
)
|
||
let descMap = new Map()
|
||
const descData = other_desc ? JSON.parse(other_desc) : {}
|
||
console.log(descData)
|
||
Object.entries(descData).forEach(
|
||
([label_class, { value, is_correct, comment }]: any) => {
|
||
descMap.set(label_class, {
|
||
value:
|
||
(typeof value === "string"
|
||
? `${value}${splitWord}${value}`
|
||
: value) || undefined,
|
||
is_correct: is_correct || undefined,
|
||
comment: comment || undefined,
|
||
})
|
||
}
|
||
)
|
||
currentDescMap.set(key, descMap)
|
||
useDescToolsStore.getState().setDescData(currentDescMap)
|
||
}
|
||
}
|
||
image_groups &&
|
||
pathGroupData.set(
|
||
key,
|
||
new Map(
|
||
Object.entries(image_groups).map(([key, value]: any) => [
|
||
key * 1,
|
||
value,
|
||
])
|
||
)
|
||
)
|
||
})
|
||
setLabel(taskLabelData)
|
||
// 获取任务详情时设置状态栈初始值
|
||
setStateStack([taskLabelData])
|
||
useDescToolsStore.getState().updateFlag(true)
|
||
useRightToolsStore.getState().setPathGroupMap(pathGroupData)
|
||
} catch (err) {
|
||
console.log(err)
|
||
setOldWorkLoad(null)
|
||
setTaskDetail(undefined)
|
||
useBottomToolsStore.getState().setAllItems([])
|
||
setLabel(new Map())
|
||
setStateStack([])
|
||
useRightToolsStore.getState().setPathGroupMap(new Map())
|
||
if (err instanceof Error && err.message.includes("视频")) {
|
||
showNotification({
|
||
color: "red",
|
||
title: "视频解码失败",
|
||
message: err.message,
|
||
})
|
||
}
|
||
} finally {
|
||
usePaperStore.getState().setLoadingData(false)
|
||
}
|
||
}
|
||
}, [
|
||
normalizeVideoTaskData,
|
||
setLabel,
|
||
setLabelTime,
|
||
setStateStack,
|
||
taskId,
|
||
user_id,
|
||
])
|
||
|
||
// 保存时更新当前workload
|
||
const updateOldWorkLoad = (data: WorkLoad) => {
|
||
setOldWorkLoad(data)
|
||
}
|
||
|
||
useEffect(() => {
|
||
asyncGetProjectDetail()
|
||
asyncGetTaskDetail()
|
||
}, [asyncGetProjectDetail, asyncGetTaskDetail])
|
||
|
||
// 定义 reducer 函数
|
||
const reducer = useCallback(
|
||
(state: LabelState, action: Action): LabelState => {
|
||
switch (action.type) {
|
||
case "INIT_IMAGES":
|
||
const newState = new Map() // 创建状态的副本
|
||
action.imageIds.forEach((imageId) => {
|
||
newState.set(
|
||
imageId,
|
||
action.objectOperations.map((item) => item.category_id.toString())
|
||
) // 将操作列表添加到新状态中
|
||
})
|
||
return newState
|
||
|
||
case "REMOVE_IMAGE":
|
||
const stateAfterRemoval = new Map(state)
|
||
if (stateAfterRemoval.has(action.imageId)) {
|
||
stateAfterRemoval.delete(action.imageId) // 删除该图片的条目
|
||
}
|
||
return stateAfterRemoval
|
||
|
||
default:
|
||
return state // 返回当前状态
|
||
}
|
||
},
|
||
[]
|
||
)
|
||
|
||
const [labelState, dispatch] = useReducer(reducer, initialState)
|
||
|
||
const initImages = (
|
||
imageIds: string[],
|
||
objectOperations: Project.LabelSchemaList[]
|
||
) => {
|
||
dispatch({
|
||
type: "INIT_IMAGES",
|
||
imageIds,
|
||
objectOperations,
|
||
})
|
||
}
|
||
|
||
const [isLabelTask, setIsLabelTask] = useState<boolean>(false)
|
||
|
||
useEffect(() => {
|
||
if (!taskDetail || !projectDetail) {
|
||
setIsLabelTask(false)
|
||
setIsView(true)
|
||
setEditMode(false)
|
||
return
|
||
}
|
||
if (taskDetail) {
|
||
if (
|
||
![201, 202, 203, 500].includes(projectDetail!.status) ||
|
||
![2, 4, 6, 7].includes(taskDetail.label_status) ||
|
||
taskDetail.current_uid !== user_id
|
||
) {
|
||
// 如果不是能标注的任务
|
||
setIsLabelTask(false)
|
||
setIsView(true)
|
||
setEditMode(false)
|
||
} else {
|
||
setIsLabelTask(true)
|
||
setIsView(false)
|
||
}
|
||
|
||
initImages(
|
||
taskDetail.data_name.map((item) => item.name?.[0]),
|
||
objectOperations
|
||
)
|
||
|
||
if (taskDetail.data_name?.length) setShowMultiFrame(true)
|
||
}
|
||
}, [
|
||
objectOperations,
|
||
setIsView,
|
||
setShowMultiFrame,
|
||
user_id,
|
||
taskDetail,
|
||
projectDetail,
|
||
setEditMode,
|
||
])
|
||
|
||
const { setPaperMode, setToolOption, setGroupScale } = usePaperStore()
|
||
|
||
// @ts-expect-error to_do
|
||
const forceUpdate = useReducer((bool) => !bool)[1]
|
||
|
||
const getOperationSchema = useCallback(
|
||
(operationId: string) => {
|
||
return (
|
||
projectDetail?.label_schema_list?.find(
|
||
(item) => item.category_id === +operationId && item.label_type !== 201
|
||
) || null
|
||
)
|
||
},
|
||
[projectDetail?.label_schema_list]
|
||
)
|
||
|
||
const setDrawType = useCallback(() => {
|
||
if (isView) {
|
||
setPaperMode("pan")
|
||
} else {
|
||
let operationSchema = getOperationSchema(activeOperation)
|
||
if (operationSchema) {
|
||
let strokeColor = `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})`
|
||
let fillColor = `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)`
|
||
let blankColor = `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)`
|
||
|
||
if (editMode) {
|
||
switch (
|
||
operationSchema &&
|
||
labelTypeMap.get(operationSchema.label_type)
|
||
) {
|
||
case "多边形":
|
||
setToolOption({
|
||
strokeColor: strokeColor,
|
||
strokeWidth: 2,
|
||
fillColor: fillColor,
|
||
blankColor: blankColor,
|
||
})
|
||
setPaperMode("polygon")
|
||
return
|
||
case "关键点":
|
||
setToolOption({
|
||
strokeColor: strokeColor,
|
||
strokeWidth: 2,
|
||
fillColor: fillColor,
|
||
blankColor: blankColor,
|
||
})
|
||
setPaperMode("point")
|
||
return
|
||
case "多线段":
|
||
setToolOption({
|
||
strokeColor: strokeColor,
|
||
strokeWidth: 2,
|
||
fillColor: fillColor,
|
||
blankColor: blankColor,
|
||
})
|
||
setPaperMode("brush")
|
||
return
|
||
case "2D框":
|
||
setToolOption({
|
||
strokeColor: strokeColor,
|
||
strokeWidth: 2,
|
||
fillColor: fillColor,
|
||
blankColor: blankColor,
|
||
})
|
||
setPaperMode("rectangle")
|
||
return
|
||
default:
|
||
setPaperMode("polygon")
|
||
return
|
||
}
|
||
} else {
|
||
setPaperMode("pan")
|
||
}
|
||
}
|
||
}
|
||
}, [
|
||
activeOperation,
|
||
editMode,
|
||
getOperationSchema,
|
||
isView,
|
||
setPaperMode,
|
||
setToolOption,
|
||
])
|
||
|
||
useEffect(() => {
|
||
setDrawType()
|
||
}, [editMode, setDrawType])
|
||
|
||
const { setShift, focusInput } = useKeyEventStore()
|
||
|
||
const handleShortcut = useCallback(
|
||
(key: string, ctrl: boolean) => {
|
||
let checkOperation = getOperationSchema(key)
|
||
const mode = usePaperStore.getState().mode
|
||
// 标注方案里面存在的非文本类型标注才能切换
|
||
if (checkOperation) {
|
||
// 辅助标注模式下非多边形类型不能切换
|
||
if (mode === "support" && checkOperation.label_type !== 102) return
|
||
if (ctrl) {
|
||
setActiveOperation((+key + 10).toString())
|
||
} else {
|
||
setActiveOperation(key)
|
||
}
|
||
}
|
||
},
|
||
[getOperationSchema, setActiveOperation]
|
||
)
|
||
|
||
useEffect(() => {
|
||
const down = (e: KeyboardEvent) => {
|
||
// console.log(e, e.key);
|
||
if (focusInput || isView) return
|
||
|
||
console.log(e.key)
|
||
const mode = usePaperStore.getState().mode
|
||
|
||
if (e.key === "Alt" && mode !== "support") {
|
||
e.preventDefault()
|
||
// console.log(e.key);
|
||
setEditMode(!editMode)
|
||
if (!editMode) setPressA(false)
|
||
}
|
||
|
||
if (e.key === "Shift") {
|
||
e.preventDefault()
|
||
setShift(true)
|
||
useKeyboardStore.getState().setShift(true)
|
||
}
|
||
|
||
if (mode !== "support")
|
||
if (Number(e.key)) {
|
||
handleShortcut(e.key, e.ctrlKey)
|
||
} else if (Number(e.key) === 0) {
|
||
handleShortcut("10", e.ctrlKey)
|
||
}
|
||
|
||
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
||
e.preventDefault()
|
||
setIsConfirmSave(true)
|
||
}
|
||
|
||
if (e.key === "g" && (e.metaKey || e.ctrlKey)) {
|
||
e.preventDefault()
|
||
paperContainerRef.current.handleCreatePathGroup()
|
||
}
|
||
|
||
if (e.key === "a") {
|
||
e.preventDefault()
|
||
setPressA(true)
|
||
}
|
||
|
||
if (e.key === "Control") {
|
||
e.preventDefault()
|
||
useKeyboardStore.getState().setCtrl(true)
|
||
}
|
||
// 辅助标注 分割
|
||
if (e.key === "F1") {
|
||
// 判断类型
|
||
e.preventDefault()
|
||
const schema = getOperationSchema(
|
||
useTopToolsStore.getState().activeOperation
|
||
)
|
||
console.log(schema)
|
||
if (schema?.label_type !== 102) {
|
||
showNotification({
|
||
title: "错误",
|
||
message: "当前类型不支持辅助标注!",
|
||
color: "red",
|
||
})
|
||
} else {
|
||
if (mode === "support") {
|
||
// 辅助标注生成对应标注结果
|
||
paperContainerRef.current.saveSupportAnnotationData()
|
||
} else {
|
||
usePaperStore.getState().setPaperMode("support")
|
||
}
|
||
}
|
||
}
|
||
|
||
// 追踪
|
||
if (e.key === "i") {
|
||
e.preventDefault()
|
||
paperContainerRef.current.renderTrackingAnnotation()
|
||
}
|
||
|
||
// 复制
|
||
if (e.key === "c" && e.ctrlKey) {
|
||
e.preventDefault()
|
||
useKeyboardStore
|
||
.getState()
|
||
.setCopyDataIds(
|
||
useObjectStore.getState().selectedPath[
|
||
useBottomToolsStore.getState().activeImage
|
||
]
|
||
)
|
||
}
|
||
|
||
// 粘贴
|
||
if (e.key === "v" && e.ctrlKey) {
|
||
e.preventDefault()
|
||
paperContainerRef.current.copyAnnotationDataToMultiFrame()
|
||
}
|
||
|
||
// 撤回
|
||
if (e.key === "z" && e.ctrlKey) {
|
||
e.preventDefault()
|
||
// 清除当前页数据
|
||
const clear = () => {
|
||
usePaperStore
|
||
.getState()
|
||
.group!.getItems({})
|
||
.filter((item) => item.data.id)
|
||
.forEach((item) => {
|
||
item.remove()
|
||
})
|
||
}
|
||
const data = useLabelStore.getState().popStateStack()
|
||
console.log("弹出栈的数据", data)
|
||
if (data) {
|
||
clear()
|
||
const imageId = useBottomToolsStore.getState().activeImage
|
||
const pathGroupMap = new Map()
|
||
for (const [imageKey, categories] of data) {
|
||
const groupMap = new Map()
|
||
for (const [key, value] of categories) {
|
||
value.forEach((item) => {
|
||
const vId = item[2].parentGroupId
|
||
if (vId)
|
||
if (groupMap.has(vId)) groupMap.get(vId).push(item[0])
|
||
else groupMap.set(vId, [item[0]])
|
||
})
|
||
if (imageId === imageKey)
|
||
paperContainerRef.current.renderPolygons(key, categories)
|
||
}
|
||
pathGroupMap.set(imageKey, groupMap)
|
||
}
|
||
useRightToolsStore.getState().setPathGroupMap(pathGroupMap)
|
||
|
||
console.log(pathGroupMap)
|
||
// if (data.size) {
|
||
// if (data.has(imageId)) {
|
||
// const map = data.get(imageId);
|
||
// if (map) {
|
||
// for (const [key, value] of map) {
|
||
// console.log(key, value);
|
||
// paperContainerRef.current.renderPolygons(key, map);
|
||
// }
|
||
// }
|
||
// }
|
||
// }
|
||
// 更新当前数据
|
||
useLabelStore.getState().setLabel(structuredClone(data))
|
||
}
|
||
}
|
||
|
||
if (e.key === "p") {
|
||
e.preventDefault()
|
||
if (projectDetail?.label_type === 6 && qaToolContainerRef.current)
|
||
qaToolContainerRef.current.checkWrongWords()
|
||
}
|
||
|
||
// if (e.key === "=" && (e.metaKey || e.ctrlKey)) {
|
||
// e.preventDefault();
|
||
// setScale((num) => {
|
||
// let newScale = num + 0.1;
|
||
// return newScale;
|
||
// });
|
||
// } else if (e.key === "-" && (e.metaKey || e.ctrlKey)) {
|
||
// e.preventDefault();
|
||
// setScale((num) => {
|
||
// if (num <= 1) {
|
||
// let newScale = num * 0.9;
|
||
// return newScale;
|
||
// } else {
|
||
// let newScale = num - 0.1;
|
||
// return newScale;
|
||
// }
|
||
// });
|
||
// } else if (e.key === "3") {
|
||
// e.preventDefault();
|
||
// } else if (e.key === "4") {
|
||
// e.preventDefault();
|
||
// } else if (e.key === "5") {
|
||
// e.preventDefault();
|
||
// } else if (e.key === "a" && (e.metaKey || e.ctrlKey)) {
|
||
// e.preventDefault();
|
||
// }
|
||
}
|
||
const up = (e: KeyboardEvent) => {
|
||
// console.log("键盘抬起", e);
|
||
if (e.key === "Shift") {
|
||
setShift(false)
|
||
useKeyboardStore.getState().setShift(false)
|
||
}
|
||
if (e.key === "Control") {
|
||
useKeyboardStore.getState().setCtrl(false)
|
||
}
|
||
}
|
||
const wheel = (e: WheelEvent) => {
|
||
e.preventDefault()
|
||
const currentScale = useTopToolsStore.getState().scale
|
||
let newScale = e.deltaY < 0 ? currentScale * 1.1 : currentScale * 0.9
|
||
if (newScale >= 100) return
|
||
setScale(newScale)
|
||
setGroupScale(newScale)
|
||
}
|
||
|
||
const mouseclick = (_e: MouseEvent) => {
|
||
useTopToolsStore.getState().setSubAttrPresetShow(false)
|
||
}
|
||
|
||
const mousemove = (event: { clientX: number; clientY: number }) => {
|
||
paperContainerRef.current.handleCrosshairMove(event)
|
||
}
|
||
|
||
let element = document.getElementById("resize-container")
|
||
document.addEventListener("keydown", down)
|
||
document.addEventListener("keyup", up)
|
||
element?.addEventListener("wheel", wheel)
|
||
element?.addEventListener("mousedown", mouseclick)
|
||
element?.addEventListener("mousemove", mousemove)
|
||
|
||
return () => {
|
||
document.removeEventListener("keydown", down)
|
||
document.removeEventListener("keyup", up)
|
||
element?.removeEventListener("wheel", wheel)
|
||
element?.removeEventListener("mousedown", mouseclick)
|
||
element?.removeEventListener("mousemove", mousemove)
|
||
}
|
||
}, [
|
||
editMode,
|
||
focusInput,
|
||
getOperationSchema,
|
||
handleShortcut,
|
||
isView,
|
||
projectDetail?.label_type,
|
||
setEditMode,
|
||
setGroupScale,
|
||
setPressA,
|
||
setScale,
|
||
setShift,
|
||
])
|
||
|
||
const setGlobalLoading = useCallback((flag: boolean) => {
|
||
setLoading(flag)
|
||
}, [])
|
||
|
||
// 切换图片时清空复制保存的数组
|
||
useEffect(() => {
|
||
useKeyboardStore.getState().setCopyDataIds([])
|
||
}, [activeImage])
|
||
|
||
const handleBeforeUnload = (e: any) => {
|
||
e.preventDefault()
|
||
// 备份
|
||
topRef.current.handleBackup("auto_backup")
|
||
// 清空操作栏状态
|
||
useObjectStore.getState().resetPathAndOperationStatus()
|
||
}
|
||
|
||
// 更新Splitter状态
|
||
const updateSplitterSizes = useCallback(() => {
|
||
console.log(
|
||
"document.documentElement.clientWidth",
|
||
document.documentElement.clientWidth
|
||
)
|
||
|
||
const len = document.documentElement.clientWidth - 4
|
||
let arr = [len, 0, 0, 0, 0, 0]
|
||
const flags = [
|
||
showTaskList,
|
||
showGroupList,
|
||
showDescList && projectDetail && projectDetail.label_type === 5,
|
||
showDescList && projectDetail && projectDetail.label_type === 6,
|
||
showDescList && projectDetail && projectDetail.label_type === 7,
|
||
showObjectList,
|
||
]
|
||
let count = 0
|
||
flags.forEach((flag) => {
|
||
if (flag) count++
|
||
})
|
||
if (count === 1 || count === 2) {
|
||
arr[0] = count === 1 ? len * 0.8 : len * 0.6
|
||
flags.forEach((flag, index) => {
|
||
if (flag) {
|
||
arr[index + 1] = len * 0.2
|
||
}
|
||
})
|
||
} else if (count === 3) {
|
||
arr[0] = len * 0.55
|
||
flags.forEach((flag, index) => {
|
||
if (flag) {
|
||
arr[index + 1] = len * 0.15
|
||
}
|
||
})
|
||
} else if (count === 4) {
|
||
arr[0] = len * 0.6
|
||
flags.forEach((flag, index) => {
|
||
if (flag) {
|
||
arr[index + 1] = len * 0.1
|
||
}
|
||
})
|
||
}
|
||
setSizes(arr)
|
||
}, [projectDetail, showDescList, showGroupList, showObjectList, showTaskList])
|
||
|
||
useEffect(() => {
|
||
updateSplitterSizes()
|
||
}, [updateSplitterSizes])
|
||
|
||
useEffect(() => {
|
||
if (!isView) {
|
||
const delay = 10 * 60000
|
||
// 编辑模式下,修改labelData时启动或更新定时器
|
||
useTimerStore.getState().startTimer(() => {
|
||
useTopToolsStore.getState().setIsView(true)
|
||
}, delay)
|
||
} else {
|
||
useTimerStore.getState().clearTimer()
|
||
}
|
||
|
||
// 组件卸载时清理定时器
|
||
return () => {
|
||
useTimerStore.getState().clearTimer()
|
||
}
|
||
}, [isView, labelData])
|
||
|
||
// auto save
|
||
useEffect(() => {
|
||
if (isAutoSave && !isView) {
|
||
const minute = 60 * 1000
|
||
useIntervalStore.getState().startTimer(() => {
|
||
topRef.current.handleSave()
|
||
}, autoSaveGap * minute)
|
||
} else {
|
||
useIntervalStore.getState().clearTimer()
|
||
}
|
||
|
||
return () => {
|
||
useIntervalStore.getState().clearTimer()
|
||
}
|
||
}, [autoSaveGap, isAutoSave, isView])
|
||
|
||
useEffect(() => {
|
||
window.addEventListener("beforeunload", handleBeforeUnload)
|
||
return () => {
|
||
window.removeEventListener("beforeunload", handleBeforeUnload)
|
||
}
|
||
}, [])
|
||
|
||
const mainBoxHeight = useMemo(() => {
|
||
if (projectDetail?.label_type === 5 && metaOperation) {
|
||
let h = `calc(100% - ${headerHeight}px - 198px)`
|
||
return h
|
||
} else if (showMultiFrame) {
|
||
let h = `calc(100% - ${headerHeight}px - 150px)`
|
||
return h
|
||
}
|
||
let h = `calc(100% - ${headerHeight}px - 70px)`
|
||
return h
|
||
}, [projectDetail?.label_type, metaOperation, showMultiFrame, headerHeight])
|
||
|
||
const fullscreenRef = useRef<HTMLDivElement>(null)
|
||
|
||
return (
|
||
<Stack w="100%" h="100vh" pos="relative" ref={fullscreenRef} gap={0}>
|
||
<LoadingOverlay
|
||
visible={loading}
|
||
zIndex={2000}
|
||
overlayProps={{ radius: "sm", blur: 2 }}
|
||
/>
|
||
<Box w="100%" h={80}>
|
||
<TopTools
|
||
ref={topRef}
|
||
taskDetail={taskDetail}
|
||
projectDetail={projectDetail}
|
||
oldWorkLoad={oldWorkLoad}
|
||
updateOldWorkLoad={updateOldWorkLoad}
|
||
isLabelTask={isLabelTask}
|
||
renderPolygons={
|
||
paperContainerRef.current
|
||
? paperContainerRef.current.renderPolygons
|
||
: null
|
||
}
|
||
/>
|
||
</Box>
|
||
<Flex w="calc(100% - 4px)" h={mainBoxHeight}>
|
||
{/* <Flex h="100%" w={"100%"}> */}
|
||
<Box w={sizes[0] - leftWidth} style={{ flexShrink: 0 }}>
|
||
<Box h="100%" id="resize-container">
|
||
<Flex h="100%" w="100%">
|
||
<Flex
|
||
direction="column"
|
||
h="100%"
|
||
w={`calc(100% - 64px)`}
|
||
pos="relative"
|
||
style={{ overflow: "hidden" }}>
|
||
<ScaleComponent
|
||
options={{
|
||
offsetX: 0,
|
||
offsetY: 0,
|
||
scale: scale,
|
||
}}
|
||
mode="horizontal"
|
||
/>
|
||
<Flex h="calc(100% - 32px)">
|
||
<ScaleComponent
|
||
options={{
|
||
offsetX: 0,
|
||
offsetY: 0,
|
||
scale: scale,
|
||
}}
|
||
mode="vertical"
|
||
/>
|
||
<PaperContainer
|
||
imgSrc={"/test.jpeg"}
|
||
ref={paperContainerRef}
|
||
forceUpdate={forceUpdate}
|
||
labelState={labelState}
|
||
projectDetail={projectDetail}
|
||
updateLoadingFlag={setGlobalLoading}
|
||
/>
|
||
</Flex>
|
||
</Flex>
|
||
<ScaleToolContainer />
|
||
</Flex>
|
||
</Box>
|
||
</Box>
|
||
{showTaskList && (
|
||
<Box w={sizes[1]} miw={300} style={{ flexShrink: 0 }}>
|
||
<RightTaskTools
|
||
projectDetail={projectDetail}
|
||
project_id={projectId}
|
||
task_id={taskId}
|
||
/>
|
||
</Box>
|
||
)}
|
||
{showGroupList && (
|
||
<Box w={sizes[2]} miw={200} style={{ flexShrink: 0 }}>
|
||
<RightGroupTools
|
||
labelState={labelState}
|
||
projectDetail={projectDetail}
|
||
/>
|
||
</Box>
|
||
)}
|
||
{showDescList && projectDetail && projectDetail.label_type === 5 && (
|
||
<Box w={sizes[3]} miw={350} style={{ flexShrink: 0 }}>
|
||
<RightDescTools taskDetail={taskDetail} />
|
||
</Box>
|
||
)}
|
||
{showDescList && projectDetail && projectDetail.label_type === 6 && (
|
||
<Box w={sizes[4]} miw={350} style={{ flexShrink: 0 }}>
|
||
<RightQATools ref={qaToolContainerRef} taskDetail={taskDetail} />
|
||
</Box>
|
||
)}
|
||
{showObjectList && (
|
||
<Box w={sizes[5]} miw={350} style={{ flexShrink: 0 }}>
|
||
<RightObjectTools
|
||
taskDetail={taskDetail}
|
||
labelState={labelState}
|
||
projectDetail={projectDetail}
|
||
/>
|
||
</Box>
|
||
)}
|
||
{/* </Flex> */}
|
||
</Flex>
|
||
<Box
|
||
w="100%"
|
||
h={projectDetail?.label_type === 5 && metaOperation ? 128 : 80}
|
||
display={showMultiFrame ? "block" : "none"}>
|
||
<BottomTools
|
||
labelState={labelState}
|
||
projectDetail={projectDetail}
|
||
taskDetail={taskDetail}
|
||
renderPolygons={
|
||
paperContainerRef.current
|
||
? paperContainerRef.current.renderPolygons
|
||
: null
|
||
}
|
||
/>
|
||
</Box>
|
||
{isConfirmSave && (
|
||
<ConfirmModal
|
||
open={isConfirmSave}
|
||
title="保存标注结果"
|
||
content="是否确定保存当前标注结果?"
|
||
onOk={async () => {
|
||
try {
|
||
await topRef.current?.handleSave()
|
||
setIsConfirmSave(false)
|
||
} catch {}
|
||
}}
|
||
onCancel={() => {
|
||
setIsConfirmSave(false)
|
||
}}
|
||
/>
|
||
)}
|
||
</Stack>
|
||
)
|
||
}
|
||
|
||
export default LabelPage
|