import { ImageIcon } from 'lucide-react'; import { type CSSProperties, type Dispatch, type ReactNode, type RefObject, type SetStateAction, } from 'react'; import { CUSTOM_EDITOR_SCENE_STYLE_PRESET } from '../../../packages/shared/src/contracts/editorScene'; import { AutoGrowTextArea } from '../common/AutoGrowTextArea'; import { PlatformFloatingMenu, PlatformFloatingMenuItem, } from '../common/PlatformFloatingMenu'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import type { CharacterReferenceImage, GenerateDialogState, UploadTarget, } from './ImageCanvasEditorTypes'; import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView'; import { calculateEditorImageGenerationPrice } from './ImageCanvasGenerationModel'; import { EDITOR_SCENE_CONTENT_REQUIRED_ERROR, EDITOR_SCENE_CUSTOM_STYLE_REQUIRED_ERROR, } from './ImageCanvasGenerationSubmissionModel'; import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss'; const EDITOR_SCENE_CONTENT_ERROR_ID = 'editor-scene-content-error'; const EDITOR_SCENE_CUSTOM_STYLE_ERROR_ID = 'editor-scene-custom-style-error'; type ReferenceLabelFormatter = ( reference: CharacterReferenceImage, index: number, ) => string; type ReferenceTone = | 'default' | 'spec' | 'icon' | 'ui' | 'quick-edit' | 'video' | 'audio'; type ImageCanvasBasicGenerationComposerViewProps = { dialog: GenerateDialogState; style: CSSProperties; setGenerateDialog: Dispatch>; generationReferenceButtonRef?: RefObject; isGenerationReferenceMenuOpen?: boolean; setIsGenerationReferenceMenuOpen?: Dispatch>; setIsPickingGenerationReferenceFromCanvas?: Dispatch>; renderEditorPortal?: (node: ReactNode) => ReactNode; buildPortalMenuStyle?: ( anchor: HTMLElement | null, placement: 'above' | 'below', ) => CSSProperties; onRequestUpload: (target: UploadTarget) => void; onPickReferenceFromCanvas?: () => void; onToggleReferenceMenu?: () => void; onRememberImageModel?: (model: string) => void; hasPendingImageReferenceUploads?: boolean; onSubmit: (dialog: GenerateDialogState) => void; dialogLabel?: string; includeDimensions?: boolean; includeModel?: boolean; includeReferences?: boolean; promptLabel?: string; promptPlaceholder?: string; optionLabelPrefix?: string; dimensionRatioAriaLabelPrefix?: string; dimensionSizeAriaLabelPrefix?: string; referenceUploadTarget?: UploadTarget; referenceButtonIcon?: ReactNode; referenceButtonLabel?: string; referenceButtonAriaLabel?: string; referenceMenuLabel?: string; referenceSlotTone?: ReferenceTone; referenceSlotDisabled?: boolean; referenceLabelFormatter?: ReferenceLabelFormatter; referenceAriaLabelFormatter?: ReferenceLabelFormatter; referenceRemoveLabelFormatter?: ReferenceLabelFormatter; showReferenceAddButton?: boolean; panelClassName?: string; footerClassName?: string; promptClassName?: string; referenceSlotClassName?: string; submitButtonClassName?: string; submitLabel?: string; submitAriaLabel?: string; submittingStatusLabel?: string; styleControl?: ReactNode; }; function resetFailedDialogStatus(dialog: GenerateDialogState) { return { ...dialog, status: dialog.status === 'failed' ? 'idle' : dialog.status, errorMessage: dialog.status === 'failed' ? undefined : dialog.errorMessage, }; } export function ImageCanvasBasicGenerationComposerView({ dialog, style, setGenerateDialog, generationReferenceButtonRef, isGenerationReferenceMenuOpen = false, setIsGenerationReferenceMenuOpen, setIsPickingGenerationReferenceFromCanvas, renderEditorPortal = (node) => node, buildPortalMenuStyle = () => ({}), onRequestUpload, onPickReferenceFromCanvas, onToggleReferenceMenu, onRememberImageModel = () => {}, hasPendingImageReferenceUploads = false, onSubmit, dialogLabel, includeDimensions = true, includeModel = true, includeReferences = true, promptLabel, promptPlaceholder, optionLabelPrefix, dimensionRatioAriaLabelPrefix, dimensionSizeAriaLabelPrefix, referenceUploadTarget = 'generation-reference', referenceButtonIcon =