调整AI自动推理入口文案和图标

- 将左侧工具栏传播入口的可访问名称和 tooltip 从“自动传播”改为“AI自动推理”。

- 新增 AI 大脑样式的 AiAutoInferenceIcon,并替换该入口原本的 AI 智能分割机器人图标。

- 更新 ToolsPalette 和 VideoWorkspace 测试,覆盖新按钮名称和 AI 大脑图标。

- 同步 README、AGENTS、前端审计、API 契约、设计冻结和测试计划文档中的入口描述。
This commit is contained in:
2026-05-04 04:16:05 +08:00
parent 87b82b882f
commit 7fc4949677
10 changed files with 50 additions and 24 deletions

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { BrainCircuit, Sparkles } from 'lucide-react';
interface AiAutoInferenceIconProps {
size?: number;
strokeWidth?: number;
}
export function AiAutoInferenceIcon({ size = 20, strokeWidth = 2 }: AiAutoInferenceIconProps) {
const sparkleSize = Math.max(8, Math.round(size * 0.42));
return (
<span
data-testid="ai-auto-inference-icon"
className="relative inline-flex items-center justify-center"
style={{ width: size, height: size }}
>
<BrainCircuit size={size} strokeWidth={strokeWidth} />
<Sparkles
size={sparkleSize}
strokeWidth={Math.max(strokeWidth, 2.1)}
className="absolute -right-1 -top-1 text-emerald-200 drop-shadow-[0_0_4px_rgba(110,231,183,0.75)]"
/>
</span>
);
}

View File

@@ -109,11 +109,11 @@ describe('ToolsPalette', () => {
);
const eraserButton = screen.getByTitle('橡皮擦 (X)');
const autoButton = screen.getByRole('button', { name: '自动传播' });
const autoButton = screen.getByRole('button', { name: 'AI自动推理' });
fireEvent.click(autoButton);
expect(autoButton).toHaveClass('bg-cyan-500/10');
expect(autoButton.querySelector('[data-testid="ai-segmentation-icon"]')).toBeInTheDocument();
expect(autoButton.querySelector('[data-testid="ai-auto-inference-icon"]')).toBeInTheDocument();
expect(eraserButton.compareDocumentPosition(autoButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(setActiveTool).toHaveBeenCalledWith('auto_propagate');
expect(onAutoPropagate).toHaveBeenCalled();
@@ -128,7 +128,7 @@ describe('ToolsPalette', () => {
const circleButton = screen.getByTitle('创建圆 (O)');
const brushButton = screen.getByTitle('画笔 (B)');
const eraserButton = screen.getByTitle('橡皮擦 (X)');
const autoButton = screen.getByRole('button', { name: '自动传播' });
const autoButton = screen.getByRole('button', { name: 'AI自动推理' });
const mergeButton = screen.getByTitle('区域合并 (+)');
const removeButton = screen.getByTitle('重叠区域去除 (-)');
const deleteButton = screen.getByTitle('删除选中遮罩 (Del)');

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { MousePointer2, CircleOff, PencilLine, Hexagon, Square, Circle, Brush, Eraser, Combine, Scissors, FileUp, Trash2 } from 'lucide-react';
import { cn } from '../lib/utils';
import { AiAutoInferenceIcon } from './AiAutoInferenceIcon';
import { AiSegmentationIcon } from './AiSegmentationIcon';
import { useStore } from '../store/useStore';
@@ -125,8 +126,8 @@ export function ToolsPalette({
onAutoPropagate?.();
}}
disabled={!canAutoPropagate || isPropagating}
aria-label="自动传播"
title={isPropagating ? '传播中...' : '自动传播'}
aria-label="AI自动推理"
title={isPropagating ? 'AI自动推理中...' : 'AI自动推理'}
className={cn(
"w-9 h-9 rounded-md flex items-center justify-center transition-all p-1.5 border",
activeTool === 'auto_propagate'
@@ -135,7 +136,7 @@ export function ToolsPalette({
(!canAutoPropagate || isPropagating) && "opacity-35 cursor-not-allowed hover:bg-cyan-500/10 hover:text-cyan-200",
)}
>
<AiSegmentationIcon size={17} strokeWidth={2.2} />
<AiAutoInferenceIcon size={17} strokeWidth={2.2} />
</button>
<div className="my-1 h-px w-9 bg-white/15" />
</>

View File

@@ -1438,7 +1438,7 @@ describe('VideoWorkspace', () => {
fireEvent.click(screen.getByRole('button', { name: '分割结果导出' }));
fireEvent.click(screen.getByRole('button', { name: '特定范围帧' }));
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
expect(screen.getByText('请在播放进度条或视频处理进度条上点击/拖拽选择传播起止帧,再点击“开始传播”')).toBeInTheDocument();
expect(screen.getAllByText('SAM 2.1 Tiny').length).toBeGreaterThan(0);
@@ -1768,7 +1768,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
fireEvent.click(screen.getByRole('button', { name: '开始传播' }));
expect(await screen.findByText('当前参考帧无遮罩')).toBeInTheDocument();
@@ -1842,7 +1842,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
fireEvent.click(screen.getByRole('button', { name: '开始传播' }));
await waitFor(() => expect(apiMock.updateAnnotation).toHaveBeenCalledTimes(1));
@@ -1919,7 +1919,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
expect(apiMock.queuePropagationTask).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: '开始传播' }));
@@ -1992,7 +1992,7 @@ describe('VideoWorkspace', () => {
});
expect(screen.queryByLabelText('传播权重')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
const propagationWeightSelect = screen.getByLabelText('传播权重');
expect(propagationWeightSelect).toHaveClass('bg-[#050809]');
expect(within(propagationWeightSelect).getByRole('option', { name: 'tiny' })).toHaveClass('text-cyan-100');
@@ -2054,7 +2054,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
fireEvent.click(screen.getByRole('button', { name: '开始传播' }));
const progressPanel = await screen.findByLabelText('自动传播进度');
@@ -2100,7 +2100,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
const processingBar = screen.getByLabelText('视频处理进度条');
vi.spyOn(processingBar, 'getBoundingClientRect').mockReturnValue({
left: 0,
@@ -2183,7 +2183,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
const processingBar = screen.getByLabelText('视频处理进度条');
vi.spyOn(processingBar, 'getBoundingClientRect').mockReturnValue({
left: 0,
@@ -2286,7 +2286,7 @@ describe('VideoWorkspace', () => {
});
});
fireEvent.click(screen.getByRole('button', { name: '自动传播' }));
fireEvent.click(screen.getByRole('button', { name: 'AI自动推理' }));
fireEvent.change(screen.getByLabelText('传播起始帧'), { target: { value: '1' } });
fireEvent.change(screen.getByLabelText('传播结束帧'), { target: { value: '3' } });
fireEvent.click(screen.getByRole('button', { name: '开始传播' }));