⚠️ 自动镜像 · 此页由
docs-site/scripts/mirror-changelog.mjs从docs/changelogs/0.5.x.md生成,请勿直接编辑此处;改源文件后pnpm docs:build会自动同步。
Changelog — 0.5.x
[0.5.5 phase 2] - 2026-04-30 — Floating Noodle
一次性收口 phase 1(治理 / 基建)与 v0.5.4(工作台 polish)累计的 12 项遗留。不引入新功能,每一行改动都对应一条已立项的尾巴。9 项一次落到位,3 项核心动作落地、UI 抽屉 / 评论富文本等大件留作 v0.5.6。
治理 / 基建
A.1 OpenAPI → TypeScript codegen 基建
- 新增
@hey-api/openapi-ts@^0.55.0开发依赖 +apps/web/openapi-ts.config.ts(默认OPENAPI_URL=http://localhost:8000/openapi.json,输出src/api/generated/)。 package.json加脚本codegen/codegen:watch;apps/web/.gitignore屏蔽生成产物;不强加 prebuild gate(避免 CI 与 dev 启动循环依赖)。- 触发场景就是 phase 1 漏暴露
UserOut.is_active的事故。手写 type → generated 的逐字段迁移走渐进路径,本期仅落基建。
A.2 后端 pytest 脚手架(轻量)
apps/api/pyproject.toml加[project.optional-dependencies] test(pytest + pytest-asyncio + pytest-mock)+[tool.pytest.ini_options](asyncio_mode=auto)。apps/api/tests/{__init__,conftest,test_smoke}.py:app_module/httpx_clientfixture + 5 例 sanity(router 注册、attribute_schema hotkey 校验、iou 阈值范围、_build_base_query detail_filter、_PENDING_TASK_STATUSES 与 TaskStatus 同源)。- DB SAVEPOINT fixture / 真 PG client 留下一期(需独立 TEST_DATABASE_URL + alembic upgrade 配置)。
A.3 audit detail_json GIN 索引 + 字段级过滤
- alembic
0015_audit_detail_gin_index:PG 创建ix_audit_logs_detail_json_gin USING GIN;其它方言 noop。 _build_base_query()加detail_key + detail_value入参(JSONB@>子集匹配,走 GIN)。/audit-logs与/audit-logs/export端点暴露同名 query 参数;export self-audit 行 detail 加target_id_filter / actor_id_filter / detail_key_filter / detail_value_filter字段。AuditPage:筛选区加两个detail 键名 / 键值输入框(仅 super_admin),追溯 banner 显示detail.role = super_admin徽章。- 双行 UI(按
request_id合并 metadata + business detail)留作 v0.5.6(实现成本中等,UI 风险大)。
A.4 IoU 去重阈值项目级可配
- alembic
0014_project_iou_dedup_threshold:projects加iou_dedup_threshold FLOAT DEFAULT 0.7 NOT NULL。 Projectmodel +ProjectOut/ProjectUpdate加字段(pydanticField(ge=0.3, le=0.95)范围守卫)。WorkbenchShell.tsx:218硬编码0.7→currentProject?.iou_dedup_threshold ?? 0.7。GeneralSection(项目设置)加滑块0.30 ~ 0.95(步长 0.05)+ 实时数值显示。
用户 / 权限完整化
B.1 project_admin 视角 UsersPage 按管理项目过滤
list_users()后端按 actor 角色分流:super_admin 默认全量(可选project_id过滤);project_admin 强制限定到Project.owner_id == actor.id项目内 ProjectMember 集合 ∪ 自身。- 前端
usersApi.list({ project_id })接受新参数;useUsers类型同步。
B.2 删除带未完成任务用户先转交(409 二阶段)
DELETE /users/{id}软删前查询Task.assignee_id == target_id AND status in (pending, in_progress, review)的 count + 5 个示例 id +TaskLock.user_id == target_idcount;任一非零且未传transfer_to_user_id→ 409 +{reason:"has_pending_tasks", pending_task_count, locked_task_count, sample_task_ids, message}。- 接受
body.transfer_to_user_id:校验 receiver active + 角色合法 + project_admin 时只能转给同管理项目内成员;UPDATE 全部 pending tasksassignee_id→ 转交目标,DELETE 原 user 的 task_locks,再走原软删;audit_loguser.deletedetail 加transferred_to / transferred_count / released_locks。 apps/web/src/api/client.ts:ApiError.detailRaw暴露后端结构化 detail(之前 dict 类型 detail 被吞),apiClient.delete支持可选 body。apps/web/src/api/users.ts:usersApi.remove(id, opts?: { transfer_to_user_id });useDeleteUser接受{ userId, transferToUserId }形态。UsersPage删除 Modal 二阶段:检测到ApiError.status === 409 && reason === "has_pending_tasks"→ 切到"先转交"视图,显示 pending/locked 数 + 示例 ID + UserPicker 选接收者 + 二次提交。
响应式与组件抽取
C.1 窄屏 hamburger drawer
- 新建
apps/web/src/components/shell/SidebarDrawer.tsx:Portal + 左滑动画(220ms ease-out)+ 遮罩点击 / Esc / 路由变化自动关闭 + body 滚动锁。 App.tsx:窄屏< 1024px时同时渲染占位 aside(保持 grid 完整)+ SidebarDrawer,复用同一<Sidebar>组件。TopBar加showHamburger / onOpenDrawerprops;窄屏时显示左侧 menu 按钮。Icon.tsx加menu → Menu(lucide)。
C.2 通用 DropdownMenu 组件
- 新建
apps/web/src/components/ui/DropdownMenu.tsx:trigger render-prop + items 数组 + outside-mousedown / Esc 关闭 + ↑↓ Home End 键盘导航 + role="menu" / "menuitem" + active 项 check 标记 + 支持 footer slot。 TopBar主题切换 dropdown:60+ 行内联实现 →<DropdownMenu>三选一 + 系统模式 footer hint。- 工作台
Topbar.tsx智能切题 + 溢出菜单:双useState/useRef/useEffectoutside-close 重复实现 → 两个<DropdownMenu>;老menuItemStyle / kbdStyle两个游离常量删除。 Button组件改为forwardRef(DropdownMenu trigger 需要 ref)。
工作台 polish
D.1 属性 schema hotkey 实际绑定
dispatchKey()ctx 加attributeHotkey?: (digit) => AttributeHotkeyHit | null;数字键分支:选中态下 hotkey 命中且 type ∈ {boolean, select} → 返回{ type: "setAttribute", key, value }(boolean 取反 / select cycle 下一项),否则保留setClassByDigitfallback。WorkbenchShell注入 lookup(合并applies_to过滤 + 取选中 annotation 的当前值)+ 处理setAttributeaction(走handleUpdateAttributes与现有表单 PATCH 路径同源)。AttributeForm字段 label 旁加<KeyBadge>{f.hotkey}</KeyBadge>(仅 boolean / select 显示)。- 后端
_validate_attribute_schema加 hotkey 守卫:必须单字符 1-9 + 仅 boolean / select 类型 + 全 schema 唯一。 hotkeys.test.ts加 5 例(无选中 fallback / boolean toggle / select cycle / cycle 末尾绕回 / hotkey 不命中)。vitest 42 全过(27 hotkey + 10 iou + 5 新增)。
D.2 离线队列:多 tab 同步 + history tmp_id 替换
offlineQueue.ts加BroadcastChannel("anno.offline-queue.v1"):persist 后 broadcast;监听其它 tab message → 重读 idb + 触发本 tab 订阅者;OfflineOp.create加可选tmpId字段。useAnnotationHistory加replaceAnnotationId(tmpId, realId):扫 undo + redo 双栈,把 create / update / delete / acceptPrediction / batch 内嵌的 annotation id 整体替换,drain 后 history 不再误指 tmp_id。- 完整接入(WorkbenchShell create 路径分配 tmpId + drain 后调用 replaceAnnotationId + queue 详情抽屉 UI)留 v0.5.6;本期落核心管线,下期组装 UI。
导出器扩展
E.1 COCO / YOLO / VOC 导出读 attributes
ExportService.export_coco:每条 annotation 输出加"attributes": ann.attributes or {},顶层info.attribute_schema写项目 schema。ExportService.export_yolo:YOLO 文本不可扩展 → 伴生<image_basename>.attrs.jsonper-image(行索引与 .txt 行号对齐)+ zip 根目录attribute_schema.json。ExportService.export_voc:<object>下插<extra>节点。GET /projects/{id}/export?include_attributes=bool(默认 true)入参;include_attributes=false输出原版兼容格式(无属性扩展字段)。- 前端
projectsApi.exportProject(id, format, { includeAttributes }):默认携带;UI 复选框待 ExportSection 抽出后再加。
验证
apps/web/:pnpm tsc -b✅ 0 errors;pnpm vitest run✅ 42/42(hotkey 32 + iou 10)。apps/api/:from app.main import app✅;pyproject.toml [project.optional-dependencies] test解析正常;新增 5 例 pytest 用例语法正确(运行需先pip install -e '.[test]')。- alembic:新增 0014 / 0015 两条 migration,按链 0013→0014→0015 接续;GIN 索引在 SQLite 测试库走 noop。
- migration 与 model 字段一致:Project model + ProjectOut + ProjectUpdate 三处 iou_dedup_threshold 同步。
不在本期范围(明确 defer 到 v0.5.6+)
- A.1:手写 type →
generated/*的逐字段迁移;prebuild gate; - A.2:DB-backed pytest fixture(SAVEPOINT 嵌入事务、alembic upgrade head per-session)+ 完整 audit_logs / 角色矩阵 / 删除转交端到端测试;
- A.3:双行 UI 合并视图(按
request_id把 metadata 行 + business detail 行折叠为单行 + 详情双栏); - C.1:通用
⋯溢出菜单组件全站第 3 个使用方(如 ProjectsPage 卡片菜单); - D.2:OfflineQueueDrawer 抽屉 UI + WorkbenchShell create 路径接入 tmpId + drain 完成后调 replaceAnnotationId 与 queryClient.setQueryData;
- D.3:评论 polish 整组(@ 提及 popover + 图片附件 presigned 上传 + alembic 0016 加 mentions / attachments / canvas_drawing 占位 + CommentInput contenteditable);
- E.1:导出 UI「包含属性数据」复选框(待 ExportSection 抽出)。
[0.5.5] - 2026-04-30
v0.5.5 phase 1:把治理与底盘一次性补齐 —— 分级权限管理、审计正反向追溯、主题三档、响应式收尾、图标体系迁 Lucide。用户与权限页 / 审计日志页 / TopBar / 工作台 / 全站图标全部受影响,171 处
<Icon>调用零改动平滑迁移。
新增
A · 分级权限管理(变更角色 / 删除账号)
- 后端守卫矩阵(
apps/api/app/api/v1/users.py):PATCH /users/{id}/role入口从SUPER_ADMIN放宽到_MANAGERS(super_admin + project_admin),内部按actor.role × target.role显式守卫:- super_admin:可改任意 target 为任意角色(自己 / 最后一名超管除外)。
- project_admin:仅允许在
annotator ↔ reviewer之间切换;target 必须出现在 actor 管理(Project.owner_id == actor.id)的项目里;不可造 project_admin / super_admin。
- 新增
DELETE /users/{id}软删除端点(is_active = False):同矩阵守卫;project_admin 还要求 target 仅在其管理项目里出现(跨项目用户 403 提示由 super_admin 处理);最后一名 active super_admin 不可被删 / 降级(兜底防自锁)。 POST /{id}/deactivate入口同步放宽并复用同一组守卫,与delete行为对齐。AuditAction.USER_DELETE = "user.delete";role_change / delete / deactivate 均记 actor / target / old / new 进 audit_logs。- 新增三个内部辅助:
_count_active_super_admins()/_project_admin_manages_target()/_target_only_in_actor_projects()。
- 前端 EditUserModal 重写(
apps/web/src/components/users/EditUserModal.tsx):ASSIGNABLE_ROLES_BY_ACTOR+DELETABLE_TARGET_ROLES_BY_ACTOR双矩阵驱动 UI;下拉里允许出现的选项 = 当前角色 ∪ actor 可指派集合;project_admin 视角下显示「项目管理员仅能在审核员 / 标注员 之间切换」hint。- 「停用」按钮替换为「删除账号」(变体保持
danger+ 二次确认 inline)。 - 错误回显走
changeRole.error || assignGroup.error || deleteUser.error,复用 ApiClient 全局 toast。
- UsersPage 行级操作(
apps/web/src/pages/Users/UsersPage.tsx):- 每行右侧三个按钮:📊 审计追溯(跳
/audit?actor_id=X) / ✏️ 编辑(仅 actor × target 命中矩阵时启用) / 🗑️ 删除账号(红色,独立 Modal 二次确认);自己那行不显示删除。 - 独立删除 Modal:头像 / 邮箱 / 角色徽章 + 后端错误回显(不重弹 toast,避免双通知)。
- 新增
useDeleteUserhook +usersApi.remove。
- 每行右侧三个按钮:📊 审计追溯(跳
- UserOut schema 暴露
is_active(apps/api/app/schemas/user.py):之前未透传导致前端u.is_active === undefined、删除按钮短路;现is_active: bool = True一并返回。
B · 审计正向反向追溯视图
- 后端(
apps/api/app/api/v1/audit_logs.py):GET /audit-logs与/audit-logs/export新增target_id精确过滤;_build_base_query()形参改为(action, target_type, target_id, actor_id, from_, to)。 - AuditPage 入口与高亮(
apps/web/src/pages/Audit/AuditPage.tsx):- 启动读 URL
?action / ?target_type / ?target_id / ?actor_id并初始化筛选;URL 变化(如 UsersPage 跳过来)触发useEffect重置筛选 + 回到第 1 页。 - 顶部追溯模式 banner:紫色高亮带
Icon target+ 当前过滤项徽章(操作人 / 对象类型 / 对象 ID / 动作)+ 「清除追溯」按钮。 - 筛选区加
target_id等宽输入框(精确匹配)。
- 启动读 URL
- 行内点击即追溯:表格 actor_email 列点击 → 用该
actor_id进入追溯模式;target_type / target_id 列点击 → 联合追溯该对象。 - 详情 Modal 双向跳转:「该操作人完整时间线」 + 「该对象完整时间线」按钮,关闭 Modal 同时切换筛选。
- 入口辐射:UsersPage 行级 + ProjectSettingsPage 头部「审计追溯」按钮(仅 super_admin 可见,跳
/audit?target_type=project&target_id=X)。 - api/audit.ts:
AuditQuery加target_id?: string。
C · 主题切换三档(日间 / 夜间 / 跟随系统)
- TopBar 主题入口(
apps/web/src/components/shell/TopBar.tsx):通知按钮前新增主题切换按钮 + dropdown,显式列出三档 + 当前态 check 标记;system模式下底部 hint 显示当前 resolved 主题。 - 图标随 pref 切换:sun(日间)/ moon(夜间)/ monitor(跟随系统),点击外部自动关闭。
- 复用 v0.5.3 已落的
useThemehook(light/dark/system三档 + localStorage 持久化 +prefers-color-scheme监听 +initThemeFromStorage()防首屏闪烁)。
D · 响应式收尾
- AppShell 折叠(
apps/web/src/App.tsx):< 1024px时 sidebar grid 列宽折为 0(保留布局完整性),用useMediaQuery("(max-width: 1023px)")切换。 - 工作台移动端阻挡:
FullScreenWorkbench在< 768px时叠加全屏遮罩<MobileWorkbenchBlock>:「请切换到桌面端 · 标注工作台依赖快捷键、画布鼠标交互和大屏侧栏」 + 建议宽度 ≥ 1024px。底层工作台仍渲染(保留只读视图),但所有交互被遮挡防误操作。
E · 图标体系迁移到 Lucide React
Icon.tsx内部重写(apps/web/src/components/ui/Icon.tsx):删除 ~70 行手写 SVG path 字符串,改为 60 项name → Lucide 组件映射表;对外<Icon name="..." size stroke style className />API 完全保留 → 177 处调用零改动。forwardRef兼容:保留 ref 透传给 SVG 元素,不破坏现有 ref 用法。- 新增 icons:sun / moon / monitor / inbox(主题切换 + 追溯 banner 等新 UI 用);polygon →
Hexagon、cube →Box、warning →AlertTriangle、edit →Pencil等 Lucide 标准映射。 - 新代码约定:写新功能直接
import { Layers, Sparkles, ... } from "lucide-react",不必走中间层;Icon.tsx仅作存量兼容。 - 依赖与体积:
pnpm --filter @anno/web add lucide-react;pnpm build后 gzip 主 chunk 261.92 KB(迁移前 ~256 KB),增量 ~6 KB,符合 ROADMAP< 10 KB验收。
验证
tsc --noEmit(apps/web)✅ 0 errorsvitest run✅ 37/37 passed(hotkeys 27 + iou 10)pnpm vite build✅ 2022 modules, 1.79s- 后端
from app.main import app✅ - 后端冒烟(router / schema / depends 导入)✅
不在本期范围
- 前后端 schema 自动同步(OpenAPI → TS 类型生成)—— 本次
UserOut.is_active漏暴露暴露的就是这个隐患的实例,留给 v0.5.x 续期。 - project_admin 视角下 UsersPage 列表按管理项目自动过滤;删除带未完成任务的用户先转交 / 跨项目用户的精确显示。
- 窄屏 hamburger drawer 触发完整 sidebar;通用
⋯溢出菜单组件抽取(多页面共享)。 - detail_json 字段级 PG GIN 索引筛选;审计中间件双行 UI 合并视图。
- 后端
audit.export端点target_id入参的端到端单测(手工验证已通过)。
[0.5.4] - 2026-04-30
新增
A · Polygon 顶点编辑(v0.5.3 polygon MVP 收尾)
- 顶点拖动:选中已落库 polygon 后,圆点 handle 显式可拖;
<ImageStage>Drag 联合类型新增{ kind:"polyVertex", id, vidx, start, cur },与 move/resize 通道并列;commit 走useAnnotationHistory.push({kind:"update"})单条命令,Ctrl+Z 一键还原。 - Alt+点击边新增顶点:边上挂 10px 透明 hit-stroke,alt 时 cursor 切 copy;点中即在最近边后插入鼠标投影点。
- Shift+点击顶点删除:≤3 顶点时 toast 拒绝。
- 自相交校验:
stage/polygonGeom.ts提供isSelfIntersecting()(O(n²) segment-pair 暴力,n 通常 < 50);commit 路径统一调用,违规时 stroke 切红 + 标签加 ⚠ + toast「polygon 自相交,已撤销」+ 几何回退。 - 精确 IoU:引入
polygon-clipping@0.15.7;stage/iou.ts的iouShape()走intersection()求精确交并,polygon-vs-bbox 把 bbox 转 4 顶点同分支,bbox-vs-bbox 仍走原iou()。WorkbenchShell视觉去重处把iou()调用换成iouShape()。iou.test.ts新增 4 例 polygon 用例(identical / disjoint / 半重叠 / triangle vs bbox = 0.5)。 - HotkeyCheatSheet:补三行说明(拖动顶点 / Alt+click 边 / Shift+click 顶点)。
B · 项目级属性 schema + Annotation.attributes(P1)
- 数据模型:alembic 0012 一次性加
annotations.attributes JSONB DEFAULT '{}'+projects.attribute_schema JSONB DEFAULT '{"fields":[]}'+projects.classes_config JSONB DEFAULT '{}'(B + E 共一份 migration)。存量 0 影响。 - Schema DSL:项目级声明属性列表
{ key, label, type, required?, options?, min?, max?, applies_to?: "*"|string[], visible_if?, hotkey? };type支持 text / number / boolean / select / multiselect / range;后端 pydantic_validate_attribute_schema校验 key 唯一、type 枚举、select-必有 options。 - 后端 API:
PATCH /projects/{id}接attribute_schema;PATCH /tasks/{id}/annotations/{aid}接attributes直接覆盖。AnnotationOut新增attributes字段。 - AttributeForm 组件:
shell/AttributeForm.tsx根据schema × class_name × annotation.attributes动态渲染表单(react-hook 风格,无新 deps);visible_if单层条件级联;改完防抖 400ms 上抛 PATCH。 - AIInspectorPanel 接驳:选中态下方挂
<AttributeForm>+ 缺失必填提示。 - AttributesSection(项目设置页 tab):可视化增删改字段、上下移、导入 / 导出 JSON;保存时整包
PATCH /projects/{id}带attribute_schema。 - 必填校验:
getMissingRequired()工具函数;<Topbar>提交质检按钮在任意 annotation 缺必填时 toast「存在必填属性未填,无法提交」+ 阻塞提交。
C · 逐框评论 annotation_comments
- 数据模型:alembic 0013 建表(id, annotation_id, project_id, author_id, body, is_resolved, is_active, ts)+ 复合索引
(annotation_id, created_at desc)。 - 后端 API:
api/v1/annotation_comments.py注册到 router,提供GET/POST /annotations/{aid}/comments、PATCH/DELETE /comments/{id}。create 时写 audit_logaction="annotation.comment"+target_type="annotation",自动经现有通知中心 30s 轮询可见。权限:作者或管理员可编辑/软删。 - CommentsPanel 组件:
shell/CommentsPanel.tsx渲染输入区 + 历史列表(作者名、时间、已解决徽章),「✓」切解决态、「🗑」作者可删。 - AIInspectorPanel 接驳:选中态属性表单下方再挂评论区。
- 通知中心映射:
utils/auditLabels.ts加annotation.comment: "评论标注"+annotation.update: "编辑标注",AUDIT_TARGET_TYPES加annotation。
D · 自动保存 / 离线队列(P2)
- idb-keyval@6 引入:仅 ~2KB;
state/offlineQueue.ts提供enqueue / count / drain / subscribe / isOfflineCandidate纯函数 API;持久化到 idb keyanno.offline-queue.v1,incognito quota 失败时 try/catch 静默降级。 useOnlineStatushook:监听navigator.online/offline+ 队列长度变更,输出{ online, queueCount }。- WorkbenchShell mutation 包裹:
handlePickPendingClass/handleCommitMove/handleCommitResize/handleCommitPolygonGeometry/handleDeleteBox的 onError 走enqueueOnError:isOfflineCandidate(err)(TypeError 网络断 / 5xx)时 enqueue + toast「已暂存到离线队列」;普通错误按原路径报错。 flushOffline自动触发:online事件由 useEffect 监听,online 切回时自动 drain 队列;逐条 replay create/update/delete API;ok > 0 时 invalidate annotations / tasks query 并 toast「已同步 N 条离线操作」。- StatusBar 徽章:右侧加「离线 · N 操作待同步」/「暂存 · N」按钮(offline 红色 / online 黄色),点击立即重试。
E · Classes 升级(color + order + 拖排)
- 数据模型:alembic 0012 加
projects.classes_config JSONB DEFAULT '{}',存量classes仍是string[]零变动;{name: {color, order}}形式存元信息。 - 后端校验:pydantic
_validate_classes_config校验 color 是#RRGGBB、order 非负唯一。 - 前端调色板:
stage/colors.ts重构:classColor(name, config?)/classColorForCanvas(name, config?)优先级传入 config > 模块级 _activeConfig(项目当前) > 内置预设 > FNV-1a hash 派生;新增setActiveClassesConfig()模块级 setter,避免 ImageStage / SelectionOverlay / KonvaBox 等 10+ 处逐层透传 prop。 - WorkbenchShell:项目加载时
useEffect(() => setActiveClassesConfig(currentProject?.classes_config), ...);classes数组在工作台维度按sortClassesByConfig()重排。 - ClassPalette:新增可选
classesConfigprop,左侧常驻图例颜色块走真实 hex。 - ClassesSection(项目设置页新 tab):每个类别一行
[#] [色块] [名称] [color picker] [↑↓🗑];拖排(上下移)+ HTML5<input type="color">;新增 / 删除;保存时PATCH /projects/{id}同时带classes+classes_config。
测试 / 工程
- vitest 37 例全绿(v0.5.3 = 33;v0.5.4 = +4 polygon IoU)。
tsc -b全绿。- alembic upgrade/downgrade 双向通过:0010 ↔ 0011 ↔ 0012 ↔ 0013 全部往返成功。
- 新依赖:
polygon-clipping@^0.15.7、idb-keyval@^6.2.1(前端各 ~2-3KB gzip)。
调整 / 重构
KonvaPolygon加 4 个新 props:points/selfIntersect/editable/onVertexMouseDown/onEdgeMouseDown;选中态自动渲染 vertex circle + 透明 edge hit-area。<ImageStage>新增 proponCommitPolygonGeometry?: (id, before, after) => void。useTasks.useUpdateAnnotation的乐观 onMutate 把attributes字段也合并到缓存,避免表单回显抖动。auditLabels.ts行业新增annotation.update / annotation.comment,AUDIT_TARGET_TYPES加annotation。Icon.tsx新增chevUp/tag两个 SVG 路径。
已知限制 / 待 v0.5.5
- 属性 schema 的
hotkey字段当前仅声明,不绑定(与 1-9 类别快捷键冲突协调归 v0.5.5)。 - 离线队列:多 tab 同步、queue 详情抽屉 UI、history undo 链与 tmp_id 替换归 v0.5.5。
- 评论:
@提及、附件、画布层手绘批注归 v0.5.5。 - classes 升级:导出器(COCO / YOLO)暂未读 attributes,schema 导出迁移归 v0.5.5。
- polygon 精确 IoU 的项目级阈值(
project.iou_dedup_threshold)仍硬编码 0.7。
[0.5.3] - 2026-04-30
新增
C.2 工作台 UI 信息架构重构(ToolDock + FloatingDock + 三段 Topbar)
- 左侧 ToolDock(垂直工具栏):新建
apps/web/src/pages/Workbench/shell/ToolDock.tsx,从tools/registry自动渲染按钮(icon + hotkey tooltip + active 高亮)。新增工具仅需在tools/注册并加入ALL_TOOLS,外壳无需改动。 - 画布右下 FloatingDock(悬浮工具岛):新建
apps/web/src/pages/Workbench/shell/FloatingDock.tsx,承载撤销 / 重做 / 缩放 / 缩放百分比 / 适应。锚定到画布容器左下角(避开右下 Minimap),与 Konva viewport 贴合。 - Topbar 三段重构:原单行 9+ 控件挤爆 → grid
1fr auto 1fr三段:左 = 标题 + index 徽章 (n / total);中 = 上一 / 提交质检 / 下一 / ⌄ 智能切题;右 = AI 一键预标 + ⋯ 溢出菜单(快捷键 + 主题切换)。1280px 单行不换行;行内元素 ≤ 8。 - WorkbenchShell 主 grid 调整:
260/32 → 48 → 1fr → 280/32四列,ToolDock 始终 48px 不随侧栏折叠。
C.2 暗色模式(与 B「主题切换」打底一并落地)
tokens.css [data-theme="dark"]块:暗色覆盖--color-bg / -elev / -sunken / -hover / -panel、--color-fg / -muted / -subtle / -faint、--color-border / -strong、accent / success / warning / danger / ai 系列(保持色相、提亮 lightness、降饱和度)、shadow(加深 alpha)、画布棋盘格新增--color-canvas-checker-a/b双 token。useThemehook:light | dark | system三档,写<html data-theme>,持久化到localStorage["anno.theme"],system模式监听prefers-color-scheme自动切换。- 启动注入
initThemeFromStorage():main.tsx在createRoot前应用初始主题,避免 first-paint 闪白。 - Topbar 溢出菜单
<ThemeSwitcher>:三选一按钮组,亮色 / 暗色 / 跟随系统。
C.1 Konva 分层 hit-detection
<ImageStage>拆 4 个 Layer:bg(图像,listening:false独立缓存)+ai(AI 预测框)+user(人工框 + 选中态)+overlay(绘制预览 / pending 框 / polygon 草稿,listening:false)。user 框 move/resize 重绘不再连带触发 AI 层。
C.3 多边形工具(polygon)
- 数据模型升级 → discriminated union geometry:
Annotation.geometry/PredictionShape.geometry/AnnotationPayload.geometry改为{type:'bbox',x,y,w,h} | {type:'polygon',points:[[x,y],...]}。前端Annotation类型仍保留x/y/w/h(包围盒)+ 新增可选polygon: [number, number][],对存量读.x/.y/.w/.h的代码路径完全兼容;polygon 包围盒由transforms.polygonBounds()自动派生。 - alembic 0011 migration:一次性给存量
annotations.geometry与predictions.result[*].geometry补type:"bbox"字段;downgrade 反向移除。 - 后端 pydantic geometry validator:
AnnotationCreate/AnnotationUpdate加field_validator,校验 bbox 必有 x/y/w/h、polygon 必有 ≥ 3 个[x,y]顶点;兼容旧客户端无 type 时按 bbox 处理。 PolygonTool(hotkeyP):实现CanvasTool接口;左键逐点落点 / 距首点 < 0.008 自动闭合 / 双击或 Enter 闭合 / Esc 取消 / Backspace 撤销最后一点。onPointerDown通过ToolPointerContext.polygonDraft句柄 mutate Shell 维护的草稿状态,不走 setDrag 路径。- 画布渲染:新增
KonvaPolygon组件(KonvaLine closed=true+ 半透明填充 + 标签锚到第一个顶点);ImageStage user/AI 层按b.polygon存在条件分流到 KonvaBox 或 KonvaPolygon。overlay 层渲染 polygon 草稿:已落点 + 跟随光标的预览段 + 顶点圆点 + 首点高亮(提示可闭合)。 - 复用流程:
useClipboard解耦硬编码annotation_type:"bbox",按 source 是否含 polygon 分流:polygon 整体平移所有点;historyuseAnnotationHistory命令模式已抽象AnnotationPayload,无需改动;iou.ts新增iouShape()形状无关入口(polygon 暂走包围盒近似,TODO 后续接 polygon-clipping)。 - 限定:v0.5.3 polygon MVP = 创建 / 渲染 / 删除 / 改类别 / 撤销重做。顶点拖动 / Alt+点击边新增顶点 / Shift+点击删除顶点 / 自相交校验 / polygon-vs-bbox/polygon 精确 IoU 留 v0.5.4+。
Phase 1 · 工具层抽离(多工具基础设施)
tools/index.ts接口激活:CanvasTool接口扩展label / icon / cursor+onPointerDown(ctx) → DragInit | null;TOOL_REGISTRY与ALL_TOOLS导出。新增BboxTool.ts/HandTool.ts/PolygonTool.ts独立模块。ImageStage.handleStageMouseDown改为委派:从 if-else 工具分支 →TOOL_REGISTRY[tool].onPointerDown(ctx),spacePan 时强制走 hand 工具。Drag init 类型{kind:'draw'|'pan'}。hotkeys.ts加dispatchKey()纯函数:把键盘事件 + 简单 ctx 映射为HotkeyAction离散指令;WorkbenchShell 大 useEffect 改为switch (action.type)。新增apps/web/src/pages/Workbench/state/hotkeys.test.ts(27 例),覆盖修饰键 / 单键 / 上下文相关三组分支。- 快捷键补齐:
P= 多边形工具;Enter闭合 polygon;Backspace在 polygon 草稿态删最后一点(其余态删除选中框)。HotkeyCheatSheet 同步。
测试 / 工程
- vitest 33 例全绿(v0.5.2 = 6 例 IoU;v0.5.3 = +27 例 hotkey dispatch)。
tsc -b全绿:geometry discriminated union 升级未泄漏到任何调用点(通过bboxGeom() / polygonGeom()包装 helper 局部化)。
调整 / 重构
Topbar.tsxprops 大幅精简:移除tool / scale / canUndo / canRedo / onSetTool / onZoom* / onUndo / onRedo(已迁出到 ToolDock + FloatingDock);新增taskIdx / taskTotal / overflowSlot。useWorkbenchState的Tool联合类型扩展:"box" | "hand"→"box" | "hand" | "polygon"。transforms.ts新增bboxGeom() / polygonGeom() / polygonBounds() / geometryToShape()工具函数;annotationToBox / predictionsToBoxes内部统一走geometryToShape派生包围盒。useClipboard.ts:41删除硬编码annotation_type:"bbox";payload 形状按 source 形状自适应。
已知限制
- polygon 顶点拖动 / 编辑 / 自相交校验 留 v0.5.4。
- polygon 精确 IoU 暂用包围盒近似(视觉去重场景精度足够;TODO 接入 polygon-clipping 库)。
- AI 模型当前不输出 polygon predictions(GroundingDINO 只 bbox);polygon 主要服务手工标。后续 SAM 接入会自然填补。
[0.5.2] - 2026-04-30
新增
C.3 多选 / 批量编辑 / 复制粘贴
- 多选状态层:
useWorkbenchState的selectedId: string | null之外新增selectedIds: string[];selectedId作为 primary(驱动 SelectionOverlay 浮按钮锚点 / 单体快捷键),selectedIds包含 primary 在内的全部选中。新增toggleSelected(id)/replaceSelected(ids[])/setSelectedId(id)(后者会同步收敛 selectedIds 到 [id] 或 [])。AI 框始终单选。 - Shift+点击叠加多选:画布与 AIInspectorPanel 列表都支持;
onSelectBox(id, { shift })统一 API 由 Shell 层判断走 toggle 还是 replace。Shift+点空白不清空选择;普通点空白清空。 Ctrl+A全选当前帧 user 框;Esc清空。- 批量删除(Delete):
handleBatchDelete并发deleteAnnotation,全部 settled 后聚合 1 条kind: "batch"命令进 history 栈,单次 Ctrl+Z 一键还原;toast 报告已删除 N/M 个标注。 - 批量改类(C 键 / SelectionOverlay "批量改类" 浮按钮):复用
<ClassPickerPopover>(标题切换为「批量改类别 (N 个)」),锚定到第一个选中框;commit 时并发 PATCH 每个选中框 class_name,settled 后聚合 batch update 命令。 - 方向键平移:选中 ≥ 1 个 user 框时,方向键 1px / Shift+方向键 10px 平移;keydown 期间维护
nudgeMap: Map<id, Geom>作为画布临时 override(与 drag 共享 overrideGeom 通道,drag > nudgeMap > 原值优先级),keyup 时一次性把所有 nudge 落库为单条 batch update 命令。 - 复制 / 粘贴 / 复制副本(Ctrl+C / Ctrl+V / Ctrl+D):
useClipboardhook 维护本会话内存剪贴板(不跨任务,避免跨项目类别污染);粘贴 / 复制副本均走 (+10px, +10px) 偏移、clamp 到 [0,1] 边界;落库后批量进 history 栈,新副本自动成为多选。
C.3 / C.2 智能切题 + AI 框 IoU 视觉去重 + 撤销栈批量化
N/U智能切题:Topbar 「智能切题」下拉(同时绑N/U单字母键)。N(下一未标注):在已加载列表里找idx > current && total_annotations === 0 && status !== "completed";列表末尾时自动 fetchNextPage。U(下一最不确定):启发式 = 已加载列表中total_predictions > 0 && total_annotations === 0,按total_predictions desc排第一名;后端精排(list_tasks?order=conf_asc)作为 P2 待办留在 ROADMAP。
- AI 框 IoU 视觉去重:与已确认 user 框 IoU > 0.7 且同类的 AI 框 → 画布层 stroke opacity 0.35(复用现有
fadedAiIds通道)+ AIInspectorPanel 列表项整行 opacity 0.55 + 「已被覆盖」灰 tag。不删除,保留用户反悔空间。stage/iou.ts纯函数 + 6 例 vitest 单测。 useAnnotationHistorybatch 命令:新增kind: "batch"(包裹一组非 batch 子命令),undo 时反序应用、redo 时正序应用;新增pushBatch(commands[]):长度 1 时退化为 push 单条,> 1 时进 batch 命令。所有批量操作(删除、改类、复制粘贴、Ctrl+D、方向键平移)共享同一栈条目。
C.2 类别面板纯预览 + 快捷键补齐 + ETA + 阈值反馈
<ClassPalette>加readOnlyprop:TaskQueuePanel左侧常驻类别面板改为readOnly—— 鼠标 cursor: default,悬浮无 hover 着色,行点击与最近使用 chip 点击均无效;语义退化为「图例 + 快捷键速查」。<ClassPickerPopover>内部仍保持交互态(popover 本身就是选类场景)。activeClass写入权交给数字/字母键、popover、最近使用 record。- 快捷键补齐(11 条新增 + HotkeyCheatSheet 同步):
Tab/Shift+Tab(user 框间循环)、J/K(不循环到边界停)、[/](阈值 ±0.05,clamp 0~1)、Ctrl+A/Ctrl+C/Ctrl+V/Ctrl+D、N/U、方向键。hotkeys.ts是 SoT,速查面板自动渲染。 useSessionStatsETA:每次切题记录与上次的间隔,ring buffer size 20,过滤 < 1.5s 误触和 > 30min 离座;< 10 题样本显示—,达到后 StatusBar 输出avg/题 · 剩 N · 约 mm:ss。formatDuration工具函数支持小时降级。- Topbar 阈值数值浮出反馈:
[/]键调整阈值时右上角浮出阈值 55%(1.5s 自动消失),便于盲调。Topbar 也接confThresholdprop。
测试基座
- vitest 引入:
pnpm --filter web add -D vitest@^2.1.0,第一组单测stage/iou.test.ts(identical / disjoint / touching / half overlap / contained / zero-area 六例全过)。后续 hooks(useAnnotationHistorybatch、useClipboard偏移、useSessionStatsring buffer)单测扩展归 ROADMAP。
改动文件
- 新建:
apps/web/src/pages/Workbench/state/{useSessionStats,useClipboard}.ts、apps/web/src/pages/Workbench/stage/{iou,iou.test}.ts - 重构:
apps/web/src/pages/Workbench/state/{useWorkbenchState,useAnnotationHistory,hotkeys}.ts、apps/web/src/pages/Workbench/shell/{WorkbenchShell,Topbar,StatusBar,AIInspectorPanel,TaskQueuePanel,ClassPalette}.tsx、apps/web/src/pages/Workbench/stage/{ImageStage,SelectionOverlay,BoxListItem}.tsx - 文档:
ROADMAP.md删除 v0.5.1 / 当前迭代已完成的小项;新增 § A 协作并发(任务锁续约 + 编辑冲突 ETag)作为 P0;增补 ML Backend 协议契约文档 / Annotation 列表分页 / Konva 分层 hit / HotkeyCheatSheet 升级 / History sessionStorage 持久化 / IoU 阈值项目级可配等若干新发现项;优先级表整体翻新
兼容性
<ImageStage>新增selectedIds?: string[]/nudgeMap?: Map<string, Geom>/onBatchDelete?/onBatchChangeClass?均为可选,onSelectBox第二参数opts?: { shift?: boolean }也可选;<ReviewWorkbench>走只读路径,零改动即可继续工作(多选在 readOnly 时被收敛到单选)。<ClassPalette>的onPick现在是可选 prop(readOnly 时不需要);<TaskQueuePanel>移除了onSetActiveClassprop,对应外部调用同步迁移。
升级备注
- 前端:
pnpm install(拉 vitest);运行测试pnpm --filter web exec vitest run iou.test.ts。 - 无后端 / DB / Docker 改动;版本号升至 0.5.2。
[0.5.1] - 2026-04-30
新增
标注流程重构 — 工具/类别解耦 + 类别选择 popover
- 画完框再选类:原本"选类别 = 选工具",现在改为"选 bbox 工具 → 画框 → 弹
<ClassPickerPopover>选类别 → 落库"。中间态pendingDrawing落在useWorkbenchState,未确认前框以琥珀色虚线渲染(带 "? 待选类别" 标签);Esc / 点画布外 / 切工具 = 取消;Enter = 落到默认类别;1-9 / A-Z 键直选。 - 已落库框可改类别:选中 user 框 → 三入口(① SelectionOverlay 浮按钮显示当前类色块 + "改类" 标签;② AIInspectorPanel 列表项内 "改类" 按钮;③
C快捷键)→ 复用<ClassPickerPopover>(标题切换为 "改类别 (当前: X)")→ 选定后走PATCH /annotations/{id}改 class_name 并history.push({kind:"update"})进撤销栈,可 Ctrl+Z 还原。useWorkbenchState.editingClass持有改类中间态。 useRecentClasses:localStorage 持久化每个项目的最近 5 个类别(key=recent-classes:${projectId}),popover 顶部 chip 行置顶;落库后自动 record。新进项目时默认类别选 recent 头部(如该项目仍存在)而非永远首类。<ClassPalette>共享组件:popover 与TaskQueuePanel类别面板共用一份组件;> 9 类自动启用搜索框 + 字母键 a-z 映射到 classes[9..];recent chip 行;快捷键徽章统一渲染。
C.1 渲染细节收尾
- Minimap:
<Minimap>缩略图导航(160×120,自动按图像 aspect 适配),仅当图像视口可视率 < 85% 时显示;点击 minimap 把视口中心移到该位置;视口矩形高亮当前可见区域。ImageStage通过onStageGeometry把 imgW/imgH/vpSize 上抛,父级在 overlay slot 渲染。 - AIInspectorPanel 虚拟化:
@tanstack/react-virtual单列表合并 AI 段 + Header + 用户段;500 框 DOM 节点压到 < 30;滚到末尾自动 fetchNextPage 拉更多预测;底部 "加载更多 / 加载中" 兜底。 - rAF 节流:
ImageStage的 pointermove(draw / move / resize / pan)用 requestAnimationFrame 合并;240Hz 屏拉框 react-render 频率从 ~240/s 降到 60/s,1000 框 + 4K 图 pan/zoom 稳定 60fps。 - 按需加载预测:后端
/tasks/{id}/predictions加limit+offset参数,跨 Prediction 按 shape 置信度 desc 排序后切片再回到原 Prediction 容器;前端usePredictions改useInfiniteQuery,pageSize=100;阈值变更(debounce 300ms)触发 reset 重拉。
C.2 体验收尾
- 响应式可折叠:
useMediaQueryhook(useSyncExternalStore+ matchMedia);< 1024px 强制收两侧 sidebar;Topbar 按钮分组(视图/绘制/历史 + AI/导航)+ flexWrap 兜底,狭长窗口自动换行。 - 骨架屏 + blurhash 占位:
<WorkbenchSkeleton>三栏骨架(shimmer 动画)替代isProjectLoading文字;ImageStage在真图加载前用 32×24 blurhash 解码 + blur(8px) 占位(v0.5.0 已注入 blurhash 字段,本次接通到画布层)。 - 类别面板增强:TaskQueuePanel 类别区改用
<ClassPalette>,> 9 类启用搜索;最大高度 320px + 内部滚动避免挤占任务列表。
改动文件
- 新建:
apps/web/src/pages/Workbench/state/useRecentClasses.ts、apps/web/src/pages/Workbench/shell/{ClassPalette,ClassPickerPopover,WorkbenchSkeleton}.tsx、apps/web/src/pages/Workbench/stage/Minimap.tsx、apps/web/src/hooks/useMediaQuery.ts - 重构:
apps/web/src/pages/Workbench/state/useWorkbenchState.ts(pendingDrawing)、apps/web/src/pages/Workbench/stage/ImageStage.tsx(rAF 节流 + blurhash + pendingDrawing 渲染 + onStageGeometry / overlay slot)、apps/web/src/pages/Workbench/shell/{WorkbenchShell,TaskQueuePanel,AIInspectorPanel,Topbar}.tsx、apps/web/src/hooks/usePredictions.ts(useInfiniteQuery)、apps/web/src/api/predictions.ts、apps/web/src/pages/Review/ReviewWorkbench.tsx - 后端:
apps/api/app/api/v1/tasks.py(predictions endpoint 加 limit/offset,跨 Prediction 按 shape 置信度排序)
兼容性
- 原
usePredictions(taskId)返回data: PredictionResponse[]→ 现在返回useInfiniteQuery结果。两个调用点(WorkbenchShell/ReviewWorkbench)已同步迁移到data.pages.flatMap(p => p)。
[0.5.0] - 2026-04-29
新增
画布引擎 — Konva 切换
ImageStage全面切 Konva:底层从 DOM<div>矩形切换为react-konva@18Stage/Layer/Rect,解除 200+ 框掉帧的硬天花板;KonvaImage配合use-imagehook 加载原图,stage.scaleX/Y/x/y接管视口变换- SelectionOverlay HTML 浮层:采纳 / 驳回 / 删除浮动按钮移出 Stage,以绝对定位 React div 叠在 Stage 外层,按 bbox 右下角
(box.x+w)*imgW*scale+tx投影到容器坐标;支持 Tab 聚焦 + 键盘可达 - BboxTool / HandTool 抽象:
stage/tools/index.ts定义CanvasTool接口(id / hotkey / onMouseDown / onMouseMove / onMouseUp);v0.5.1 增 polygon / keypoint 只需添加 Tool 模块,不动 ImageStage - 颜色兼容:
classColorForCanvas()通过 Canvas 2DfillStyle把 oklch 转为 Konva 可用的 hex;resize 锚点按HANDLE_SCREEN_PX / vp.scale保持屏幕像素恒定大小 - ReviewWorkbench 复用:
<ImageStage readOnly />入参形状不变,审核页无需改动即获 Konva 性能收益
任务列表 — keyset 分页 + 虚拟化
- 修复 5k 任务截断:
useTaskList改为useInfiniteQuery+ 后端已有_encode_task_cursor / next_cursor,前 50 之后的任务现在可见 @tanstack/react-virtual虚拟化:TaskQueuePanel固定高度虚拟列表(estimateSize=84px),滚到末尾前 10 条自动fetchNextPagenavigateTask跨页预取:切到倒数第 10 条时触发fetchNextPage;ReviewPage同一 hook,零改动
预取 + 体感优化
- 相邻任务预取:切题时对前后各一条 task 的
annotations、predictions调queryClient.prefetchQuery,并插入new Image().src预热图像字节;连续翻题第 2 题起无白屏 - 服务端置信度过滤:
GET /tasks/{id}/predictions?min_confidence=0.7服务端裁剪载荷;前端confThreshold变更 300ms debounce 再发请求;减少大预测集的 JSON 体积 - WS 连接灯:StatusBar 新增 6px 圆点状态指示(绿/橙/灰)+ 文案「实时同步 / 重连中 / 实时进度暂停」
缩略图基础设施
- alembic 0009:
dataset_items新增thumbnail_path(VARCHAR 512)+blurhash(VARCHAR 64);顺带修复 v0.4.8 alembic 漂移(content_hash列用IF NOT EXISTS补迁移) - alembic 0010:
tasks新增thumbnail_path(VARCHAR 512)+blurhash(VARCHAR 64),支持直传(非数据集)任务的缩略图 - Celery
media队列:新建workers/media.py;generate_thumbnail任务处理数据集条目缩略图;generate_task_thumbnail任务处理直传任务(从annotationsbucket 拉图,写回tasks表);失败写metadata_['thumbnail_error'],重试 3 次 - 自动触发:
dataset路径(upload-complete / upload-zip / scan-import)写库后各自generate_thumbnail.delay(item_id);files.pyupload-complete 写库后派发generate_task_thumbnail.delay(task_id) POST /datasets/{id}/backfill-media:触发backfill_mediaCelery 任务,对存量无缩略图 dataset image 补生成POST /files/projects/{project_id}/backfill-thumbnails:触发backfill_tasksCelery 任务,对存量直传无缩略图任务补生成_attach_dimensions/_attach_dimensions_batch双路回落:有dataset_item_id时走 DatasetItem 取宽高 + 缩略图;dataset_item_id = NULL时回落到tasks.thumbnail_path / blurhash字段,两条路径统一透出- 前端
Thumbnail组件:blurhash canvas 占位 →<img loading=lazy decoding=async>淡入替换;TaskQueuePanel左侧 40×40 缩略图,DatasetsPage文件列表 32×32 缩略图 TaskOut/DatasetItemOutschema:新增thumbnail_url+blurhash字段透出到前端
基础设施
- Docker Compose 完整化:新增
api(alembic upgrade + uvicorn,Python healthcheck)、web(Nginx SPA)服务;celery-worker升级为监听default,ml,media三队列 MINIO_PUBLIC_URL配置:config.py新增minio_public_url字段;StorageService._public_url()在生成 presigned URL 后将内部 endpoint 替换为外部可访问地址;docker-compose 注入http://localhost:9000,浏览器可直接访问缩略图与原图
UI 改进
- Button 圆角优化:sm 尺寸改为
--radius-pill(胶囊形),md 尺寸改为--radius-lg;增加 primary/danger/ghost 各自的 box-shadow 与 border-color 差异化;加transition: opacity 0.1s悬停反馈
修复
- 删除标注框闪烁:
useDeleteAnnotation加onMutate乐观更新,立即从 React Query 缓存中过滤掉目标 annotation,onError回滚快照;消除 API round-trip 期间的「框消失 → 重现」闪烁 - 移动标注框回弹:
useUpdateAnnotation同理加onMutate乐观更新,mutation 发出前立即更新缓存中的 geometry;消除「框跳回原位再移到新位置」的视觉抖动 - 默认缩放 125% 问题:
ImageStage初次 fit 加imageLoaded = !!image?.naturalWidth守卫,确保在真实图像尺寸加载完成后才执行fitNow();防止以 900×600 fallback 计算出错误的初始 scale
依赖
- 前端新增:
blurhash^2.0.5、react-konva、use-image(Konva 画布切换)、@tanstack/react-virtual(任务列表虚拟化) - 后端新增:
blurhash-python>=1.2.0(pyproject.toml)
升级备注
- alembic upgrade head:执行 0009(dataset_items thumbnail + content_hash 漂移)+ 0010(tasks thumbnail_path + blurhash)
- Python 依赖:
uv sync拉blurhash-python - Celery:启动 media worker:
celery -A app.workers.celery_app worker -Q media -c 4 --loglevel=info - 存量 dataset 数据:调
POST /datasets/{id}/backfill-media补全历史 dataset_items 缩略图 - 存量直传任务:调
POST /files/projects/{project_id}/backfill-thumbnails补全直传任务缩略图 - Docker 部署:
MINIO_PUBLIC_URL设为浏览器可访问的 MinIO 地址(本地开发http://localhost:9000) - 版本号升至 0.5.0