From 5c83142fecbd4b10d2722474aa0052a66375dce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 11 Sep 2026 11:01:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0UI=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=87=E5=88=86=E7=B4=A0=E6=9D=90=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将多轮整图分层与DFS面积批次写入权威专题 明确TextRemovalOnly、视觉绑定和atlas输出合同 删除平行专题文档并保留后续TODO 补充UI编辑器自动分离多轮分层方案 新增整图语义分层与多轮 image-edit 方案 明确 DFS 批次、TextRemovalOnly 和视觉绑定合同 记录面积预算、恢复边界与后续 TODO --- ...案】UI编辑器自动切分素材工作流-2026-09-08.md | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md b/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md index 7bce4bfc5..76e9864d4 100644 --- a/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md +++ b/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md @@ -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 面积预算时的专门策略。