feat(annotation): 优化文本标注分段功能实现

- 新增 getEditorTaskSegmentsUsingGet 接口用于获取任务分段信息
- 移除 SegmentInfo 中的 text、start、end 字段,精简数据结构
- 添加 EditorTaskSegmentsResponse 类型定义用于分段摘要响应
- 实现服务端 get_task_segments 方法,支持分段信息查询
- 重构前端组件缓存机制,使用 segmentSummaryFileRef 管理分段状态
- 优化分段构建逻辑,提取 _build_segment_contexts 公共方法
- 调整后端 _build_text_task 方法中的分段处理流程
- 更新 API 类型定义,统一 RequestParams 和 RequestPayload 类型
This commit is contained in:
2026-02-04 16:59:04 +08:00
parent 394e2bda18
commit cda22a720c
5 changed files with 250 additions and 108 deletions

View File

@@ -6,6 +6,7 @@ import { useNavigate, useParams } from "react-router";
import {
getEditorProjectInfoUsingGet,
getEditorTaskUsingGet,
getEditorTaskSegmentsUsingGet,
listEditorTasksUsingGet,
upsertEditorAnnotationUsingPut,
} from "../annotation.api";
@@ -38,9 +39,6 @@ type LsfMessage = {
type SegmentInfo = {
idx: number;
text: string;
start: number;
end: number;
hasAnnotation: boolean;
lineIndex: number;
chunkIndex: number;
@@ -66,10 +64,16 @@ type EditorTaskPayload = {
type EditorTaskResponse = {
task?: EditorTaskPayload;
segmented?: boolean;
segments?: SegmentInfo[];
totalSegments?: number;
currentSegmentIndex?: number;
};
type EditorTaskSegmentsResponse = {
segmented?: boolean;
segments?: SegmentInfo[];
totalSegments?: number;
};
type EditorTaskListResponse = {
content?: EditorTaskListItem[];
totalElements?: number;
@@ -288,6 +292,7 @@ export default function LabelStudioTextEditor() {
const segmentStatsCacheRef = useRef<Record<string, SegmentStats>>({});
const segmentStatsSeqRef = useRef(0);
const segmentStatsLoadingRef = useRef<Set<string>>(new Set());
const segmentSummaryFileRef = useRef<string>("");
const [loadingProject, setLoadingProject] = useState(true);
const [loadingTasks, setLoadingTasks] = useState(false);
@@ -358,9 +363,7 @@ export default function LabelStudioTextEditor() {
if (segmentStatsCacheRef.current[fileId] || segmentStatsLoadingRef.current.has(fileId)) return;
segmentStatsLoadingRef.current.add(fileId);
try {
const resp = (await getEditorTaskUsingGet(projectId, fileId, {
segmentIndex: 0,
})) as ApiResponse<EditorTaskResponse>;
const resp = (await getEditorTaskSegmentsUsingGet(projectId, fileId)) as ApiResponse<EditorTaskSegmentsResponse>;
if (segmentStatsSeqRef.current !== seq) return;
const data = resp?.data;
if (!data?.segmented) return;
@@ -591,20 +594,38 @@ export default function LabelStudioTextEditor() {
if (seq !== initSeqRef.current) return;
// 更新分段状态
const segmentIndex = data?.segmented
const isSegmented = !!data?.segmented;
const segmentIndex = isSegmented
? resolveSegmentIndex(data.currentSegmentIndex) ?? 0
: undefined;
if (data?.segmented) {
const stats = buildSegmentStats(data.segments);
if (isSegmented) {
let nextSegments: SegmentInfo[] = [];
if (segmentSummaryFileRef.current === fileId && segments.length > 0) {
nextSegments = segments;
} else {
try {
const segmentResp = (await getEditorTaskSegmentsUsingGet(projectId, fileId)) as ApiResponse<EditorTaskSegmentsResponse>;
if (seq !== initSeqRef.current) return;
const segmentData = segmentResp?.data;
if (segmentData?.segmented) {
nextSegments = Array.isArray(segmentData.segments) ? segmentData.segments : [];
}
} catch (e) {
console.error(e);
}
}
const stats = buildSegmentStats(nextSegments);
setSegmented(true);
setSegments(data.segments || []);
setSegments(nextSegments);
setCurrentSegmentIndex(segmentIndex ?? 0);
updateSegmentStatsCache(fileId, stats);
segmentSummaryFileRef.current = fileId;
} else {
setSegmented(false);
setSegments([]);
setCurrentSegmentIndex(0);
updateSegmentStatsCache(fileId, null);
segmentSummaryFileRef.current = fileId;
}
const taskData = {
@@ -664,7 +685,7 @@ export default function LabelStudioTextEditor() {
} finally {
if (seq === initSeqRef.current) setLoadingTaskDetail(false);
}
}, [iframeReady, message, postToIframe, project, projectId, updateSegmentStatsCache]);
}, [iframeReady, message, postToIframe, project, projectId, segments, updateSegmentStatsCache]);
const advanceAfterSave = useCallback(async (fileId: string, segmentIndex?: number) => {
if (!fileId) return;
@@ -979,6 +1000,7 @@ export default function LabelStudioTextEditor() {
setSegmented(false);
setSegments([]);
setCurrentSegmentIndex(0);
segmentSummaryFileRef.current = "";
savedSnapshotsRef.current = {};
segmentStatsSeqRef.current += 1;
segmentStatsCacheRef.current = {};

View File

@@ -1,20 +1,23 @@
import { get, post, put, del, download } from "@/utils/request";
// 导出格式类型
export type ExportFormat = "json" | "jsonl" | "csv" | "coco" | "yolo";
// 标注任务管理相关接口
export function queryAnnotationTasksUsingGet(params?: any) {
return get("/api/annotation/project", params);
}
export function createAnnotationTaskUsingPost(data: any) {
return post("/api/annotation/project", data);
}
export function syncAnnotationTaskUsingPost(data: any) {
return post(`/api/annotation/task/sync`, data);
}
// 导出格式类型
export type ExportFormat = "json" | "jsonl" | "csv" | "coco" | "yolo";
type RequestParams = Record<string, unknown>;
type RequestPayload = Record<string, unknown>;
// 标注任务管理相关接口
export function queryAnnotationTasksUsingGet(params?: RequestParams) {
return get("/api/annotation/project", params);
}
export function createAnnotationTaskUsingPost(data: RequestPayload) {
return post("/api/annotation/project", data);
}
export function syncAnnotationTaskUsingPost(data: RequestPayload) {
return post(`/api/annotation/task/sync`, data);
}
export function deleteAnnotationTaskByIdUsingDelete(mappingId: string) {
// Backend expects mapping UUID as path parameter
@@ -25,9 +28,9 @@ export function getAnnotationTaskByIdUsingGet(taskId: string) {
return get(`/api/annotation/project/${taskId}`);
}
export function updateAnnotationTaskByIdUsingPut(taskId: string, data: any) {
return put(`/api/annotation/project/${taskId}`, data);
}
export function updateAnnotationTaskByIdUsingPut(taskId: string, data: RequestPayload) {
return put(`/api/annotation/project/${taskId}`, data);
}
// 标签配置管理
export function getTagConfigUsingGet() {
@@ -35,20 +38,20 @@ export function getTagConfigUsingGet() {
}
// 标注模板管理
export function queryAnnotationTemplatesUsingGet(params?: any) {
return get("/api/annotation/template", params);
}
export function queryAnnotationTemplatesUsingGet(params?: RequestParams) {
return get("/api/annotation/template", params);
}
export function createAnnotationTemplateUsingPost(data: RequestPayload) {
return post("/api/annotation/template", data);
}
export function createAnnotationTemplateUsingPost(data: any) {
return post("/api/annotation/template", data);
}
export function updateAnnotationTemplateByIdUsingPut(
templateId: string | number,
data: any
) {
return put(`/api/annotation/template/${templateId}`, data);
}
export function updateAnnotationTemplateByIdUsingPut(
templateId: string | number,
data: RequestPayload
) {
return put(`/api/annotation/template/${templateId}`, data);
}
export function deleteAnnotationTemplateByIdUsingDelete(
templateId: string | number
@@ -65,27 +68,31 @@ export function getEditorProjectInfoUsingGet(projectId: string) {
return get(`/api/annotation/editor/projects/${projectId}`);
}
export function listEditorTasksUsingGet(projectId: string, params?: any) {
return get(`/api/annotation/editor/projects/${projectId}/tasks`, params);
}
export function listEditorTasksUsingGet(projectId: string, params?: RequestParams) {
return get(`/api/annotation/editor/projects/${projectId}/tasks`, params);
}
export function getEditorTaskUsingGet(
projectId: string,
fileId: string,
params?: { segmentIndex?: number }
) {
return get(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}`, params);
}
export function getEditorTaskUsingGet(
projectId: string,
fileId: string,
params?: { segmentIndex?: number }
) {
return get(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}`, params);
}
export function getEditorTaskSegmentsUsingGet(projectId: string, fileId: string) {
return get(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}/segments`);
}
export function upsertEditorAnnotationUsingPut(
projectId: string,
fileId: string,
data: {
annotation: any;
expectedUpdatedAt?: string;
segmentIndex?: number;
}
) {
projectId: string,
fileId: string,
data: {
annotation: Record<string, unknown>;
expectedUpdatedAt?: string;
segmentIndex?: number;
}
) {
return put(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}/annotation`, data);
}