import { type CSSProperties, type Dispatch, type ReactNode, type RefObject, type SetStateAction, } from 'react'; import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; import type { GenerateDialogState, QuickEditPanelState, UploadTarget, } from './ImageCanvasEditorTypes'; import { QUICK_EDIT_REFERENCE_LIMIT, resolveExtraImageReferenceLimit, } from './ImageCanvasGenerationModel'; export type ImageCanvasQuickEditPanelViewProps = { panel: QuickEditPanelState; 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; hasPendingImageReferenceUploads?: boolean; 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 { return { mode: 'quick-edit', 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, // 中文注释:dialog 由 createQuickEditDialog 从面板自身派生,组合器只会做 // failed→idle 的重置,不会写入 'pending-confirmation' 这类完美像素专属状态。 // 因此归一化后的 dialog 状态恒等于归一化后的面板状态,直接取面板侧即可, // 也让面板的三态联合类型不必在这里做运行时收窄。 status: normalizedPanel.status, errorMessage: normalizedDialog.errorMessage, }; } export function ImageCanvasQuickEditPanelView({ panel, style, referenceButtonRef, isReferenceMenuOpen = false, setIsReferenceMenuOpen, setIsPickingQuickEditReferenceFromCanvas, setQuickEditPanel, renderEditorPortal = (node) => node, buildPortalMenuStyle = () => ({}), onRequestUpload, onRememberImageModel = () => {}, hasPendingImageReferenceUploads = false, onSubmit, }: ImageCanvasQuickEditPanelViewProps) { const referenceCount = panel.quickEditReferences?.length ?? 0; const canAddReferences = referenceCount < resolveExtraImageReferenceLimit(panel.model, QUICK_EDIT_REFERENCE_LIMIT, 1); 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} hasPendingImageReferenceUploads={hasPendingImageReferenceUploads} onSubmit={onSubmit} dialogLabel="快速编辑图片" includeDimensions={true} includeModel={true} includeReferences={false} promptLabel="快速编辑提示词" promptPlaceholder="你希望素材如何修改?" optionLabelPrefix="快速编辑" dimensionRatioAriaLabelPrefix="比例" dimensionSizeAriaLabelPrefix="尺寸" referenceUploadTarget="quick-edit-reference" referenceButtonLabel="添加快速编辑参考图" referenceButtonAriaLabel="添加快速编辑参考图" referenceMenuLabel="快速编辑参考图来源" referenceSlotTone="quick-edit" referenceSlotDisabled={!canAddReferences} showReferenceAddButton={false} referenceLabelFormatter={(_, index) => `图${index + 1}`} referenceAriaLabelFormatter={(_, index) => `图${index + 1}`} referenceRemoveLabelFormatter={(_, index) => `删除图${index + 1}`} panelClassName="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="修改" submitAriaLabel="修改" submittingStatusLabel="修改中" renderEditorPortal={renderEditorPortal} buildPortalMenuStyle={buildPortalMenuStyle} /> ); }