import { Check, ChevronDown, Cpu, Film, ImageIcon, Music } from 'lucide-react'; import { type CSSProperties, type Dispatch, type ReactNode, type RefObject, type SetStateAction, useCallback, useRef, useState, } from 'react'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformFloatingMenu, PlatformFloatingMenuItem, } from '../common/PlatformFloatingMenu'; import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformTextField } from '../common/PlatformTextField'; import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; import { ImageCanvasCharacterAnimationPanelView } from './ImageCanvasCharacterAnimationPanelView'; import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView'; import { ImageCanvasCropExpandPanelView } from './ImageCanvasCropExpandPanelView'; import { ImageCanvasEditGenerationModalView } from './ImageCanvasEditGenerationModalView'; import { ImageCanvasEditorPortal } from './ImageCanvasEditorPortal'; import type { CanvasLayer, CharacterAnimationPanelState, CropExpandPanelState, GenerateDialogState, QuickEditPanelState, SpecFormValues, SpecGenerationType, UploadTarget, } from './ImageCanvasEditorTypes'; import { calculateEditorBackgroundMusicPrice, calculateEditorSoundEffectPrice, calculateEditorVideoPrice, DEFAULT_BACKGROUND_MUSIC_MODEL, DEFAULT_SOUND_EFFECT_DURATION_SECONDS, DEFAULT_SOUND_EFFECT_MODEL, DEFAULT_VIDEO_ASPECT_RATIO, DEFAULT_VIDEO_DURATION_SECONDS, DEFAULT_VIDEO_SOUND, EDITOR_SOUND_EFFECT_MODEL_OPTIONS, EDITOR_VIDEO_ASPECT_RATIO_OPTIONS, EDITOR_VIDEO_MODEL_OPTIONS, EDITOR_VIDEO_RESOLUTION_OPTIONS, resizeGenerationPlaceholderToVideoSelection, SPEC_TYPE_LABEL, VIDEO_MODEL_SEEDANCE_2_FAST, } from './ImageCanvasGenerationModel'; import { ImageCanvasIconSpritesheetComposerView } from './ImageCanvasIconSpritesheetComposerView'; import { ImageCanvasPublicationMaterialsDemoPanelView } from './ImageCanvasPublicationMaterialsDemoPanelView'; import { getPublicationMaterialsWorkflow } from './ImageCanvasPublicationMaterialsModel'; import { ImageCanvasQuickEditPanelView } from './ImageCanvasQuickEditPanelView'; import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { ImageCanvasSpecGenerationPanelView } from './ImageCanvasSpecGenerationPanelView'; import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss'; type ImageCanvasGenerationComposerViewProps = { specToolWrapRef: RefObject; publicationReferenceButtonRef: RefObject; characterSpecButtonRef: RefObject; characterReferenceButtonRef: RefObject; generationReferenceButtonRef: RefObject; iconSpecButtonRef: RefObject; isSpecMenuOpen: boolean; isGenerationReferenceMenuOpen: boolean; isPublicationReferenceMenuOpen: boolean; isCharacterSpecMenuOpen: boolean; isCharacterReferenceMenuOpen: boolean; isIconSpecMenuOpen: boolean; isUiDesignSpecMenuOpen: boolean; isPickingGenerationReferenceFromCanvas: boolean; isPickingQuickEditReferenceFromCanvas: boolean; isPickingPublicationReferenceFromCanvas: boolean; isPickingCharacterSpecFromCanvas: boolean; isPickingCharacterReferenceFromCanvas: boolean; isPickingIconSpecFromCanvas: boolean; isPickingUiDesignSpecFromCanvas: boolean; generateDialog: GenerateDialogState | null; generationComposerStyle: CSSProperties | null; iconComposerStyle: CSSProperties | null; quickEditPanel: QuickEditPanelState | null; quickEditSourceLayer: CanvasLayer | null; quickEditPanelStyle: CSSProperties | null; cropExpandPanel: CropExpandPanelState | null; cropExpandSourceLayer: CanvasLayer | null; cropExpandPanelStyle: CSSProperties | null; characterAnimationPanel: CharacterAnimationPanelState | null; characterAnimationSourceLayer: CanvasLayer | null; characterAnimationPanelStyle: CSSProperties | null; characterAnimationPrice: number; setGenerateDialog: Dispatch>; setQuickEditPanel: Dispatch>; setCropExpandPanel: Dispatch>; setCharacterAnimationPanel: Dispatch< SetStateAction >; setIsGenerationReferenceMenuOpen: Dispatch>; setIsPublicationReferenceMenuOpen: Dispatch>; setIsCharacterSpecMenuOpen: Dispatch>; setIsCharacterReferenceMenuOpen: Dispatch>; setIsIconSpecMenuOpen: Dispatch>; setIsUiDesignSpecMenuOpen: Dispatch>; setIsPickingGenerationReferenceFromCanvas: Dispatch>; setIsPickingQuickEditReferenceFromCanvas: Dispatch>; setIsPickingPublicationReferenceFromCanvas: Dispatch< SetStateAction >; setIsPickingCharacterSpecFromCanvas: Dispatch>; setIsPickingCharacterReferenceFromCanvas: Dispatch>; setIsPickingIconSpecFromCanvas: Dispatch>; setIsPickingUiDesignSpecFromCanvas: Dispatch>; onOpenSpecDialog: (specType: SpecGenerationType) => void; onRequestUpload: (target: UploadTarget) => void; onSubmitImageGeneration: (dialog: GenerateDialogState) => void; onSubmitIconSpritesheetGeneration: (dialog: GenerateDialogState) => void; onSubmitQuickEdit: () => void; onSubmitCropExpand: () => void; onSubmitCharacterAnimation: () => void; onUpdateSpecFormValue: (key: keyof SpecFormValues, value: string) => void; onUpdateIconDescriptionText: (value: string) => void; onUpdateCharacterAnimationDuration: (frameCountValue: string) => void; onRememberImageModel: (model: string) => void; onSpecMenuPointerEnter?: () => void; onSpecMenuPointerLeave?: () => void; }; function buildPortalMenuStyle( anchor: HTMLElement | null, placement: 'above' | 'below', ): CSSProperties { const rect = anchor?.getBoundingClientRect(); if (!rect) { return { position: 'fixed', left: 0, top: 0, right: 'auto', bottom: 'auto', zIndex: 70, }; } return { position: 'fixed', left: Math.round(rect.left), top: placement === 'above' ? Math.round(rect.top) : Math.round(rect.bottom + 8), right: 'auto', bottom: 'auto', zIndex: 70, transform: placement === 'above' ? 'translateY(calc(-100% - 0.45rem))' : undefined, }; } function renderEditorPortal(node: ReactNode) { return {node}; } function resetFailedVideoDialogStatus(dialog: GenerateDialogState) { return { ...dialog, status: dialog.status === 'failed' ? 'idle' : dialog.status, errorMessage: dialog.status === 'failed' ? undefined : dialog.errorMessage, }; } function isSeedanceVideoModel(model: string | undefined) { return model === 'seedance2.0' || model === 'seedance2.0-fast'; } function VideoReferenceChip({ reference, index, onRemove, }: { reference: { id: string; label: string; src: string; mediaType?: string; objectKey?: string | null; }; index: number; onRemove: () => void; }) { const mediaType = reference.mediaType ?? 'image'; const labelPrefix = mediaType === 'video' ? '参考视频' : mediaType === 'audio' ? '参考音频' : '参考图'; const label = reference.label || `${labelPrefix}${index + 1}`; const icon = mediaType === 'video' ? (