diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index 0149d953a..93977e9c3 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -111,6 +111,7 @@ const allowedUncalledTauriCommands = [ 'chat_with_game_creator_agent', 'check_ui_editor_font_glyph_coverage', 'create_ui_design_resource', + 'normalize_local_project_raster_resource', 'open_game_creator_launcher_window', 'open_game_creator_workspace_window', 'read_direct_project_conversation', diff --git a/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx b/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx deleted file mode 100644 index e77e7d7a7..000000000 --- a/apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx +++ /dev/null @@ -1,4518 +0,0 @@ -/* eslint-disable react-refresh/only-export-components -- The surface exports its host callback contracts and deterministic fixture helpers for integration tests. */ - -import './assetCanvasSurface.css'; - -import { - type CanvasHistoryAction, - type CanvasHistorySnapshot, - type CanvasLayer, - type CanvasViewport, - createMinimapModel, - fitViewportToLayers, - type ImageCanvasDraft, - type ImageCanvasDraftCanvas, - type ImageCanvasGenerationProgress, - type ImageCanvasGenerationProgressPhase, - type ImageCanvasGenerationRecord, - type ImageCanvasGenerationServiceIdentityConfirmation, - type ImageCanvasHostScope, - type ImageCanvasMediaRef, - moveViewportFromMinimapPointer, - moveViewportFromPan, - removeCanvasLayers, - resizeCanvasLayerBounds, - resolveLayerPointerSelection, - resolveQuickEditFocusViewport, - resolveViewportFromWheel, - scaleViewportFromScreenPoint, - transformCanvasLayers, -} from '@genarrative/image-canvas-core'; -import { - CanvasChromeButton, - CanvasPortal, - CanvasToolbar, - CanvasToolbarDivider, - CanvasToolbarGroup, - CanvasViewport as SharedCanvasViewport, - CanvasWorld, - LayerRenderer, - Minimap, - useCanvasHistory, - ZoomControls, -} from '@genarrative/image-canvas-react'; -import { - ArrowLeft, - Check, - CircleCheck, - CircleX, - Clock3, - ImagePlus, - ListChecks, - LoaderCircle, - LocateFixed, - Maximize2, - Minus, - Plus, - Redo2, - RotateCcw, - Save, - Sparkles, - Trash2, - Undo2, - Upload, - X, -} from 'lucide-react'; -import { - type ChangeEvent, - type CSSProperties, - type PointerEvent as ReactPointerEvent, - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from 'react'; - -import type { GameCreationAppManifest } from '../../../../../packages/shared/src/contracts/gameCreationApp'; -import { useWalletStore } from '../../stores/useWalletStore'; -import { isValidAssetCanvasName } from './assetCanvasNaming'; -import type { - LocalAssetCommittedEvent, - TauriImageCanvasHostAdapter, -} from './tauriImageCanvasHostAdapter'; - -export type AssetCanvasLifecycleState = - | { kind: 'canvas.creating' } - | { kind: 'canvas.editing'; dirty: boolean } - | { - kind: 'canvas.saving'; - stage: 'draft' | 'staging' | 'committing' | 'projecting'; - } - | { kind: 'canvas.recovering' } - | { - kind: 'canvas.generating'; - phase: ImageCanvasGenerationProgressPhase; - } - | { - kind: 'canvas.failed'; - operation: - | 'generation' - | 'draft-save' - | 'asset-commit' - | 'recovery' - | 'cancellation'; - code: string; - message: string; - reconciliationRequired: boolean; - }; - -export type AssetCanvasSaveAttempt = { - saveAttemptId: string; - sessionId: string; - projectId: string; - draftId: string; - commitId: string; -}; - -export type AssetCanvasExitResult = { - draftId: string; - disposition: 'kept' | 'discarded'; -}; - -export type AssetCanvasCommitNotification = { - source: 'command' | 'event'; - projectPath: string; - projectId: string; - draftId: string; - commitId: string; - assetId: string; - manifest: GameCreationAppManifest; - projectRevision: number; - committedProjectRevision: number; - eventId?: string; -}; - -export const ASSET_CANVAS_KIND_OPTIONS = [ - { value: 'game-art', label: '普通游戏美术' }, - { value: 'icon-spec', label: '统一视觉规范' }, - { value: 'ui-prototype', label: '游戏界面原型' }, - { value: 'art-spritesheet', label: '核心美术图集' }, -] as const; - -type RuntimeCanvasLayer = CanvasLayer & { mediaRef: ImageCanvasMediaRef }; - -type PendingGenerationIdentity = { - saveAttemptId: string; - intentId: string; - generationId: string; - idempotencyKey: string; - commitId: string; - commitIdempotencyKey: string; -}; - -const ASSET_CANVAS_WORLD_SIZE = 12_000; - -const GENERATION_ASPECT_RATIOS = ['1:1', '2:3', '3:2', '9:16', '16:9'] as const; - -type GenerationAspectRatio = (typeof GENERATION_ASPECT_RATIOS)[number]; - -function numericAspectRatio(aspectRatio: GenerationAspectRatio) { - const [width = 1, height = 1] = aspectRatio.split(':').map(Number); - return width / height; -} - -export function generationAspectRatioForOriginalImage( - width: number, - height: number, -): GenerationAspectRatio { - if ( - !Number.isFinite(width) || - !Number.isFinite(height) || - width <= 0 || - height <= 0 - ) { - return '1:1'; - } - const originalRatio = width / height; - return GENERATION_ASPECT_RATIOS.reduce( - (closest, candidate) => - Math.abs(Math.log(originalRatio / numericAspectRatio(candidate))) < - Math.abs(Math.log(originalRatio / numericAspectRatio(closest))) - ? candidate - : closest, - '1:1', - ); -} - -export function resolveQuickEditPanelPosition(input: { - anchorX: number; - belowTop: number; - aboveTop: number; - canvasSize: { width: number; height: number }; - panelSize: { width: number; height: number }; - edgePadding?: number; -}) { - const edgePadding = input.edgePadding ?? 12; - const panelWidth = Math.max(0, input.panelSize.width); - const panelHeight = Math.max(0, input.panelSize.height); - const minCenterX = edgePadding + panelWidth / 2; - const maxCenterX = Math.max( - minCenterX, - input.canvasSize.width - edgePadding - panelWidth / 2, - ); - const maxTop = Math.max( - edgePadding, - input.canvasSize.height - edgePadding - panelHeight, - ); - const top = - input.belowTop + panelHeight <= input.canvasSize.height - edgePadding - ? input.belowTop - : input.aboveTop; - return { - left: Math.min(Math.max(input.anchorX, minCenterX), maxCenterX), - top: Math.min(Math.max(top, edgePadding), maxTop), - }; -} - -type RuntimeGenerationTask = { - generationId: string; - sourceLayerId: string | null; - placeholder: { x: number; y: number; width: number; height: number } | null; - phase: ImageCanvasGenerationProgressPhase; - progress: number | null; - errorCode: string | null; - createdAt: number; - updatedAt: number; -}; - -type GenerationTaskVisualStatus = 'pending' | 'running' | 'failed' | 'done'; - -function generationTaskVisualStatus( - phase: ImageCanvasGenerationProgressPhase, -): GenerationTaskVisualStatus { - if (phase === 'failed') return 'failed'; - if ( - phase === 'candidate-ready' || - phase === 'asset-durable-committed' || - phase === 'manifest-projected' || - phase === 'layout-ready' || - phase === 'selected' - ) { - return 'done'; - } - if (phase === 'confirmation-required' || phase === 'generation-accepted') { - return 'pending'; - } - return 'running'; -} - -function generationTaskStatusLabel(status: GenerationTaskVisualStatus) { - if (status === 'pending') return '排队中'; - if (status === 'running') return '生成中'; - if (status === 'failed') return '失败'; - return '已完成'; -} - -function generationTaskStatusIcon(status: GenerationTaskVisualStatus) { - if (status === 'pending') return