恢复游戏创作 External Editor 配置入口
普通 Launcher、开发工作台和独立 game-chat 统一显示 External Editor 配置。 补齐配置可见性、密码输入和持久化回归测试。 同步配置静态门禁、PRD、技术合同与共享决策。
This commit is contained in:
@@ -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'",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user