关键改变:在初始化 LabelStudio 时,直接在 task 对象中传递 annotations 数组,而不是在 onStorageInitialized 中手动创建。
// 准备标注数据
const annotations = [];
const existingResult = loadedAnnotationRef.current;
if (existingResult && existingResult.result && Array.isArray(existingResult.result)) {
annotations.push({
id: 'existing',
result: existingResult.result,
});
}
// 初始化 LabelStudio
lsfInstanceRef.current = new LabelStudio(editorContainerRef.current, {
config: currentProject.config,
task: {
id: ...,
data: currentTask.data,
annotations: annotations.length > 0 ? annotations : undefined, // 关键!
},
// ...
});
onStorageInitialized: (LS: any) => {
const initAnnotation = () => {
const as = LS.annotationStore;
// 如果有已加载的标注,选择第一个
if (as.annotations && as.annotations.length > 0) {
as.selectAnnotation(as.annotations[0].id);
// 设置 snapshot 监听器
} else {
// 创建新的空白标注
const annotation = as.createAnnotation();
as.selectAnnotation(annotation.id);
}
};
setTimeout(initAnnotation, 100);
}
const loadedAnnotationRef = useRef<any>(null); // 存储从 API 加载的标注
const annotationResultRef = useRef<any>(null); // 存储当前编辑器的标注
这样可以避免在清理时丢失已加载的数据。
问题:在组件卸载时,snapshot 监听器还在尝试访问已销毁的 MobX 对象。
解决方案:
isCleanedUp 和 lsfInstanceRef.current 确保实例还存在先销毁 snapshot 监听器,再销毁 LabelStudio 实例
// 在 snapshot 回调中
snapshotDisposer = onSnapshot(annotation, () => {
if (!isCleanedUp && lsfInstanceRef.current) {
try {
annotationResultRef.current = annotation.serializeAnnotation();
} catch (e) {
// Ignore errors during cleanup
console.warn('Error serializing annotation:', e);
}
}
});
// 清理顺序
function cleanup() {
// 1. 先销毁 snapshot 监听器
if (snapshotDisposer) {
snapshotDisposer();
snapshotDisposer = null;
}
// 2. 再销毁 LabelStudio 实例
if (lsfInstanceRef.current) {
lsfInstanceRef.current.destroy();
lsfInstanceRef.current = null;
}
}
LabelStudio 支持两种方式加载标注:
通过 task.annotations(推荐)✅
task: {
data: {...},
annotations: [{ id: 'xxx', result: [...] }]
}
通过 createAnnotation()(不推荐用于加载已有标注)❌
as.createAnnotation({ result: [...] })
我们使用第一种方式,因为:
MobX State Tree 要求:
正确的清理顺序:
isCleanedUp = true 标志web/apps/lq_label/src/views/annotation-view/annotation-view.tsx - 标注视图(主要修改)web/apps/lq_label/src/services/api.ts - API 服务backend/routers/annotation.py - 标注 API 路由backend/routers/task.py - 任务 API 路由移除调试日志
添加加载状态
性能优化
用户体验
通过使用 LabelStudio 的标准 API 和正确的清理逻辑,我们成功解决了:
现在标注系统可以正常工作,用户可以: