修正GT未知类别导入为待分类

- GT Mask 未知 maskid 选择保留时落到黑色 maskid:0 的待分类类别,而不是绿色未定义类别。

- 前端导入预览中未知 maskid 使用黑色待分类覆盖色,并把按钮文案改为导入为待分类。

- 待分类兜底颜色统一为 #000000,和模板保留类、GT_label/Pro_label 导出规则一致。

- 补充后端回归断言并更新 AGENTS 与文档说明,保留 gt_unknown_class 和原始 gt_label_value 供后续重命名追溯。
This commit is contained in:
2026-05-04 06:06:04 +08:00
parent 5d73eacefe
commit 10fe17476d
11 changed files with 47 additions and 28 deletions

View File

@@ -265,7 +265,7 @@ describe('VideoWorkspace', () => {
className: '待分类',
classMaskId: 0,
classId: undefined,
color: '#9ca3af',
color: '#000000',
saved: false,
saveStatus: 'dirty',
metadata: expect.objectContaining({
@@ -1712,8 +1712,8 @@ describe('VideoWorkspace', () => {
const file = new File(['mask'], 'mask.png', { type: 'image/png' });
fireEvent.change(fileInput, { target: { files: [file] } });
expect(screen.getByText('导入结果预览')).toBeInTheDocument();
await waitFor(() => expect(screen.getByRole('button', { name: '导入为未定义' })).not.toBeDisabled());
fireEvent.click(screen.getByRole('button', { name: '导入为未定义' }));
await waitFor(() => expect(screen.getByRole('button', { name: '导入为待分类' })).not.toBeDisabled());
fireEvent.click(screen.getByRole('button', { name: '导入为待分类' }));
await waitFor(() => expect(apiMock.importGtMask).toHaveBeenCalledWith(file, '1', '10', null, {
unknownColorPolicy: 'undefined',

View File

@@ -148,7 +148,7 @@ const classByMaskId = (classes: TemplateClass[]) => new Map(
);
const UNCLASSIFIED_MASK_LABEL = '待分类';
const UNCLASSIFIED_MASK_COLOR = '#9ca3af';
const UNCLASSIFIED_MASK_COLOR = '#000000';
const normalizeMaskAgainstTemplates = (mask: Mask, templates: Template[]): Mask => {
const hasClassReference = Boolean(mask.classId || mask.className || mask.classMaskId !== undefined);
@@ -1430,6 +1430,7 @@ export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void
const targetImage = targetContext.getImageData(0, 0, targetWidth, targetHeight);
const overlayImage = overlayContext.createImageData(targetWidth, targetHeight);
const classesByMaskId = classByMaskId(gtTemplateClasses);
const hasPositiveTemplateClasses = gtTemplateClasses.some((templateClass) => Number(templateClass.maskId) > 0);
for (let index = 0; index < targetImage.data.length; index += 4) {
const maskId = targetImage.data[index];
const alpha = targetImage.data[index + 3];
@@ -1437,7 +1438,9 @@ export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void
const templateClass = classesByMaskId.get(maskId);
const [red, green, blue] = templateClass
? parseHexColor(templateClass.color)
: fallbackMaskColor(maskId);
: hasPositiveTemplateClasses
? parseHexColor(UNCLASSIFIED_MASK_COLOR, [0, 0, 0])
: fallbackMaskColor(maskId);
overlayImage.data[index] = red;
overlayImage.data[index + 1] = green;
overlayImage.data[index + 2] = blue;
@@ -1446,7 +1449,7 @@ export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void
overlayContext.putImageData(overlayImage, 0, 0);
const sortedMaskIds = Array.from(maskIds).sort((a, b) => a - b);
const unknownMaskIds = gtTemplateClasses.length > 0
const unknownMaskIds = hasPositiveTemplateClasses
? sortedMaskIds.filter((maskId) => !classesByMaskId.has(maskId))
: [];
const resized = sourceWidth !== targetWidth || sourceHeight !== targetHeight;
@@ -2015,7 +2018,7 @@ export function VideoWorkspace({ onNavigateToAI }: { onNavigateToAI?: () => void
disabled={gtMaskPreview?.status !== 'ready'}
className="rounded border border-cyan-500/30 bg-cyan-500/15 px-3 py-2 text-xs text-cyan-100 hover:bg-cyan-500/25"
>
</button>
</div>
<button