You've already forked DataMate
feat(annotation): implement file version management for annotation feature
Add support for detecting new file versions and switching to them: Backend Changes: - Add file_version column to AnnotationResult model - Create Alembic migration for database schema update - Implement check_file_version() method to compare annotation and file versions - Implement use_new_version() method to clear annotations and update version - Update upsert_annotation() to record file version when saving - Add new API endpoints: GET /version and POST /use-new-version - Add FileVersionCheckResponse and UseNewVersionResponse schemas Frontend Changes: - Add checkFileVersionUsingGet and useNewVersionUsingPost API calls - Add version warning banner showing current vs latest file version - Add 'Use New Version' button with confirmation dialog - Clear version info state when switching files to avoid stale warnings Bug Fixes: - Fix previousFileVersion returning updated value (save before update) - Handle null file_version for historical data compatibility - Fix segmented annotation clearing (preserve structure, clear results) - Fix files without annotations incorrectly showing new version warnings - Preserve total_segments when clearing segmented annotations Files Modified: - frontend/src/pages/DataAnnotation/Annotate/LabelStudioTextEditor.tsx - frontend/src/pages/DataAnnotation/annotation.api.ts - runtime/datamate-python/app/db/models/annotation_management.py - runtime/datamate-python/app/module/annotation/interface/editor.py - runtime/datamate-python/app/module/annotation/schema/editor.py - runtime/datamate-python/app/module/annotation/service/editor.py New Files: - runtime/datamate-python/alembic.ini - runtime/datamate-python/alembic/env.py - runtime/datamate-python/alembic/script.py.mako - runtime/datamate-python/alembic/versions/20250205_0001_add_file_version.py
This commit is contained in:
@@ -8,6 +8,9 @@ import {
|
||||
getEditorTaskUsingGet,
|
||||
listEditorTasksUsingGet,
|
||||
upsertEditorAnnotationUsingPut,
|
||||
checkFileVersionUsingGet,
|
||||
useNewVersionUsingPost,
|
||||
type FileVersionCheckResponse,
|
||||
} from "../annotation.api";
|
||||
import { AnnotationResultStatus } from "../annotation.model";
|
||||
|
||||
@@ -269,6 +272,11 @@ export default function LabelStudioTextEditor() {
|
||||
return Array.from({ length: segmentTotal }, (_, index) => index);
|
||||
}, [segmentTotal]);
|
||||
|
||||
// 文件版本相关状态
|
||||
const [fileVersionInfo, setFileVersionInfo] = useState<FileVersionCheckResponse | null>(null);
|
||||
const [checkingFileVersion, setCheckingFileVersion] = useState(false);
|
||||
const [usingNewVersion, setUsingNewVersion] = useState(false);
|
||||
|
||||
const focusIframe = useCallback(() => {
|
||||
const iframe = iframeRef.current;
|
||||
if (!iframe) return;
|
||||
@@ -548,6 +556,77 @@ export default function LabelStudioTextEditor() {
|
||||
}
|
||||
}, [iframeReady, message, postToIframe, project, projectId]);
|
||||
|
||||
const checkFileVersion = useCallback(async (fileId: string) => {
|
||||
if (!projectId || !fileId) return;
|
||||
setCheckingFileVersion(true);
|
||||
try {
|
||||
const resp = (await checkFileVersionUsingGet(projectId, fileId)) as ApiResponse<FileVersionCheckResponse>;
|
||||
const data = resp?.data;
|
||||
if (data) {
|
||||
setFileVersionInfo(data);
|
||||
if (data.hasNewVersion) {
|
||||
modal.warning({
|
||||
title: "文件有新版本",
|
||||
content: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography.Text>
|
||||
文件已更新到新版本(当前版本: {data.currentFileVersion},标注版本: {data.annotationFileVersion})。
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
点击"使用新版本"可清空当前标注并使用最新版本的文件内容。
|
||||
</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
okText: "我知道了",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("检查文件版本失败", e);
|
||||
} finally {
|
||||
setCheckingFileVersion(false);
|
||||
}
|
||||
}, [modal, message, projectId]);
|
||||
|
||||
const handleUseNewVersion = useCallback(async () => {
|
||||
if (!selectedFileId) return;
|
||||
|
||||
modal.confirm({
|
||||
title: "确认使用新版本",
|
||||
content: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography.Text>
|
||||
确认使用新版本?这将清空当前标注并使用最新版本的文件内容。
|
||||
</Typography.Text>
|
||||
{fileVersionInfo && (
|
||||
<Typography.Text type="secondary">
|
||||
当前标注版本: {fileVersionInfo.annotationFileVersion},最新文件版本: {fileVersionInfo.currentFileVersion}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
okText: "确认",
|
||||
okType: "danger",
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
if (!projectId || !selectedFileId) return;
|
||||
setUsingNewVersion(true);
|
||||
try {
|
||||
await useNewVersionUsingPost(projectId, selectedFileId);
|
||||
message.success("已使用新版本并清空标注");
|
||||
setFileVersionInfo(null);
|
||||
await loadTasks({ mode: "reset" });
|
||||
await initEditorForFile(selectedFileId);
|
||||
} catch (e) {
|
||||
console.error("使用新版本失败", e);
|
||||
message.error("使用新版本失败");
|
||||
} finally {
|
||||
setUsingNewVersion(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
}, [modal, message, projectId, selectedFileId, fileVersionInfo, loadTasks, initEditorForFile]);
|
||||
|
||||
const advanceAfterSave = useCallback(async (fileId: string, segmentIndex?: number) => {
|
||||
if (!fileId) return;
|
||||
if (segmented && segmentTotal > 0) {
|
||||
@@ -815,6 +894,13 @@ export default function LabelStudioTextEditor() {
|
||||
return () => window.removeEventListener("message", handler);
|
||||
}, [message, origin, saveFromExport]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedFileId && project?.supported) {
|
||||
setFileVersionInfo(null);
|
||||
checkFileVersion(selectedFileId);
|
||||
}
|
||||
}, [selectedFileId, project?.supported, checkFileVersion]);
|
||||
|
||||
const canLoadMore = taskTotalPages > 0 && taskPage + 1 < taskTotalPages;
|
||||
const saveDisabled =
|
||||
!iframeReady || !selectedFileId || saving || loadingTaskDetail;
|
||||
@@ -896,6 +982,22 @@ export default function LabelStudioTextEditor() {
|
||||
</Typography.Title>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
{fileVersionInfo?.hasNewVersion && (
|
||||
<div className="flex items-center gap-2 mr-4">
|
||||
<Typography.Text type="warning" className="text-xs">
|
||||
⚠ 文件有新版本({fileVersionInfo.currentFileVersion} > {fileVersionInfo.annotationFileVersion})
|
||||
</Typography.Text>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
danger
|
||||
loading={usingNewVersion}
|
||||
onClick={handleUseNewVersion}
|
||||
>
|
||||
使用新版本
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
|
||||
@@ -88,7 +88,7 @@ export function getEditorTaskSegmentUsingGet(
|
||||
return get(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}/segments`, params);
|
||||
}
|
||||
|
||||
export function upsertEditorAnnotationUsingPut(
|
||||
export function upsertEditorAnnotationUsingPut(
|
||||
projectId: string,
|
||||
fileId: string,
|
||||
data: {
|
||||
@@ -97,8 +97,31 @@ export function upsertEditorAnnotationUsingPut(
|
||||
segmentIndex?: number;
|
||||
}
|
||||
) {
|
||||
return put(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}/annotation`, data);
|
||||
}
|
||||
return put(`/api/annotation/editor/projects/${projectId}/tasks/${fileId}/annotation`, data);
|
||||
}
|
||||
|
||||
export interface FileVersionCheckResponse {
|
||||
fileId: string;
|
||||
currentFileVersion: number;
|
||||
annotationFileVersion: number | null;
|
||||
hasNewVersion: boolean;
|
||||
}
|
||||
|
||||
export function checkFileVersionUsingGet(projectId: string, fileId: string) {
|
||||
return get(`/api/annotation/editor/projects/${projectId}/files/${fileId}/version`);
|
||||
}
|
||||
|
||||
export interface UseNewVersionResponse {
|
||||
fileId: string;
|
||||
previousFileVersion: number | null;
|
||||
currentFileVersion: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function useNewVersionUsingPost(projectId: string, fileId: string) {
|
||||
return post(`/api/annotation/editor/projects/${projectId}/files/${fileId}/use-new-version`, {});
|
||||
}
|
||||
|
||||
|
||||
// =====================
|
||||
// 标注数据导出
|
||||
|
||||
Reference in New Issue
Block a user