完成客户端素材创作无限画布阶段三闭环
新增客户端共享画布宿主适配与独立创作界面 实现草稿并发保存、恢复副本和本地媒体安全校验 实现项目写锁内的可恢复素材提交与清单登记 补齐非破坏精修、幂等并发和故障恢复测试 同步阶段三技术方案与团队共享记忆
This commit is contained in:
@@ -107,8 +107,17 @@ const rustSharedContractSource = fs.readFileSync(
|
||||
);
|
||||
const allowedUncalledTauriCommands = [
|
||||
'chat_with_game_creator_agent',
|
||||
'commit_local_project_asset',
|
||||
'create_local_project_asset_canvas_draft',
|
||||
'discard_local_project_asset_canvas_draft',
|
||||
'open_game_creator_launcher_window',
|
||||
'open_game_creator_workspace_window',
|
||||
'read_local_project_asset_canvas_draft',
|
||||
'read_local_project_asset_canvas_media',
|
||||
'recover_local_project_asset_canvas_transactions',
|
||||
'stage_local_project_asset_canvas_image',
|
||||
'store_local_project_asset_canvas_media',
|
||||
'update_local_project_asset_canvas_draft',
|
||||
];
|
||||
const sourceExtensions = new Set([
|
||||
'.json',
|
||||
|
||||
+14
@@ -1591,6 +1591,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chromiumoxide",
|
||||
"futures",
|
||||
"getrandom 0.3.4",
|
||||
"http",
|
||||
"image",
|
||||
"jsonschema",
|
||||
@@ -2171,10 +2172,23 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"image-webp",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -19,8 +19,9 @@ agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" }
|
||||
base64 = "0.22"
|
||||
chromiumoxide = "0.9.1"
|
||||
futures = "0.3"
|
||||
getrandom = "0.3"
|
||||
http = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
|
||||
jsonschema = { version = "0.49.3", default-features = false }
|
||||
oxc_allocator = "0.143.0"
|
||||
oxc_ast = "0.143.0"
|
||||
|
||||
@@ -232,6 +232,94 @@ pub(crate) fn update_local_project_resource_canvas_layout(
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn create_local_project_asset_canvas_draft(
|
||||
input: CreateAssetCanvasDraftInput,
|
||||
) -> Result<CreateAssetCanvasDraftResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
create_asset_canvas_draft_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn read_local_project_asset_canvas_draft(
|
||||
input: ReadAssetCanvasDraftInput,
|
||||
) -> Result<ReadAssetCanvasDraftResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
read_asset_canvas_draft_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn update_local_project_asset_canvas_draft(
|
||||
input: UpdateAssetCanvasDraftInput,
|
||||
) -> Result<UpdateAssetCanvasDraftResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
update_asset_canvas_draft_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn store_local_project_asset_canvas_media(
|
||||
input: StoreAssetCanvasMediaInput,
|
||||
) -> Result<StoreAssetCanvasMediaResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
store_asset_canvas_media_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn stage_local_project_asset_canvas_image(
|
||||
input: StageAssetCanvasImageInput,
|
||||
) -> Result<StageAssetCanvasImageResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
stage_asset_canvas_image_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn read_local_project_asset_canvas_media(
|
||||
input: ReadAssetCanvasMediaInput,
|
||||
) -> Result<ReadAssetCanvasMediaResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
read_asset_canvas_media_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn discard_local_project_asset_canvas_draft(
|
||||
input: DiscardAssetCanvasDraftInput,
|
||||
) -> Result<DiscardAssetCanvasDraftResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
discard_asset_canvas_draft_at(&root, &input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn commit_local_project_asset(
|
||||
app: tauri::AppHandle,
|
||||
input: CommitAssetCanvasInput,
|
||||
) -> Result<CommitAssetCanvasResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
let execution = commit_asset_canvas_at(&root, &input)?;
|
||||
if let Some(event) = execution.event.as_ref() {
|
||||
publish_asset_canvas_event_after_commit_at(&root, event, |payload| {
|
||||
app.emit("game-creator-local-asset-committed", payload)
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
}
|
||||
Ok(execution.result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn recover_local_project_asset_canvas_transactions(
|
||||
app: tauri::AppHandle,
|
||||
input: RecoverAssetCanvasTransactionsInput,
|
||||
) -> Result<RecoverAssetCanvasTransactionsResult, String> {
|
||||
let root = validated_local_project_directory_path(input.project_path.trim())?;
|
||||
let execution = recover_asset_canvas_transactions_at(&root, &input.expected_project_id)?;
|
||||
for event in &execution.events {
|
||||
publish_asset_canvas_event_after_commit_at(&root, event, |payload| {
|
||||
app.emit("game-creator-local-asset-committed", payload)
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
}
|
||||
Ok(execution.result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn control_agent_run(
|
||||
app: tauri::AppHandle,
|
||||
|
||||
@@ -2209,6 +2209,15 @@ fn main() {
|
||||
read_local_project_resource_canvas_layout,
|
||||
read_local_project_resource_graph,
|
||||
update_local_project_resource_canvas_layout,
|
||||
create_local_project_asset_canvas_draft,
|
||||
read_local_project_asset_canvas_draft,
|
||||
update_local_project_asset_canvas_draft,
|
||||
store_local_project_asset_canvas_media,
|
||||
stage_local_project_asset_canvas_image,
|
||||
read_local_project_asset_canvas_media,
|
||||
discard_local_project_asset_canvas_draft,
|
||||
recover_local_project_asset_canvas_transactions,
|
||||
commit_local_project_asset,
|
||||
get_local_game_project_revision,
|
||||
get_local_game_manifest
|
||||
])
|
||||
|
||||
@@ -4,6 +4,7 @@ use similar::TextDiff;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
mod agent_db;
|
||||
mod asset_canvas;
|
||||
mod checkpoint;
|
||||
mod conversation;
|
||||
mod export;
|
||||
@@ -15,6 +16,7 @@ mod resource_layout;
|
||||
mod verification;
|
||||
|
||||
pub(crate) use agent_db::*;
|
||||
pub(crate) use asset_canvas::*;
|
||||
pub(crate) use checkpoint::*;
|
||||
pub(crate) use conversation::*;
|
||||
pub(crate) use export::*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,161 @@
|
||||
.asset-canvas-surface {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
width: 100%;
|
||||
height: min(800px, 100vh);
|
||||
min-height: 540px;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
background: #e8e6e2;
|
||||
color: #2d2926;
|
||||
}
|
||||
|
||||
.asset-canvas-surface--loading {
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__toolbar {
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(63, 54, 48, 0.12);
|
||||
background: rgba(252, 251, 249, 0.96);
|
||||
}
|
||||
|
||||
.asset-canvas-surface button,
|
||||
.asset-canvas-surface input,
|
||||
.asset-canvas-surface select,
|
||||
.asset-canvas-surface__import {
|
||||
min-height: 34px;
|
||||
border: 1px solid rgba(63, 54, 48, 0.18);
|
||||
border-radius: 9px;
|
||||
background: #fff;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.asset-canvas-surface button,
|
||||
.asset-canvas-surface__import {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.asset-canvas-surface button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.asset-canvas-surface input,
|
||||
.asset-canvas-surface select {
|
||||
min-width: 0;
|
||||
padding: 0 9px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__import input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__save {
|
||||
border-color: #c7653d !important;
|
||||
background: #c7653d !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__viewport {
|
||||
grid-row: 2;
|
||||
min-height: 0;
|
||||
background-image: radial-gradient(rgba(45, 41, 38, 0.12) 1px, transparent 1px);
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__resize-handle {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
bottom: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
background: #c7653d;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__viewport-tools {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
right: 14px;
|
||||
bottom: 46px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__zoom {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__zoom button {
|
||||
min-height: 30px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.asset-canvas-surface .genarrative-image-canvas__minimap {
|
||||
width: 132px;
|
||||
height: 84px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.asset-canvas-surface .genarrative-image-canvas__minimap-layer {
|
||||
background: rgba(199, 101, 61, 0.32);
|
||||
}
|
||||
|
||||
.asset-canvas-surface .genarrative-image-canvas__minimap-viewport {
|
||||
border: 1px solid #c7653d;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__status {
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
min-height: 34px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 12px;
|
||||
border-top: 1px solid rgba(63, 54, 48, 0.12);
|
||||
background: rgba(252, 251, 249, 0.96);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.asset-canvas-surface {
|
||||
height: 100dvh;
|
||||
min-height: 480px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__toolbar {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__toolbar input {
|
||||
width: 112px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,15 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-08-05 无限画布阶段三以 Tauri 持久草稿和项目 mutation 事务形成独立图片闭环
|
||||
|
||||
- 决策:阶段三新增独立 `AssetCanvasSurface` 和 Tauri Host Adapter,直接消费共享 core/React/ports;前端状态固定为 `canvas.creating / editing / saving / recovering / failed`,generation 明确使用 unsupported mock。本阶段不接资源总览入口、保存后自动选中或真实 AI,避免提前修改工作台状态合同。
|
||||
- 草稿边界:`.agent/workbench/asset-canvas/` 使用持久 OS 文件锁和 draft revision CAS,不使用 React state、localStorage 或进程内 mutex 作为正式状态。主草稿损坏时,固定 recovery JSON 必须通过针对实际字节的 SHA-256、schema/身份和全部受控媒体复核才可恢复;连续保存产生的 revision 直接从 CAS 结果推进。
|
||||
- 提交边界:正式提交复用项目 mutation 写锁,锁内按身份/revision 重检、prepared journal/ledger、最终图片安装、manifest、project revision、回读验证、ledger/草稿终态推进。`commitId/idempotencyKey` 与完整请求指纹绑定;同请求重放返回同一 asset/event,同 revision 竞争不得覆盖。释放锁后才发布至少一次 Tauri event,emit 失败不回滚已提交 manifest。
|
||||
- 恢复边界:最终文件分为 absent、matches、mismatch。只有 absent 或摘要/大小/尺寸完全匹配时可自动回滚;mismatch 保留现场并标记 `reconciliation-required`。manifest after/revision before 前向补 revision,二者 after 时补 ledger、草稿和事件。只能删除能证明属于当前事务的新文件。
|
||||
- 精修与异步边界:refine 源必须是 manifest 唯一登记且通过签名、完整解码、容量、普通文件、符号链接/硬链接门禁的图片;源 asset/文件永久保留,新 `canvas-<commitId>` 通过规范 resourceId 引用源。前端以 project/draft/session epoch 丢弃迟到 Promise/事件,以 eventId 去重 command/event 投影。
|
||||
- 验证:阶段三 Surface `8/8`、共享 core/React `7/7`、AppSurface `351/351`、Rust 素材画布定向测试 `11/11`,并执行客户端 typecheck、Rust check、编码与 diff 门禁。
|
||||
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
## 2026-08-05 无限画布阶段二以共享包成为网站与 Tauri 唯一核心源码
|
||||
|
||||
- 决策:把现役网站画布的纯交互模型、history/viewport/stage hooks 和通用 React 容器迁到 `packages/image-canvas-core`、`packages/image-canvas-react`;原 `src/components/image-editor` 同名模型/hook 只保留 re-export。网站 Stage/World/Dock/Portal 已直接使用共享 React,Tauri 壳只建立共享源码编译入口和作用域 CSS,不在本阶段接素材创作业务闭环或修改资源总览。
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 正式保存不能等待 React state 才取得草稿 CAS 的新 revision
|
||||
|
||||
- 现象:用户刚完成编辑就点击“保存到项目”,自动草稿保存已经成功,但正式提交仍携带旧 `expectedDraftRevision`,于是单窗口也得到 draft revision conflict;快速连续保存时还可能使用不同 commitId 重复 staging。
|
||||
- 原因:`setDraft(result.value)` 的 React state 提交晚于当前 Promise 链,正式保存若从闭包或下一次 render 读取 revision,会把 UI 调度时序误当成持久化顺序。相同问题也会出现在选择变化未标脏、父组件每次 render 新建 scope 对象而重复恢复、项目切换后旧 generation 回调继续写 notice。
|
||||
- 处理:草稿保存队列在 CAS 成功后同步更新 `draftRef.current` 并直接返回权威 draft;正式提交继续使用该返回值的 revision。scope 按 project/draft/intent/source 原始字段稳定化,所有导入、保存、生成和事件回调捕获当前 epoch,选择变化属于草稿合同并必须标脏。首次正式保存冻结 commitId/idempotencyKey,未知结果只重放原请求。
|
||||
- 验证:用 deferred Promise 证明草稿 CAS 完成后正式 commit 使用新 revision;相同 scope 值重渲染不重复 recover/load;锁定图层选择进入草稿更新;项目切换后迟到 generation/commit/event 均不改变新会话。
|
||||
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx`。
|
||||
|
||||
## 共享画布不能用全局 DOM 查询或宿主整包 CSS 作为隐式依赖
|
||||
|
||||
- 现象:页面挂两个画布时,第二个画布点击小地图会移动第一个画布;反复挂载后 wheel 触发多次。Tauri 单独引入网站 `index.css` 时还会带入账号、项目页和历史业务样式,或因仓库外源码解析到第二份 React 而出现 Hook 错误。
|
||||
|
||||
@@ -928,3 +928,13 @@ game-project/
|
||||
- 共享样式固定为 `packages/image-canvas-react/src/styles.css`,全部以 `.genarrative-image-canvas` 作用域开头;网站可以继续用 `src/index.css` 提供站点皮肤,Tauri 只导入共享样式和自身 `styles.css`。两套 Vite 都把共享源码显式 alias 到仓库 `packages/`,Tauri dev server 保留 `fs.allow=[repoRoot]`,并通过 `resolve.dedupe=['react','react-dom']` 保证单 React 实例。
|
||||
- portal 默认进入当前 Provider 指定的 root,未指定时才回退 `document.body`;事件监听、`ResizeObserver`、animation frame 和小地图查询均按组件实例清理或限定在当前 viewport。文档中只有一个小地图的旧调用形态允许唯一元素回退,页面存在多个小地图时不做全局猜测。
|
||||
- 阶段二定向证据为共享 core/React 7 项与现役 `src/components/image-editor` 74 个文件、892 项测试通过;主站与 AI 游戏创作壳 typecheck 通过。构建、编码和 diff 门禁以本次任务最终验证记录为准。
|
||||
|
||||
## 2026-08-05 客户端素材创作无限画布阶段三 Tauri 独立闭环
|
||||
|
||||
- 阶段三在 `apps/ai-game-creator-shell/src/features/asset-canvas/` 增加独立 `AssetCanvasSurface` 与薄 Tauri Host Adapter,直接消费阶段二的 core/React/ports。页面实现 `canvas.creating / editing / saving / recovering / failed`,支持 PNG/JPEG/WebP 本地导入、选择、多选、移动、缩放、删除、60 步撤销重做、viewport 平移缩放、小地图、复位和浏览器合成导出;generation port 明确返回 mock unsupported,不发送真实 AI 请求。
|
||||
- 草稿正式保存到 `.agent/workbench/asset-canvas/`,由 Tauri Rust 使用持久 OS 文件锁和 `expectedDraftRevision` CAS 串行化;主草稿、固定 recovery JSON 与实际 recovery 字节的裸 SHA-256 一起安装。主文件损坏时只有 schema、项目/草稿身份、摘要及全部媒体签名、摘要、尺寸和普通文件身份都可信才恢复,否则进入 `reconciliation-required`。单文件限制 `64 MiB`,单草稿媒体总量限制 `512 MiB`。
|
||||
- 正式提交先把合成结果安全写入受控 staging,再进入既有项目 mutation 写锁。锁内重读并校验 manifest、项目身份、项目 revision、草稿 revision 和提交指纹,写 prepared journal/ledger 与 before/after 快照,安全安装唯一最终图片,原子更新 manifest 和 project revision,回读图片/manifest/revision 后提交 ledger 与草稿;释放锁后才发布 `game-creator-local-asset-committed`。同 `commitId/idempotencyKey` 同请求返回原权威结果,同键不同请求失败关闭,同 expected revision 竞争最多一个成功。
|
||||
- 崩溃恢复只按 journal、ledger、before/after 摘要和最终文件三态推进:图片与 manifest/revision 都在 before 时,只清理摘要和尺寸可证明属于本事务的新文件;最终路径不存在可直接回滚;最终路径存在但身份不匹配必须保留文件并进入对账。manifest 已安装而 revision 未安装时前向补 revision;manifest/revision 都是 after 时补齐 ledger、草稿和事件。业务事实完成后 emit 失败不回滚,后续恢复继续按固定 eventId 发布。
|
||||
- refine 只允许从当前 manifest 唯一登记的 PNG/JPEG/WebP 普通文件建立草稿,并复核签名、完整解码、大小、符号链接和硬链接。正式保存保留源 asset 与源文件,为缺失 resourceId 的源补 `local-asset:<sourceAssetId>`,新建 `canvas-<commitId>` 并以 `referenceResourceIds` 引用源资源。
|
||||
- 页面以 project/draft/session epoch 丢弃项目切换、会话切换和卸载后的旧 Promise/事件,并以 eventId 去重 command 返回与至少一次事件。相同 scope 字段的父级重渲染不会重复打开草稿,选择变化也进入草稿 CAS;正式保存直接消费 CAS 返回的新 draft revision,不等待 React state 提交时序。
|
||||
- 本阶段只提供独立 Surface/fixture 与 Tauri commands,不接 `project-development` 资源总览按钮、不做保存后的资源总览自动选中,也不接真实 External Editor API。定向证据为阶段三 Surface `8/8`、共享 core/React `7/7`、既有 AppSurface `351/351`、Rust 素材画布 `11/11` 与客户端 typecheck 通过;AppSurface 保留仓库既有 React `act(...)` 警告,不影响测试结果。
|
||||
|
||||
@@ -33,23 +33,88 @@ export type ImageCanvasHostResult<T> =
|
||||
|
||||
export type ImageCanvasMediaRef =
|
||||
| { kind: 'project-asset'; assetId: string }
|
||||
| {
|
||||
kind: 'draft-media';
|
||||
mediaId: string;
|
||||
mediaType: 'image/png' | 'image/jpeg' | 'image/webp';
|
||||
sha256: string;
|
||||
byteLength: number;
|
||||
pixelWidth: number;
|
||||
pixelHeight: number;
|
||||
}
|
||||
| { kind: 'host-resource'; resourceId: string };
|
||||
|
||||
export type ImageCanvasDraftLayer = {
|
||||
layerId: string;
|
||||
resourceId: string;
|
||||
title: string;
|
||||
mediaRef: ImageCanvasMediaRef;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
originalWidth: number;
|
||||
originalHeight: number;
|
||||
zIndex: number;
|
||||
groupId: string | null;
|
||||
hidden: boolean;
|
||||
locked: boolean;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
};
|
||||
|
||||
export type ImageCanvasDraftCanvas = {
|
||||
viewport: { x: number; y: number; scale: number };
|
||||
backgroundColor: string;
|
||||
layers: ImageCanvasDraftLayer[];
|
||||
selectedLayerIds: string[];
|
||||
primarySelectedLayerId: string | null;
|
||||
};
|
||||
|
||||
export type ImageCanvasDraft = {
|
||||
schemaVersion: 'game-creator-asset-canvas-draft.v1';
|
||||
draftId: string;
|
||||
projectId: string;
|
||||
intent: 'create' | 'refine';
|
||||
sourceAssetId: string | null;
|
||||
sourceResourceId: string | null;
|
||||
revision: number;
|
||||
hostRevision: string;
|
||||
canvas: unknown;
|
||||
status:
|
||||
| 'editing'
|
||||
| 'generating'
|
||||
| 'commit-prepared'
|
||||
| 'committed'
|
||||
| 'cancelled'
|
||||
| 'reconciliation-required';
|
||||
canvas: ImageCanvasDraftCanvas;
|
||||
generations: ImageCanvasGenerationRecord[];
|
||||
pendingCommit: {
|
||||
commitId: string;
|
||||
idempotencyKey: string;
|
||||
requestFingerprint: string;
|
||||
} | null;
|
||||
lastCommit: {
|
||||
commitId: string;
|
||||
idempotencyKey: string;
|
||||
assetId: string;
|
||||
eventId: string;
|
||||
committedProjectRevision: number;
|
||||
} | null;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
export type ImageCanvasGenerationRecord = {
|
||||
generationId: string;
|
||||
idempotencyKey: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
resourceIds: string[];
|
||||
errorCode?: string | null;
|
||||
status: 'accepted' | 'polling' | 'completed' | 'failed';
|
||||
prompt: string;
|
||||
operationId: string | null;
|
||||
referenceResourceIds: string[];
|
||||
outputMediaIds: string[];
|
||||
errorCode: string | null;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
export type ImageCanvasHostInputImage = {
|
||||
@@ -149,6 +214,9 @@ export interface ImageCanvasCompletionPort {
|
||||
resourceId: string;
|
||||
draftRevision: number;
|
||||
hostRevision: string;
|
||||
commitStatus?: 'committed' | 'already-committed';
|
||||
manifest?: unknown;
|
||||
eventId?: string;
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ export default defineConfig({
|
||||
'apps/admin-web/src/**/*.test.ts',
|
||||
'apps/admin-web/src/**/*.test.tsx',
|
||||
'apps/ai-game-creator-shell/tests/**/*.test.ts',
|
||||
'apps/ai-game-creator-shell/tests/**/*.test.tsx',
|
||||
'miniprogram/**/*.test.js',
|
||||
'scripts/**/*.test.ts',
|
||||
'packages/shared/src/contracts/hostBridge.test.ts',
|
||||
|
||||
Reference in New Issue
Block a user