From 5e16837f3ddf67705f00b651ea91a6ec03ed35d5 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 12 Aug 2026 18:43:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E6=B8=B8=E6=88=8F=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=20External=20Editor=20=E9=85=8D=E7=BD=AE=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 普通 Launcher、开发工作台和独立 game-chat 统一显示 External Editor 配置。 补齐配置可见性、密码输入和持久化回归测试。 同步配置静态门禁、PRD、技术合同与共享决策。 --- .../scripts/check-config.mjs | 4 +- apps/ai-game-creator-shell/src/App.tsx | 1 - .../runtime-config/RuntimeConfigDialog.tsx | 56 ++++++++----------- .../appSurface/project-development.suite.ts | 42 ++++++++++++++ .../appSurface/runtime-settings.suite.ts | 35 +++++++----- ...AI游戏创作】项目开发工作台PRD-2026-07-20.md | 4 +- .../shared-memory/decision-log.md | 1 + ...客户端素材创作无限画布阶段一合同-2026-08-05.md | 4 +- 8 files changed, 93 insertions(+), 54 deletions(-) diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index 0d913a01b..9b1a0a318 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -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'", diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 875d09ec6..2dc8e129b 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -11187,7 +11187,6 @@ export function App({ {runtimeConfigOpen ? ( setRuntimeConfigOpen(false)} onLog={(entry) => setCommandLog((current) => [...current, entry])} /> diff --git a/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx b/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx index ce52e5f90..c8c9fdd23 100644 --- a/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx +++ b/apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx @@ -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 ? ( - <> - - - - ) : null} + +
diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index eed4da3a4..a4a3dbea6 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -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') diff --git a/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts index 6b2941d40..1851a5e4c 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts @@ -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: '保存' }), ); diff --git a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md index c7d75812e..2f47de891 100644 --- a/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md +++ b/docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md @@ -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 或子版本,不覆盖、删除或重排源记录。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 7df22658b..72ad69f78 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -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`。 diff --git a/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md b/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md index 1deb38396..b3c50e6ce 100644 --- a/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md +++ b/docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md @@ -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