import { type CSSProperties, type Dispatch, type ReactNode, type RefObject, type SetStateAction, } from 'react'; import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; import type { CanvasLayer, GenerateDialogState, QuickEditPanelState, UploadTarget, } from './ImageCanvasEditorTypes'; import { QUICK_EDIT_REFERENCE_LIMIT } from './ImageCanvasGenerationModel'; export type ImageCanvasQuickEditPanelViewProps = { panel: QuickEditPanelState; sourceLayer: CanvasLayer; style: CSSProperties; referenceButtonRef?: RefObject; isReferenceMenuOpen?: boolean; setIsReferenceMenuOpen?: Dispatch>; setIsPickingQuickEditReferenceFromCanvas?: Dispatch>; setQuickEditPanel: Dispatch>; renderEditorPortal?: (node: ReactNode) => ReactNode; buildPortalMenuStyle?: ( anchor: HTMLElement | null, placement: 'above' | 'below', ) => CSSProperties; onRequestUpload?: (target: UploadTarget) => void; onRememberImageModel?: (model: string) => void; onSubmit: () => void; }; function resetFailedPanelStatus< T extends { status: string; errorMessage?: string }, >(panel: T) { return { ...panel, status: panel.status === 'failed' ? 'idle' : panel.status, errorMessage: panel.status === 'failed' ? undefined : panel.errorMessage, }; } function createQuickEditDialog( panel: QuickEditPanelState, ): GenerateDialogState { const isQuickEdit = (panel.mode ?? 'quick-edit') === 'quick-edit'; return { mode: isQuickEdit ? 'quick-edit' : 'generate', prompt: panel.prompt, status: panel.status, errorMessage: panel.errorMessage, sourceLayerId: panel.sourceLayerId, generationReferences: panel.quickEditReferences, assetLabel: panel.assetLabel, imageModel: panel.model, aspectRatio: panel.aspectRatio, imageSize: panel.imageSize, }; } function syncPanelFromDialogUpdate( panel: QuickEditPanelState, dialog: GenerateDialogState, ) { const normalizedPanel = resetFailedPanelStatus(panel); const normalizedDialog = resetFailedPanelStatus(dialog); return { ...normalizedPanel, size: panel.size, prompt: dialog.prompt, aspectRatio: normalizedDialog.aspectRatio ?? normalizedPanel.aspectRatio ?? '1:1', imageSize: normalizedDialog.imageSize ?? normalizedPanel.imageSize ?? '1K', model: normalizedDialog.imageModel ?? normalizedPanel.model, quickEditReferences: normalizedDialog.generationReferences, assetLabel: normalizedDialog.assetLabel, status: normalizedDialog.status, errorMessage: normalizedDialog.errorMessage, }; } export function ImageCanvasQuickEditPanelView({ panel, sourceLayer, style, referenceButtonRef, isReferenceMenuOpen = false, setIsReferenceMenuOpen, setIsPickingQuickEditReferenceFromCanvas, setQuickEditPanel, renderEditorPortal = (node) => node, buildPortalMenuStyle = () => ({}), onRequestUpload, onRememberImageModel = () => {}, onSubmit, }: ImageCanvasQuickEditPanelViewProps) { const isRedraw = panel.mode === 'redraw'; const isImageQuickEdit = !isRedraw && sourceLayer.mediaType !== 'video'; const referenceCount = panel.quickEditReferences?.length ?? 0; const canAddReferences = !isRedraw && referenceCount < QUICK_EDIT_REFERENCE_LIMIT; const quickEditDialog = createQuickEditDialog(panel); return ( { setQuickEditPanel((currentPanel) => { if (!currentPanel) { return currentPanel; } const currentDialog = createQuickEditDialog(currentPanel); const nextDialog = typeof valueOrUpdater === 'function' ? valueOrUpdater(currentDialog) : valueOrUpdater; if (!nextDialog) { return currentPanel; } return syncPanelFromDialogUpdate(currentPanel, nextDialog); }); }} generationReferenceButtonRef={referenceButtonRef} isGenerationReferenceMenuOpen={isReferenceMenuOpen} setIsGenerationReferenceMenuOpen={setIsReferenceMenuOpen} setIsPickingGenerationReferenceFromCanvas={ setIsPickingQuickEditReferenceFromCanvas } onRequestUpload={onRequestUpload ?? (() => {})} onPickReferenceFromCanvas={() => setIsPickingQuickEditReferenceFromCanvas?.(true) } onToggleReferenceMenu={() => setIsReferenceMenuOpen?.((open) => !open)} onRememberImageModel={onRememberImageModel} onSubmit={onSubmit} dialogLabel={isRedraw ? '重绘图片' : '快速编辑图片'} includeDimensions={isRedraw || isImageQuickEdit} includeModel={true} includeReferences={false} promptLabel={isRedraw ? '重绘提示词' : '快速编辑提示词'} promptPlaceholder={ isRedraw ? '把画面重绘成什么样?' : '你希望素材如何修改?' } optionLabelPrefix={isRedraw ? '重绘' : '快速编辑'} dimensionRatioAriaLabelPrefix="比例" dimensionSizeAriaLabelPrefix="尺寸" referenceUploadTarget="quick-edit-reference" referenceButtonLabel={isRedraw ? '' : '添加快速编辑参考图'} referenceButtonAriaLabel={isRedraw ? undefined : '添加快速编辑参考图'} referenceMenuLabel={isRedraw ? '' : '快速编辑参考图来源'} referenceSlotTone="quick-edit" referenceSlotDisabled={!canAddReferences} showReferenceAddButton={false} referenceLabelFormatter={(_, index) => `图${index + 1}`} referenceAriaLabelFormatter={(_, index) => `图${index + 1}`} referenceRemoveLabelFormatter={(_, index) => `删除图${index + 1}`} formClassName="image-canvas-editor__quick-edit-panel image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--quick-edit" footerClassName="image-canvas-editor__generation-composer-footer image-canvas-editor__quick-edit-footer" promptClassName="image-canvas-editor__generation-prompt image-canvas-editor__quick-edit-prompt" referenceSlotClassName="image-canvas-editor__quick-edit-reference" submitButtonClassName="image-canvas-editor__generation-submit image-canvas-editor__quick-edit-submit" submitLabel={isRedraw ? '生成' : '修改'} submitAriaLabel={isRedraw ? '生成' : '修改'} submittingStatusLabel={isRedraw ? '生成中' : '修改中'} renderEditorPortal={renderEditorPortal} buildPortalMenuStyle={buildPortalMenuStyle} /> ); }