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

Reviewed-on: #304
This commit was merged in pull request #304.
This commit is contained in:
2026-09-14 18:59:00 +08:00
115 changed files with 6587 additions and 1323 deletions
+2
View File
@@ -58,6 +58,8 @@
## 图片画布与媒体
- [共享基础组件库与展示页](./technical/【前端架构】共享基础组件库与展示页-2026-08-26.md):网站与客户端复用的无业务 UI chrome、样式边界和 `/components` 展示页。
- [Raw GPT Image 2 图片编辑代理](./technical/【技术方案】Raw GPT Image 2图片编辑代理-2026-09-07.md):主站客户端调用的同步图片编辑代理、multipart 输入、预检查与计费边界。
- [UI 编辑器自动切分素材工作流](./technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md):UI 设计图素材切分、Raw GPT Image 2 调用与结果持久化边界。
- [图片画布结构化持久化与迁移回滚](./【编辑器】图片画布结构化持久化与迁移回滚方案-2026-07-19.md)
- [编辑器生成结果原子提交与幂等重放](./technical/【后端架构】编辑器生成结果原子提交与幂等重放方案-2026-08-06.md)
- [画板音乐生成入口](./【编辑器】画板音乐生成入口设计-2026-06-18.md):BGM/SFX 共享视图、独立业务规则和当前发布门禁。
@@ -8124,7 +8124,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
## 2026-08-24 AGC UI 原型桥接与自主 UI workflow
- 决策:`ui-prototype` 图片与 `UI` JSON 编辑资源保持两种正式类型。Agent 通过受控 `ui.workflow.run``prepare -> recognize -> status -> finalize` 创建页面资源、关联源图、持久化 UI State 和 manifest 阶段;`recognize` 直接复用 UI Editor 的 provider-backed 结构识别、多树合并与组件绑定命令,按 `reference-ready -> structure-ready -> merge-ready -> binding-ready` 逐阶段写入并推进项目 revision。页面可显式关联已登记图片/图标和字体,图片/图标按 5 项一批绑定,字体安全元数据进入绑定上下文且未知引用失败关闭。Runtime 回执携带 `revisionAdvanceCount`;Provider 未配置、请求失败、工具调用缺失、结果不匹配、未产出可渲染组件或仍有待审节点时保留最近真实阶段,禁止用 deterministic seed 冒充语义处理完成。
- 客户端:画布点击 `ui-prototype` 先幂等桥接到 `UI` JSON,并立即刷新 manifest;关联查找按 canonical resource identity 且优先已完成 workflow 资源。全部页面完成后,工作台自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段。
- 客户端:画布点击 `ui-prototype` 先幂等桥接到 `UI` JSON,并立即刷新 manifest;关联查找按 canonical resource identity 且优先已完成 workflow 资源。全部页面完成后,工作台自动打开首个页面的 UI 编辑器 `asset-separation` 最终阶段。
- 完成门:`finalize` 必须为每个页面提供 `game/` 下真实 UTF-8 应用文件并安装当前 UI State revision 标记;缺少结构、组件、页面或标记时拒绝完成。详细输入、阶段与恢复契约见 [`docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。
- 验证:前端 bridge 6/6、资源实时集成 19/19、AppSurface 410/410、AGC typecheck、Rust workflow 定向测试覆盖 provider 前的 reference 阶段与真实调用失败关闭、Rust bridge 1/1、编码、格式和 diff 门禁通过;认证登录与真实 Provider 生成的桌面端 E2E 尚未具备可用会话,保持未验证。
@@ -8294,6 +8294,16 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- AGC LLM 对话入口在解析 Router 凭据和访问上游前先读取用户 `wallet_balance`。余额为 `0` 时直接返回 `409 MUD_POINTS_INSUFFICIENT`,客户端显示“泥点余额不足”;不创建、续期或使用 Router 账号。余额读取失败同样失败关闭,返回“泥点余额暂时不可用”。
- 余额大于 `0` 的请求继续走 Router,成功后仍按 best-effort 后置结算;退款占用、冻结或扣费时余额不足的处理继续由钱包事务和既有结算规则负责。
## 2026-09-09 UI Editor 结构化请求 repair history
- UI Editor 的结构化 LLM repair 由 `run_with_repair_history` 统一维护 append-only `LlmMessage` history;调用方只构造初始 prompt 并提供 `requester(history) -> Result<T, String>`
- `validater(&T) -> Result<(), String>` 只负责业务校验。网络、模型、tool 缺失、JSON 或反序列化错误只按原 history 重试;只有业务校验失败才把序列化后的响应和校验错误合并为一条 system message 追加到 history。
- history 仅存在本次请求内存中,不重复图片、不截断、不扩展 `platform-llm` 消息协议;重试次数参数统一使用 `max_retries`
## 2026-09-11 UI 编辑器素材切分边界使用固定像素上限
- UI 编辑器自动切分在 cut 前对视觉模型返回的 `BindingArea` 做像素边界归一化时,每条边相对原始区域最多移动 `32px`,不再按原始区域宽高的百分比计算;Rust 常量为 `MAX_BINDING_AREA_EDGE_ADJUSTMENT_PX`,技术方案同步记录该固定上限。
## 2026-08-29 DirectProject 受控联网搜索默认与边界
- 正式产品本次只覆盖 `DirectProject` 单 Codex Agent。`Provider``ToolHost``DirectHome` 不是 Agent,也不是本次联网主链路;不新增全路由联网或工具桥。唯一受控联网工具为 `agc_tools.agc_web_search`,链路固定为 Codex MCP 工具目录 -> 客户端 loopback `DirectToolBridge` -> 有界 Bing RSS HTTPS -> 过滤 / 脱敏 -> MCP 结果回传。
@@ -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 结果追加到 boundNeedRework 节点在达到返工上限后追加到 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` 矩形、描述、可选返工意见和递归 childrenYAML 不携带 opaque NodeId,树的嵌套关系替代 `depth/role` 字段。
- 由于 raw endpoint 每次只返回一张 PNGprompt 要求 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` 批次计数,改为用户可读的切分结果。
失败状态保留实际错误文本,仅在弹窗标题中补充步骤和失败上下文,正文同样以“请检查”收尾。
@@ -10,8 +10,8 @@
- 滑杆位于预览区右下角固定工具栏;不提供移动端或窄窗口 fallback。
- 保留 ```+` 按钮。点击轨道可跳转,拖动滑块实时更新画布和百分比。
- 缩放以视口中心为中心,继续支持 Ctrl/Cmd + 滚轮缩放。
- 滑杆支持方向键、Home、End;百分比文本可点击编辑并在失焦时限制到有效范围
- 百分比文本使用数字输入框呈现;输入提交后同步视口缩放,适配画布保持独立按钮,点击百分比不会触发适配
- 滑杆支持方向键、Home、End;百分比按钮可通过键盘聚焦和激活
- 百分比使用只读按钮呈现;点击百分比按钮重新适配画布,滑杆仍只负责调整当前视口缩放
- 缩放滑杆获得焦点时,Delete/Backspace 不触发 UI 节点快捷删除;缩放按钮鼠标点击不改变快捷键焦点。
- Windows/Linux 使用 `Ctrl`macOS 使用 `Cmd``+``=``NumpadAdd` 放大,`-``NumpadSubtract` 缩小,允许按键重复时连续缩放。
- 快捷键直接复用现有 `+`/`` 按钮动作:以视口中心为锚点,按 `×1.16`/`×0.86` 改变缩放。
@@ -38,7 +38,7 @@ Agent 通过白名单工具 `ui.workflow.run` 发起工作流。项目路径由
1. `prepare` 为每个页面创建确定性的 `kind=UI` JSON 资源,引用源 `ui-prototype` 和页面设计图,载入设计图尺寸与相对路径到 `ui_design_images`,并保存 State。
2. `recognize` 依次执行 Provider 多模态结构识别、现有多树合并器、最多每批 5 项的图片/图标组件绑定,并把已登记字体的安全元数据提供给绑定器;阶段分别持久化为 `structure-ready``merge-ready``binding-ready`,重复执行从最近真实阶段恢复。
3. `status` 只回读 State、页面阶段和 blockers,不推进项目 revision。
4. `finalize` 只接受 `game/` 下的真实 UTF-8 文件,写入与 UI State revision 绑定的应用标记;所有页面通过应用门禁后才返回 `visual-binding` 最终阶段路由。缺少页面、资源、组件或应用标记时拒绝伪造完成。
4. `finalize` 只接受 `game/` 下的真实 UTF-8 文件,写入与 UI State revision 绑定的应用标记;所有页面通过应用门禁后才返回 `asset-separation` 最终阶段路由。缺少页面、资源、组件或应用标记时拒绝伪造完成。
每次 State 或 manifest 阶段变化都推进项目 revision。Runtime 回执带有 `revisionAdvanceCount`,用于并发项目 revision 门禁;manifest 资产的 `source.generationKind` 依次记录:
@@ -55,7 +55,9 @@ ui-workflow.completed
最终回执写入 `.agent/ui-workflows/<source-hash>.json`,客户端可据此恢复页面清单和最终编辑器路由。
`recognize` 现在直接复用 UI Editor 的 provider-backed `recognize_ui_impl``merge_ui_impl``bind_components_impl`:先对页面设计图执行多模态结构识别,再落盘合并后的唯一页面树,最后按 5 项一批绑定已登记图片/图标,并向模型提供 State 内已验证字体的 ID、family、face、weight 与 style。由 Agent Runtime 调用时,这三个阶段携带当前 `agent_id/run_id`,统一走活动 Provider 的 mode、请求快照、重试和恢复链路,不再从工作流偷偷创建另一套传统 HTTP client。Codex app-server 会把输入图片暂存到该连接的隔离工作区 `input-images/`,通过原生 `localImage` 输入发送;文本提示只保留图片占位符,避免把 base64 复制进提示词或 JSON-RPC。所有 LLM 工具参数仍沿用 UI Editor 的严格 schema、节点/深度/素材和字体白名单及有界输入校验。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用绑定没有可渲染组件或仍有 `NeedReview/Blocked` 时,完成阶段不会推进;已落盘的中间阶段仍通过 manifest invalidation 更新客户端,不再使用 deterministic seed 冒充语义处理通过。
`recognize` 现在直接复用 UI Editor 的 provider-backed `recognize_ui_impl``merge_ui_impl``bind_components_impl`:先对页面设计图执行多模态结构识别,再落盘合并后的唯一页面树,最后按 5 项一批绑定已登记图片/图标,并向模型提供 State 内已验证字体的 ID、family、face、weight 与 style。由 Agent Runtime 调用时,这三个阶段携带当前 `agent_id/run_id`,统一走活动 Provider 的 mode、请求快照、重试和恢复链路,不再从工作流偷偷创建另一套传统 HTTP client。Codex app-server 会把输入图片暂存到该连接的隔离工作区 `input-images/`,通过原生 `localImage` 输入发送;文本提示只保留图片占位符,避免把 base64 复制进提示词或 JSON-RPC。所有 LLM 工具参数仍沿用 UI Editor 的严格 schema、节点/深度/素材和字体白名单及有界输入校验。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用绑定没有可渲染组件时,完成阶段不会推进;自动切分达到返工上限的 problematic 节点属于“已尽力完成”的可人工收尾结果,由 UI State 回写 `component_status = NeedReview(...)` 并交给编辑器概览定位,不阻止本地分离流程报告成功。已落盘的中间阶段仍通过 manifest invalidation 更新客户端,不再使用 deterministic seed 冒充语义处理通过。
UI 编辑器的分离提示会对节点描述和返工备注做长度与控制字符清洗,避免用户/模型文本改变提示结构;远端图片的 Base64 解码、处理图尺寸及 multipart 上传解析均受有界检查和准备截止时间约束。切分 sidecar 产物使用 UUID 文件名,失败检查点只记录附加错误并保留原始业务错误,便于恢复与排障。
真实 Provider 鉴权失败时,Codex app-server 可能只返回 `codexErrorInfo=other`,而把上游 `401/403` 放在错误正文中。Runtime 必须从受控错误字段识别为 `codex-app-server-error:unauthorized`(公共摘要为 `codex-app-server-unauthorized`),只向公共运行记录暴露错误类别和指纹,不记录 Token 或上游原文。此错误不能伪造为 UI 工作流阶段完成;修复凭据后应从原有 run 的恢复边界重新执行。
@@ -67,7 +69,9 @@ ui-workflow.completed
- 没有关联时原子创建 `ui/UI 设计 N.json`,登记 `kind=UI``application/json`,并把原型图作为首张页面设计图载入 State。
- 成功后通过 `onManifestChange` 更新客户端资源投影,再打开 UI 编辑器;普通桥接从 `reference-analysis` 开始。
点击已有 `UI` 资源直接打开 UI 编辑器。若 manifest 阶段为 `ui-workflow.completed`,工作台自动打开该资源的 `visual-binding` 阶段(最远步骤为 2),交给用户做最终检查和手动调整。
点击已有 `UI` 资源直接打开 UI 编辑器。若 manifest 阶段为 `ui-workflow.completed`,工作台自动打开该资源的 `asset-separation` 阶段(最远步骤为 2),交给用户做最终检查和手动调整。
自动切分达到返工上限的 problematic 节点会随分离 DTO 返回每节点的 `problem_history`,并由编辑器回写为 `component_status = NeedReview(...)``SeparationOverview` 只读取 UI State 中的状态来计数和定位;该结果仍按“已尽力完成”报告成功并执行既有 finalize,剩余节点由用户在概览定位后手动处理或再次发起分离。
## 诚实完成门禁