import { ClipboardList, Cpu, ImagePlus } from 'lucide-react'; import { type CSSProperties, type Dispatch, type ReactNode, type RefObject, type SetStateAction, } from 'react'; import { AutoGrowTextArea } from '../common/AutoGrowTextArea'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; import { PlatformFloatingMenu, PlatformFloatingMenuItem, } from '../common/PlatformFloatingMenu'; import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformSelectField, PlatformTextField, } from '../common/PlatformTextField'; import type { GenerateDialogState, SpecFormValues, SpecGenerationType, UploadTarget, } from './ImageCanvasEditorTypes'; import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView'; import { calculateEditorSpecGenerationPrice, calculateEditorUiDesignPrice, CHARACTER_SPEC_VIEW_OPTIONS, getEditorImageModelDisplayName, IMAGE_MODEL_GPT_IMAGE_2, resolveEditorImageSizeLabel, SPEC_GENERATION_ASPECT_RATIO, SPEC_GENERATION_IMAGE_SIZE, SPEC_GENERATION_MODEL, } from './ImageCanvasGenerationModel'; import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss'; type ImageCanvasSpecGenerationPanelViewProps = { dialog: GenerateDialogState; style: CSSProperties; isGenerationReferenceMenuOpen?: boolean; generationReferenceButtonRef?: RefObject; setIsGenerationReferenceMenuOpen?: Dispatch>; setIsPickingGenerationReferenceFromCanvas?: Dispatch>; setGenerateDialog?: Dispatch>; renderEditorPortal?: (node: ReactNode) => ReactNode; buildPortalMenuStyle?: ( anchor: HTMLElement | null, placement: 'above' | 'below', ) => CSSProperties; onOpenSpecDialog?: (specType: SpecGenerationType) => void; onUpdateSpecFormValue: (key: keyof SpecFormValues, value: string) => void; onRequestUpload: (target: UploadTarget) => void; onRememberImageModel?: (model: string) => void; hasPendingImageReferenceUploads?: boolean; onSubmit: (dialog: GenerateDialogState) => void; }; function shouldShowSpecInputPlaceholders(dialog: GenerateDialogState) { return dialog.specType === 'character' || dialog.specType === 'ui'; } export function ImageCanvasSpecGenerationPanelView({ dialog, style, isGenerationReferenceMenuOpen = false, generationReferenceButtonRef, setIsGenerationReferenceMenuOpen, setIsPickingGenerationReferenceFromCanvas, setGenerateDialog, renderEditorPortal = (node) => node, buildPortalMenuStyle = () => ({}), onOpenSpecDialog, onUpdateSpecFormValue, onRequestUpload, onRememberImageModel = () => {}, hasPendingImageReferenceUploads = false, onSubmit, }: ImageCanvasSpecGenerationPanelViewProps) { const isUiDesignDialog = dialog.mode === 'ui-design'; const showSpecInputPlaceholders = shouldShowSpecInputPlaceholders(dialog); const specSizeLabel = resolveEditorImageSizeLabel({ aspectRatio: SPEC_GENERATION_ASPECT_RATIO, imageSize: SPEC_GENERATION_IMAGE_SIZE, }); const specModelLabel = getEditorImageModelDisplayName(SPEC_GENERATION_MODEL); const referenceLabel = isUiDesignDialog ? 'UI设计图标规范' : '参考图'; const referenceSlotLabel = isUiDesignDialog ? '图标规范' : dialog.specType === 'character' ? '角色规范' : '参考图'; const uploadTarget: UploadTarget = isUiDesignDialog ? 'ui-design-icon-spec' : 'spec-reference'; const reference = isUiDesignDialog ? dialog.uiDesignSpecReference : dialog.specReference; const isGenerating = dialog.status === 'generating'; const specGenerationCost = calculateEditorSpecGenerationPrice( SPEC_GENERATION_MODEL, ); useImageCanvasFloatingOptionDismiss({ isOpen: isGenerationReferenceMenuOpen, boundaryRefs: [generationReferenceButtonRef], onDismiss: () => setIsGenerationReferenceMenuOpen?.(false), }); const openReferenceMenu = () => { if (setIsGenerationReferenceMenuOpen) { setIsGenerationReferenceMenuOpen((open) => !open); return; } onRequestUpload(uploadTarget); }; return ( <>
event.stopPropagation()} onSubmit={(event) => { event.preventDefault(); if (dialog.status !== 'generating') { onSubmit(dialog); } }} >
{isUiDesignDialog || dialog.specType ? (
{isUiDesignDialog ? ( ) : dialog.specType === 'custom' ? ( ) : ( <> {dialog.specType === 'character' ? ( <> ) : null} )}
{dialog.status === 'failed' ? ( {dialog.errorMessage} ) : null}
{isUiDesignDialog ? ( undefined)} includeDimensions onRememberImageModel={onRememberImageModel} hasPendingImageReferenceUploads={hasPendingImageReferenceUploads} lockedModel={IMAGE_MODEL_GPT_IMAGE_2} cost={calculateEditorUiDesignPrice( IMAGE_MODEL_GPT_IMAGE_2, dialog.imageSize, )} submitLabel="生成" submitAriaLabel="生成UI设计图" optionLabelPrefix="生成图片" renderEditorPortal={renderEditorPortal} buildPortalMenuStyle={buildPortalMenuStyle} /> ) : ( <>
{specSizeLabel}
{specModelLabel}
{isGenerating ? ( '生成中' ) : ( <> 生成 {specGenerationCost}泥点 )} )}
{isGenerationReferenceMenuOpen && generationReferenceButtonRef ? renderEditorPortal( { setIsGenerationReferenceMenuOpen?.(false); setIsPickingGenerationReferenceFromCanvas?.(true); }} > 从画布中选择 {isUiDesignDialog ? ( { setIsGenerationReferenceMenuOpen?.(false); onOpenSpecDialog?.('icon'); }} > 新建图标规范 ) : null} { setIsGenerationReferenceMenuOpen?.(false); setIsPickingGenerationReferenceFromCanvas?.(false); onRequestUpload(uploadTarget); }} > 上传图片 , ) : null} ); }