diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs index 460d8dfad..94abf882b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs @@ -1832,7 +1832,7 @@ mod tests { assert!(with_canvas.contains("由 Runtime 在收束门内同时核对固定 owner 文档")); assert!(!with_canvas.contains("成功动作本身就是当前 revision 的验证")); assert!(with_canvas.contains("调用 ui.workflow.run")); - assert!(with_canvas.contains("visual-binding 最终编辑器路由")); + assert!(with_canvas.contains("asset-separation 最终编辑器路由")); assert!(with_canvas.contains("每个功能页面各写一行 @genarrative-ui-page")); assert!(with_canvas.contains("ui.workflow.run 的 discover")); assert!(with_canvas.contains("assets/ui-pages/{pageId}.png")); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs index 235177a31..52af400ac 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_audit.rs @@ -801,7 +801,7 @@ fn agent_runtime_action_receipt_safe_detail_with_owner( let initial_step = route.get("initialStep")?.as_str()?; let render_mode = route.get("renderMode")?.as_str()?; if resource_id.is_empty() - || initial_step != "visual-binding" + || initial_step != "asset-separation" || render_mode != "final-preview" { return None; diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs index ea905233e..f07769b28 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs @@ -296,7 +296,7 @@ pub(crate) async fn run_ui_workflow_at_with_provider( ) { let route = UiWorkflowFinalStageRoute { resource_id: statuses[0].ui_asset_id.clone(), - initial_step: "visual-binding".to_string(), + initial_step: "asset-separation".to_string(), render_mode: "final-preview".to_string(), }; if finalized { diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/recognition.ts b/apps/ai-game-creator-shell/src/features/ui-editor/recognition.ts index 2238c6b40..68bd9e003 100644 --- a/apps/ai-game-creator-shell/src/features/ui-editor/recognition.ts +++ b/apps/ai-game-creator-shell/src/features/ui-editor/recognition.ts @@ -3,7 +3,7 @@ import type { State } from './types/State'; /** * 识别结果是整棵结构草稿树的替换结果,不与旧树逐节点合并。 - * 识别阶段有意不携带视觉组件;组件绑定由后续 visual-binding 阶段完成。 + * 识别阶段有意不携带 SpriteAsset;视觉素材由后续 asset-separation 阶段自动切分并回填。 */ export function applyRecognitionResult( state: State, diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/bindingOverview.ts b/apps/ai-game-creator-shell/src/features/ui-editor/separationOverview.ts similarity index 65% rename from apps/ai-game-creator-shell/src/features/ui-editor/bindingOverview.ts rename to apps/ai-game-creator-shell/src/features/ui-editor/separationOverview.ts index fe770e022..1330e2b7c 100644 --- a/apps/ai-game-creator-shell/src/features/ui-editor/bindingOverview.ts +++ b/apps/ai-game-creator-shell/src/features/ui-editor/separationOverview.ts @@ -8,27 +8,27 @@ import type { Component } from './types/Component'; import type { SpriteAsset } from './types/SpriteAsset'; import type { UITree } from './types/UITree'; -export type ComponentBindingCounts = { +export type ComponentSeparationCounts = { componentsNeedingAssets: number; assetSlots: number; boundSlots: number; pendingSlots: number; }; -export type BindingOverview = ComponentBindingCounts & { +export type SeparationOverview = ComponentSeparationCounts & { needsAttention: number; blocked: number; independentAssets: number; }; -const EMPTY_COMPONENT_BINDING_COUNTS: ComponentBindingCounts = { +const EMPTY_COMPONENT_SEPARATION_COUNTS: ComponentSeparationCounts = { componentsNeedingAssets: 0, assetSlots: 0, boundSlots: 0, pendingSlots: 0, }; -function countRequiredAssetSlot(isBound: boolean): ComponentBindingCounts { +function countRequiredAssetSlot(isBound: boolean): ComponentSeparationCounts { return { componentsNeedingAssets: 1, assetSlots: 1, @@ -37,40 +37,40 @@ function countRequiredAssetSlot(isBound: boolean): ComponentBindingCounts { }; } -export function getImageBindingCounts( +export function getImageSeparationCounts( component: Extract['Image'], -): ComponentBindingCounts { +): ComponentSeparationCounts { return countRequiredAssetSlot(component.target_graphic !== null); } -export function getTextBindingCounts( +export function getTextSeparationCounts( component: Extract['Text'], -): ComponentBindingCounts { +): ComponentSeparationCounts { if ( typeof component.font !== 'object' || component.font === null || !('Bound' in component.font) ) { - return EMPTY_COMPONENT_BINDING_COUNTS; + return EMPTY_COMPONENT_SEPARATION_COUNTS; } return countRequiredAssetSlot(true); } -export function getComponentBindingCounts( +export function getComponentSeparationCounts( component: Component, -): ComponentBindingCounts { +): ComponentSeparationCounts { if ('Image' in component) { - return getImageBindingCounts(component.Image); + return getImageSeparationCounts(component.Image); } if ('Text' in component) { - return getTextBindingCounts(component.Text); + return getTextSeparationCounts(component.Text); } - return EMPTY_COMPONENT_BINDING_COUNTS; + return EMPTY_COMPONENT_SEPARATION_COUNTS; } -function addBindingCounts( - overview: ComponentBindingCounts, - counts: ComponentBindingCounts, +function addSeparationCounts( + overview: ComponentSeparationCounts, + counts: ComponentSeparationCounts, ) { overview.componentsNeedingAssets += counts.componentsNeedingAssets; overview.assetSlots += counts.assetSlots; @@ -78,12 +78,12 @@ function addBindingCounts( overview.pendingSlots += counts.pendingSlots; } -export function getBindingOverview( +export function getSeparationOverview( uiTrees: UITree[], spriteAssets: Record, -): BindingOverview { - const overview: BindingOverview = { - ...EMPTY_COMPONENT_BINDING_COUNTS, +): SeparationOverview { + const overview: SeparationOverview = { + ...EMPTY_COMPONENT_SEPARATION_COUNTS, needsAttention: 0, blocked: 0, independentAssets: Object.keys(spriteAssets).length, @@ -96,17 +96,20 @@ export function getBindingOverview( overview.needsAttention += 1; } if (node.component) { - addBindingCounts(overview, getComponentBindingCounts(node.component)); + addSeparationCounts( + overview, + getComponentSeparationCounts(node.component), + ); } } return overview; } -export function nodeHasPendingBinding(target: UiTreeNodeTarget): boolean { +export function nodeHasPendingSeparation(target: UiTreeNodeTarget): boolean { return ( target.node.component !== null && - getComponentBindingCounts(target.node.component).pendingSlots > 0 + getComponentSeparationCounts(target.node.component).pendingSlots > 0 ); } diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index f539fc02f..cc8d7ec43 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -3335,7 +3335,7 @@ export default function ProjectDevelopmentView({ resource.label, ...(result.asset.source.generationKind === 'ui-workflow.completed' ? { - initialStep: 'visual-binding', + initialStep: 'asset-separation', initialFurthestStepIndex: 2, } : {}), @@ -3376,7 +3376,7 @@ export default function ProjectDevelopmentView({ (asset) => asset.id === resource.manifestAssetId, )?.source.generationKind === 'ui-workflow.completed' ? { - initialStep: 'visual-binding' as const, + initialStep: 'asset-separation' as const, initialFurthestStepIndex: 2, } : {}), @@ -3445,7 +3445,7 @@ export default function ProjectDevelopmentView({ resourceLabel: completed.localPath.split(/[\\/]/u).filter(Boolean).pop() ?? 'UI 设计资源', - initialStep: 'visual-binding', + initialStep: 'asset-separation', initialFurthestStepIndex: 2, }); }, [advanceFocusGeneration, manifest.assets, uiEditorRoute]); diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/BindingOverview.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/SeparationOverview.tsx similarity index 87% rename from apps/ai-game-creator-shell/src/view/ui-editor/components/BindingOverview.tsx rename to apps/ai-game-creator-shell/src/view/ui-editor/components/SeparationOverview.tsx index ee8d89bac..6a24c82f7 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/BindingOverview.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/SeparationOverview.tsx @@ -1,17 +1,17 @@ import { useMemo } from 'react'; import { - getBindingOverview, + getSeparationOverview, nodeHasBlockedComponents, nodeNeedsComponentReview, -} from '../../../features/ui-editor/bindingOverview'; +} from '../../../features/ui-editor/separationOverview'; import type { NodeId } from '../../../features/ui-editor/types/NodeId'; import type { SpriteAsset } from '../../../features/ui-editor/types/SpriteAsset'; import type { UIDesignImageId } from '../../../features/ui-editor/types/UIDesignImageId'; import type { UITree } from '../../../features/ui-editor/types/UITree'; import { useUiTreeNodeCycle } from '../useUiTreeNodeCycle'; -export function BindingOverview({ +export function SeparationOverview({ uiTrees, sprites, onFocusStatusNode, @@ -21,7 +21,7 @@ export function BindingOverview({ onFocusStatusNode: (treeId: UIDesignImageId, nodeId: NodeId) => void; }) { const overview = useMemo( - () => getBindingOverview(uiTrees, sprites), + () => getSeparationOverview(uiTrees, sprites), [sprites, uiTrees], ); const attentionCycle = useUiTreeNodeCycle({ @@ -38,20 +38,20 @@ export function BindingOverview({ return (
Overview -

绑定概览

+

自动切分素材概览

- - + + -

发现未完成的自动分离

+

+ 发现未完成的自动切分素材 +

- 上次分离留下了可恢复状态(已登记{' '} + 上次自动切分素材留下了可恢复状态(已登记{' '} {workflow.separationRecovery?.bound_node_count ?? 0}{' '} - 个节点)。请选择继续上次分离,或开始新的分离。 + 个节点)。请选择继续上次自动切分素材,或开始新的自动切分素材。

@@ -115,8 +117,8 @@ function getStepAction(workflow: UiEditorWorkflowProjection) { }; } return { - label: '自动分离并绑定视觉素材', - runningLabel: '自动分离中…', + label: '自动切分素材', + runningLabel: '素材切分中…', action: workflow.separateUi, }; } diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts b/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts index 54b77ef1f..1f0b8f943 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts @@ -21,7 +21,7 @@ export function prerequisiteIssuesForStep( return []; case 'structure-recognition': return validateComponentRecognitionPrerequisites(state); - case 'visual-binding': + case 'asset-separation': return validateAssetRecognitionPrerequisites(state); } } @@ -35,7 +35,7 @@ export function postCheckIssuesForStep( return validateReferenceAnalysisResult(state); case 'structure-recognition': return validateStructureRecognitionResult(state); - case 'visual-binding': + case 'asset-separation': return validateVisualBindingResult(state); } } @@ -59,7 +59,7 @@ export function activeStepPrerequisiteIssues( return validateComponentRecognitionPrerequisites(state); case 'structure-recognition': return validateAssetRecognitionPrerequisites(state); - case 'visual-binding': + case 'asset-separation': return validateLayoutReviewPrerequisites(state); } } diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts b/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts index ff90b6dd5..02c5b317a 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts @@ -18,8 +18,8 @@ export function workflowStepLabel(step: UiEditorStepId): string { return '分析参考图'; case 'structure-recognition': return '识别界面结构'; - case 'visual-binding': - return '绑定视觉素材'; + case 'asset-separation': + return '自动切分素材'; } const exhaustiveCheck: never = step; return exhaustiveCheck; diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx index 11c2c82df..2ea43b868 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx @@ -7,13 +7,13 @@ import { type IUiDesignStateStore, uiDesignStateStore, } from '../../features/ui-editor/uiDesignStateStore'; -import { BindingOverview } from './components/BindingOverview'; import { EditorDialogs } from './components/EditorDialogs'; import { ImportOverview } from './components/ImportOverview'; import { InputSidebar } from './components/InputSidebar'; import { InspectorSidebar } from './components/Inspector/InspectorSidebar'; import { PreviewWorkspace } from './components/preview/PreviewWorkspace'; import { RecognitionOverview } from './components/RecognitionOverview'; +import { SeparationOverview } from './components/SeparationOverview'; import { ToolNavigation } from './components/ToolNavigation'; import { WorkflowActionCard } from './components/WorkflowActionCard'; import { WorkflowCompletionModal } from './components/WorkflowCompletionModal'; @@ -265,8 +265,8 @@ export default function UiEditorPage({ session.input.highlightStatusField(nodeId, 'layout_status'); }} /> - ) : session.input.activeStep === 'visual-binding' ? ( - { diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/model.ts b/apps/ai-game-creator-shell/src/view/ui-editor/model.ts index 13ec54cc7..f03f6ef47 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/model.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/model.ts @@ -6,7 +6,7 @@ import type { RemovalImpact } from '../../features/ui-editor/useUiEditorState'; export type UiEditorStepId = | 'reference-analysis' | 'structure-recognition' - | 'visual-binding'; + | 'asset-separation'; export type UiEditorImportKind = 'design-image' | 'font' | 'sprite'; export type UiEditorNodeFocusRequest = { @@ -27,7 +27,7 @@ export const UI_EDITOR_STEPS: Array<{ }> = [ { id: 'reference-analysis', label: '分析参考图' }, { id: 'structure-recognition', label: '识别界面结构' }, - { id: 'visual-binding', label: '绑定视觉素材' }, + { id: 'asset-separation', label: '自动切分素材' }, ]; export const UI_DESIGN_IMAGE_ROLES: Array<{ diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts index e09978662..c449a56e7 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts @@ -417,7 +417,7 @@ export function useUiEditorSession( activeStep === 'reference-analysis' ? 'structure-recognition' : activeStep === 'structure-recognition' - ? 'visual-binding' + ? 'asset-separation' : null; const spriteReferenceCounts = useMemo(() => { @@ -1111,7 +1111,7 @@ export function useUiEditorSession( (path) => !importedByPath.has(path), ); backfillErrors = missingImports.map( - (path) => `未能登记分离图片:${path}`, + (path) => `未能登记自动切分素材图片:${path}`, ); const importedAssets: ImportedAsset[] = [ ...new Map( @@ -1147,7 +1147,7 @@ export function useUiEditorSession( const sprite = spriteByPath.get(path); if (!sprite) { backfillErrors.push( - `节点 ${bound.node_id} 缺少已登记的分离图片:${path}`, + `节点 ${bound.node_id} 缺少已登记的自动切分素材图片:${path}`, ); continue; } @@ -1183,18 +1183,19 @@ export function useUiEditorSession( ]), ), })); - if (separationResult === null) throw new Error('自动分离没有返回结果'); + if (separationResult === null) + throw new Error('自动切分素材没有返回结果'); const completedResult = separationResult as SeparationDTO; if (!(await save())) { throw new Error( - '分离结果已写入编辑器,但 State 保存失败;sidecar 已保留,可继续恢复。', + '自动切分素材结果已写入编辑器,但 State 保存失败;sidecar 已保留,可继续恢复。', ); } if (backfillErrors.length > 0) { reportWorkflowCompletion( - 'visual-binding', + 'asset-separation', 'failure', - `自动分离已完成,但有 ${backfillErrors.length} 项未能回填;已登记素材并保留恢复状态。\n${backfillErrors.join('\n')}`, + `自动切分素材已完成,但有 ${backfillErrors.length} 项未能回填;已登记素材并保留恢复状态。\n${backfillErrors.join('\n')}`, setSeparationStatus, ); return; @@ -1205,14 +1206,14 @@ export function useUiEditorSession( }); setHasSeparated(true); reportWorkflowCompletion( - 'visual-binding', + 'asset-separation', 'success', - `自动分离完成:${completedResult.bound_nodes.length} 个已绑定,${completedResult.problematic_nodes.length} 个待处理。`, + `自动切分素材完成:${completedResult.bound_nodes.length} 个已切分并回填,${completedResult.problematic_nodes.length} 个待处理。`, setSeparationStatus, ); } catch (cause) { reportWorkflowCompletion( - 'visual-binding', + 'asset-separation', 'failure', cause instanceof Error ? cause.message : String(cause), setSeparationStatus, diff --git a/apps/ai-game-creator-shell/tests/bindingOverview.test.ts b/apps/ai-game-creator-shell/tests/separationOverview.test.ts similarity index 89% rename from apps/ai-game-creator-shell/tests/bindingOverview.test.ts rename to apps/ai-game-creator-shell/tests/separationOverview.test.ts index c7ab782a5..91b39ca72 100644 --- a/apps/ai-game-creator-shell/tests/bindingOverview.test.ts +++ b/apps/ai-game-creator-shell/tests/separationOverview.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; import { - getBindingOverview, + getSeparationOverview, nodeHasBlockedComponents, - nodeHasPendingBinding, + nodeHasPendingSeparation, nodeNeedsComponentReview, -} from '../src/features/ui-editor/bindingOverview'; +} from '../src/features/ui-editor/separationOverview'; import { getNextMatchingUiTreeNodeTarget } from '../src/features/ui-editor/stageStatusOverview'; import type { Component } from '../src/features/ui-editor/types/Component'; import type { Node } from '../src/features/ui-editor/types/Node'; @@ -97,9 +97,9 @@ const trees: UITree[] = [ }, ]; -describe('getBindingOverview', () => { +describe('getSeparationOverview', () => { it('uses per-component helpers to include both image and text slots', () => { - expect(getBindingOverview(trees, sprites)).toEqual({ + expect(getSeparationOverview(trees, sprites)).toEqual({ componentsNeedingAssets: 2, assetSlots: 2, boundSlots: 1, @@ -110,15 +110,15 @@ describe('getBindingOverview', () => { }); }); - it('reuses the common preorder next-target search for every binding queue', () => { + it('reuses the common preorder next-target search for every separation queue', () => { expect( getNextMatchingUiTreeNodeTarget(trees, null, (target) => - nodeHasPendingBinding(target), + nodeHasPendingSeparation(target), )?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, 'review', (target) => - nodeHasPendingBinding(target), + nodeHasPendingSeparation(target), )?.node.id, ).toBe('review'); expect( diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index 2f4bc4114..f9615a790 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -347,12 +347,14 @@ describe('UiEditorPage', () => { fireEvent.click(screen.getByRole('button', { name: '仍然继续' })); expect(screen.getByRole('heading', { name: '识别概览' })).toBeTruthy(); - fireEvent.click(screen.getByRole('button', { name: /绑定视觉素材/ })); + fireEvent.click(screen.getByRole('button', { name: /自动切分素材/ })); fireEvent.click(screen.getByRole('button', { name: '仍然继续' })); - expect(screen.getByRole('heading', { name: '绑定概览' })).toBeTruthy(); + expect( + screen.getByRole('heading', { name: '自动切分素材概览' }), + ).toBeTruthy(); }); - it('opens a completed workflow directly at the visual binding review stage', async () => { + it('opens a completed workflow directly at the asset separation review stage', async () => { const stateStore: IUiDesignStateStore = { load: vi.fn().mockResolvedValue({ revision: 3, @@ -367,25 +369,25 @@ describe('UiEditorPage', () => { projectPath: '/tmp/ui-editor-final-review', resourceId: 'ui-resource', stateStore, - initialStep: 'visual-binding', + initialStep: 'asset-separation', initialFurthestStepIndex: 2, }), ); expect( - await screen.findByRole('heading', { name: '绑定概览' }), + await screen.findByRole('heading', { name: '自动切分素材概览' }), ).toBeTruthy(); expect( screen .getByRole('navigation', { name: 'UI 编辑流程' }) .querySelector('button[aria-current="step"]')?.textContent, - ).toContain('绑定视觉素材'); + ).toContain('自动切分素材'); }); it('keeps the pending binding count informational instead of navigable', () => { render(createElement(UiEditorPage, { projectPath: '/tmp/ui-editor' })); - fireEvent.click(screen.getByRole('button', { name: /绑定视觉素材/ })); + fireEvent.click(screen.getByRole('button', { name: /自动切分素材/ })); fireEvent.click(screen.getByRole('button', { name: '仍然继续' })); expect( @@ -396,7 +398,7 @@ describe('UiEditorPage', () => { it('switches tools freely without inventing completed workflow state', () => { render(createElement(UiEditorPage, { projectPath: '/tmp/ui-editor' })); - fireEvent.click(screen.getByRole('button', { name: /绑定视觉素材/ })); + fireEvent.click(screen.getByRole('button', { name: /自动切分素材/ })); expect(screen.getByRole('heading', { name: '检查发现问题' })).toBeTruthy(); }); diff --git a/apps/ai-game-creator-shell/tests/workflowCompletionNotice.test.ts b/apps/ai-game-creator-shell/tests/workflowCompletionNotice.test.ts index 01016c4ae..af3f2d1cc 100644 --- a/apps/ai-game-creator-shell/tests/workflowCompletionNotice.test.ts +++ b/apps/ai-game-creator-shell/tests/workflowCompletionNotice.test.ts @@ -15,6 +15,6 @@ describe('workflow completion notice helpers', () => { it('maps every workflow step to a user-facing label', () => { expect(workflowStepLabel('reference-analysis')).toBe('分析参考图'); expect(workflowStepLabel('structure-recognition')).toBe('识别界面结构'); - expect(workflowStepLabel('visual-binding')).toBe('绑定视觉素材'); + expect(workflowStepLabel('asset-separation')).toBe('自动切分素材'); }); }); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 2e6658f85..5e45daf45 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -7903,7 +7903,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 ## 2026-08-24 AGC UI 原型桥接与自主 UI workflow - 决策:`ui-prototype` 图片与 `UI` JSON 编辑资源保持两种正式类型。Agent 通过受控 `ui.workflow.run` 按 `prepare -> recognize -> status -> finalize` 创建页面资源、关联源图、持久化 UI State 和 manifest 阶段;`recognize` 直接复用 UI Editor 的 provider-backed 结构识别、多树合并与组件绑定命令,按 `reference-ready -> structure-ready -> merge-ready -> binding-ready` 逐阶段写入并推进项目 revision。页面可显式关联已登记图片/图标和字体,图片/图标按 5 项一批绑定,字体安全元数据进入绑定上下文且未知引用失败关闭。Runtime 回执携带 `revisionAdvanceCount`;Provider 未配置、请求失败、工具调用缺失、结果不匹配、未产出可渲染组件或仍有待审节点时保留最近真实阶段,禁止用 deterministic seed 冒充语义处理完成。 -- 客户端:画布点击 `ui-prototype` 先幂等桥接到 `UI` JSON,并立即刷新 manifest;关联查找按 canonical resource identity 且优先已完成 workflow 资源。全部页面完成后,工作台自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段。 +- 客户端:画布点击 `ui-prototype` 先幂等桥接到 `UI` JSON,并立即刷新 manifest;关联查找按 canonical resource identity 且优先已完成 workflow 资源。全部页面完成后,工作台自动打开首个页面的 UI 编辑器 `asset-separation` 最终阶段。 - 完成门:`finalize` 必须为每个页面提供 `game/` 下真实 UTF-8 应用文件并安装当前 UI State revision 标记;缺少结构、组件、页面或标记时拒绝完成。详细输入、阶段与恢复契约见 [`docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。 - 验证:前端 bridge 6/6、资源实时集成 19/19、AppSurface 410/410、AGC typecheck、Rust workflow 定向测试覆盖 provider 前的 reference 阶段与真实调用失败关闭、Rust bridge 1/1、编码、格式和 diff 门禁通过;认证登录与真实 Provider 生成的桌面端 E2E 尚未具备可用会话,保持未验证。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 2f63f00e6..8f5321635 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -106,7 +106,7 @@ UI Editor Inspector 的全局只读状态唯一来源是 `controller.editor.isLo ## 2026-08-18 UI Editor 结构识别、合并与增量导入边界 -UI Editor 当前把“识别界面结构”定义为结构草稿阶段,而不是完整视觉还原阶段。识别 DTO 只负责输出节点层级、几何、名称、描述和置信度;节点组件暂为空,由后续“绑定视觉素材”阶段补齐 Image / Text 组件。`applyRecognitionResult` 可以整体替换当前 `ui_trees`,但该替换只代表结构结果,不能宣称已经保留截图中的视觉内容;组件状态使用 `NoProblem`,前置检查仍会根据空组件和素材绑定情况阻止跳过绑定阶段。 +UI Editor 当前把“识别界面结构”定义为结构草稿阶段,而不是完整视觉还原阶段。识别 DTO 只负责输出节点层级、几何、名称、描述和置信度;节点组件暂为空,由后续“自动切分素材”阶段补齐 Image / Text 组件。`applyRecognitionResult` 可以整体替换当前 `ui_trees`,但该替换只代表结构结果,不能宣称已经保留截图中的视觉内容;组件状态使用 `NoProblem`,前置检查仍会根据空组件和素材切分情况阻止跳过自动切分阶段。 结构识别、界面语义建议、多图合并和组件绑定只接受不超过 `1 MiB` 的 LLM 工具调用 arguments,并在递归业务类型反序列化前先解析为通用 JSON、迭代检查结构预算。结构识别按每棵返回树独立限制为最多 `512` 个 LLM 节点和 `32` 层,不汇总多棵树的节点数,也不计 Rust 自动补建的页面根;界面语义建议最多 `4` 个节点和 `4` 层;合并计划最多 `512` 个计划节点和 `32` 层,`Simple.children` 与 `Merged.merged_from` 使用同一计数和深度口径;组件绑定 `changes` 不得超过当前可编辑节点数且绝对上限为 `10,000`,每个 change 的完整组件栈最多 `64` 个组件。任何超限结果均整次拒绝,不截断、不返回部分结果,也不把工具 arguments 正文写入日志。 @@ -1286,7 +1286,7 @@ game-project/ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络保持关闭,联网资料继续走受控 `agc_web_search`。多 Agent、Apps、完整插件 Runtime、hooks、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制仍关闭,避免绕过 AGC durable delegation、浏览器证据和副作用审计;图片生成通过客户端审核的 `agc_tools.agc_generate_image` 暴露普通单图、角色图、视觉规范图和 UI 设计图,完整游戏美术包继续使用 `agc_tools.taonier_prepare_game_art`,两者都复用同一客户端登录态、幂等账本、下载校验和 manifest/revision 投影,不开放 Codex 原生 image tool。app-server 使用隔离 `CODEX_HOME`:内置 `agc_tools` 由客户端启动参数注入,用户在客户端扩展列表启用的独立第三方 MCP 以原生配置写入该次隔离 home;全局 Codex MCP、禁用项、Plugin hooks/apps 和其它插件能力不进入 DirectProject。第三方项固定非 required,配置或启动失败只记录该项,不替换 `agc_tools`;provider session token、工具桥地址和受控搜索标记不得通过第三方 MCP 的环境转发字段泄露。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅使用连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。`agc_tools` 的平台授权由 AGC 客户端当前登录会话和受控后端完成,普通客户端不得把 DirectProject 请求改成外部 API Key 请求;401/403 只投影为客户端登录或权限异常,不向用户索要凭据或暴露内部 URL。shell 子进程采用 `shell_environment_policy` core 继承及 secret/proxy/bridge 排除,provider key 和桥接凭据不得进入命令环境。系统提示词不再预注入项目源码快照或 Skill 正文,Codex 按需读取当前 cwd 文件。 ## 2026-08-24 AGC UI 原型桥接与自主 UI workflow -- 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批组件绑定,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用、未产出可渲染组件或仍有待审节点时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段,交给用户检查和手动调整。UI 编辑器独立的语义建议请求也必须复用统一 LLM 传输选择,`llm.stream=true` 时发送 `stream=true` 并聚合完整工具调用后再校验结果。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。 +- 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批自动切分素材,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用、未产出可渲染组件或仍有待审节点时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `asset-separation` 最终阶段,交给用户检查和手动调整。UI 编辑器独立的语义建议请求也必须复用统一 LLM 传输选择,`llm.stream=true` 时发送 `stream=true` 并聚合完整工具调用后再校验结果。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。 ## 2026-08-28 AGC 自主构建 relaxed 编排覆盖 diff --git a/docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md b/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md similarity index 93% rename from docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md rename to docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md index a0691c6b6..d06c1ef77 100644 --- a/docs/technical/【技术方案】UI编辑器自动分离工作流-2026-09-08.md +++ b/docs/technical/【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md @@ -1,10 +1,10 @@ -# UI 编辑器自动分离工作流 +# UI 编辑器自动切分素材工作流 更新时间:`2026-09-08` ## 目标 -将 UI 编辑器现有“用户先提供独立图片/图标,再执行组件绑定”的入口替换为自动分离:结构识别阶段直接返回可渲染组件草稿,分离阶段按整页叶节点批次调用图片编辑模型,再由视觉模型确认处理图中的区域与目标节点。 +将 UI 编辑器现有“用户先提供独立图片/图标,再执行组件绑定”的入口替换为自动切分素材:结构识别阶段直接返回可渲染组件草稿,切分阶段按整页叶节点批次调用图片编辑模型,再由视觉模型确认处理图中的区域与目标节点。 ## 识别结果 @@ -84,8 +84,8 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正 ## 前端正式接入 -- UI 编辑器点击“自动分离”时,前端只调用一次 `separate_ui`,消费完整 `SeparationDTO`;separation 内部 batch 不向前端暴露,也不在 UI 中显示 batch 进度。 -- 如果 sidecar 已存在未完成的 `state.json`,点击入口时先打开独立弹窗,由用户选择“继续上次分离”或“开始新的分离”。继续复用 sidecar 的 pending tree;重新开始只替换当前 `state.json`,不删除 sidecar 图片。 +- UI 编辑器点击“自动切分素材”时,前端只调用一次 `separate_ui`,消费完整 `SeparationDTO`;separation 内部 batch 不向前端暴露,也不在 UI 中显示 batch 进度。 +- 如果 sidecar 已存在未完成的 `state.json`,点击入口时先打开独立弹窗,由用户选择“继续上次自动切分素材”或“开始新的自动切分素材”。继续复用 sidecar 的 pending tree;重新开始只替换当前 `state.json`,不删除 sidecar 图片。 - `BoundNode.cut_image_path` 必须是项目根相对路径。前端使用现有 `import_local_project_image_assets` 登记 cut 图片;由于该通用命令单次最多 100 个路径,前端可以在资源登记阶段按 100 条分组调用,但这不属于 separation batch,也不向用户展示。 - 现有本地资源导入按清洗后的文件名 stem 与内容摘要生成目标路径;相同目标路径直接复用已有 manifest asset ID,内容不同则拒绝覆盖或生成不同摘要路径。前端不自行猜测 SpriteAsset 是否存在,也不从 NodeId 派生 SpriteAssetId。 - 全部可登记图片完成导入后,前端在一个 `runWithStateLocked` 中复用 `addSpriteAssets` 的内部 State 变换逻辑,加入返回的 SpriteAsset 并回填仍匹配 Node 的唯一未绑定 Image component,最后一次性提交 State。公开 `addSpriteAssets` 的普通 mutation guard 不放宽。 @@ -104,7 +104,7 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正 ## TODO - 正在执行 batch 的持久化和恢复。 -- 前端自动分离接入已实现;仍需补齐真实 Tauri/前端联调回归测试与失败注入测试。 +- 前端自动切分素材接入已实现;仍需补齐真实 Tauri/前端联调回归测试与失败注入测试。 - 临时图片清理/归档策略。 - 手动抠图能力。 - problematic 对更高层 workflow 完成门禁的最终定义。 diff --git a/docs/technical/【设计】UI编辑器工作流完成通知弹窗-2026-09-04.md b/docs/technical/【设计】UI编辑器工作流完成通知弹窗-2026-09-04.md index 5cdc52832..385dcea54 100644 --- a/docs/technical/【设计】UI编辑器工作流完成通知弹窗-2026-09-04.md +++ b/docs/technical/【设计】UI编辑器工作流完成通知弹窗-2026-09-04.md @@ -2,7 +2,7 @@ ## 目标 -UI 编辑器的“分析参考图”“识别界面结构”“绑定视觉素材”三个工作流动作在每次运行结束后,用独立的阻塞通知弹窗明确反馈结果,避免仅依赖卡片内一行状态文本而被忽略。 +UI 编辑器的“分析参考图”“识别界面结构”“自动切分素材”三个工作流动作在每次运行结束后,用独立的阻塞通知弹窗明确反馈结果,避免仅依赖卡片内一行状态文本而被忽略。 ## 交互约定 @@ -15,13 +15,13 @@ UI 编辑器的“分析参考图”“识别界面结构”“绑定视觉素 ## 文案 -弹窗标题由步骤名和结果态组成,例如“识别界面结构完成”或“绑定视觉素材失败”。正文复用卡片状态文本,并逐条扩展结果信息,统一以“请检查”收尾。 +弹窗标题由步骤名和结果态组成,例如“识别界面结构完成”或“自动切分素材失败”。正文复用卡片状态文本,并逐条扩展结果信息,统一以“请检查”收尾。 成功状态的基线文案: - 分析参考图:保留已应用的语义建议数量;若现有状态可可靠取得问题/待确认数量,则一并展示。 - 识别界面结构:保留替换的界面树数量,并展示识别结果中的待检查/必须修复数量(若可取得)。 -- 绑定视觉素材:保留现有 `B/B` 批次计数,改为用户可读的绑定结果。 +- 自动切分素材:保留现有 `B/B` 批次计数,改为用户可读的切分结果。 失败状态保留实际错误文本,仅在弹窗标题中补充步骤和失败上下文,正文同样以“请检查”收尾。 diff --git a/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md b/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md index fe5c971a3..61258c68e 100644 --- a/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md +++ b/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md @@ -38,7 +38,7 @@ Agent 通过白名单工具 `ui.workflow.run` 发起工作流。项目路径由 1. `prepare` 为每个页面创建确定性的 `kind=UI` JSON 资源,引用源 `ui-prototype` 和页面设计图,载入设计图尺寸与相对路径到 `ui_design_images`,并保存 State。 2. `recognize` 依次执行 Provider 多模态结构识别、现有多树合并器、最多每批 5 项的图片/图标组件绑定,并把已登记字体的安全元数据提供给绑定器;阶段分别持久化为 `structure-ready`、`merge-ready`、`binding-ready`,重复执行从最近真实阶段恢复。 3. `status` 只回读 State、页面阶段和 blockers,不推进项目 revision。 -4. `finalize` 只接受 `game/` 下的真实 UTF-8 文件,写入与 UI State revision 绑定的应用标记;所有页面通过应用门禁后才返回 `visual-binding` 最终阶段路由。缺少页面、资源、组件或应用标记时拒绝伪造完成。 +4. `finalize` 只接受 `game/` 下的真实 UTF-8 文件,写入与 UI State revision 绑定的应用标记;所有页面通过应用门禁后才返回 `asset-separation` 最终阶段路由。缺少页面、资源、组件或应用标记时拒绝伪造完成。 每次 State 或 manifest 阶段变化都推进项目 revision。Runtime 回执带有 `revisionAdvanceCount`,用于并发项目 revision 门禁;manifest 资产的 `source.generationKind` 依次记录: @@ -67,7 +67,7 @@ ui-workflow.completed - 没有关联时原子创建 `ui/UI 设计 N.json`,登记 `kind=UI`、`application/json`,并把原型图作为首张页面设计图载入 State。 - 成功后通过 `onManifestChange` 更新客户端资源投影,再打开 UI 编辑器;普通桥接从 `reference-analysis` 开始。 -点击已有 `UI` 资源直接打开 UI 编辑器。若 manifest 阶段为 `ui-workflow.completed`,工作台自动打开该资源的 `visual-binding` 阶段(最远步骤为 2),交给用户做最终检查和手动调整。 +点击已有 `UI` 资源直接打开 UI 编辑器。若 manifest 阶段为 `ui-workflow.completed`,工作台自动打开该资源的 `asset-separation` 阶段(最远步骤为 2),交给用户做最终检查和手动调整。 ## 诚实完成门禁