更新UI编辑器自动切分素材工作流方案
将多轮整图分层与DFS面积批次写入权威专题 明确TextRemovalOnly、视觉绑定和atlas输出合同 删除平行专题文档并保留后续TODO 补充UI编辑器自动分离多轮分层方案 新增整图语义分层与多轮 image-edit 方案 明确 DFS 批次、TextRemovalOnly 和视觉绑定合同 记录面积预算、恢复边界与后续 TODO
This commit is contained in:
@@ -4,12 +4,12 @@
|
||||
|
||||
## 目标
|
||||
|
||||
将 UI 编辑器现有“用户先提供独立图片/图标,再执行组件绑定”的入口替换为自动切分素材:结构识别阶段直接返回可渲染组件草稿,切分阶段按整页叶节点批次调用图片编辑模型,再由视觉模型确认处理图中的区域与目标节点。
|
||||
将 UI 编辑器现有“用户先提供独立图片/图标,再执行组件绑定”的入口替换为自动切分素材:结构识别阶段返回完整 UI 树和组件语义,切分阶段让 image-edit 模型按整页分层说明生成单张透明 atlas,再由视觉模型确认 atlas 中图片区域与目标节点的对应关系。
|
||||
|
||||
## 识别结果
|
||||
|
||||
- `recognize` 返回完整 `Node.component` 草稿,不再要求用户先导入独立素材。
|
||||
- `component = null` 表示纯节点。
|
||||
- `NodeComponent::PureNode` 表示纯节点;`NodeComponent::WithComponent` 携带完整组件。
|
||||
- `ImageComponent.target_graphic = None` 表示图片组件等待分离结果回填;它不是“明确没有图片”。
|
||||
- 一个 Node 最多承载一个 `Component`;需要多个视觉层时使用多个 Node 表达。
|
||||
- 组件草稿直接保存在正式 UI Node 中;临时 separation tree 不复制组件。
|
||||
@@ -26,37 +26,36 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正
|
||||
## Separation tree
|
||||
|
||||
- recognition 完成后由 UI tree 构造临时 separation tree;每棵树保留原 UI tree 的 `src_ui_design` 与真实 `root`,不生成 synthetic root。
|
||||
- 非 root 的纯容器、纯 Text 节点和不需要切图的节点在构造时过滤,被过滤节点的 children 向上透传。真实 root 始终保留;`root_extractable` 表示 root 是否含未绑定图片组件并可作为候选。
|
||||
- 非 root 的纯容器和已绑定图片节点在构造时过滤并透传 children;真实 root 始终保留,`root_extractable` 表示 root 是否含未绑定图片组件并可作为候选。
|
||||
- 节点的 `children` 在整个 workflow 中始终保留,不能因处理成功或失败而从树上删除。节点终态由 `bound`、`problematic_nodes` 反查;两者均不存在时仍待处理,`rework_count` 仅记录视觉模型返工次数。
|
||||
- 逻辑叶必须是未终止、可处理且所有 children 都已终止的节点;root 在 `root_extractable=true` 时按普通节点参与,否则只递归其 children。
|
||||
- 候选按 DFS 和 children 原顺序遍历,使用简单贪心选择与已选矩形无正面积交集的节点组成 batch;边或角接触不算重叠,不做面积或偏差排序检查。正常树结构下有候选时至少选中一个。
|
||||
- 一个 batch 是当前树中整批互不重叠的逻辑叶节点。
|
||||
- 一个 batch 的最小处理单元是:一次 image-edit + 一次 visual binding。
|
||||
- batch 成功后只把结果追加到 bound 容器,失败节点在达到返工上限后追加到 problematic 容器;树拓扑不变,流程继续消费剩余树。
|
||||
- `SeparationNodeKind::ImageTarget` 表示可以产出 Sprite 的未绑定 Image;`SeparationNodeKind::TextRemovalOnly` 表示普通 Text 的层级和矩形上下文。Text 不进入目标 batch、visual binding、bound 或 problematic。
|
||||
- 候选按前序 DFS 和 children 原顺序遍历。root 在 `root_extractable=true` 时按普通 Image 目标参与,否则只递归其 children;已终止 Image 跳过自身但继续访问 descendants。
|
||||
- 一个 batch 是 DFS 顺序上的连续 Image 目标片段,不做 overlap 筛选、排序或面积抵消。按源矩形 `width_px × height_px` 使用 `u64` 累加,正常上限为 `2880 × 2880 × IMAGE_EDIT_AREA_UTILIZATION`;利用率是命名常量,当前为 `0.8`。加入下一个目标会超限时停止;若当前为空则强制加入第一个 Image 目标以保证进度。root/超大图片的专门 atlas 策略列 TODO。
|
||||
- parent 与 child 可以同批;若 child 因面积预算未入批,仍作为 prompt 上下文,要求 parent 背景一并移除该 child,下一批再输出 child。
|
||||
- 一个 batch 的最小处理单元是:一次 image-edit + 一次 visual binding。batch 成功后只把 Image 结果追加到 bound,NeedRework 节点在达到返工上限后追加到 problematic;树拓扑不变,流程继续消费剩余树。
|
||||
- 不额外维护节点状态枚举;节点是否仍在 pending tree、`rework_count` 和 problematic 容器共同表达状态。
|
||||
|
||||
### 普通文字遮罩
|
||||
### 普通文字
|
||||
|
||||
- 普通 `Text` 仍是正式 UI tree 中的独立 UI 元素,不进入 separation tree,也不参与 batch、绿色框、visual binding 或 bound 结果。
|
||||
- 构造 separation tree 时,把 Text 节点的布局矩形转换为页面像素坐标,挂到最近的未绑定图片节点(`ImageComponent.target_graphic == None`)的 `text_mask_areas`。纯容器只透传;嵌套图片下归最近图片;没有可切图片祖先的 Text 直接忽略。
|
||||
- `text_mask_areas` 只保存 `global_pos_x_px`、`global_pos_y_px`、`width_px`、`height_px`。不保存 NodeId、父节点、文字内容、字体样式,也不做 OCR、字形估算、偏差检查、合并或去重。
|
||||
- marker 阶段在 image-edit 前把当前 batch 节点自身的文字矩形填充为紫色;它与子图片区域一起绘制,绿色框随后绘制并位于最上层。文字遮罩不递归读取后代节点的 mask。
|
||||
- mask 仅是 image-edit 输入标记,未对 image-edit 残留文字增加 OCR 或视觉复核;正式文字语义仍由 `TextComponent` 保持。
|
||||
- 普通 `Text` 仍是正式 UI tree 中的独立 UI 元素,并作为 `TextRemovalOnly` 节点保留在 separation tree;使用现有 name/description 提供语义,不新增 OCR 或文字内容字段。
|
||||
- Text 只进入完整页面上下文,不进入 Image 目标集合,也不传给 visual binding。image-edit 只需从所属父图片/背景层中移除并重建普通文字,不生成文字 Sprite。
|
||||
- Text 的 NodeId、矩形和父子关系必须保留,使 parent/child 分层 prompt 能说明文字所在层;Text 不计入 batch 面积或目标数量,也不阻塞树完成。
|
||||
|
||||
## 图片编辑与视觉绑定
|
||||
|
||||
- image-edit 使用源 UI design 图片及由 Rust 生成的绿色标记/紫色重建输入。处理父节点时,紫色填充其 children 的矩形区域(包括已 problematic 的 children),再在父节点自身外围绘制绿色框和角到角的绿色交叉线;绿色标记覆盖在紫色之上。叶节点只绘制绿色框和角到角的绿色交叉线,不填充自身。
|
||||
- image-edit 直接使用原始 UI design PNG,不再生成或发送绿色框、紫色填充等 marker 图。Rust 保留现有 extraction prompt,并在其末尾追加由当前 separation state 生成的完整页面分层清单和 batch 状态;本次实现不改写既有 prompt 文案,由维护者手工整合 marker 旧句子。
|
||||
- 追加清单区分本轮 Image 输出目标、已完成 Image、仅作父子/遮挡上下文的 Image,以及只需从父图片移除的 Text。清单使用人类可读的编号、name/description、位置和层级,不向 image-edit 暴露 opaque NodeId。
|
||||
- 由于 raw endpoint 每次只返回一张 PNG,prompt 要求 image-edit 输出透明 atlas:本轮图片层可以移动和缩放,放置在不会互相遮挡的位置;视觉模型返回每层在 processed 图中的实际区域。源节点矩形只用于语义定位,不用于裁切区域推断。
|
||||
- 请求尺寸始终使用源 UI design 尺寸;Raw GPT Image 2 API 保证返回相同尺寸,客户端不额外做尺寸拒绝检查。
|
||||
- 标记图构建、处理图解码/写入和 cut 裁切属于本地 CPU/文件操作,放入独立的
|
||||
`spawn_blocking` 任务;image-edit 与 visual binding 网络请求仍运行在 async future 中。
|
||||
- 视觉 binding 输入源图与处理图,必须为当前 batch 每个节点恰好返回一次 `Ok` 或 `NeedRework`。
|
||||
- 处理图解码/写入和 cut 裁切属于本地 CPU/文件操作,放入独立的 `spawn_blocking` 任务;image-edit 与 visual binding 网络请求仍运行在 async future 中。
|
||||
- 视觉 binding 输入源图与处理图,只接收当前 batch 的 Image targets,必须为每个 Image target 恰好返回一次 `Ok` 或 `NeedRework`。每个决定继续携带 `to_node: NodeId`;Text 不出现在请求或 schema 中。
|
||||
- `Ok` 返回 `NodeId + BindingArea`;Rust 仅校验 NodeId、区域边界和非零尺寸,不检查与原节点框的偏差,也不要求区域不重叠。
|
||||
- cut 前会对视觉模型返回的 `BindingArea` 做本地像素边界归一化。处理图是透明 PNG,有效像素定义为 `alpha > 0`。四条边以模型 area 为起点,每条边根据首次扫描结果固定方向:边上无有效像素则只向内收缩,边上有有效像素则只向外扩展;四边每轮从同一矩形快照同时逐像素推进,直到达到“内侧有像素、外侧无像素”的分界、图像边界或每条边相对原始 area 的位移上限。每条边最多相对原始 area 移动 `32px`,由模块级常量 `MAX_BINDING_AREA_EDGE_ADJUSTMENT_PX` 定义,与原始 area 尺寸无关。方向固定用于避免稀疏像素造成边界来回振荡;没有理想分界时使用受限范围内的最终 area,不重新请求视觉模型,也不转 problematic。全透明处理图不走特殊错误分支,仍沿同一规则得到最终 area 后裁切。归一化只影响本地 cut,不改写原始 `BindingDecision`、sidecar 或 DTO;日志记录原始 area、最终 area、是否变更,以及仍需移动时是否受到该常量上限、图像边界或非零尺寸约束。性能优化列 TODO。
|
||||
- `NeedRework` 携带短问题描述(最多 512 个 Unicode 字符);通过校验后按产生顺序追加到目标 `SeparationNode.note.rework_notes`,下一次该节点进入 image-edit 时全部意见会注入提取 prompt。结构化工具调用失败时使用可复用 repair harness,把错误反馈给模型并额外请求一次;image-edit 不使用该 harness。
|
||||
- 达到返工上限时仍先保留最后一条视觉模型意见,再把节点追加到 problematic;网络、IO、裁切等基础设施错误不写入节点意见。
|
||||
- 达到模块级重做常量后,节点移入 problematic;不中断整条工作流,最终统一通知用户。image-edit、图像写入或裁切失败保留当前 state 并返回错误,不自动把整批标记为 problematic。
|
||||
- 父节点背景重建由 image-edit 模型完成,不由 Rust 硬编码重建算法完成。
|
||||
- 性能观测沿用 `app_log!`:marker 记录端到端与 `spawn_blocking` 耗时,area 记录 `elapsed_us` 与扫描轮数,image-edit 与 visual binding 记录整个请求耗时,处理图写入、cut 和 batch 记录阶段耗时;日志不写入 prompt、图片内容、绝对路径或模型原文。
|
||||
- 性能观测沿用 `app_log!`:image-edit 与 visual binding 记录整个请求耗时,处理图写入、cut 和 batch 记录阶段耗时;日志不写入 prompt、图片内容、绝对路径或模型原文。
|
||||
|
||||
## 临时 sidecar
|
||||
|
||||
@@ -64,10 +63,10 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正
|
||||
- sidecar 目录按 UI manifest `asset_id` 生成,复用 `generated_file_stem(asset_id)` 的安全字符替换和 SHA-256 摘要规则,位于项目 `ui/` 下。
|
||||
- 目录只保存一份当前 separation state,而不是每 batch 一个状态文件。
|
||||
- state 文件只保留 `schema_version`、separation trees、bound 结果和 problematic 节点,不重复保存 `projectId / assetId / uiStateRevision`。
|
||||
- `SeparationNode.text_mask_areas` 是 separation tree 的必选字段,当前开发阶段继续使用 `ui-editor-separation-state.v1`,不提供旧 sidecar 迁移或回退。
|
||||
- `SeparationNode.kind` 与 tree children 一起持久化;当前数据结构变更提升 separation state schema 版本,不提供旧 sidecar 迁移或回退。
|
||||
- sidecar 只在 separation 未完成期间存在;完成后删除 state JSON。
|
||||
- 当前只持久化已经完成的 batch;正在执行 batch 的恢复语义列 TODO。
|
||||
- 临时图片可跨重启保留。raw image-edit 返回图、绿色/紫色标记图、处理图和 cut 图片当前都保留用于 debug;理论上只应在内存中,清理/归档策略列 TODO。
|
||||
- 临时图片可跨重启保留。raw image-edit 返回图、处理图和 cut 图片当前都保留用于 debug;理论上 raw/processed 中间图只应在内存中,清理/归档策略列 TODO。
|
||||
- 并发边界:当前由前端 `isSeparating` 与 `runWithStateLocked` 保证同一 UI 编辑会话
|
||||
同时只有一次 separation。sidecar 是临时恢复状态,不是正式 UI 资产真相,不参与
|
||||
manifest 或项目 revision,因此当前不额外持有项目写锁;若未来支持多窗口/多进程并发,
|
||||
@@ -112,3 +111,6 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正
|
||||
- problematic 对更高层 workflow 完成门禁的最终定义。
|
||||
- separation workflow 与 manifest/stage 的接入。
|
||||
- Raw GPT Image 2 后端 raw operation 持久状态及恢复 worker。
|
||||
- 完整页面上下文对 image-edit 效果的人工评估。
|
||||
- raw prompt 上限从 4 KiB 扩展到 16 KiB 的代理合同与预算。
|
||||
- root/超大图片超过 atlas 面积预算时的专门策略。
|
||||
|
||||
Reference in New Issue
Block a user