收紧UI原型生成与视觉验收
强化UI原型专用2K横屏提示词与画板Art Spec 要求当前图片通过结构化视觉语义验收后才能完成 将未验收图片明确标记为候选界面图 修正UI设计负面词并补齐Runtime、界面与文档回归
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -578,7 +578,7 @@ mod tests {
|
||||
)
|
||||
.err()
|
||||
.expect("fake image rejected");
|
||||
assert!(fake_error.contains("只支持 PNG、JPEG、WEBP 或 GIF"));
|
||||
assert!(fake_error.contains("只支持 PNG、JPEG 或 WEBP"));
|
||||
let oversized_error = load_agent_runtime_inspection_images(
|
||||
root.path(),
|
||||
"code-prototype",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -323,14 +323,21 @@ function resourcesFromProject(
|
||||
const task = asset.source.taskId
|
||||
? taskById.get(asset.source.taskId)
|
||||
: undefined;
|
||||
const isPendingUiPrototype =
|
||||
asset.kind === 'ui-prototype' &&
|
||||
taskById.get('design-foundation')?.status !== 'completed';
|
||||
resources.push({
|
||||
id: `asset:${asset.id}`,
|
||||
category: categoryFromResource(asset.localPath, asset.mediaType),
|
||||
label: fileName(asset.localPath),
|
||||
label: `${fileName(asset.localPath)}${
|
||||
isPendingUiPrototype ? '(待视觉验收)' : ''
|
||||
}`,
|
||||
path: asset.localPath,
|
||||
mediaType: asset.mediaType,
|
||||
sourceLabel:
|
||||
asset.source.kind === 'canvas'
|
||||
isPendingUiPrototype && asset.source.kind === 'canvas'
|
||||
? '画板 · 候选界面图'
|
||||
: asset.source.kind === 'canvas'
|
||||
? '画板'
|
||||
: asset.source.kind === 'generated'
|
||||
? 'Agent 生成'
|
||||
@@ -649,8 +656,9 @@ export default function ProjectDevelopmentView({
|
||||
const selectedResourceIsImage = Boolean(
|
||||
selectedResource && isRasterImageResource(selectedResource),
|
||||
);
|
||||
const hasRegisteredImageAssets = manifest.assets.some((asset) =>
|
||||
asset.mediaType.startsWith('image/'),
|
||||
const hasRegisteredArtImageAssets = manifest.assets.some(
|
||||
(asset) =>
|
||||
asset.kind === 'art-spritesheet' && asset.mediaType.startsWith('image/'),
|
||||
);
|
||||
const allAgentSummaries = [
|
||||
summarizeAgent(manifest, 'design', '策划 Agent'),
|
||||
@@ -667,7 +675,7 @@ export default function ProjectDevelopmentView({
|
||||
if (
|
||||
runtimeSummary.group === 'art' &&
|
||||
runtimeSummary.status === 'completed' &&
|
||||
!hasRegisteredImageAssets
|
||||
!hasRegisteredArtImageAssets
|
||||
) {
|
||||
return {
|
||||
...runtimeSummary,
|
||||
|
||||
@@ -940,6 +940,60 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('marks an unvalidated UI prototype as a candidate image', () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-ui-candidate',
|
||||
'候选界面图测试',
|
||||
);
|
||||
manifest.assets.push({
|
||||
id: 'ui-prototype-candidate',
|
||||
kind: 'ui-prototype',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/ui-prototype.png',
|
||||
source: {
|
||||
kind: 'canvas',
|
||||
taskId: 'design-foundation',
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
React.createElement(ProjectDevelopmentView, {
|
||||
projectName: '候选界面图测试',
|
||||
projectPath: '/tmp/workbench-ui-candidate',
|
||||
manifest,
|
||||
attachments: [],
|
||||
recentRunStatus: null,
|
||||
recentRunStopReason: null,
|
||||
agentRuntimeSummaries: [
|
||||
{
|
||||
group: 'art',
|
||||
label: '美术 Agent',
|
||||
status: 'completed',
|
||||
statusLabel: '已完成',
|
||||
currentTask: '本轮工作已完成',
|
||||
currentAction: null,
|
||||
waitingOn: null,
|
||||
completedCount: 4,
|
||||
totalCount: 4,
|
||||
},
|
||||
],
|
||||
supervisor: React.createElement('div', null, '项目总控'),
|
||||
onHomeOpen: vi.fn(),
|
||||
onProjectsOpen: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
const candidate = screen.getByRole('button', {
|
||||
name: /ui-prototype\.png(待视觉验收)/,
|
||||
});
|
||||
expect(candidate.textContent).toContain('画板 · 候选界面图');
|
||||
expect(candidate.textContent).toContain('assets/ui-prototype.png');
|
||||
expect(screen.getByText('仅完成计划')).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText('美术资源计划已完成,尚未生成或登记图片'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('refuses to embed a non-loopback game preview in the client workbench', () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-remote-preview',
|
||||
|
||||
@@ -4914,8 +4914,9 @@
|
||||
|
||||
- 问题:`design-foundation` 与 `art-asset-plan` 的旧 seed / 委派合同允许空 `expectedArtifacts`,因此专业 Agent 只提交策划或美术计划文本也会进入 `evidence-ready / completed`;真实项目没有界面原型图或美术图片。
|
||||
- canonical 合同:策划必须交付 `assets/ui-prototype.png`(16:9 横屏界面原型),美术必须交付 `assets/art-spritesheet.png`(首版核心美术素材)。Supervisor 发起这两类新委派时,`expectedArtifacts` 必须包含对应确定路径;普通只读委派仍允许空产物。
|
||||
- 完成门禁:Runtime 只在图片文件存在、manifest 中存在同路径 `image/*` 项、来源为 `canvas` 且 kind 分别为 `ui-prototype / art-spritesheet` 时允许专业 Agent 完成。`task.update completed` 使用同一门禁;旧 manifest 即使保留资产登记,只要真实图片已丢失也把 completed 降回 pending。缺 Key、待确认、生成失败、只有文本或只有未登记文件时保持明确阻塞,不得伪造 completed。
|
||||
- 外部与本地一致性:`canvas.asset_generate` 生成前创建或复用与本地项目同名的 External Editor 画布项目和素材库目录;生成请求必须携带 `projectId + assetFolderId + canvasCompletion`,使结果同时进入画布与素材库,再下载到确定本地路径并登记 manifest。canonical 策划 / 美术 Agent 不允许覆盖固定路径、比例、尺寸、kind 或展示名,避免真实扣费后生成无法通过完成门禁的旁路图片。路径限定为项目 `assets/` 下 png/jpg/jpeg/webp,拒绝父目录、绝对路径、符号链接与静默覆盖;登记失败时删除本轮新文件。
|
||||
- 完成门禁:Runtime 只在图片文件存在、manifest 中存在同路径 `image/*` 项、来源为 `canvas` 且 kind 分别为 `ui-prototype / art-spritesheet` 时允许专业 Agent 完成。策划 UI 图不能再以“文件存在”代替语义验收:`design-foundation` 必须用 `image.inspect` 对当前 `assets/ui-prototype.png` SHA 写入 `validationProfile=ui-prototype.v1`;资源栏、单位卡槽、战场网格、敌人入口、波次状态、主要控件、实现清晰度与原创主题八项全 true 且 issues 为空才通过。Runtime finalization 只接受同 run 证据;`task.update completed` 允许读取当前 SHA 的最新有效证据,但不能用旧 SHA 或无结构化检查的记录绕过。视觉 transport/解析失败、任一检查失败、finalization run 不匹配或图片 SHA 变化均继续阻塞。旧 manifest 即使保留资产登记,只要真实图片已丢失或 UI 图未通过当前 SHA 验收也不能完成。缺 Key、待确认、生成失败、只有文本或只有未登记文件时保持明确阻塞,不得伪造 completed。
|
||||
- 外部与本地一致性:`canvas.asset_generate` 生成前创建或复用与本地项目同名的 External Editor 画布项目和素材库目录;生成请求必须携带 `projectId + assetFolderId + canvasCompletion`,使结果同时进入画布与素材库,再下载到确定本地路径并登记 manifest。canonical 策划 / 美术 Agent 不允许覆盖固定路径、比例、尺寸、kind 或展示名,避免真实扣费后生成无法通过完成门禁的旁路图片。UI 原型走专用 prompt 与 art spec;External Editor 的 `ui-design` 负面词不得排除文字、边框、按钮和 UI 控件,应排除无界面场景插画、海报与地图。由于 `gpt-image-2` 的 `1K + 16:9` 实际返回 `1536×1024`,策划 UI 合同固定为 `2K + 16:9`,对应 `2048×1152`。路径限定为项目 `assets/` 下 png/jpg/jpeg/webp,拒绝父目录、绝对路径、符号链接与静默覆盖;登记失败时删除本轮新文件。旧候选图不合格时先经 `file.delete` 权限确认再重新生成,不自动覆盖、不自动确认、不自动扣费。
|
||||
- 用户面:已登记但 `design-foundation` 未完成的 `ui-prototype` 只显示为“画板 · 候选界面图 / 待视觉验收”,允许用户查看但不得称为正式 UI 原型;新的同 SHA 验收通过后才恢复正式资源名称。
|
||||
- 历史恢复:旧 delivery 合同不可被 repair 扩大。已有项目缺图时创建新的独立补图委派并保持 `repairOf=null`;不得修改历史 delivery,也不得要求用户新建项目。
|
||||
|
||||
## 2026-07-20 AI 游戏创作总控失败恢复边界
|
||||
|
||||
@@ -64,7 +64,7 @@ V1.17 计划快照随 `game-creator-runtime-context-bundle.v3` 持久化,v2
|
||||
|
||||
2026-07-19 起,当前父 run 下的专业 Agent 进入 `failed` 后,正式工作台必须提供“在当前项目重试”恢复入口,不得要求用户新建项目。重试必须精确核对原 `agentId + runId + parentRunId`,复用原 task、active Session 和父 run 归属,同时生成新的专业 Agent runId;新 run 继承已持久化的上下文和父子绑定,不覆写旧失败 run 的审计事实,也不得把 UI 重试解释为底层 transport 根因已修复。`agent.resume` 默认 `confirm` 不变:自动 retry command 继续执行 auto gate;正式失败卡按钮自身是本次明确确认,使用 deny-only 的 confirmed retry command。按钮必须原卡即时显示“正在提交重试”、受理或安全错误;若 Supervisor 已为同一 delegation 准备合同 repair,则该按钮优先确认既有 repair,避免重复派发。
|
||||
|
||||
completed 专业 Agent 的用户可见成果不能仅依赖项目文件。当委派合同为 `expectedArtifacts=[]` 或未产生显式文件时,工作台必须读取该 Agent 持久对话的最后一条 assistant,作为明确标注的“专业 Agent 文本回执”提供查看,并投影到“资源管理 → 文档”。该投影是持久回执的可见视图,必须保留来源 Agent 与 run 身份,不冒充 manifest asset、项目目录中的实际文件或可下载交付物;内部 Agent ID 不进入用户资源名。美术只交付计划且 manifest 没有图片时必须显示“仅完成计划,尚未生成或登记图片”,不能把文本回执称为美术图片产物。策划 `design-foundation` 与美术 `art-asset-plan` 从 2026-07-20 起属于图片产物型 canonical task:前者必须生成并登记 `assets/ui-prototype.png` 横屏界面原型图,后者必须生成并登记 `assets/art-spritesheet.png` 首版核心美术素材;只有文本计划或空 `expectedArtifacts` 不构成完成。图片生成未配置、待确认或失败时保持阻塞/失败,不得投影为 completed。资源卡允许同分类内做当前会话拖拽重排;详情使用受 viewport 与底部 dock 约束的独立可拖浮层,长正文由唯一外层滚动容器承载并用 Markdown 安全渲染。PDF 方案外的顶部项目标题条移除,dock 下方不得保留空白;底部专业 Agent 为只读状态卡,不得因点击保留多个详情浮层。
|
||||
completed 专业 Agent 的用户可见成果不能仅依赖项目文件。当委派合同为 `expectedArtifacts=[]` 或未产生显式文件时,工作台必须读取该 Agent 持久对话的最后一条 assistant,作为明确标注的“专业 Agent 文本回执”提供查看,并投影到“资源管理 → 文档”。该投影是持久回执的可见视图,必须保留来源 Agent 与 run 身份,不冒充 manifest asset、项目目录中的实际文件或可下载交付物;内部 Agent ID 不进入用户资源名。美术只交付计划且 manifest 没有图片时必须显示“仅完成计划,尚未生成或登记图片”,不能把文本回执称为美术图片产物。策划 `design-foundation` 与美术 `art-asset-plan` 从 2026-07-20 起属于图片产物型 canonical task:前者必须生成并登记 `assets/ui-prototype.png` 横屏界面原型图,后者必须生成并登记 `assets/art-spritesheet.png` 首版核心美术素材;只有文本计划或空 `expectedArtifacts` 不构成完成。策划图还必须由 `design-foundation` 的 `image.inspect` 对当前图片 SHA 形成 `ui-prototype.v1` 结构化视觉验收,资源栏、单位卡槽、战场网格、敌人入口、波次状态、主要控件、实现清晰度与原创主题八项全部通过且问题列表为空;Runtime finalization 只接受同 run 证据,`task.update completed` 只接受当前 SHA 的最新有效证据。视觉调用失败、响应不可解析、检查未通过或图片被替换后 SHA 不一致都继续阻塞。未通过的已登记图片在工作台只称“候选界面图(待视觉验收)”,不得冒充正式 UI 原型。图片生成未配置、待确认或失败时保持阻塞/失败,不得投影为 completed。资源卡允许同分类内做当前会话拖拽重排;详情使用受 viewport 与底部 dock 约束的独立可拖浮层,长正文由唯一外层滚动容器承载并用 Markdown 安全渲染。PDF 方案外的顶部项目标题条移除,dock 下方不得保留空白;底部专业 Agent 为只读状态卡,不得因点击保留多个详情浮层。
|
||||
|
||||
V1.17 同时把 finalization journal 升级为 v2 并绑定最终完整计划快照:assistant 已落盘而 Runtime state 丢失时,从 v2 journal 恢复原结构化计划后补齐终态;assistant 尚未落盘且 state 丢失时失败关闭。外层 `failed / budget-exhausted` 只保留最后可信计划,不把未完成步骤机械改成失败。thinking summary、legacy plan event 和 tool-plan repair 公共审计只保留哈希、字符数或计数,必要的模型输出与错误上下文仅留在有界私有 repair 请求中。
|
||||
|
||||
@@ -167,7 +167,7 @@ Agent Runtime 负责:
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `project.restore`。Agent 可在 diff 或自检发现本轮修改走偏后请求恢复到指定 checkpoint;Runtime 复用 `project.restore` 权限策略和项目写锁,observation 只返回 checkpoint id、恢复文件数和删除文件数,不返回本机绝对路径。默认确认策略下不会静默回滚用户项目。
|
||||
- 2026-07-10 补充:单 Agent 聊天和后台 planning prompt 会读取同一个 Agent 的 Runtime 连续上下文,把本 Agent 最近 status / phase / runId / 当前任务 / 下一步、最近回复、计划、观察、最近 3 条工具动作、最近事件、最近 3 条任务记录和工具策略摘要带入下一轮推理;上下文按规范 taskId 隔离,不读取其他 Agent 的 runtime 文件,并在进入 prompt 前过滤密钥和本机绝对路径。新后台 run 启动时会继承本 Agent 上次 `recentToolCalls` 和 `lastResponse`,让多轮任务不丢失结构化行动证据。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `preview.start`。Agent 可在 loop 中自行请求启动当前项目的本地 HTTP 预览;Runtime 会复用 `preview.start` 策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑,并把 `agent.runtime.preview.start` 写入 `.agent/agent.db`。该 observation 只向 LLM 返回 localhost URL 与端口,不返回用户项目绝对路径。
|
||||
- 2026-07-10 补充,2026-07-20 收紧:后台任务工具箱提供 `canvas.asset_generate`。Agent 在 loop 中给出素材 prompt、`outputPath`、比例、尺寸、kind 与展示名;Runtime 通过 AppData / Tauri 配置里的 `editorApi` 调用 External Editor API。生成前按本地项目名称创建或复用同名画布项目和同名素材库目录,请求必须携带 `projectId + assetFolderId + canvasCompletion`,生成结果同时进入平台画布、平台素材库和本地项目。canonical 策划 / 美术任务分别固定落到 `assets/ui-prototype.png` 与 `assets/art-spritesheet.png`;其他任务仍可落到 `assets/canvas-generated/`。确定输出路径只允许项目 `assets/` 下的 png/jpg/jpeg/webp,拒绝父目录、绝对路径、符号链接和覆盖;本地 manifest 登记失败时删除刚写入文件。Runtime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
|
||||
- 2026-07-10 补充,2026-07-20 收紧:后台任务工具箱提供 `canvas.asset_generate`。Agent 在 loop 中给出素材 prompt、`outputPath`、比例、尺寸、kind 与展示名;Runtime 通过 AppData / Tauri 配置里的 `editorApi` 调用 External Editor API。生成前按本地项目名称创建或复用同名画布项目和同名素材库目录,请求必须携带 `projectId + assetFolderId + canvasCompletion`,生成结果同时进入平台画布、平台素材库和本地项目。canonical 策划 / 美术任务分别固定落到 `assets/ui-prototype.png` 与 `assets/art-spritesheet.png`;其他任务仍可落到 `assets/canvas-generated/`。UI 原型使用专用 prompt 与 `generationInputs.artSpec`,明确要求 HUD、单位卡槽、战场网格、敌人入口、波次与开始/暂停/重开控件,并禁止无 HUD 场景图、海报和地图;`ui-design` 的 provider 负面词不得再排除文字、边框、按钮或 UI 控件。`gpt-image-2` 的 `1K + 16:9` 实际映射为 `1536×1024`,因此 canonical UI 原型固定请求 `2K + 16:9`,取得真正的 `2048×1152` 横屏文件。确定输出路径只允许项目 `assets/` 下的 png/jpg/jpeg/webp,拒绝父目录、绝对路径、符号链接和覆盖;旧候选不合格时必须先按 `file.delete` 权限合同显式删除,再重新确认生成,不能自动覆盖或自动扣费。本地 manifest 登记失败时删除刚写入文件。Runtime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `task.list`。Agent 可在 loop 中读取 manifest 任务图、每个 seed task 的状态 / 依赖 / 产物交接,以及按依赖计算的 `readyTaskIds`;Runtime 复用 `task.list` 项目权限策略,策略要求确认或拒绝时只返回策略 observation,不向 LLM 暴露任务图细节。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `task.update`。Agent 可在 loop 中把 manifest 种子任务状态更新为 `pending / running / waiting-for-confirmation / completed / failed`,用于表达长期后台任务的当前进度;Runtime 复用 `task.update` 策略和项目写锁,实际只修改 `.agent/manifest.json` 中已有 taskId 的 `status`,并写入 `agent.runtime.task.update` 审计记录。策略要求确认或拒绝时不会修改 manifest,也不会创建新任务。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `file.list`。Agent 可在 loop 中自行列出项目文件摘要或某个相对目录下的文件摘要,再决定是否继续读取具体文件;Runtime 复用 `file.list` 项目权限策略,observation 只包含项目相对路径、类型和大小,不读取文件内容、不返回项目绝对路径。
|
||||
|
||||
@@ -1463,7 +1463,9 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
.or_else(|| payload.project_id.clone()),
|
||||
);
|
||||
let http_client = build_openai_image_http_client(&settings)?;
|
||||
let negative_prompt = Some("文字、水印、边框、按钮、UI 控件、低清晰度、变形主体");
|
||||
let negative_prompt = Some(editor_image_generation_negative_prompt(
|
||||
is_ui_design_generation,
|
||||
));
|
||||
let reference_images = if reference_sources.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
@@ -6378,6 +6380,14 @@ fn build_editor_ui_design_prompt(user_input: &str, has_icon_spec_reference: bool
|
||||
prompt.join("\n")
|
||||
}
|
||||
|
||||
fn editor_image_generation_negative_prompt(is_ui_design_generation: bool) -> &'static str {
|
||||
if is_ui_design_generation {
|
||||
"水印、无界面的纯场景插画、海报、地图、低清晰度、变形主体、不可读布局"
|
||||
} else {
|
||||
"文字、水印、边框、按钮、UI 控件、低清晰度、变形主体"
|
||||
}
|
||||
}
|
||||
|
||||
fn build_editor_character_image_prompt(
|
||||
role_setting: &str,
|
||||
screen_color: EditorScreenBackgroundColor,
|
||||
@@ -8013,6 +8023,23 @@ mod tests {
|
||||
assert!(!no_reference_prompt.contains("参考图1为图标素材规范"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_ui_design_generation_keeps_ui_controls_out_of_negative_prompt() {
|
||||
let ui_design_negative_prompt = editor_image_generation_negative_prompt(true);
|
||||
for required_ui_element in ["文字", "边框", "按钮", "UI 控件"] {
|
||||
assert!(!ui_design_negative_prompt.contains(required_ui_element));
|
||||
}
|
||||
for excluded_non_ui_output in ["无界面的纯场景插画", "海报", "地图", "不可读布局"]
|
||||
{
|
||||
assert!(ui_design_negative_prompt.contains(excluded_non_ui_output));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
editor_image_generation_negative_prompt(false),
|
||||
"文字、水印、边框、按钮、UI 控件、低清晰度、变形主体"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_character_image_prompt_appends_user_role_setting() {
|
||||
let prompt = build_editor_character_image_prompt(
|
||||
|
||||
Reference in New Issue
Block a user