Merge branch 'master' into codex/fix-main-canvas-reference-images
Project CI / Repository checks (pull_request) Successful in 1m8s
Project CI / Frontend tests (pull_request) Successful in 3m29s
Project CI / Backend tests (pull_request) Successful in 4m7s
Project CI / Native shell tests (pull_request) Successful in 16m21s

This commit is contained in:
2026-08-12 18:56:38 +08:00
8 changed files with 93 additions and 54 deletions
@@ -1688,8 +1688,8 @@ for (const snippet of [
"'write_game_creator_app_config'",
'aria-label="运行时配置"',
'LLM API Key',
'showDeveloperEditorApi',
'开发者 External Editor API Key',
'External Editor Base URL',
'External Editor API Key',
'runtime_config.save',
"'/run:运行自检,启动本地 HTTP 预览并载入客户端运行视图'",
"'activate_local_game_preview'",
-1
View File
@@ -11187,7 +11187,6 @@ export function App({
{runtimeConfigOpen ? (
<RuntimeConfigDialog
projectPath={localProject?.projectPath}
showDeveloperEditorApi={devMode}
onClose={() => setRuntimeConfigOpen(false)}
onLog={(entry) => setCommandLog((current) => [...current, entry])}
/>
@@ -522,12 +522,10 @@ function normalizeRuntimeConfigDraft(
export function RuntimeConfigDialog({
projectPath,
showDeveloperEditorApi = false,
onClose,
onLog,
}: {
projectPath?: string;
showDeveloperEditorApi?: boolean;
onClose: () => void;
onLog?: (entry: string) => void;
}) {
@@ -1381,38 +1379,28 @@ export function RuntimeConfigDialog({
})}
</>
) : null}
{showDeveloperEditorApi ? (
<>
<label>
External Editor Base URL
<input
aria-label="开发者 External Editor Base URL"
value={runtimeConfigDraft.editorApi.baseUrl}
onChange={(event) =>
updateRuntimeEditorConfig(
'baseUrl',
event.currentTarget.value,
)
}
/>
</label>
<label>
External Editor API Key
<input
aria-label="开发者 External Editor API Key"
autoComplete="off"
type="password"
value={runtimeConfigDraft.editorApi.apiKey}
onChange={(event) =>
updateRuntimeEditorConfig(
'apiKey',
event.currentTarget.value,
)
}
/>
</label>
</>
) : null}
<label>
External Editor Base URL
<input
aria-label="External Editor Base URL"
value={runtimeConfigDraft.editorApi.baseUrl}
onChange={(event) =>
updateRuntimeEditorConfig('baseUrl', event.currentTarget.value)
}
/>
</label>
<label>
External Editor API Key
<input
aria-label="External Editor API Key"
autoComplete="off"
type="password"
value={runtimeConfigDraft.editorApi.apiKey}
onChange={(event) =>
updateRuntimeEditorConfig('apiKey', event.currentTarget.value)
}
/>
</label>
<section className="runtime-mcp-section" aria-label="MCP servers">
<header className="runtime-mcp-header">
<div>
@@ -4278,6 +4278,48 @@ export function registerProjectSupervisorSurfaceTests() {
);
});
it('exposes External Editor credentials from the game-chat settings', async () => {
const invoke = vi.fn(async (command: string) => {
if (command === 'read_game_creator_app_config') {
return {
path: '/home/test/AppData/game-creator.config.json',
config: {
editorApi: {
baseUrl: 'http://127.0.0.1:8082',
apiKey: 'game-chat-editor-secret',
},
},
};
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
renderGameChatStatus({
runtime: gameChatRuntimeState(),
viewOverrides: {
onOpenRuntimeConfig: vi.fn(),
runtimeConfigOpen: true,
},
});
expect(
await screen.findByRole('dialog', { name: '运行时配置' }),
).not.toBeNull();
expect(screen.getByLabelText('External Editor Base URL')).toHaveProperty(
'value',
'http://127.0.0.1:8082',
);
expect(screen.getByLabelText('External Editor API Key')).toHaveProperty(
'type',
'password',
);
expect(
screen
.getByLabelText('External Editor API Key')
.getAttribute('autocomplete'),
).toBe('off');
});
it('opens the dedicated game-chat release without platform authentication', async () => {
const fetchMock = vi
.spyOn(globalThis, 'fetch')
@@ -434,6 +434,19 @@ export function registerRuntimeSettingsTests() {
'value',
'12000',
);
expect(screen.getByLabelText('External Editor Base URL')).toHaveProperty(
'value',
'http://127.0.0.1:8082',
);
expect(screen.getByLabelText('External Editor API Key')).toHaveProperty(
'type',
'password',
);
expect(
screen
.getByLabelText('External Editor API Key')
.getAttribute('autocomplete'),
).toBe('off');
fireEvent.change(screen.getByLabelText('LLM 模型'), {
target: { value: 'gpt-launcher-updated' },
});
@@ -856,15 +869,13 @@ export function registerPublishedRuntimeSettingsTests() {
expect(
screen.getByLabelText('LLM API Key').getAttribute('autocomplete'),
).toBe('off');
expect(
screen.getByLabelText('开发者 External Editor API Key'),
).toHaveProperty(
expect(screen.getByLabelText('External Editor API Key')).toHaveProperty(
'type',
'password',
);
expect(
screen
.getByLabelText('开发者 External Editor API Key')
.getByLabelText('External Editor API Key')
.getAttribute('autocomplete'),
).toBe('off');
expect(screen.getByLabelText('Planner LLM API Key')).toHaveProperty(
@@ -1007,13 +1018,10 @@ export function registerPublishedRuntimeSettingsTests() {
target: { value: 'ark' },
},
);
fireEvent.change(
screen.getByLabelText('开发者 External Editor Base URL'),
{
fireEvent.change(screen.getByLabelText('External Editor Base URL'), {
target: { value: 'http://127.0.0.1:8099' },
},
);
fireEvent.change(screen.getByLabelText('开发者 External Editor API Key'), {
});
fireEvent.change(screen.getByLabelText('External Editor API Key'), {
target: { value: 'editor-new-secret' },
});
fireEvent.click(
@@ -1166,9 +1174,10 @@ export function registerPublishedRuntimeSettingsTests() {
'value',
'12000',
);
expect(
screen.getByLabelText('开发者 External Editor Base URL'),
).toHaveProperty('value', 'http://127.0.0.1:8082');
expect(screen.getByLabelText('External Editor Base URL')).toHaveProperty(
'value',
'http://127.0.0.1:8082',
);
fireEvent.click(
within(runtimeConfigDialog).getByRole('button', { name: '保存' }),
);
@@ -109,7 +109,7 @@
- 主站账号、钱包、服务端项目、云端素材库和生成面板仍留在网站宿主;客户端本地项目、manifest、Runtime、审批、草稿、生成与正式提交仍留在 Tauri 宿主。共享视觉组件不得读取这些业务事实。
- 客户端 UI 对齐采用“同视觉、同组件、保留四区布局”,不复制主站整页布局。资源总览、运行视图和 Supervisor 对话继续是 Game Agent 工作台独有语义。
- 客户端正式产品仍只按 `1280×800` 横屏合同交付;更窄浏览器样式只负责不崩溃和开发兼容,不改成移动端创作工作台。
- 普通用户界面不默认展示内部错误码、开发者 API 配置或本机绝对路径;确需诊断的信息进入受控详情或开发模式,不与主要动作并列
- 普通用户界面不默认展示内部错误码或本机绝对路径;External Editor 配置只进入独立“运行时配置”对话框,不与素材画布主要动作并列,确需诊断的信息进入受控详情或开发模式。
- 素材画布的“素材名称”是用户可编辑的正式输出名称;“资源用途”是 manifest subtype,不向普通用户开放自由文本。新增资源默认“普通游戏美术”,可从普通游戏美术、统一视觉规范、游戏界面原型、核心美术图集四项中选择;精修资源继承源用途且不可改。导出格式继续限定 PNG/JPEG/WebP。工具动作与保存设置分层展示,“保存到项目”在 `1280×800` 和窄容器中都必须完整可见。
### 3.8 现有 Godot 项目
@@ -530,7 +530,7 @@ type ProjectAgentMudPointAttribution = {
### 7.6 素材创作无限画布阶段一至五最终验收
实现状态(2026-08-11):当前产品切片禁用“新增资源”,只从现有资源进入非破坏性编辑。图片进入中央 refine 画布,其他现役类型进入统一派生编辑壳;取消恢复、正式 manifest/revision 实时合并、依赖图重建、dependency/type 双布局协调和三阶段自动定位已经接通。command/event 任意顺序按项目、commit、event 与 revision 去重;低 revision、旧 graph/layout 和失效 focus generation 均不能倒灌。Tauri 远端媒体编辑固定使用 AppData 私有 `editorApi.baseUrl/apiKey` 访问 `/api/external/v1/*`,不把 Key 传入 WebView、项目事实、账本、日志或普通错误;画布 UI 不提供 Base URL/API Key 输入。重启恢复只继续原 operation,不以新请求、新幂等键或新 operationId 替代结果未知的旧任务。
实现状态(2026-08-12):当前产品切片禁用“新增资源”,只从现有资源进入非破坏性编辑。图片进入中央 refine 画布,其他现役类型进入统一派生编辑壳;取消恢复、正式 manifest/revision 实时合并、依赖图重建、dependency/type 双布局协调和三阶段自动定位已经接通。command/event 任意顺序按项目、commit、event 与 revision 去重;低 revision、旧 graph/layout 和失效 focus generation 均不能倒灌。Tauri 远端媒体编辑固定使用 AppData 私有 `editorApi.baseUrl/apiKey` 访问 `/api/external/v1/*`普通 Launcher、开发工作台和独立 game-chat 的“运行时配置”都可编辑这两个字段;不把 Key 传入 WebView、项目事实、账本、日志或普通错误,素材画布工作区自身不提供凭据输入。重启恢复只继续原 operation,不以新请求、新幂等键或新 operationId 替代结果未知的旧任务。
1. 网站与 Tauri 实际 import 同一份 `@genarrative/image-canvas-core``@genarrative/image-canvas-react`,客户端没有复制的主站画布目录;viewport、selection、变换、renderer 与 history 算法位于共享层,宿主只保留事件接线与 adapter 副作用。
2. “新增资源”在当前产品切片中保持禁用;“编辑资源”只接受现有资源。图片 refine 保留原资产与原文件、创建新资产,并用规范 `referenceResourceIds` 登记直接血缘;其他类型同样只追加派生文件/asset 或子版本,不覆盖、删除或重排源记录。
@@ -58,6 +58,7 @@
- 产品边界:主站网页画布继续使用登录态 `/api/editor/*``/api/assets/*``/api/runtime/external-generation/jobs/*`;AI 游戏创作 Tauri 客户端没有网页画布宿主,图片、图片引用、视频、音效和背景音乐等远端媒体编辑固定使用 `/api/external/v1/*`。两条入口继续复用相同请求 DTO、owner 归属、统一生成队列和正式资产结果,不新建平行生成服务。
- 凭据边界:客户端沿用发布 AppData 私有运行时配置中的 `editorApi.baseUrl/apiKey`,不实现登录后自动签发 Developer API Key,不把站内 Access Token 传给 Tauri 生成命令,也不把 API Key 打包进仓库、传入 WebView、写入项目、账本、日志或普通错误。Key 缺失投影安全配置错误,`401/403` 投影 External 凭据无效或权限不足,不再描述为站内登录失效。
- 配置入口:普通 Launcher、开发工作台和独立 game-chat 复用同一“运行时配置”对话框,均可编辑 AppData 私有 `editorApi.baseUrl/apiKey`;字段可见性不依赖开发模式或 Agent 模式。素材画布工作区自身不承接凭据输入,密钥输入保持 password 类型并禁用浏览器自动填充。
- 路由边界:图片编辑、视频、音效和 BGM 分别使用 `/api/external/v1/editor/images/edits``/api/external/v1/editor/videos/generations``/api/external/v1/editor/audios/sound-effects/generations``/api/external/v1/editor/audios/background-music/generations`;上传、确认、轮询和换签固定使用 External v1 对应端点。SVG、UTF-8 文档、代码、Agent 回执与项目版本仍是本地派生,不制造无意义的远端请求。
- 可靠性边界:External POST 固定携带原稳定 `Idempotency-Key` 并只接受 `202 + operationId``prepared` 只重放原正文和原键,`accepted/running` 只查询 `/api/external/v1/generations/{operationId}`。账本使用 `service-origin-v1` 绑定规范化 External base URL 的服务指纹,不绑定或保存明文 Key;升级前 Key-bound 素材画布生成账本无法用当前 Key 验证时,必须由用户显式确认面板中展示的当前 origin 才能恢复冻结请求;全类型资源编辑的旧指纹无法验证时保留原 operation 对账。升级前已保存的站内 endpoint 只保留为待对账状态,禁止拿 External Key 自动重放。本地参考媒体继续严格执行 ticket → OSS form → confirm,结果只消费稳定 `objectKey/resource/asset`,旧资源保留且派生资源追加。
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/tauriImageCanvasHostAdapter.ts``apps/ai-game-creator-shell/src/view/project-development/resourceEditModel.ts``apps/ai-game-creator-shell/src/view/project-development/index.tsx``apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs``apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`
@@ -58,7 +58,7 @@ apps/ai-game-creator-shell/src/features/asset-canvas/tauriImageCanvasHostAdapter
- `image-canvas-core` 只含纯 TypeScript 的画布模型、几何、选择、图层命令、历史、序列化、防御校验和状态机;不得依赖 React、DOM、Tauri、HTTP、账号、钱包或浏览器存储。
- `image-canvas-react` 只含 React 视图、hooks、交互控制器和通用 UI,依赖 core 和注入的 Host Port;不得直接 import Tauri API、站点请求客户端、账户 store 或钱包 store。
- 网站 adapter 可以依赖账户、钱包、现有服务端 editor project、云端素材库、OSS/asset object 和生成 API。
- Tauri adapter 可以依赖 `invoke/listen`、本地项目上下文、受控媒体命令、manifest、项目 revision、草稿 sidecar 和当前平台登录态;受控开发者模式才可依赖 External Editor API 配置。
- Tauri adapter 可以依赖 `invoke/listen`、本地项目上下文、受控媒体命令、manifest、项目 revision、草稿 sidecar 和 AppData 私有 External Editor API 配置;该配置由独立“运行时配置”对话框维护,不进入共享画布或项目事实
- 依赖方向只能是“宿主 adapter -> React/UI -> core”。core/react 不得反向 import 任一宿主。
### 3.2 禁止复制的验收门
@@ -200,7 +200,7 @@ interface ImageCanvasHostPort {
- `hostRevision` 是宿主权威提交版本的字符串表示:Tauri 使用十进制项目 mutation revision,网站使用现有服务端 editor project revision。共享 UI 只透传/展示,不比较不同宿主的 revision。
- `commitId/idempotencyKey` 由共享流程在第一次正式保存前生成;响应未知时两宿主都复用原完整请求。`expectedHostRevision` 由 adapter 从已加载的权威宿主快照提供,Tauri 必须无损解析为本文的安全整数 `expectedRevision`
- Web adapter 把草稿、导入、生成、导出和提交映射到现有服务端 editor project、云端素材库及账户/钱包链路。
- Tauri adapter 把草稿、导入、导出和提交映射到本文第 7 至 11 节的本地合同;普通客户生成通过平台登录态调用现有内部编辑器/资产/任务状态 API,后端按 owner 进入统一生成队列与泥点预扣/退款。客户界面不显示或要求填写 Base URL / API Key。External Editor API 只作为第三方 Agent、CLI 和受控内部验收通道;任一模式的凭据不能进入项目 sidecar、manifest、事件或错误正文。
- Tauri adapter 把草稿、导入、导出和提交映射到本文第 7 至 11 节的本地合同;远端媒体编辑通过 AppData 私有 `editorApi.baseUrl/apiKey` 使用 External v1,后端按 owner 进入统一生成队列与泥点预扣/退款。素材画布工作区不显示或要求填写 Base URL / API Key,普通 Launcher、开发工作台和独立 game-chat 的独立“运行时配置”对话框均可维护这两个字段;凭据不能进入共享画布、项目 sidecar、manifest、事件或错误正文。
### 3.4 主站 UI 对齐与共享画布 chrome