Merge pull request 'UI编辑器自动分图层切图标' (#304) from feat/ui-editor-auto-seperation into master
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m9s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 4m33s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m31s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 4m27s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m26s
Project CI / AI game creator shell Rust crates (push) Successful in 2m0s
Project CI / Frontend tests (push) Successful in 4m14s
Project CI / Repository checks (push) Successful in 3m25s
Project CI / Backend tests (push) Failing after 5m18s
Project CI / Native shell tests (push) Successful in 6m55s
Project CI / AI game creator shell web tests (push) Successful in 3m17s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m9s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 4m33s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m31s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 4m27s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m26s
Project CI / AI game creator shell Rust crates (push) Successful in 2m0s
Project CI / Frontend tests (push) Successful in 4m14s
Project CI / Repository checks (push) Successful in 3m25s
Project CI / Backend tests (push) Failing after 5m18s
Project CI / Native shell tests (push) Successful in 6m55s
Project CI / AI game creator shell web tests (push) Successful in 3m17s
Reviewed-on: #304
This commit was merged in pull request #304.
This commit is contained in:
@@ -143,7 +143,7 @@ UI Editor Inspector 的全局只读状态唯一来源是 `controller.editor.isLo
|
||||
|
||||
## 2026-08-18 UI Editor 结构识别、合并与增量导入边界
|
||||
|
||||
UI Editor 当前把“识别界面结构”定义为结构草稿阶段,而不是完整视觉还原阶段。识别 DTO 只负责输出节点层级、几何、名称、描述和置信度;节点组件暂为空,由后续“绑定视觉素材”阶段补齐 Image / Text 组件。`applyRecognitionResult` 可以整体替换当前 `ui_trees`,但该替换只代表结构结果,不能宣称已经保留截图中的视觉内容;组件状态使用 `NoProblem`,前置检查仍会根据空组件和素材绑定情况阻止跳过绑定阶段。
|
||||
UI Editor 当前把“识别界面结构”定义为结构草稿阶段,而不是完整视觉还原阶段。识别 DTO 只负责输出节点层级、几何、名称、描述和置信度;节点组件暂为空,由后续“自动切分素材”阶段补齐 Image / Text 组件。`applyRecognitionResult` 可以整体替换当前 `ui_trees`,但该替换只代表结构结果,不能宣称已经保留截图中的视觉内容;组件状态使用 `NoProblem`,前置检查仍会根据空组件和素材切分情况阻止跳过自动切分阶段。
|
||||
|
||||
结构识别、界面语义建议、多图合并和组件绑定只接受不超过 `1 MiB` 的 LLM 工具调用 arguments,并在递归业务类型反序列化前先解析为通用 JSON、迭代检查结构预算。结构识别按每棵返回树独立限制为最多 `512` 个 LLM 节点和 `32` 层,不汇总多棵树的节点数,也不计 Rust 自动补建的页面根;界面语义建议最多 `4` 个节点和 `4` 层;合并计划最多 `512` 个计划节点和 `32` 层,`Simple.children` 与 `Merged.merged_from` 使用同一计数和深度口径;组件绑定 `changes` 不得超过当前可编辑节点数且绝对上限为 `10,000`,每个 change 的完整组件栈最多 `64` 个组件。任何超限结果均整次拒绝,不截断、不返回部分结果,也不把工具 arguments 正文写入日志。
|
||||
|
||||
@@ -1282,7 +1282,7 @@ DirectProject 现明确采用 Codex app-server 的 `danger-full-access` sandbox
|
||||
DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络随完整 sandbox 开放;联网资料仍可走受控 `agc_web_search`。多 Agent、Apps、完整插件 Runtime、hooks、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制仍关闭,避免绕过 AGC durable delegation、浏览器证据和副作用审计;图片生成通过客户端审核的 `agc_tools.agc_generate_image` 暴露普通单图、角色图、视觉规范图和 UI 设计图,完整游戏美术包继续使用 `agc_tools.taonier_prepare_game_art`,两者都复用同一客户端登录态、幂等账本、下载校验和 manifest/revision 投影,不开放 Codex 原生 image tool。app-server 使用隔离 `CODEX_HOME`:内置 `agc_tools` 由客户端启动参数注入,用户在客户端扩展列表启用的独立第三方 MCP 以原生配置写入该次隔离 home;全局 Codex MCP、禁用项、Plugin hooks/apps 和其它插件能力不进入 DirectProject。第三方项固定非 required,配置或启动失败只记录该项,不替换 `agc_tools`;provider session token、工具桥地址和受控搜索标记不得通过第三方 MCP 的环境转发字段泄露。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅使用连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。`agc_tools` 的平台授权由 AGC 客户端当前登录会话和受控后端完成,普通客户端不得把 DirectProject 请求改成外部 API Key 请求;401/403 只投影为客户端登录或权限异常,不向用户索要凭据或暴露内部 URL。shell 子进程采用 `shell_environment_policy` core 继承及 secret/proxy/bridge 排除,provider key 和桥接凭据不得进入命令环境。系统提示词不再预注入项目源码快照或 Skill 正文,Codex 按需读取当前 cwd 文件。
|
||||
## 2026-08-24 AGC UI 原型桥接与自主 UI workflow
|
||||
|
||||
- 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批组件绑定,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用、未产出可渲染组件或仍有待审节点时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段,交给用户检查和手动调整。UI 编辑器独立的语义建议请求也必须复用统一 LLM 传输选择,`llm.stream=true` 时发送 `stream=true` 并聚合完整工具调用后再校验结果。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。
|
||||
- 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批自动切分素材,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用或未产出可渲染组件时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成;自动切分达到返工上限的 problematic 节点则回写 UI State 的 `component_status = NeedReview(...)`,作为已尽力完成、交由用户在 UI 编辑器中处理的结果。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `asset-separation` 最终阶段,交给用户检查和手动调整。UI 编辑器独立的语义建议请求也必须复用统一 LLM 传输选择,`llm.stream=true` 时发送 `stream=true` 并聚合完整工具调用后再校验结果。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。
|
||||
|
||||
## 2026-08-28 AGC 自主构建 relaxed 编排覆盖
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Raw GPT Image 2 图片编辑代理
|
||||
|
||||
更新时间:`2026-09-08`
|
||||
|
||||
## 目标
|
||||
|
||||
提供一个由主站客户端调用的独立同步图片编辑代理:
|
||||
|
||||
```text
|
||||
POST /api/raw/v1/images/edit
|
||||
```
|
||||
|
||||
该入口使用登录态 Bearer access token,不进入 External v1 / MCP OpenAPI,不读取或写入画布、项目资源、素材库、OSS 结果或 `external_generation_job`。
|
||||
|
||||
## 请求合同
|
||||
|
||||
请求使用 `multipart/form-data`,不再接受 JSON/base64 入站格式。图片直接作为文件字段上传,避免 base64 膨胀和入站解码;服务端仍在扣费前完成 PNG 完整解码与资源限制校验。
|
||||
|
||||
```text
|
||||
image: <PNG 文件,必填>
|
||||
mask: <PNG 文件,可选>
|
||||
prompt: 修改图片
|
||||
quality: auto
|
||||
background: auto
|
||||
output_format: png
|
||||
width: 1536
|
||||
height: 1024
|
||||
```
|
||||
|
||||
`image` 和 `mask` 必须是 `image/png` 文件字段;服务端不信任客户端文件名,转发时使用固定文件名。空文件、非 PNG 字节、MIME 不匹配或 mask 与 image 尺寸不一致均在扣费前返回 400。`prompt` 必填,UTF-8 原始字节长度不得超过 `16 KiB`;其它文本字段也使用同一 `16 KiB` 有界流式读取,超限在扣费前返回 400。`quality`、`background` 和 `output_format` 采用 GPT Image 模型支持的值。字段不能重复,未知字段拒绝;缺失的必填字段拒绝。
|
||||
|
||||
`width`、`height` 使用严格输出尺寸规则,均在扣费前校验:
|
||||
|
||||
1. 单边最大值为 `3840px`;
|
||||
2. 宽、高均为 `16px` 的倍数;
|
||||
3. 长边 / 短边不超过 `3:1`;
|
||||
4. 总像素范围为 `655360` 至 `8294400`(含边界)。
|
||||
|
||||
校验通过后按整数尺寸发送给 provider,不静默 clamp 或改写调用者尺寸。
|
||||
|
||||
Raw 路由的 multipart body limit 为 `64 MiB`,覆盖图片和文本字段;每个 `image` / `mask` 字段最多 `32 MiB`,两者图片字节总计最多 `48 MiB`,图片字段按 chunk 流式收集,越过任一上限立即返回 `413`,文本字段按 chunk 流式读取并在达到 `16 KiB` 时立即拒绝。随后图片校验在独立的 raw-image 解码 semaphore(进程内最多 4 个 blocking 解码任务)中执行,并受 30 秒本地处理截止时间约束;超时只停止等待,不会提前释放仍在运行任务持有的槽位。PNG 仅接受 8-bit/channel(`png::BitDepth::Eight`),其它位深在解码前以 400 返回“`{field} 必须为 8-bit PNG(每通道 8 位)`”;PNG 解码使用与输出合同一致的资源上限:宽高各不超过 `3840`,解码分配不超过 `8294400 × 4` 字节;不再执行 base64 入站解码。
|
||||
|
||||
服务端发送给 `platform-image` 时固定注入:
|
||||
|
||||
```text
|
||||
model = gpt-image-2
|
||||
n = 1
|
||||
```
|
||||
|
||||
请求不暴露 `model`、`n`、`response_format`、`style`、`user` 或 `output_compression`。
|
||||
|
||||
## 成功响应合同
|
||||
|
||||
响应始终为 JSON,响应只保留 `data` 字段,图片内容只以 base64 返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"b64_json": "<base64>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`data` 保持数组形状,即使服务端固定 `n=1`。响应不重复返回请求参数,不返回 URL、资源 ID、任务 ID、provider 原始 JSON 或 editor 字段。
|
||||
|
||||
## 预检查与计费事务
|
||||
|
||||
所有 multipart 字段、图片结构和 provider 参数检查必须在扣费前完成。预检查失败直接返回 4xx,不产生钱包流水,也不调用 provider。
|
||||
|
||||
检查通过后,api-server 进入现有资产操作计费边界,通过 SpacetimeDB 钱包事务 procedure 原子完成:
|
||||
|
||||
1. 按共享常量 `platform-image::GPT_IMAGE_2_2K_LONG_EDGE_THRESHOLD`(当前值 `1536`)解析图片价格:长边不超过阈值使用 1K 价格,否则使用 2K 价格;当前默认价格为 3 / 5 泥点;
|
||||
2. 以认证后的用户、`raw-image-edit` 命名空间和请求 ID 组成幂等扣费流水 ID;
|
||||
3. 原子扣除用户泥点并写入 `asset_operation_consume` 流水。
|
||||
|
||||
provider 调用在 SpacetimeDB 事务之外执行。失败时由现有计费边界把幂等退款事实写入 SpacetimeDB refund outbox,再由 worker 完成退款。
|
||||
|
||||
TODO:新增 raw 操作持久化状态,将“创建 raw 操作事实 + 扣费”收入同一事务,并由恢复 worker 对“已扣费但未收口”状态自动退款,填补进程在扣费后、写入 refund outbox 前崩溃的窗口。
|
||||
|
||||
raw 操作使用独立的 operation / ledger 命名空间,例如 `raw-image-edit`,不能复用编辑器资源 ID、编辑器任务 ID 或 `external_generation_job`。
|
||||
|
||||
## Provider 边界
|
||||
|
||||
`platform-image` 保留 VectorEngine 协议细节。raw handler 只负责:认证、multipart 字段解析、PNG 预检查、计费编排和响应映射。provider 请求仍由 `platform-image` 统一构造,并携带 `model`、`n`、`quality`、`background`、`output_format`、尺寸及图片参考字节。
|
||||
|
||||
provider 响应只提取并透传 `data[].b64_json` 字符串,不在服务端解码图片 base64,也不读取或回传 provider 的 `output_format`(该字段只是请求参数回显)。GPT-Image-2 原生只返回 `b64_json`,因此不发送 `response_format` 参数,也不实现 URL 响应下载或兼容分支。发送、响应读取、上游状态、响应解析和缺图失败必须生成 `PlatformImageFailureAudit`,由 api-server 写入现有外部 API 失败审计链;成功结果同时写入统一的 `external_generation_run` 追踪事件。raw handler 只将上游 `b64_json` 原样写入 `data[].b64_json`。
|
||||
|
||||
## 代码拆分
|
||||
|
||||
- `server-rs/crates/api-server/src/raw_image.rs`:独立路由 handler、multipart 字段解析、请求/响应 DTO、PNG 输入校验、预检查和 raw billing 编排。
|
||||
- `server-rs/crates/platform-image/src/vector_engine/raw_edit.rs`:raw 编辑选项、严格尺寸校验、独立 provider 请求映射和 `b64_json` 响应透传;由 api-server 按现有图片 API 传统构造并传入共享的 VectorEngine `reqwest::Client`,不在每个 raw 调用内部重复构造 client。每次请求仍用 `effective_request_timeout_ms` 通过 request builder 设置剩余 deadline;不修改统一 transport builder 的连接池策略。
|
||||
|
||||
raw-edit 的图片输入使用独立的 `RawImageEditImage`(`bytes::Bytes`),由 reqwest `Part::stream(Body::from(Bytes))` 直接接管 multipart 请求体,避免整图和掩码在 `Part::bytes` 的 `Cow<[u8]>` 转换中再次复制。既有 `ReferenceImage`、`DownloadedImage` 及 curl/编辑器链路继续保持 `Vec<u8>` 契约,不因 raw-edit 引入全局字节类型迁移。
|
||||
|
||||
- `server-rs/crates/api-server/src/modules/raw.rs`:只注册 `/api/raw/v1/images/edit` 并挂载 Bearer middleware。
|
||||
|
||||
不修改 External v1 OpenAPI;不在 `external_editor_api.rs`、编辑器项目模块或外部生成 worker 中增加 raw 分支。
|
||||
|
||||
## 验收
|
||||
|
||||
- 未认证请求被 Bearer middleware 拒绝。
|
||||
- 预检查失败时钱包无扣费、provider 无请求。
|
||||
- 成功响应严格只包含 `data[].b64_json`。
|
||||
- provider 失败时 raw 操作失败事务产生可恢复退款事实。
|
||||
- raw 请求不创建 `external_generation_job`,不写 editor project/resource/asset/OSS。
|
||||
- 运行 api-server 与 platform-image 定向测试、`npm run check:encoding` 和 `git diff --check`。
|
||||
@@ -0,0 +1,127 @@
|
||||
# UI 编辑器自动切分素材工作流
|
||||
|
||||
更新时间:`2026-09-11`
|
||||
|
||||
## 目标
|
||||
|
||||
将 UI 编辑器现有“用户先提供独立图片/图标,再执行组件绑定”的入口替换为自动切分素材:结构识别阶段返回完整 UI 树和组件语义,切分阶段让 image-edit 模型按整页分层说明生成单张透明 atlas,再由视觉模型确认 atlas 中图片区域与目标节点的对应关系。
|
||||
|
||||
## 识别结果
|
||||
|
||||
- `recognize` 返回完整 `Node.component` 草稿,不再要求用户先导入独立素材。
|
||||
- `NodeComponent::PureNode` 表示纯节点;`NodeComponent::WithComponent` 携带完整组件。
|
||||
- `ImageComponent.target_graphic = None` 表示图片组件等待分离结果回填;它不是“明确没有图片”。
|
||||
- 一个 Node 最多承载一个 `Component`;需要多个视觉层时使用多个 Node 表达。
|
||||
- 组件草稿直接保存在正式 UI Node 中;临时 separation tree 不复制组件。
|
||||
|
||||
### LLM 工具中的组件载荷
|
||||
|
||||
正式 `Node` 使用 `component: Option<Component>`,因为 Rust/前端状态需要直接表达“纯结构节点”或唯一组件;但识别和绑定 LLM 工具不使用可空字段。部分模型在严格工具 schema 下不会稳定地产生 `null`,因此工具返回显式外部枚举:
|
||||
|
||||
- `"PureNode"` 表示纯结构节点;
|
||||
- `{ "WithComponent": <完整 Component> }` 表示该节点拥有一个组件。
|
||||
|
||||
Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正式 Node 的 `None` / `Some(Component)`。`PureNode + NeedReview/Blocked` 是非法组合;`PureNode + NoProblem` 合法,带组件节点可以处于 `NoProblem`、`NeedReview` 或 `Blocked`。该枚举只解决工具调用的可判别性,不引入旧 `null` 格式兼容或迁移。
|
||||
|
||||
## Separation tree
|
||||
|
||||
- recognition 完成后由 UI tree 构造临时 separation tree;每棵树保留原 UI tree 的 `src_ui_design` 与真实 `root`,不生成 synthetic root。
|
||||
- 非 root 的纯容器和已绑定图片节点在构造时过滤并透传 children;真实 root 始终保留,`root_extractable` 表示 root 是否含未绑定图片组件并可作为候选。
|
||||
- 节点的 `children` 在整个 workflow 中始终保留,不能因处理成功或失败而从树上删除。节点终态由 `bound`、`problematic_nodes` 反查;两者均不存在时仍待处理,`rework_count` 仅记录视觉模型返工次数。
|
||||
- `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 元素,并作为 `TextRemovalOnly` 节点保留在 separation tree;使用现有 name/description 提供语义,不新增 OCR 或文字内容字段。
|
||||
- Text 只进入完整页面上下文,不进入 Image 目标集合,也不传给 visual binding。image-edit 只需从所属父图片/背景层中移除并重建普通文字,不生成文字 Sprite。
|
||||
- Text 的 NodeId、矩形和父子关系必须保留,使 parent/child 分层 prompt 能说明文字所在层;Text 不计入 batch 面积或目标数量,也不阻塞树完成。
|
||||
|
||||
## 图片编辑与视觉绑定
|
||||
|
||||
- image-edit 直接使用原始 UI design PNG,不再生成或发送绿色框、紫色填充等额外辅助输入图。提取 prompt 直接描述完整页面分层清单和当前 batch 状态。
|
||||
- 原因:部分视觉模型不会可靠读取 PNG alpha;image-edit 返回的处理图还可能出现只包含 `1..254`、缺少 `0` 和 `255` 的异常 alpha,导致 visual binding 无法稳定区分透明区域与素材内容。该问题只影响视觉模型的观察输入,不改变正式 cut 使用的 RGBA 真相。
|
||||
- 追加清单区分本轮 Image 输出目标、已完成 Image、仅作父子/遮挡上下文的 Image,以及只需从父图片移除的 Text。清单使用人类可读的编号、name/description、位置和层级,不向 image-edit 暴露 opaque NodeId。
|
||||
- 提取 prompt 先把 separation tree 投影为小型 YAML 视图,再注入固定规则文本:每个节点只包含展示编号、状态、`x/y/width/height` 矩形、描述、可选返工意见和递归 children;YAML 不携带 opaque NodeId,树的嵌套关系替代 `depth/role` 字段。
|
||||
- 由于 raw endpoint 每次只返回一张 PNG,prompt 要求 image-edit 输出透明 atlas:本轮图片层可以移动和缩放,放置在不会互相遮挡的位置;视觉模型返回每层在 processed 图中的实际区域。源节点矩形只用于语义定位,不用于裁切区域推断。
|
||||
- image-edit 请求尺寸由当前 batch 的源矩形面积决定,不再始终使用源 UI design 尺寸。设 batch 面积为 `A`、面积上限为 `L = IMAGE_EDIT_AREA_LIMIT_PX`,先计算 `floor(IMAGE_EDIT_MAX_DIMENSION_PX × sqrt(A / L))`,再按 `IMAGE_EDIT_DIMENSION_ALIGNMENT_PX` 向下对齐,并限制在 `IMAGE_EDIT_MIN_DIMENSION_PX` 至 `IMAGE_EDIT_MAX_DIMENSION_PX`(当前为 `816` 至 `2880`)之间;请求使用正方形 `N × N`。Raw GPT Image 2 API 保证返回与请求相同尺寸,客户端不额外做尺寸拒绝检查。最终 cut 继续依据 processed PNG 的实际尺寸执行,不使用源图尺寸换算。
|
||||
- 处理图解码/写入和 cut 裁切属于本地 CPU/文件操作,放入独立的 `spawn_blocking` 任务;image-edit 与 visual binding 网络请求仍运行在 async future 中。
|
||||
- visual binding 请求前新增本地预处理:复用 `MIN_VISIBLE_ALPHA`,将 alpha 小于该阈值的像素替换为不透明洋红标记色 `[255, 0, 255, 255]`,其余像素保留 RGB 并将 alpha 设为 `255`。预处理图只用于 visual binding,原始 processed RGBA 继续用于 cut;不新增质量门禁、alpha 统计判定或重试。
|
||||
- 视觉 binding 输入源图与预处理后的不透明处理图,只接收当前 batch 的 Image targets,必须为每个 Image target 恰好返回一次 `Ok` 或 `NeedRework`。每个决定继续携带 `to_node: NodeId`;Text 不出现在请求或 schema 中。
|
||||
- binding prompt 使用英文明确说明:洋红色是本工作流在请求前注入的透明区域标记,不是 image-edit 缺陷,也不是 UI 素材;模型不得将该颜色计入 extracted area。颜色文本由 `VISUAL_BINDING_TRANSPARENT_MARKER_RGBA` 常量生成,避免提示词与实现漂移。
|
||||
- `Ok` 返回 `NodeId + BindingArea`;Rust 仅校验 NodeId、区域边界和非零尺寸,不检查与原节点框的偏差,也不要求区域不重叠。
|
||||
- cut 前会对视觉模型返回的 `BindingArea` 做本地像素边界归一化。处理图是透明 PNG,有效像素定义为 alpha 不低于模块级常量 `MIN_VISIBLE_ALPHA`(当前为 `16`);边缘扫描还要求至少连续 `MIN_CONSECUTIVE_VISIBLE_EDGE_PIXELS` 个有效像素(当前为 `2`),避免半透明光晕和孤立噪点驱动边界移动。四条边以模型 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!`:image-edit 与 visual binding 记录整个请求耗时,处理图写入、cut 和 batch 记录阶段耗时;日志不写入 prompt、图片内容、绝对路径或模型原文。
|
||||
|
||||
## 临时 sidecar
|
||||
|
||||
- separation 状态不写入 UI JSON,也不进入 manifest。
|
||||
- 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.kind` 与 tree children 一起持久化;当前数据结构变更提升 separation state schema 版本,不提供旧 sidecar 迁移或回退。
|
||||
- sidecar 只在 separation 未完成期间存在;完成后删除 state JSON。
|
||||
- 当前只持久化已经完成的 batch;正在执行 batch 的恢复语义列 TODO。
|
||||
- 临时图片可跨重启保留。image-edit 返回的 processed 图、visual binding 预处理图和 cut 图片当前都保留用于 debug;预处理图位于同一 sidecar,命名为 `binding-<随机 UUID>.png`,不写入 separation state。清理/归档策略列 TODO。
|
||||
- 并发边界:当前由前端 `isSeparating` 与 `runWithStateLocked` 保证同一 UI 编辑会话
|
||||
同时只有一次 separation。sidecar 是临时恢复状态,不是正式 UI 资产真相,不参与
|
||||
manifest 或项目 revision,因此当前不额外持有项目写锁;若未来支持多窗口/多进程并发,
|
||||
再增加按 UI asset 的 sidecar 进程级锁。
|
||||
|
||||
## bound 与 problematic
|
||||
|
||||
- bound 结果仅保存 `NodeId + cut_image_path`,不保存 `BindingArea` 或 component kind。
|
||||
- problematic 记录原始 NodeId、问题描述和 `rework_count`;原始 UI Node 保留不变。
|
||||
- `SeparationDTO` 不返回计数字段,只返回 `bound_nodes` 与 `problematic_nodes`。
|
||||
- separation Rust 流程不自动登记项目级 SpriteAsset。
|
||||
- 前端调用方消费 `SeparationDTO.bound_nodes`,复制/登记 cut 图片为项目级 SpriteAsset,再回填对应 Node 的唯一 Image component。
|
||||
- 每次重做产生新的 SpriteAssetId,不假设 NodeId 到 SpriteAssetId 的稳定映射。
|
||||
- sidecar 中的图片保留,正式 SpriteAsset 的最终清理策略列 TODO。
|
||||
|
||||
## 前端正式接入
|
||||
|
||||
- UI 编辑器点击“自动切分素材”时,前端只调用一次 `separate_ui`,消费完整 `SeparationDTO`;separation 内部 batch 不向前端暴露,也不在 UI 中显示 batch 进度。
|
||||
- 自动切分的前置检查只要求界面图及对应 UI tree 有效,不要求用户预先导入 SpriteAsset;结果检查负责报告未回填的 Image、丢失的切分素材引用、丢失的字体引用及组件审阅状态。新建和重新开始切分时执行前置检查,继续已有 sidecar 时直接按恢复状态尽力完成。
|
||||
- 如果 sidecar 已存在未完成的 `state.json`,点击入口时先打开独立弹窗,由用户选择“继续上次自动切分素材”或“开始新的自动切分素材”。继续复用 sidecar 的 pending tree;重新开始只替换当前 `state.json`,不删除 sidecar 图片。
|
||||
- `BoundNode.cut_image_path` 必须是项目根相对路径。前端使用现有 `import_local_project_image_assets` 登记 cut 图片;由于该通用命令单次最多 100 个路径,前端可以在资源登记阶段按 100 条分组调用,但这不属于 separation batch,也不向用户展示。
|
||||
- 现有本地资源导入按清洗后的文件名 stem 与内容摘要生成目标路径;相同目标路径直接复用已有 manifest asset ID,内容不同则拒绝覆盖或生成不同摘要路径。前端不自行猜测 SpriteAsset 是否存在,也不从 NodeId 派生 SpriteAssetId。
|
||||
- 全部可登记图片完成导入后,前端在一个 `runWithStateLocked` 中复用 `addSpriteAssets` 的内部 State 变换逻辑,加入返回的 SpriteAsset 并回填仍匹配 Node 的唯一未绑定 Image component,最后一次性提交 State。公开 `addSpriteAssets` 的普通 mutation guard 不放宽。
|
||||
- UI tree 在 separation 期间发生变化时,已登记的 cut 图片和可匹配节点的回填保留;找不到 Node 或没有未绑定 Image 的结果产生明确问题提示,不回滚已登记资源,不静默跳过。
|
||||
- State 保存成功后才调用 `finalize_separation` 删除 sidecar `state.json`;登记、回填或保存失败时保留 sidecar,允许下次选择继续。sidecar 图片按当前 debug 策略保留。
|
||||
- UI 编辑器独立页面的 `bindComponents` 前端入口、`binding.rs`、`bind_components` Tauri 命令及 Runtime `workflow.rs` 统一使用当前单组件模型;LLM 工具载荷使用 `NodeComponent`,正式 Node 仍使用 `Option<Component>`。
|
||||
|
||||
## 重启与 Raw GPT Image 2
|
||||
|
||||
- 已保存的 separation state 是跨重启继续工作的最小单位;重启后从上一个已保存 batch 的状态继续。
|
||||
- 当前执行中的 batch 是否持久化、以及如何避免 image-edit 成功后在 patch 前崩溃导致重复调用,列为 TODO。
|
||||
- Raw endpoint 每次 HTTP 调用都是一次新操作;客户端不保存或复用 raw operation ID,不实现第二套本地幂等账本。
|
||||
- 图片编辑调用当前 Raw GPT Image 2 multipart 合同:`image`(PNG 文件)、`prompt`、`width`、`height`、`output_format=png`、`background=transparent`;不再发送旧 JSON/base64 请求体。
|
||||
- 后端 raw operation 的持久状态与扣费后崩溃恢复窗口,遵循 Raw GPT Image 2 方案中的独立 TODO。
|
||||
|
||||
## 实现组织
|
||||
|
||||
- separation prompt 按职责拆分为 `prompt/extract.rs` 与 `prompt/binding.rs`,由 `prompt/mod.rs` 统一导出。
|
||||
- separation workflow 按执行边界拆分为 `workflow/image_edit.rs`(Raw image-edit 与处理图写入)、`workflow/binding.rs`(视觉绑定)、`workflow/cut.rs`(像素归一化与裁切)、`workflow/patch.rs`(批次状态 patch);新增 `image_preprocess.rs`(visual binding 请求前的 RGBA 标记图转换、PNG 写入和 data URL 生成);`workflow/mod.rs` 仅负责批次编排与 sidecar 检查点。
|
||||
- `image_preprocess.rs` 的像素转换和 debug 文件写入运行在独立 `spawn_blocking` 任务;`visual_binding` 记录预处理阶段的 `outcome` 与 `elapsed_ms`,不记录图片内容或绝对路径。预处理单元测试只验证像素转换和 PNG 可解码,不把 debug 文件是否存在作为测试契约。
|
||||
- 上述拆分只调整 Rust 模块边界,不改变批次选择、重试、sidecar 持久化、绑定校验或错误恢复语义。
|
||||
|
||||
## TODO
|
||||
|
||||
- 正在执行 batch 的持久化和恢复。
|
||||
- 前端自动切分素材接入已实现;仍需补齐真实 Tauri/前端联调回归测试与失败注入测试。
|
||||
- 临时图片清理/归档策略。
|
||||
- 手动抠图能力。
|
||||
- problematic 对更高层 workflow 完成门禁的最终定义。
|
||||
- separation workflow 与 manifest/stage 的接入。
|
||||
- Raw GPT Image 2 后端 raw operation 持久状态及恢复 worker。
|
||||
- 完整页面上下文对 image-edit 效果的人工评估。
|
||||
- raw prompt 上限从 4 KiB 扩展到 16 KiB 的代理合同与预算。
|
||||
- root/超大图片超过 atlas 面积预算时的专门策略。
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 目标
|
||||
|
||||
UI 编辑器的“分析参考图”“识别界面结构”“绑定视觉素材”三个工作流动作在每次运行结束后,用独立的阻塞通知弹窗明确反馈结果,避免仅依赖卡片内一行状态文本而被忽略。
|
||||
UI 编辑器的“分析参考图”“识别界面结构”“自动切分素材”三个工作流动作在每次运行结束后,用独立的阻塞通知弹窗明确反馈结果,避免仅依赖卡片内一行状态文本而被忽略。
|
||||
|
||||
## 交互约定
|
||||
|
||||
@@ -15,13 +15,13 @@ UI 编辑器的“分析参考图”“识别界面结构”“绑定视觉素
|
||||
|
||||
## 文案
|
||||
|
||||
弹窗标题由步骤名和结果态组成,例如“识别界面结构完成”或“绑定视觉素材失败”。正文复用卡片状态文本,并逐条扩展结果信息,统一以“请检查”收尾。
|
||||
弹窗标题由步骤名和结果态组成,例如“识别界面结构完成”或“自动切分素材失败”。正文复用卡片状态文本,并逐条扩展结果信息,统一以“请检查”收尾。
|
||||
|
||||
成功状态的基线文案:
|
||||
|
||||
- 分析参考图:保留已应用的语义建议数量;若现有状态可可靠取得问题/待确认数量,则一并展示。
|
||||
- 识别界面结构:保留替换的界面树数量,并展示识别结果中的待检查/必须修复数量(若可取得)。
|
||||
- 绑定视觉素材:保留现有 `B/B` 批次计数,改为用户可读的绑定结果。
|
||||
- 自动切分素材:保留现有 `B/B` 批次计数,改为用户可读的切分结果。
|
||||
|
||||
失败状态保留实际错误文本,仅在弹窗标题中补充步骤和失败上下文,正文同样以“请检查”收尾。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user