feat(annotation): 添加不适用标注状态支持

- 在 AnnotationResultStatus 枚举中新增 NOT_APPLICABLE 状态
- 将无标注/不适用合并为两个独立的状态选项
- 更新前端标签显示逻辑以支持新的状态类型
- 修改确认对话框允许选择不适用状态
- 在后端数据库模型中添加 NOT_APPLICABLE 状态值
- 更新 API schema 描述以反映新的状态选项
- 调整标注状态判断和保存逻辑以处理三种状态
- 更新数据库表结构注释包含新状态类型
This commit is contained in:
2026-01-31 13:28:08 +08:00
parent f4fc574687
commit f2403f00ce
6 changed files with 43 additions and 19 deletions

View File

@@ -90,9 +90,11 @@ type SwitchDecision = "save" | "discard" | "cancel";
const LSF_IFRAME_SRC = "/lsf/lsf.html";
const TASK_PAGE_START = 0;
const TASK_PAGE_SIZE = 200;
const NO_ANNOTATION_LABEL = "无标注/不适用";
const NO_ANNOTATION_LABEL = "无标注";
const NOT_APPLICABLE_LABEL = "不适用";
const NO_ANNOTATION_CONFIRM_TITLE = "没有标注任何内容";
const NO_ANNOTATION_CONFIRM_OK_TEXT = "设为无标注并保存";
const NOT_APPLICABLE_CONFIRM_TEXT = "设为不适用并保存";
const NO_ANNOTATION_CONFIRM_CANCEL_TEXT = "继续标注";
type NormalizedTaskList = {
@@ -140,6 +142,9 @@ const resolveTaskStatusMeta = (item: EditorTaskListItem) => {
if (item.annotationStatus === AnnotationResultStatus.NO_ANNOTATION) {
return { text: NO_ANNOTATION_LABEL, type: "warning" as const };
}
if (item.annotationStatus === AnnotationResultStatus.NOT_APPLICABLE) {
return { text: NOT_APPLICABLE_LABEL, type: "warning" as const };
}
return { text: "已标注", type: "success" as const };
};
@@ -271,26 +276,32 @@ export default function LabelStudioTextEditor() {
win.postMessage({ type, payload }, origin);
}, [origin]);
const confirmNoAnnotation = useCallback(() => {
return new Promise<boolean>((resolve) => {
const confirmEmptyAnnotationStatus = useCallback(() => {
return new Promise<AnnotationResultStatus | null>((resolve) => {
let resolved = false;
const settle = (value: boolean) => {
let modalInstance: { destroy: () => void } | null = null;
const settle = (value: AnnotationResultStatus | null) => {
if (resolved) return;
resolved = true;
resolve(value);
if (modalInstance) modalInstance.destroy();
};
modal.confirm({
const handleNotApplicable = () => settle(AnnotationResultStatus.NOT_APPLICABLE);
modalInstance = modal.confirm({
title: NO_ANNOTATION_CONFIRM_TITLE,
content: (
<div className="flex flex-col gap-2">
<Typography.Text></Typography.Text>
<Typography.Text type="secondary">/</Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
<Button type="link" style={{ padding: 0, height: "auto" }} onClick={handleNotApplicable}>
{NOT_APPLICABLE_CONFIRM_TEXT}
</Button>
</div>
),
okText: NO_ANNOTATION_CONFIRM_OK_TEXT,
cancelText: NO_ANNOTATION_CONFIRM_CANCEL_TEXT,
onOk: () => settle(true),
onCancel: () => settle(false),
onOk: () => settle(AnnotationResultStatus.NO_ANNOTATION),
onCancel: () => settle(null),
});
});
}, [modal]);
@@ -588,15 +599,17 @@ export default function LabelStudioTextEditor() {
: undefined;
const annotationRecord = annotation as Record<string, unknown>;
const currentTask = tasks.find((item) => item.fileId === String(fileId));
const currentStatus = currentTask?.annotationStatus;
const hasExistingAnnotation = !!currentTask?.hasAnnotation;
let resolvedStatus: AnnotationResultStatus;
if (isAnnotationResultEmpty(annotationRecord)) {
const currentStatus = tasks.find((item) => item.fileId === String(fileId))?.annotationStatus;
if (currentStatus === AnnotationResultStatus.NO_ANNOTATION) {
resolvedStatus = AnnotationResultStatus.NO_ANNOTATION;
if (currentStatus === AnnotationResultStatus.ANNOTATED || (hasExistingAnnotation && !currentStatus)) {
resolvedStatus = AnnotationResultStatus.ANNOTATED;
} else {
const confirmed = await confirmNoAnnotation();
if (!confirmed) return false;
resolvedStatus = AnnotationResultStatus.NO_ANNOTATION;
const selectedStatus = await confirmEmptyAnnotationStatus();
if (!selectedStatus) return false;
resolvedStatus = selectedStatus;
}
} else {
resolvedStatus = AnnotationResultStatus.ANNOTATED;
@@ -651,7 +664,7 @@ export default function LabelStudioTextEditor() {
}
}, [
advanceAfterSave,
confirmNoAnnotation,
confirmEmptyAnnotationStatus,
currentSegmentIndex,
message,
projectId,

View File

@@ -11,6 +11,7 @@ export enum AnnotationTaskStatus {
export enum AnnotationResultStatus {
ANNOTATED = "ANNOTATED",
NO_ANNOTATION = "NO_ANNOTATION",
NOT_APPLICABLE = "NOT_APPLICABLE",
}
export interface AnnotationTask {