Merge branch 'zh' into 'main'
Zh See merge request prism/lable/labelimage!2
This commit is contained in:
@@ -5,10 +5,10 @@ NEXT_PUBLIC_ENV=development
|
|||||||
|
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
# NEXT_PUBLIC_REDIS_URL="172.16.112.128"
|
# NEXT_PUBLIC_REDIS_URL="172.16.115.128"
|
||||||
NEXT_PUBLIC_REDIS_URL="localhost"
|
NEXT_PUBLIC_REDIS_URL="172.30.21.213"
|
||||||
NEXT_PUBLIC_REDIS_PORT=9765
|
NEXT_PUBLIC_REDIS_PORT=9765
|
||||||
NEXT_PUBLIC_REDIS_PASSWORD=password
|
NEXT_PUBLIC_REDIS_PASSWORD=password
|
||||||
NEXT_PUBLIC_REDIS_USERNAME=default
|
NEXT_PUBLIC_REDIS_USERNAME=default
|
||||||
NEXT_PUBLIC_REDIS_DB=0
|
NEXT_PUBLIC_REDIS_DB=1
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ NEXT_PUBLIC_BASE_PATH="/image"
|
|||||||
NEXT_PUBLIC_ENV=production
|
NEXT_PUBLIC_ENV=production
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
NEXT_PUBLIC_REDIS_URL="172.16.105.154"
|
NEXT_PUBLIC_REDIS_URL="172.16.104.13"
|
||||||
NEXT_PUBLIC_REDIS_PORT=8015
|
NEXT_PUBLIC_REDIS_PORT=6379
|
||||||
NEXT_PUBLIC_REDIS_PASSWORD=aAXJM2uMzPu76L93
|
NEXT_PUBLIC_REDIS_PASSWORD=123456
|
||||||
NEXT_PUBLIC_REDIS_USERNAME=default
|
NEXT_PUBLIC_REDIS_USERNAME=default
|
||||||
NEXT_PUBLIC_REDIS_DB=10
|
NEXT_PUBLIC_REDIS_DB=10
|
||||||
@@ -3,7 +3,7 @@ NEXT_PUBLIC_BASE_PATH="/image"
|
|||||||
NEXT_PUBLIC_ENV=staging
|
NEXT_PUBLIC_ENV=staging
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
NEXT_PUBLIC_REDIS_URL="172.30.21.213"
|
NEXT_PUBLIC_REDIS_URL="172.16.115.128"
|
||||||
NEXT_PUBLIC_REDIS_PORT=9765
|
NEXT_PUBLIC_REDIS_PORT=9765
|
||||||
NEXT_PUBLIC_REDIS_PASSWORD=password
|
NEXT_PUBLIC_REDIS_PASSWORD=password
|
||||||
NEXT_PUBLIC_REDIS_USERNAME=default
|
NEXT_PUBLIC_REDIS_USERNAME=default
|
||||||
|
|||||||
28
.gitlab-ci.yml
Normal file
28
.gitlab-ci.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
stages:
|
||||||
|
- sync_github
|
||||||
|
|
||||||
|
sync_github:
|
||||||
|
stage: sync_github
|
||||||
|
tags:
|
||||||
|
- webbuild
|
||||||
|
image: harbor.cowarobot.cn/softrepo/rust_build:1.94
|
||||||
|
script:
|
||||||
|
- echo "代码已合并到 main, 开始执行发布逻辑..."
|
||||||
|
- |
|
||||||
|
for i in {1..10}; do
|
||||||
|
if command -v docker >/dev/null 2>&1 && [ -S /var/run/docker.sock ]; then
|
||||||
|
echo "Docker is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for Docker..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
- docker info
|
||||||
|
- echo ${CI_PROJECT_NAME} ${CI_PROJECT_PATH} ${CI_PROJECT_DIR} ${CI_PROJECT_URL} ${CI_COMMIT_SHA}
|
||||||
|
- echo ${HARBORKEY} | docker login harbor.cowarobot.cn -u 'robot$softpro+cibuild' --password-stdin
|
||||||
|
- rm -rf ${CI_PROJECT_NAME}
|
||||||
|
- git clone https://oauth2:${TOKEN}@git.cowarobot.com/${CI_PROJECT_PATH}
|
||||||
|
- cd ${CI_PROJECT_NAME}
|
||||||
|
- bash build.sh prod
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == "main"
|
||||||
@@ -25,3 +25,42 @@ body {
|
|||||||
:-webkit-full-screen::backdrop {
|
:-webkit-full-screen::backdrop {
|
||||||
background: var(--mantine-color-body);
|
background: var(--mantine-color-body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qa-preview-content {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--mantine-color-text);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qa-preview-content p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qa-preview-content h1,
|
||||||
|
.qa-preview-content h2,
|
||||||
|
.qa-preview-content h3,
|
||||||
|
.qa-preview-content h4,
|
||||||
|
.qa-preview-content h5,
|
||||||
|
.qa-preview-content h6 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qa-preview-content ul,
|
||||||
|
.qa-preview-content ol {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qa-preview-content pre,
|
||||||
|
.qa-preview-content code {
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qa-preview-empty {
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export default function OwnTaskTableContainer(props: {
|
|||||||
const backUrl = type
|
const backUrl = type
|
||||||
? `/project/detail?id=${projectId}&type=${type}`
|
? `/project/detail?id=${projectId}&type=${type}`
|
||||||
: `/project/detail?id=${projectId}`
|
: `/project/detail?id=${projectId}`
|
||||||
setBackProps(backUrl, "own")
|
setBackProps(backUrl, "own", "/image")
|
||||||
|
|
||||||
if (info && [4, 5, 6, 7].includes(info.label_type)) {
|
if (info && [4, 5, 6, 7].includes(info.label_type)) {
|
||||||
router.push(
|
router.push(
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ export default function TaskTableContainer(props: {
|
|||||||
const backUrl = type
|
const backUrl = type
|
||||||
? `/project/detail?id=${projectId}&type=${type}`
|
? `/project/detail?id=${projectId}&type=${type}`
|
||||||
: `/project/detail?id=${projectId}`
|
: `/project/detail?id=${projectId}`
|
||||||
setBackProps(backUrl, "all")
|
setBackProps(backUrl, "all", "/image")
|
||||||
|
|
||||||
if (info && [4, 5, 6, 7].includes(info.label_type)) {
|
if (info && [4, 5, 6, 7].includes(info.label_type)) {
|
||||||
router.push(
|
router.push(
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export default function CollectionDetailPage() {
|
|||||||
const backUrl = type
|
const backUrl = type
|
||||||
? `/project/detail?id=${projectId}&type=${type}`
|
? `/project/detail?id=${projectId}&type=${type}`
|
||||||
: `/project/detail?id=${projectId}`
|
: `/project/detail?id=${projectId}`
|
||||||
setBackProps(backUrl, next)
|
setBackProps(backUrl, next, "/image")
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: 14,
|
paddingLeft: 14,
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { usePermissionStore } from "@/components/label/store/auth"
|
||||||
import AppLayout from "@/components/layout/AppLayout"
|
import AppLayout from "@/components/layout/AppLayout"
|
||||||
import { PathnameRecorder } from "@/components/layout/PathnameRecorder"
|
import { PathnameRecorder } from "@/components/layout/PathnameRecorder"
|
||||||
import { componentList, showList } from "@/components/layout/common"
|
import { componentList, showList } from "@/components/layout/common"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
|
||||||
async function getMenu() {
|
// async function getMenu() {
|
||||||
return [...componentList, ...showList]
|
// return [...componentList, ...showList]
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
const detailInfo = usePermissionStore((s) => s.detailInfo)
|
||||||
|
|
||||||
|
const menu = useMemo(() => {
|
||||||
|
if (detailInfo?.roles?.length) {
|
||||||
|
return [...componentList, ...showList]
|
||||||
|
} else {
|
||||||
|
return [...componentList, ...showList].filter(
|
||||||
|
(item) => item.title !== "管理中心"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [detailInfo?.roles?.length])
|
||||||
|
|
||||||
export default async function Layout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const menu = await getMenu()
|
|
||||||
return (
|
return (
|
||||||
<AppLayout menu={menu}>
|
<AppLayout menu={menu}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
26
build.sh
26
build.sh
@@ -51,11 +51,11 @@ FLAG_PROD="${ENV}_latest"
|
|||||||
|
|
||||||
cd $SHELL_FOLDER
|
cd $SHELL_FOLDER
|
||||||
|
|
||||||
# 检查git状态
|
# # 检查git状态
|
||||||
echo -e "${GREEN}Checking git status...${NC}"
|
# echo -e "${GREEN}Checking git status...${NC}"
|
||||||
git fetch
|
# git fetch
|
||||||
git merge
|
# git merge
|
||||||
if [[ $? != 0 ]]; then echo -e "${RED}git pull error${NC}"; exit 100; fi
|
# if [[ $? != 0 ]]; then echo -e "${RED}git pull error${NC}"; exit 100; fi
|
||||||
|
|
||||||
BRANCH_NAME=`git branch | grep "*"`
|
BRANCH_NAME=`git branch | grep "*"`
|
||||||
echo -e "${GREEN}Current branch: ${BRANCH_NAME/* /}${NC}"
|
echo -e "${GREEN}Current branch: ${BRANCH_NAME/* /}${NC}"
|
||||||
@@ -71,23 +71,27 @@ pnpm run $BUILD_FLAG
|
|||||||
if [[ $? != 0 ]]; then echo -e "${RED}build error${NC}"; exit 100; fi
|
if [[ $? != 0 ]]; then echo -e "${RED}build error${NC}"; exit 100; fi
|
||||||
|
|
||||||
# Docker构建和推送
|
# Docker构建和推送
|
||||||
MAIN_NAME=digitalspace_labelimage
|
MAIN_NAME=web_digitalspace_labelimage
|
||||||
DOCKER_FILE=$SHELL_FOLDER/Dockerfile
|
DOCKER_FILE=$SHELL_FOLDER/Dockerfile
|
||||||
|
HARBOR_REPO=softrepo
|
||||||
|
if [[ $ENV == prod ]]; then
|
||||||
|
HARBOR_REPO=softpro
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $OUTPUT_TYPE == export ]]; then
|
if [[ $OUTPUT_TYPE == export ]]; then
|
||||||
DOCKER_FILE=$SHELL_FOLDER/docker/nginx.dockerfile
|
DOCKER_FILE=$SHELL_FOLDER/docker/nginx.dockerfile
|
||||||
MAIN_NAME=digitalspace_labelimage_static
|
MAIN_NAME=web_digitalspace_labelimage_static
|
||||||
elif [[ $OUTPUT_TYPE == standalone ]]; then
|
elif [[ $OUTPUT_TYPE == standalone ]]; then
|
||||||
DOCKER_FILE=$SHELL_FOLDER/docker/node.dockerfile
|
DOCKER_FILE=$SHELL_FOLDER/docker/node.dockerfile
|
||||||
MAIN_NAME=digitalspace_labelimage_ssr
|
MAIN_NAME=web_digitalspace_labelimage_ssr
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}Building docker image...${NC}"
|
echo -e "${GREEN}Building docker image...${NC}"
|
||||||
sudo docker build -f $DOCKER_FILE -t $MAIN_NAME $SHELL_FOLDER
|
docker build -f $DOCKER_FILE -t $MAIN_NAME $SHELL_FOLDER
|
||||||
if [[ $? != 0 ]]; then echo -e "${RED}build docker with error${NC}"; exit 100; fi
|
if [[ $? != 0 ]]; then echo -e "${RED}build docker with error${NC}"; exit 100; fi
|
||||||
|
|
||||||
echo -e "${GREEN}Tagging and pushing docker image...${NC}"
|
echo -e "${GREEN}Tagging and pushing docker image...${NC}"
|
||||||
sudo docker tag $MAIN_NAME harbor.cowarobot.cn/gopro/$MAIN_NAME:$FLAG_PROD
|
docker tag $MAIN_NAME harbor.cowarobot.cn/$HARBOR_REPO/$MAIN_NAME:$FLAG_PROD
|
||||||
# docker push harbor.cowarobot.cn/gopro/$MAIN_NAME:$FLAG_PROD
|
docker push harbor.cowarobot.cn/$HARBOR_REPO/$MAIN_NAME:$FLAG_PROD
|
||||||
|
|
||||||
echo -e "${GREEN}Build completed successfully${NC}"
|
echo -e "${GREEN}Build completed successfully${NC}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const BASE_LABEL_API =
|
export const BASE_LABEL_API =
|
||||||
process.env.NEXT_PUBLIC_ENV === "production"
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
? "https://label.cowarobot.com"
|
? "https://label.soft.cowarobot.com"
|
||||||
: process.env.NEXT_PUBLIC_ENV === "staging"
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
? "https://label.softtest.cowarobot.com"
|
? "http://172.16.115.128:9110"
|
||||||
: "https://label.softtest.cowarobot.com"
|
: "https://label.softtest.cowarobot.com"
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const getAuxiliaryAnnotation = (data: any) => {
|
|||||||
|
|
||||||
export const getAuxiliaryAnnotation2 = (data: {
|
export const getAuxiliaryAnnotation2 = (data: {
|
||||||
project_id: string
|
project_id: string
|
||||||
image_name: string
|
image: string
|
||||||
prompt: {
|
prompt: {
|
||||||
foreground_points?: Array<[number, number]>
|
foreground_points?: Array<[number, number]>
|
||||||
background_points?: Array<[number, number]>
|
background_points?: Array<[number, number]>
|
||||||
|
|||||||
@@ -1,21 +1,51 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { Box } from "@mantine/core"
|
||||||
|
import { RichTextEditor } from "@mantine/tiptap"
|
||||||
|
import "@mantine/tiptap/styles.css"
|
||||||
|
import { IconPhoto } from "@tabler/icons-react"
|
||||||
|
import Image from "@tiptap/extension-image"
|
||||||
|
import Link from "@tiptap/extension-link"
|
||||||
|
import Placeholder from "@tiptap/extension-placeholder"
|
||||||
|
import Underline from "@tiptap/extension-underline"
|
||||||
import { useEditor } from "@tiptap/react"
|
import { useEditor } from "@tiptap/react"
|
||||||
import StarterKit from "@tiptap/starter-kit"
|
import StarterKit from "@tiptap/starter-kit"
|
||||||
import Underline from "@tiptap/extension-underline"
|
import { useEffect, useRef, type ChangeEvent } from "react"
|
||||||
import Link from "@tiptap/extension-link"
|
|
||||||
import Image from "@tiptap/extension-image"
|
|
||||||
import Placeholder from "@tiptap/extension-placeholder"
|
|
||||||
import { RichTextEditor } from "@mantine/tiptap"
|
|
||||||
import { useTopToolsStore } from "../useTopToolsStore"
|
|
||||||
import { useKeyEventStore } from "../store"
|
import { useKeyEventStore } from "../store"
|
||||||
import { useEffect, useRef } from "react"
|
import { useTopToolsStore } from "../useTopToolsStore"
|
||||||
import { Box } from "@mantine/core"
|
|
||||||
import { IconPhoto } from "@tabler/icons-react"
|
|
||||||
import "@mantine/tiptap/styles.css"
|
|
||||||
|
|
||||||
export const splitWord = "@1024-"
|
export const splitWord = "@1024-"
|
||||||
|
|
||||||
|
const getEditorHtml = (rawValue?: string) => {
|
||||||
|
if (!rawValue) return ""
|
||||||
|
const [html] = rawValue.split(splitWord)
|
||||||
|
return html ?? rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorLabels = {
|
||||||
|
boldControlLabel: "加粗",
|
||||||
|
italicControlLabel: "斜体",
|
||||||
|
underlineControlLabel: "下划线",
|
||||||
|
strikeControlLabel: "删除线",
|
||||||
|
clearFormattingControlLabel: "清除格式",
|
||||||
|
codeControlLabel: "行内代码",
|
||||||
|
h1ControlLabel: "一级标题",
|
||||||
|
h2ControlLabel: "二级标题",
|
||||||
|
h3ControlLabel: "三级标题",
|
||||||
|
h4ControlLabel: "四级标题",
|
||||||
|
bulletListControlLabel: "无序列表",
|
||||||
|
orderedListControlLabel: "有序列表",
|
||||||
|
blockquoteControlLabel: "引用",
|
||||||
|
hrControlLabel: "分割线",
|
||||||
|
linkControlLabel: "插入链接",
|
||||||
|
unlinkControlLabel: "移除链接",
|
||||||
|
linkEditorInputLabel: "输入链接地址",
|
||||||
|
linkEditorInputPlaceholder: "请输入链接,例如:https://example.com",
|
||||||
|
linkEditorExternalLink: "在新标签页打开",
|
||||||
|
linkEditorInternalLink: "在当前标签页打开",
|
||||||
|
linkEditorSave: "保存",
|
||||||
|
}
|
||||||
|
|
||||||
interface EditorContainerProps {
|
interface EditorContainerProps {
|
||||||
value: string
|
value: string
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
@@ -33,6 +63,7 @@ const EditorContainer = ({
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
|
immediatelyRender: false,
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
@@ -40,11 +71,11 @@ const EditorContainer = ({
|
|||||||
Image,
|
Image,
|
||||||
Placeholder.configure({ placeholder: "请输入内容..." }),
|
Placeholder.configure({ placeholder: "请输入内容..." }),
|
||||||
],
|
],
|
||||||
content: value,
|
content: getEditorHtml(value),
|
||||||
editable: !disabled && !isView,
|
editable: !disabled && !isView,
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
// Preserve splitWord format
|
const html = getEditorHtml(editor.getHTML())
|
||||||
onChange(`${editor.getHTML()}${splitWord}${editor.getText()}`)
|
onChange(`${html}${splitWord}${editor.getText()}`)
|
||||||
},
|
},
|
||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
useKeyEventStore.getState().setFocusInput(true)
|
useKeyEventStore.getState().setFocusInput(true)
|
||||||
@@ -61,18 +92,15 @@ const EditorContainer = ({
|
|||||||
}, [disabled, isView, editor])
|
}, [disabled, isView, editor])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Sync external value changes to editor
|
if (!editor) return
|
||||||
// Check if editor content differs from value to avoid cursor jump and loops
|
const incomingHtml = getEditorHtml(value) || "<p></p>"
|
||||||
if (editor && value && editor.getHTML() !== value) {
|
if (editor.getHTML() !== incomingHtml) {
|
||||||
// Only set content if it's substantially different.
|
// Keep editor content in sync with external state, including empty value switch.
|
||||||
// Note: editor.getHTML() might return different string than value (e.g. attribute order).
|
editor.commands.setContent(incomingHtml, { emitUpdate: false })
|
||||||
// Ideally we should compare content, but string comparison is a basic check.
|
|
||||||
// If value is empty string, we should clear.
|
|
||||||
editor.commands.setContent(value)
|
|
||||||
}
|
}
|
||||||
}, [value, editor])
|
}, [value, editor])
|
||||||
|
|
||||||
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImageUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0]
|
const file = e.target.files?.[0]
|
||||||
if (file && editor) {
|
if (file && editor) {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
@@ -92,8 +120,11 @@ const EditorContainer = ({
|
|||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
padding: "4px",
|
padding: "4px",
|
||||||
}}>
|
}}>
|
||||||
<RichTextEditor editor={editor} style={{ border: 0 }}>
|
<RichTextEditor
|
||||||
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
editor={editor}
|
||||||
|
labels={editorLabels}
|
||||||
|
style={{ border: 0 }}>
|
||||||
|
<RichTextEditor.Toolbar>
|
||||||
<RichTextEditor.ControlsGroup>
|
<RichTextEditor.ControlsGroup>
|
||||||
<RichTextEditor.Bold />
|
<RichTextEditor.Bold />
|
||||||
<RichTextEditor.Italic />
|
<RichTextEditor.Italic />
|
||||||
@@ -125,8 +156,8 @@ const EditorContainer = ({
|
|||||||
<RichTextEditor.ControlsGroup>
|
<RichTextEditor.ControlsGroup>
|
||||||
<RichTextEditor.Control
|
<RichTextEditor.Control
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
aria-label="Upload image"
|
aria-label="上传图片"
|
||||||
title="Upload image">
|
title="上传图片">
|
||||||
<IconPhoto stroke={1.5} size="1rem" />
|
<IconPhoto stroke={1.5} size="1rem" />
|
||||||
</RichTextEditor.Control>
|
</RichTextEditor.Control>
|
||||||
</RichTextEditor.ControlsGroup>
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ const PaperContainer = (
|
|||||||
try {
|
try {
|
||||||
const data = await getAuxiliaryAnnotation2({
|
const data = await getAuxiliaryAnnotation2({
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
image_name: activeImage,
|
image: activeImage,
|
||||||
// project_id: "image_test_bk",
|
// project_id: "image_test_bk",
|
||||||
prompt: {
|
prompt: {
|
||||||
foreground_points: promptPoints,
|
foreground_points: promptPoints,
|
||||||
@@ -1311,7 +1311,7 @@ const PaperContainer = (
|
|||||||
},
|
},
|
||||||
hollow.map((item: any) =>
|
hollow.map((item: any) =>
|
||||||
item.map((p: any) => {
|
item.map((p: any) => {
|
||||||
if (paper instanceof paper.Segment) {
|
if (p instanceof paper.Segment) {
|
||||||
return [
|
return [
|
||||||
p.point.x / activeScale,
|
p.point.x / activeScale,
|
||||||
p.point.y / activeScale,
|
p.point.y / activeScale,
|
||||||
@@ -1376,8 +1376,10 @@ const PaperContainer = (
|
|||||||
let findIndexInArr = arr.findIndex((v) => v[0] === id)
|
let findIndexInArr = arr.findIndex((v) => v[0] === id)
|
||||||
let oldData = arr.find((a) => a[0] === id)!
|
let oldData = arr.find((a) => a[0] === id)!
|
||||||
let arr2 = needCopyMap.get(needCopyCategoryIds[index])!
|
let arr2 = needCopyMap.get(needCopyCategoryIds[index])!
|
||||||
|
let copiedItem = arr2.find((b: any) => b[0] === id)
|
||||||
|
if (!copiedItem) return
|
||||||
let newData = {
|
let newData = {
|
||||||
...arr2.find((b: any) => b[0] === id),
|
...copiedItem[2],
|
||||||
uid: oldData[2].uid,
|
uid: oldData[2].uid,
|
||||||
create_timestamp: oldData[2].create_timestamp,
|
create_timestamp: oldData[2].create_timestamp,
|
||||||
first_modified_timestamp:
|
first_modified_timestamp:
|
||||||
@@ -1444,7 +1446,21 @@ const PaperContainer = (
|
|||||||
data.set(imageId, newCategoryMap)
|
data.set(imageId, newCategoryMap)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data.set(imageId, needCopyMap)
|
let newCategoryMap = new Map()
|
||||||
|
for (let [key, needCopyItemsArr] of needCopyMap) {
|
||||||
|
let arr = needCopyItemsArr.map((item: any) => [
|
||||||
|
item[0],
|
||||||
|
item[1].map((path: any) =>
|
||||||
|
path.map(([x, y]: any) => [x * picScale, y * picScale])
|
||||||
|
),
|
||||||
|
item[2],
|
||||||
|
item[3].map((path: any) =>
|
||||||
|
path.map(([x, y]: any) => [x * picScale, y * picScale])
|
||||||
|
),
|
||||||
|
])
|
||||||
|
newCategoryMap.set(key, arr)
|
||||||
|
}
|
||||||
|
data.set(imageId, newCategoryMap)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
notifications.show({ message: "复制成功", color: "green" })
|
notifications.show({ message: "复制成功", color: "green" })
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { Task } from "../api/task/typing"
|
|||||||
import { useBottomToolsStore } from "../useBottomToolsStore"
|
import { useBottomToolsStore } from "../useBottomToolsStore"
|
||||||
import { useDescToolsStore } from "../useDescToolsStore"
|
import { useDescToolsStore } from "../useDescToolsStore"
|
||||||
import { useTopToolsStore } from "../useTopToolsStore"
|
import { useTopToolsStore } from "../useTopToolsStore"
|
||||||
|
import { safeClone } from "../utils/clone"
|
||||||
import EditorContainer from "./EditorContainer"
|
import EditorContainer from "./EditorContainer"
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
@@ -40,46 +41,67 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isUpdatingFromStore = useRef(false)
|
const isUpdatingFromStore = useRef(false)
|
||||||
|
const syncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
const handleFormUpdate = () => {
|
const setOperationField = (
|
||||||
|
labelClass: string,
|
||||||
|
field: string,
|
||||||
|
value: string | string[]
|
||||||
|
) => {
|
||||||
|
const current = ((form.values as any)[labelClass] ?? {}) as Record<
|
||||||
|
string,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
form.setFieldValue(labelClass, {
|
||||||
|
...current,
|
||||||
|
[field]: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFormUpdate = (values: any) => {
|
||||||
if (isUpdatingFromStore.current) return
|
if (isUpdatingFromStore.current) return
|
||||||
|
|
||||||
const data = form.values as any
|
const currentDescData = safeClone(useDescToolsStore.getState().descData)
|
||||||
let dataMap = new Map()
|
const updateMap = new Map()
|
||||||
taskDetail?.data_name.forEach((detail_name) => {
|
Object.entries(values).forEach(([label_class, val]: any) => {
|
||||||
if (detail_name.name[0] !== activeImage) {
|
let value = { ...val }
|
||||||
if (useDescToolsStore.getState().descData.has(detail_name.name[0])) {
|
if (value.is_correct !== undefined) {
|
||||||
dataMap.set(
|
value.is_correct = value.is_correct === "true"
|
||||||
detail_name.name[0],
|
}
|
||||||
useDescToolsStore.getState().descData.get(detail_name.name[0])
|
const keys = Object.keys(value)
|
||||||
)
|
if (keys.includes("value")) {
|
||||||
}
|
updateMap.set(label_class, value)
|
||||||
} else {
|
} else {
|
||||||
const updateMap = new Map()
|
updateMap.set(label_class, { value: value })
|
||||||
Object.entries(data).forEach(([label_class, val]: any) => {
|
|
||||||
let value = { ...val }
|
|
||||||
if (value.is_correct !== undefined) {
|
|
||||||
value.is_correct = value.is_correct === "true"
|
|
||||||
}
|
|
||||||
const keys = Object.keys(value)
|
|
||||||
if (keys.includes("value")) {
|
|
||||||
updateMap.set(label_class, value)
|
|
||||||
} else {
|
|
||||||
updateMap.set(label_class, { value: value })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dataMap.set(activeImage, updateMap)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setDescData(dataMap)
|
currentDescData.set(activeImage, updateMap)
|
||||||
|
setDescData(currentDescData)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleFormUpdate()
|
if (isUpdatingFromStore.current) return
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
}
|
||||||
|
syncTimerRef.current = setTimeout(() => {
|
||||||
|
handleFormUpdate(form.values)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}, 180)
|
||||||
|
return () => {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [form.values])
|
}, [form.values])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
isUpdatingFromStore.current = true
|
isUpdatingFromStore.current = true
|
||||||
form.reset()
|
form.reset()
|
||||||
if (activeImage) {
|
if (activeImage) {
|
||||||
@@ -109,6 +131,10 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updateDescDataFlag) {
|
if (updateDescDataFlag) {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
isUpdatingFromStore.current = true
|
isUpdatingFromStore.current = true
|
||||||
const formData = useDescToolsStore
|
const formData = useDescToolsStore
|
||||||
.getState()
|
.getState()
|
||||||
@@ -133,6 +159,15 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [updateDescDataFlag])
|
}, [updateDescDataFlag])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (syncTimerRef.current) {
|
||||||
|
clearTimeout(syncTimerRef.current)
|
||||||
|
syncTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box h="100%">
|
<Box h="100%">
|
||||||
<Box
|
<Box
|
||||||
@@ -156,7 +191,9 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
activeImage,
|
activeImage,
|
||||||
operation.label_class
|
operation.label_class
|
||||||
)
|
)
|
||||||
const hasComment = currentData?.comment
|
const hasComment =
|
||||||
|
!!(form.values as any)?.[operation.label_class]?.comment ||
|
||||||
|
!!currentData?.comment
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={operation.label_class}>
|
<Box key={operation.label_class}>
|
||||||
@@ -171,9 +208,17 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
{operation.label_class}
|
{operation.label_class}
|
||||||
</Text>
|
</Text>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.is_correct`
|
(form.values as any)[operation.label_class]
|
||||||
)}
|
?.is_correct || ""
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
"is_correct",
|
||||||
|
val
|
||||||
|
)
|
||||||
|
}}
|
||||||
size="xs">
|
size="xs">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Radio
|
<Radio
|
||||||
@@ -195,8 +240,9 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.setFieldValue(
|
setOperationField(
|
||||||
`${operation.label_class}.comment`,
|
operation.label_class,
|
||||||
|
"comment",
|
||||||
"<p></p>"
|
"<p></p>"
|
||||||
)
|
)
|
||||||
}}>
|
}}>
|
||||||
@@ -215,9 +261,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
placeholder={`请输入${subDesc.chinese_name}`}
|
placeholder={`请输入${subDesc.chinese_name}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
required={!!subDesc.isrequired}
|
required={!!subDesc.isrequired}
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.${subDesc.chinese_name}`
|
(form.values as any)[operation.label_class]?.[
|
||||||
)}
|
subDesc.chinese_name
|
||||||
|
] || ""
|
||||||
|
}
|
||||||
|
onChange={(event) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
subDesc.chinese_name,
|
||||||
|
event.currentTarget.value
|
||||||
|
)
|
||||||
|
}}
|
||||||
disabled={!!isView}
|
disabled={!!isView}
|
||||||
/>
|
/>
|
||||||
) : subDesc.select_mode === 1 ? (
|
) : subDesc.select_mode === 1 ? (
|
||||||
@@ -225,9 +280,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
label={subDesc.chinese_name}
|
label={subDesc.chinese_name}
|
||||||
size="xs"
|
size="xs"
|
||||||
required={!!subDesc.isrequired}
|
required={!!subDesc.isrequired}
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.${subDesc.chinese_name}`
|
(form.values as any)[operation.label_class]?.[
|
||||||
)}>
|
subDesc.chinese_name
|
||||||
|
] || ""
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
subDesc.chinese_name,
|
||||||
|
val
|
||||||
|
)
|
||||||
|
}}>
|
||||||
<Group gap="xs" mt={4}>
|
<Group gap="xs" mt={4}>
|
||||||
{subDesc.optional_item.map((option) => (
|
{subDesc.optional_item.map((option) => (
|
||||||
<Radio
|
<Radio
|
||||||
@@ -244,9 +308,18 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
label={subDesc.chinese_name}
|
label={subDesc.chinese_name}
|
||||||
size="xs"
|
size="xs"
|
||||||
required={!!subDesc.isrequired}
|
required={!!subDesc.isrequired}
|
||||||
{...form.getInputProps(
|
value={
|
||||||
`${operation.label_class}.${subDesc.chinese_name}`
|
(form.values as any)[operation.label_class]?.[
|
||||||
)}>
|
subDesc.chinese_name
|
||||||
|
] || []
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
setOperationField(
|
||||||
|
operation.label_class,
|
||||||
|
subDesc.chinese_name,
|
||||||
|
val
|
||||||
|
)
|
||||||
|
}}>
|
||||||
<Group gap="xs" mt={4}>
|
<Group gap="xs" mt={4}>
|
||||||
{subDesc.optional_item.map((option) => (
|
{subDesc.optional_item.map((option) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -266,13 +339,11 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
<Box mb="xs">
|
<Box mb="xs">
|
||||||
<EditorContainer
|
<EditorContainer
|
||||||
value={
|
value={
|
||||||
(form.values as any)[operation.label_class]?.value
|
(form.values as any)[operation.label_class]?.value ||
|
||||||
|
""
|
||||||
}
|
}
|
||||||
onChange={(val: any) =>
|
onChange={(val: any) =>
|
||||||
form.setFieldValue(
|
setOperationField(operation.label_class, "value", val)
|
||||||
`${operation.label_class}.value`,
|
|
||||||
val
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -282,14 +353,16 @@ const RightDescTools = ({ taskDetail }: ComponentProps) => {
|
|||||||
<EditorContainer
|
<EditorContainer
|
||||||
color="border-danger"
|
color="border-danger"
|
||||||
value={
|
value={
|
||||||
(form.values as any)[operation.label_class]?.comment
|
(form.values as any)[operation.label_class]
|
||||||
|
?.comment || ""
|
||||||
}
|
}
|
||||||
onChange={(val: any) =>
|
onChange={(val: any) => {
|
||||||
form.setFieldValue(
|
setOperationField(
|
||||||
`${operation.label_class}.comment`,
|
operation.label_class,
|
||||||
|
"comment",
|
||||||
val
|
val
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -379,6 +379,33 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTagVisibility = (tagCategory?: string) => {
|
||||||
|
const { showTags, showTagsConfigs } = useTopToolsStore.getState()
|
||||||
|
if (!showTags) return false
|
||||||
|
if (tagCategory === "mask") return showTagsConfigs.includes("外框")
|
||||||
|
if (tagCategory === "point_text") return showTagsConfigs.includes("点ID")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncObjectItemsVisibility = useCallback(
|
||||||
|
(objectId: number, hide: boolean) => {
|
||||||
|
const sameIdItems = getItemsById(objectId)
|
||||||
|
if (!sameIdItems?.length) return
|
||||||
|
sameIdItems.forEach((item) => {
|
||||||
|
if (hide) {
|
||||||
|
item.visible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.data?.type === "tag") {
|
||||||
|
item.visible = getTagVisibility(item.data?.tag_category)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.visible = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[getItemsById]
|
||||||
|
)
|
||||||
|
|
||||||
const operationHide = useCallback(
|
const operationHide = useCallback(
|
||||||
(operationId: string, hide: boolean) => {
|
(operationId: string, hide: boolean) => {
|
||||||
updateOperationStatus(activeImage + operationId, "HIDE", hide)
|
updateOperationStatus(activeImage + operationId, "HIDE", hide)
|
||||||
@@ -388,19 +415,15 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
|||||||
?.get(operationId)
|
?.get(operationId)
|
||||||
?.map((child) => {
|
?.map((child) => {
|
||||||
updatePathStatus(activeImage + child[0], "HIDE", hide)
|
updatePathStatus(activeImage + child[0], "HIDE", hide)
|
||||||
if (getItemsById(child[0]) && getItemsById(child[0]).length) {
|
syncObjectItemsVisibility(child[0], hide)
|
||||||
getItemsById(child[0]).forEach((item) => {
|
// 隐藏时清空选中状态
|
||||||
item.visible = !hide
|
if (hide) setSelectedItemFalse(child[0])
|
||||||
})
|
|
||||||
// 隐藏时清空选中状态
|
|
||||||
if (hide) setSelectedItemFalse(child[0])
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
activeImage,
|
activeImage,
|
||||||
getItemsById,
|
|
||||||
storeLabel,
|
storeLabel,
|
||||||
|
syncObjectItemsVisibility,
|
||||||
updateOperationStatus,
|
updateOperationStatus,
|
||||||
updatePathStatus,
|
updatePathStatus,
|
||||||
]
|
]
|
||||||
@@ -410,11 +433,9 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
|||||||
(operationId: string, objectId: number, hide: boolean) => {
|
(operationId: string, objectId: number, hide: boolean) => {
|
||||||
if (hide) {
|
if (hide) {
|
||||||
updatePathStatus(activeImage + objectId, "HIDE", hide)
|
updatePathStatus(activeImage + objectId, "HIDE", hide)
|
||||||
if (getItemsById(objectId) && getItemsById(objectId).length) {
|
syncObjectItemsVisibility(objectId, hide)
|
||||||
getItemsById(objectId).forEach((item) => (item.visible = !hide))
|
// 隐藏时清空选中状态
|
||||||
// 隐藏时清空选中状态
|
setSelectedItemFalse(objectId)
|
||||||
setSelectedItemFalse(objectId)
|
|
||||||
}
|
|
||||||
let childrenHideStatus = storeLabel
|
let childrenHideStatus = storeLabel
|
||||||
.get(activeImage)
|
.get(activeImage)
|
||||||
?.get(operationId)
|
?.get(operationId)
|
||||||
@@ -427,28 +448,14 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
updateOperationStatus(activeImage + operationId, "HIDE", hide)
|
updateOperationStatus(activeImage + operationId, "HIDE", hide)
|
||||||
updatePathStatus(activeImage + objectId, "HIDE", hide)
|
updatePathStatus(activeImage + objectId, "HIDE", hide)
|
||||||
const configs = useTopToolsStore.getState().showTagsConfigs
|
syncObjectItemsVisibility(objectId, hide)
|
||||||
if (getItemsById(objectId) && getItemsById(objectId).length)
|
|
||||||
getItemsById(objectId).forEach((item) => {
|
|
||||||
let visible = !hide
|
|
||||||
if (item.data.type === "tag") {
|
|
||||||
if (item.data.tag_category === "mask") {
|
|
||||||
visible = configs.includes("外框")
|
|
||||||
} else if (item.data.tag_category === "point_text") {
|
|
||||||
visible = configs.includes("点ID")
|
|
||||||
} else {
|
|
||||||
visible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.visible = visible
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
activeImage,
|
activeImage,
|
||||||
getItemById,
|
getItemById,
|
||||||
getItemsById,
|
|
||||||
storeLabel,
|
storeLabel,
|
||||||
|
syncObjectItemsVisibility,
|
||||||
updateOperationStatus,
|
updateOperationStatus,
|
||||||
updatePathStatus,
|
updatePathStatus,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
Radio,
|
Radio,
|
||||||
|
ScrollArea,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
@@ -19,12 +20,15 @@ import { IconEdit } from "@tabler/icons-react"
|
|||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import parse from "html-react-parser"
|
import parse from "html-react-parser"
|
||||||
import { ArrowUpIcon, ArrowUpToLineIcon } from "lucide-react"
|
import { ArrowUpIcon, ArrowUpToLineIcon } from "lucide-react"
|
||||||
|
import dynamic from "next/dynamic"
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
import { getWrongWordList } from "../api/scheme"
|
import { getWrongWordList } from "../api/scheme"
|
||||||
@@ -36,7 +40,11 @@ import { useDescToolsStore } from "../useDescToolsStore"
|
|||||||
import { useTopToolsStore } from "../useTopToolsStore"
|
import { useTopToolsStore } from "../useTopToolsStore"
|
||||||
import { safeClone } from "../utils/clone"
|
import { safeClone } from "../utils/clone"
|
||||||
import CustomModal from "./CustomModal"
|
import CustomModal from "./CustomModal"
|
||||||
import EditorContainer, { splitWord } from "./EditorContainer"
|
import { splitWord } from "./EditorContainer"
|
||||||
|
|
||||||
|
const EditorContainer = dynamic(() => import("./EditorContainer"), {
|
||||||
|
ssr: false,
|
||||||
|
})
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
taskDetail?: Task.DataProps
|
taskDetail?: Task.DataProps
|
||||||
@@ -54,6 +62,65 @@ const escapeRegExp = (str: string) => {
|
|||||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getHtmlFromStoredValue = (value?: string) => {
|
||||||
|
if (!value) return ""
|
||||||
|
const [html] = value.split(splitWord)
|
||||||
|
return html ?? value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTextFromHtml = (html: string) => {
|
||||||
|
return html
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureStoredValue = (value?: string) => {
|
||||||
|
if (!value) return ""
|
||||||
|
if (value.includes(splitWord)) return value
|
||||||
|
return `${value}${splitWord}${getTextFromHtml(value)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPreviewContent = (html?: string) => {
|
||||||
|
if (!html) return false
|
||||||
|
if (/<(img|video|audio|iframe|hr)\b/i.test(html)) return true
|
||||||
|
return (
|
||||||
|
html
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.trim().length > 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const QaPreviewContent = memo(
|
||||||
|
({
|
||||||
|
html,
|
||||||
|
hasContent,
|
||||||
|
emptyText,
|
||||||
|
}: {
|
||||||
|
html: string
|
||||||
|
hasContent: boolean
|
||||||
|
emptyText: string
|
||||||
|
}) => {
|
||||||
|
const parsedContent = useMemo(() => {
|
||||||
|
if (!hasContent) return null
|
||||||
|
return parse(html)
|
||||||
|
}, [hasContent, html])
|
||||||
|
|
||||||
|
if (!hasContent) {
|
||||||
|
return (
|
||||||
|
<Text size="xs" c="dimmed" className="qa-preview-empty">
|
||||||
|
{emptyText}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Box className="qa-preview-content">{parsedContent}</Box>
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
QaPreviewContent.displayName = "QaPreviewContent"
|
||||||
|
|
||||||
const RightQAContent = forwardRef(
|
const RightQAContent = forwardRef(
|
||||||
({ taskDetail }: ComponentProps, ref: React.Ref<unknown>) => {
|
({ taskDetail }: ComponentProps, ref: React.Ref<unknown>) => {
|
||||||
const { isView } = useTopToolsStore()
|
const { isView } = useTopToolsStore()
|
||||||
@@ -88,7 +155,7 @@ const RightQAContent = forwardRef(
|
|||||||
const counts = useMemo(() => {
|
const counts = useMemo(() => {
|
||||||
return countAllTurnsAndQaNumber(activeImage)
|
return countAllTurnsAndQaNumber(activeImage)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [activeImage, qaData, updateDescDataFlag, countAllTurnsAndQaNumber])
|
}, [activeImage, qaData, countAllTurnsAndQaNumber])
|
||||||
|
|
||||||
const [open, setOpen] = useState({
|
const [open, setOpen] = useState({
|
||||||
status: false,
|
status: false,
|
||||||
@@ -97,13 +164,17 @@ const RightQAContent = forwardRef(
|
|||||||
isEdit: false,
|
isEdit: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const valueSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
// 新增问题
|
// 新增问题
|
||||||
const onClickAdd = (label: string, index: number) => {
|
const onClickAdd = (label: string, index: number) => {
|
||||||
|
flushPendingValueSync()
|
||||||
setOpen({ status: true, label, idx: index, isEdit: false })
|
setOpen({ status: true, label, idx: index, isEdit: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑问题名称
|
// 编辑问题名称
|
||||||
const onClickEdit = (label: string, index: number, val: EditFormProps) => {
|
const onClickEdit = (label: string, index: number, val: EditFormProps) => {
|
||||||
|
flushPendingValueSync()
|
||||||
textForm.setValues({
|
textForm.setValues({
|
||||||
question_name: val.question_name,
|
question_name: val.question_name,
|
||||||
value: val.value || "",
|
value: val.value || "",
|
||||||
@@ -114,55 +185,101 @@ const RightQAContent = forwardRef(
|
|||||||
|
|
||||||
type QaObj = Record<string, any>
|
type QaObj = Record<string, any>
|
||||||
|
|
||||||
const handleFormUpdate = (changeValues: any) => {
|
const handleFormUpdate = (
|
||||||
|
changeValues: Record<string, any>,
|
||||||
|
rawEditorValue?: string,
|
||||||
|
options?: { notifyFlag?: boolean }
|
||||||
|
) => {
|
||||||
if (!selectedQuestion) return
|
if (!selectedQuestion) return
|
||||||
const keys = Object.keys(changeValues)
|
const keys = Object.keys(changeValues)
|
||||||
const hasTagChange = keys.includes("tag")
|
const hasTagChange = keys.includes("tag")
|
||||||
const formData = form.values
|
const formData = {
|
||||||
|
...form.values,
|
||||||
|
...changeValues,
|
||||||
|
}
|
||||||
|
const nextStoredValue =
|
||||||
|
typeof rawEditorValue === "string"
|
||||||
|
? ensureStoredValue(rawEditorValue)
|
||||||
|
: ensureStoredValue(formData.value)
|
||||||
const { label, type, category } = selectedQuestion
|
const { label, type, category } = selectedQuestion
|
||||||
const currentQaDataArr = currentQaData[category] ?? []
|
|
||||||
let newArr: { [x: string]: any }[] = []
|
|
||||||
currentQaDataArr.forEach((qaObj: QaObj) => {
|
|
||||||
const [question_name, detail] = Object.entries(qaObj)[0]
|
|
||||||
if (question_name === label) {
|
|
||||||
newArr.push({
|
|
||||||
[question_name]: {
|
|
||||||
value: type === "value" ? formData.value : detail.value,
|
|
||||||
id: formData.round,
|
|
||||||
is_pre: detail.is_pre,
|
|
||||||
tag: formData.tag,
|
|
||||||
uid: detail.uid
|
|
||||||
? detail.uid
|
|
||||||
: usePermissionStore.getState().user_id,
|
|
||||||
create_timestamp: detail.create_timestamp
|
|
||||||
? detail.create_timestamp
|
|
||||||
: dayjs().unix(),
|
|
||||||
modify_uid: usePermissionStore.getState().user_id,
|
|
||||||
modify_timestamp: dayjs().unix(),
|
|
||||||
comment: type === "comment" ? formData.value : detail.comment,
|
|
||||||
flag: hasTagChange ? 0 : detail.tag === 2 ? 1 : 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
newArr.push(qaObj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
try {
|
try {
|
||||||
const finalData = safeClone(currentQaData)
|
const latestQaData = useDescToolsStore.getState().qaData
|
||||||
finalData[category] = newArr
|
const latestCurrentQaData =
|
||||||
qaData.set(activeImage, finalData)
|
latestQaData.get(activeImage) ?? latestQaData.get("init") ?? {}
|
||||||
setQaData(new Map(qaData)) // Force update
|
const latestQaDataArr = latestCurrentQaData[category] ?? []
|
||||||
updateFlag(true) // Ensure flag is updated to trigger recalculations if needed
|
let latestNewArr: { [x: string]: any }[] = []
|
||||||
|
latestQaDataArr.forEach((qaObj: QaObj) => {
|
||||||
|
const [question_name, detail] = Object.entries(qaObj)[0]
|
||||||
|
if (question_name === label) {
|
||||||
|
latestNewArr.push({
|
||||||
|
[question_name]: {
|
||||||
|
value: type === "value" ? nextStoredValue : detail.value,
|
||||||
|
id: formData.round,
|
||||||
|
is_pre: detail.is_pre,
|
||||||
|
tag: formData.tag,
|
||||||
|
uid: detail.uid
|
||||||
|
? detail.uid
|
||||||
|
: usePermissionStore.getState().user_id,
|
||||||
|
create_timestamp: detail.create_timestamp
|
||||||
|
? detail.create_timestamp
|
||||||
|
: dayjs().unix(),
|
||||||
|
modify_uid: usePermissionStore.getState().user_id,
|
||||||
|
modify_timestamp: dayjs().unix(),
|
||||||
|
comment: type === "comment" ? nextStoredValue : detail.comment,
|
||||||
|
flag: hasTagChange ? 0 : detail.tag === 2 ? 1 : 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
latestNewArr.push(qaObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const finalData = safeClone(latestCurrentQaData)
|
||||||
|
finalData[category] = latestNewArr
|
||||||
|
latestQaData.set(activeImage, finalData)
|
||||||
|
setQaData(new Map(latestQaData)) // Force update
|
||||||
|
if (options?.notifyFlag) {
|
||||||
|
updateFlag(true) // Ensure flag is updated to trigger recalculations if needed
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFieldChange = (field: string, value: any) => {
|
const flushPendingValueSync = () => {
|
||||||
form.setFieldValue(field, value)
|
if (!valueSyncTimerRef.current) return
|
||||||
handleFormUpdate({ [field]: value })
|
clearTimeout(valueSyncTimerRef.current)
|
||||||
|
valueSyncTimerRef.current = null
|
||||||
|
handleFormUpdate({ value: form.values.value }, form.values.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onFieldChange = (
|
||||||
|
field: string,
|
||||||
|
value: any,
|
||||||
|
rawEditorValue?: string
|
||||||
|
) => {
|
||||||
|
form.setFieldValue(field, value)
|
||||||
|
if (field === "value") {
|
||||||
|
if (valueSyncTimerRef.current) {
|
||||||
|
clearTimeout(valueSyncTimerRef.current)
|
||||||
|
}
|
||||||
|
valueSyncTimerRef.current = setTimeout(() => {
|
||||||
|
handleFormUpdate({ [field]: value }, rawEditorValue)
|
||||||
|
valueSyncTimerRef.current = null
|
||||||
|
}, 180)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleFormUpdate({ [field]: value }, rawEditorValue, { notifyFlag: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (valueSyncTimerRef.current) {
|
||||||
|
clearTimeout(valueSyncTimerRef.current)
|
||||||
|
valueSyncTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedQuestion) return
|
if (!selectedQuestion) return
|
||||||
if (
|
if (
|
||||||
@@ -170,12 +287,10 @@ const RightQAContent = forwardRef(
|
|||||||
selectedQuestion.type === "comment"
|
selectedQuestion.type === "comment"
|
||||||
) {
|
) {
|
||||||
const { value: v } = selectedQuestion
|
const { value: v } = selectedQuestion
|
||||||
let htmlText = ""
|
const htmlText =
|
||||||
if (selectedQuestion.type === "value") {
|
selectedQuestion.type === "value"
|
||||||
htmlText = v.value ? v.value.split(splitWord)[0] : ""
|
? getHtmlFromStoredValue(v.value)
|
||||||
} else {
|
: getHtmlFromStoredValue(v.comment)
|
||||||
htmlText = v.comment ? v.comment.split(splitWord)[0] : ""
|
|
||||||
}
|
|
||||||
const obj = {
|
const obj = {
|
||||||
value: htmlText,
|
value: htmlText,
|
||||||
round: v.id,
|
round: v.id,
|
||||||
@@ -183,7 +298,8 @@ const RightQAContent = forwardRef(
|
|||||||
}
|
}
|
||||||
form.setValues(obj)
|
form.setValues(obj)
|
||||||
}
|
}
|
||||||
}, [selectedQuestion, form])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [selectedQuestion])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updateDescDataFlag) {
|
if (updateDescDataFlag) {
|
||||||
@@ -230,9 +346,10 @@ const RightQAContent = forwardRef(
|
|||||||
const newQaData = safeClone(currentQaData)
|
const newQaData = safeClone(currentQaData)
|
||||||
|
|
||||||
const handleText = (v: string) => {
|
const handleText = (v: string) => {
|
||||||
// 提取文本内容
|
// 优先使用存储中的纯文本部分;若历史数据不规范则兜底从 HTML 中提取
|
||||||
const text = v.split(splitWord)[1]
|
const text =
|
||||||
let textContent = text.replace(/<[^>]+>/g, "")
|
v.split(splitWord)[1] ?? getTextFromHtml(getHtmlFromStoredValue(v))
|
||||||
|
let textContent = text
|
||||||
// 替换错词
|
// 替换错词
|
||||||
for (let i = 0; i < wrongWords.length; i++) {
|
for (let i = 0; i < wrongWords.length; i++) {
|
||||||
const wrong = escapeRegExp(wrongWords[i])
|
const wrong = escapeRegExp(wrongWords[i])
|
||||||
@@ -256,17 +373,20 @@ const RightQAContent = forwardRef(
|
|||||||
const { value, comment } = questionData
|
const { value, comment } = questionData
|
||||||
|
|
||||||
const update = (v: string, key: "value" | "comment") => {
|
const update = (v: string, key: "value" | "comment") => {
|
||||||
const checkValue = v.split(splitWord)[1]
|
const checkValue =
|
||||||
|
v.split(splitWord)[1] ??
|
||||||
|
getTextFromHtml(getHtmlFromStoredValue(v))
|
||||||
const updatedValue = handleText(v)
|
const updatedValue = handleText(v)
|
||||||
|
|
||||||
if (checkValue !== updatedValue) {
|
if (checkValue !== updatedValue) {
|
||||||
let newValue = `<p>${updatedValue}</p>${splitWord}${updatedValue}`
|
const htmlValue = `<p>${updatedValue}</p>`
|
||||||
|
let newValue = `${htmlValue}${splitWord}${updatedValue}`
|
||||||
questionData[key] = newValue
|
questionData[key] = newValue
|
||||||
if (
|
if (
|
||||||
selectedQuestion?.label === name &&
|
selectedQuestion?.label === name &&
|
||||||
selectedQuestion?.type === key
|
selectedQuestion?.type === key
|
||||||
)
|
)
|
||||||
form.setFieldValue("value", `<p>${updatedValue}</p>`)
|
form.setFieldValue("value", htmlValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,16 +405,19 @@ const RightQAContent = forwardRef(
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box h="100%">
|
<Box
|
||||||
<Text
|
h="100%"
|
||||||
size="sm"
|
style={{ display: "flex", flexDirection: "column", minHeight: 0 }}>
|
||||||
fw={600}
|
<Box px="xs" pt="xs" pb={6}>
|
||||||
px="xs"
|
<Text
|
||||||
mb="xs">{`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`}</Text>
|
size="xs"
|
||||||
<Box
|
fw={500}
|
||||||
|
c="dimmed">{`总轮数:${counts.turns}, 总问题数:${counts.question_size},新增单轮数:${counts.new_single_turns},新增多轮数:${counts.new_multiple_turns}`}</Text>
|
||||||
|
</Box>
|
||||||
|
<ScrollArea
|
||||||
className="qa-tools-container"
|
className="qa-tools-container"
|
||||||
h="calc(100% - 300px)"
|
style={{ flex: 1, minHeight: 0 }}
|
||||||
style={{ overflowY: "auto" }}>
|
px="xs">
|
||||||
<Accordion multiple>
|
<Accordion multiple>
|
||||||
{Object.entries(currentQaData).map(([qaCategory, qaDataArr]) => {
|
{Object.entries(currentQaData).map(([qaCategory, qaDataArr]) => {
|
||||||
const counts = useDescToolsStore
|
const counts = useDescToolsStore
|
||||||
@@ -304,20 +427,22 @@ const RightQAContent = forwardRef(
|
|||||||
<Accordion.Item key={qaCategory} value={qaCategory}>
|
<Accordion.Item key={qaCategory} value={qaCategory}>
|
||||||
<Accordion.Control>
|
<Accordion.Control>
|
||||||
<Text
|
<Text
|
||||||
|
size="sm"
|
||||||
fw={
|
fw={
|
||||||
600
|
500
|
||||||
}>{`${qaCategory} 轮数:${counts.turns},问题数:${counts.question_size}`}</Text>
|
}>{`${qaCategory} 轮数:${counts.turns},问题数:${counts.question_size}`}</Text>
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{qaDataArr.map((qaObj, index) => {
|
{qaDataArr.map((qaObj, index) => {
|
||||||
const [question_name, detail] = Object.entries(qaObj)[0]
|
const [question_name, detail] = Object.entries(qaObj)[0]
|
||||||
const htmlText = detail.value
|
const htmlText = getHtmlFromStoredValue(detail.value)
|
||||||
? detail.value.split(splitWord)[0]
|
const commentHtmlText = getHtmlFromStoredValue(
|
||||||
: ""
|
detail.comment
|
||||||
const commentHtmlText = detail.comment
|
)
|
||||||
? detail.comment.split(splitWord)[0]
|
const hasValueContent = hasPreviewContent(htmlText)
|
||||||
: ""
|
const hasCommentContent =
|
||||||
|
hasPreviewContent(commentHtmlText)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -332,7 +457,9 @@ const RightQAContent = forwardRef(
|
|||||||
}}>
|
}}>
|
||||||
<Flex gap="xs" mb="xs">
|
<Flex gap="xs" mb="xs">
|
||||||
<Flex align="center">
|
<Flex align="center">
|
||||||
<Text>{question_name}</Text>
|
<Text size="sm" fw={500}>
|
||||||
|
{question_name}
|
||||||
|
</Text>
|
||||||
{!detail.is_pre && !isView ? (
|
{!detail.is_pre && !isView ? (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@@ -351,9 +478,13 @@ const RightQAContent = forwardRef(
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Box>
|
<Box>
|
||||||
{detail.tag === 2 ? (
|
{detail.tag === 2 ? (
|
||||||
<Text c="red">错误</Text>
|
<Text size="xs" fw={500} c="red">
|
||||||
|
错误
|
||||||
|
</Text>
|
||||||
) : detail.tag === 1 ? (
|
) : detail.tag === 1 ? (
|
||||||
<Text c="green">正确</Text>
|
<Text size="xs" fw={500} c="green">
|
||||||
|
正确
|
||||||
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -361,7 +492,7 @@ const RightQAContent = forwardRef(
|
|||||||
justify="space-between"
|
justify="space-between"
|
||||||
align="center"
|
align="center"
|
||||||
mb="xs">
|
mb="xs">
|
||||||
<Text size="sm">轮次:{detail.id}</Text>
|
<Text size="xs">轮次:{detail.id}</Text>
|
||||||
{!isView ? (
|
{!isView ? (
|
||||||
<Flex gap="xs">
|
<Flex gap="xs">
|
||||||
{!detail.is_pre ? (
|
{!detail.is_pre ? (
|
||||||
@@ -464,22 +595,28 @@ const RightQAContent = forwardRef(
|
|||||||
: "1px solid var(--mantine-color-gray-3)",
|
: "1px solid var(--mantine-color-gray-3)",
|
||||||
borderRadius: "var(--mantine-radius-md)",
|
borderRadius: "var(--mantine-radius-md)",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
minHeight: "40px",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
flushPendingValueSync()
|
||||||
setSelectedQuestion({
|
setSelectedQuestion({
|
||||||
label: question_name,
|
label: question_name,
|
||||||
value: detail,
|
value: detail,
|
||||||
type: "value",
|
type: "value",
|
||||||
category: qaCategory,
|
category: qaCategory,
|
||||||
})
|
})
|
||||||
console.log("点击批注", htmlText)
|
// console.log("点击批注", htmlText)
|
||||||
form.setValues({
|
form.setValues({
|
||||||
value: htmlText,
|
value: htmlText,
|
||||||
round: detail.id,
|
round: detail.id,
|
||||||
tag: detail.tag,
|
tag: detail.tag,
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
{parse(htmlText)}
|
<QaPreviewContent
|
||||||
|
html={htmlText}
|
||||||
|
hasContent={hasValueContent}
|
||||||
|
emptyText="暂无内容"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{commentHtmlText ? (
|
{commentHtmlText ? (
|
||||||
<Box
|
<Box
|
||||||
@@ -489,8 +626,10 @@ const RightQAContent = forwardRef(
|
|||||||
"1px solid var(--mantine-color-red-filled)",
|
"1px solid var(--mantine-color-red-filled)",
|
||||||
borderRadius: "var(--mantine-radius-md)",
|
borderRadius: "var(--mantine-radius-md)",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
minHeight: "40px",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
flushPendingValueSync()
|
||||||
setSelectedQuestion({
|
setSelectedQuestion({
|
||||||
label: question_name,
|
label: question_name,
|
||||||
value: detail,
|
value: detail,
|
||||||
@@ -503,7 +642,11 @@ const RightQAContent = forwardRef(
|
|||||||
tag: detail.tag,
|
tag: detail.tag,
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
{parse(commentHtmlText)}
|
<QaPreviewContent
|
||||||
|
html={commentHtmlText}
|
||||||
|
hasContent={hasCommentContent}
|
||||||
|
emptyText="暂无批注内容"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -516,52 +659,89 @@ const RightQAContent = forwardRef(
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Box>
|
</ScrollArea>
|
||||||
<Box
|
<Box
|
||||||
style={{ borderTop: "2px solid var(--mantine-color-gray-3)" }}
|
style={{
|
||||||
|
borderTop:
|
||||||
|
"1px solid light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
pt="sm"
|
pt="sm"
|
||||||
px="sm">
|
px="sm"
|
||||||
|
pb="sm">
|
||||||
<Stack
|
<Stack
|
||||||
gap="xs"
|
gap="xs"
|
||||||
opacity={!isView ? 1 : 0.5}
|
opacity={!isView ? 1 : 0.5}
|
||||||
style={{ pointerEvents: !isView ? "auto" : "none" }}>
|
style={{ pointerEvents: !isView ? "auto" : "none" }}>
|
||||||
<Flex align="center" gap="xs">
|
<Box
|
||||||
<Text fw={600}>问题</Text>
|
p="xs"
|
||||||
<Text>{selectedQuestion?.label || ""}</Text>
|
style={{
|
||||||
</Flex>
|
border:
|
||||||
<Flex align="center" gap="xs">
|
"1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4))",
|
||||||
<Text>轮次</Text>
|
borderRadius: "var(--mantine-radius-md)",
|
||||||
<NumberInput
|
background:
|
||||||
size="xs"
|
"light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6))",
|
||||||
min={1}
|
}}>
|
||||||
{...form.getInputProps("round")}
|
<Flex
|
||||||
onChange={(val) => onFieldChange("round", val)}
|
align="flex-end"
|
||||||
disabled={
|
justify="space-between"
|
||||||
!selectedQuestion ||
|
gap="sm"
|
||||||
(selectedQuestion && selectedQuestion.value?.is_pre)
|
wrap="wrap">
|
||||||
}
|
<Box style={{ flex: 1, minWidth: 140 }}>
|
||||||
/>
|
<Text size="xs" c="dimmed">
|
||||||
<Radio.Group
|
当前问题
|
||||||
size="xs"
|
</Text>
|
||||||
{...form.getInputProps("tag")}
|
<Text size="sm" fw={500} truncate="end">
|
||||||
onChange={(val) => onFieldChange("tag", Number(val))}>
|
{selectedQuestion?.label || "未选择问题"}
|
||||||
<Group gap="xs">
|
</Text>
|
||||||
<Radio
|
</Box>
|
||||||
label="正确"
|
<Box w={100}>
|
||||||
value={1}
|
<Text size="xs" c="dimmed" mb={4}>
|
||||||
disabled={!isReview || isView}
|
轮次
|
||||||
|
</Text>
|
||||||
|
<NumberInput
|
||||||
|
size="xs"
|
||||||
|
min={1}
|
||||||
|
{...form.getInputProps("round")}
|
||||||
|
onChange={(val) => onFieldChange("round", val)}
|
||||||
|
disabled={
|
||||||
|
!selectedQuestion ||
|
||||||
|
(selectedQuestion && selectedQuestion.value?.is_pre)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Radio
|
</Box>
|
||||||
label="错误"
|
</Flex>
|
||||||
value={2}
|
<Flex align="center" gap="xs" mt="xs">
|
||||||
disabled={!isReview || isView}
|
<Text size="xs" c="dimmed">
|
||||||
/>
|
审核结果
|
||||||
</Group>
|
</Text>
|
||||||
</Radio.Group>
|
<Radio.Group
|
||||||
</Flex>
|
size="xs"
|
||||||
|
{...form.getInputProps("tag")}
|
||||||
|
onChange={(val) => onFieldChange("tag", Number(val))}>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Radio
|
||||||
|
label="正确"
|
||||||
|
value={1}
|
||||||
|
disabled={!isReview || isView}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
label="错误"
|
||||||
|
value={2}
|
||||||
|
disabled={!isReview || isView}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Radio.Group>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
内容编辑
|
||||||
|
</Text>
|
||||||
<EditorContainer
|
<EditorContainer
|
||||||
value={form.values.value}
|
value={form.values.value}
|
||||||
onChange={(val: any) => onFieldChange("value", val)}
|
onChange={(val: string) =>
|
||||||
|
onFieldChange("value", getHtmlFromStoredValue(val), val)
|
||||||
|
}
|
||||||
disabled={!selectedQuestion}
|
disabled={!selectedQuestion}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -601,7 +781,7 @@ const RightQAContent = forwardRef(
|
|||||||
const finalData = safeClone(currentQaData)
|
const finalData = safeClone(currentQaData)
|
||||||
const updateData = safeClone(currentOperation)
|
const updateData = safeClone(currentOperation)
|
||||||
let obj = {
|
let obj = {
|
||||||
value: formData.value ?? "<p></p>",
|
value: ensureStoredValue(formData.value ?? "<p></p>"),
|
||||||
id: formData.round_id,
|
id: formData.round_id,
|
||||||
is_pre: false,
|
is_pre: false,
|
||||||
tag: 0,
|
tag: 0,
|
||||||
@@ -657,7 +837,9 @@ const RightQAContent = forwardRef(
|
|||||||
</Text>
|
</Text>
|
||||||
<EditorContainer
|
<EditorContainer
|
||||||
value={textForm.values.value}
|
value={textForm.values.value}
|
||||||
onChange={(val: any) => textForm.setFieldValue("value", val)}
|
onChange={(val: string) =>
|
||||||
|
textForm.setFieldValue("value", getHtmlFromStoredValue(val))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ const TopTools = (
|
|||||||
isLabelTask,
|
isLabelTask,
|
||||||
renderPolygons,
|
renderPolygons,
|
||||||
} = props
|
} = props
|
||||||
const { backUrl } = useBackUrlStore()
|
const { backUrl, basePath } = useBackUrlStore()
|
||||||
const { mode, loadingData } = usePaperStore()
|
const { mode, loadingData } = usePaperStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user_id = usePermissionStore.getState().user_id
|
const user_id = usePermissionStore.getState().user_id
|
||||||
@@ -1091,9 +1091,9 @@ const TopTools = (
|
|||||||
useObjectStore.getState().resetPathAndOperationStatus()
|
useObjectStore.getState().resetPathAndOperationStatus()
|
||||||
} else {
|
} else {
|
||||||
// 无id返回时跳转回任务列表页
|
// 无id返回时跳转回任务列表页
|
||||||
const url = backUrl || "/"
|
const url = backUrl ? basePath + backUrl : "/"
|
||||||
router.push(url)
|
// router.push(url)
|
||||||
// window.location.href = url
|
window.location.href = url
|
||||||
// 重置图片比例及选中标注对象
|
// 重置图片比例及选中标注对象
|
||||||
usePaperStore.getState().resetRasterScale()
|
usePaperStore.getState().resetRasterScale()
|
||||||
useObjectStore.getState().resetPathAndOperationStatus()
|
useObjectStore.getState().resetPathAndOperationStatus()
|
||||||
@@ -1757,9 +1757,9 @@ const TopTools = (
|
|||||||
c="dark"
|
c="dark"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isView) {
|
if (isView) {
|
||||||
const url = backUrl || "/"
|
const url = backUrl ? basePath + backUrl : "/"
|
||||||
router.push(url)
|
// router.push(url)
|
||||||
// window.location.href = url
|
window.location.href = url
|
||||||
// handleBackup("auto_backup");
|
// handleBackup("auto_backup");
|
||||||
// 重置图片比例及选中标注对象
|
// 重置图片比例及选中标注对象
|
||||||
usePaperStore.getState().resetRasterScale()
|
usePaperStore.getState().resetRasterScale()
|
||||||
@@ -1827,7 +1827,7 @@ const TopTools = (
|
|||||||
</HoverCard.Dropdown>
|
</HoverCard.Dropdown>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
<Box style={{ maxWidth: 300 }}>
|
<Box style={{ maxWidth: 300 }}>
|
||||||
<Text truncate="end" size="sm">
|
<Text truncate="end" size="sm" title={activeImage}>
|
||||||
{activeImage}
|
{activeImage}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -2450,9 +2450,9 @@ const TopTools = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setConfirmOpen(false)
|
setConfirmOpen(false)
|
||||||
const url = backUrl || "/"
|
const url = backUrl ? basePath + backUrl : "/"
|
||||||
router.push(url)
|
// router.push(url)
|
||||||
// window.location.href = url
|
window.location.href = url
|
||||||
// 重置图片比例及选中标注对象
|
// 重置图片比例及选中标注对象
|
||||||
usePaperStore.getState().resetRasterScale()
|
usePaperStore.getState().resetRasterScale()
|
||||||
useObjectStore.getState().resetPathAndOperationStatus()
|
useObjectStore.getState().resetPathAndOperationStatus()
|
||||||
@@ -2461,9 +2461,9 @@ const TopTools = (
|
|||||||
try {
|
try {
|
||||||
setConfirmOpen(false)
|
setConfirmOpen(false)
|
||||||
handleBackup("auto_backup")
|
handleBackup("auto_backup")
|
||||||
const url = backUrl || "/"
|
const url = backUrl ? basePath + backUrl : "/"
|
||||||
router.push(url)
|
// router.push(url)
|
||||||
// window.location.href = url
|
window.location.href = url
|
||||||
// 重置图片比例及选中标注对象
|
// 重置图片比例及选中标注对象
|
||||||
usePaperStore.getState().resetRasterScale()
|
usePaperStore.getState().resetRasterScale()
|
||||||
useObjectStore.getState().resetPathAndOperationStatus()
|
useObjectStore.getState().resetPathAndOperationStatus()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ALL_CODE } from "../utils/constants"
|
import dayjs from "dayjs"
|
||||||
import { create } from "zustand"
|
import { create } from "zustand"
|
||||||
import { persist } from "zustand/middleware"
|
import { persist } from "zustand/middleware"
|
||||||
import { storage } from "../store"
|
import { storage } from "../store"
|
||||||
import dayjs from "dayjs"
|
import { ALL_CODE } from "../utils/constants"
|
||||||
|
|
||||||
const initUserInfo = {
|
const initUserInfo = {
|
||||||
user_id: null,
|
user_id: null,
|
||||||
@@ -263,8 +263,13 @@ export const useBackUrlStore = create(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
backUrl: null,
|
backUrl: null,
|
||||||
backTitle: null,
|
backTitle: null,
|
||||||
setBackProps: (url: string, title?: string) =>
|
basePath: "",
|
||||||
set({ backUrl: url, backTitle: title || null }),
|
setBackProps: (url: string, title?: string, basePath?: string) =>
|
||||||
|
set({
|
||||||
|
backUrl: url,
|
||||||
|
backTitle: title || null,
|
||||||
|
basePath: basePath || "",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "back_url",
|
name: "back_url",
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ export const componentList: MenuItem[] = [
|
|||||||
url: "video",
|
url: "video",
|
||||||
icon: "SystemIcon",
|
icon: "SystemIcon",
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: "点云标注",
|
// title: "点云标注",
|
||||||
url: "lidar",
|
// url: "lidar",
|
||||||
icon: "SystemIcon",
|
// icon: "SystemIcon",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: "管理中心",
|
title: "管理中心",
|
||||||
url: "mgt",
|
url: "mgt",
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { Login } from "./types"
|
|||||||
|
|
||||||
const BASE_PATH =
|
const BASE_PATH =
|
||||||
process.env.NEXT_PUBLIC_ENV === "production"
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
? "https://label.cowarobot.com"
|
? "https://label.soft.cowarobot.com"
|
||||||
: process.env.NEXT_PUBLIC_ENV === "staging"
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
? "https://label.softtest.cowarobot.com"
|
? "http://172.16.115.128:9110"
|
||||||
: "https://label.softtest.cowarobot.com"
|
: "https://label.softtest.cowarobot.com"
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
digitalspace_labelimage_ssr:
|
web_digitalspace_labelimage_ssr:
|
||||||
container_name: digitalspace_labelimage_ssr
|
container_name: web_digitalspace_labelimage_ssr
|
||||||
image: harbor.cowarobot.cn/gopro/digitalspace_labelimage_ssr:stage_latest
|
image: harbor.cowarobot.cn/softrepo/web_digitalspace_labelimage_ssr:stage_latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 5532:5532
|
- 5532:5532
|
||||||
|
|||||||
Reference in New Issue
Block a user