diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index 69104cdbf..677acef17 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -994,6 +994,17 @@ const getLocalEditRegionsFromMetadata = ( metadata: Record | undefined ) => normalizeLocalEditRegions(metadata?.[LOCAL_EDIT_METADATA_KEY]); +const createLocalEditPromptDraftMap = ( + regions: LocalEditRegion[], + draftSource?: Record +): Record => + Object.fromEntries( + regions.map((region) => [ + region.id, + draftSource?.[region.id] ?? region.prompt, + ]) + ); + const getNormalizedRectOverlayStyle = ( rect: NormalizedCropRect, metrics: ImageDisplayMetrics @@ -1877,6 +1888,9 @@ export function ImageNode({ const [localEditRegions, setLocalEditRegions] = useState( () => getLocalEditRegionsFromMetadata(data.metadata) ); + const [localEditPromptDrafts, setLocalEditPromptDrafts] = useState< + Record + >({}); const [isLocalEditDrawing, setIsLocalEditDrawing] = useState(false); const [localEditDragStart, setLocalEditDragStart] = useState(null); @@ -2547,11 +2561,14 @@ export function ImageNode({ const nextRegions = reindexLocalEditRegions( regions.slice(0, LOCAL_EDIT_MAX_REGIONS) ); + const nextDrafts = createLocalEditPromptDraftMap( + nextRegions, + localEditPromptDraftRefs.current + ); localEditRegionsRef.current = nextRegions; setLocalEditRegions(nextRegions); - localEditPromptDraftRefs.current = Object.fromEntries( - nextRegions.map((region) => [region.id, region.prompt]) - ); + localEditPromptDraftRefs.current = nextDrafts; + setLocalEditPromptDrafts(nextDrafts); const nextMetadata = data.metadata && typeof data.metadata === "object" @@ -2574,6 +2591,21 @@ export function ImageNode({ [data, node.id, updateNode] ); + const updateLocalEditPromptDraft = useCallback( + (regionId: string, prompt: string) => { + localEditPromptDraftRefs.current[regionId] = prompt; + setLocalEditPromptDrafts((current) => + current[regionId] === prompt + ? current + : { + ...current, + [regionId]: prompt, + } + ); + }, + [] + ); + const refreshBoxCutoutMetrics = useCallback(() => { const imageElement = displayImageRef.current; if (!imageElement) { @@ -2718,7 +2750,11 @@ export function ImageNode({ useEffect(() => { if (localEditMode) return; - setLocalEditRegions(getLocalEditRegionsFromMetadata(data.metadata)); + const nextRegions = getLocalEditRegionsFromMetadata(data.metadata); + const nextDrafts = createLocalEditPromptDraftMap(nextRegions); + localEditPromptDraftRefs.current = nextDrafts; + setLocalEditPromptDrafts(nextDrafts); + setLocalEditRegions(nextRegions); }, [data.metadata, data.src, localEditMode]); useEffect(() => { @@ -3649,19 +3685,25 @@ export function ImageNode({ const persistLocalEditRegionPrompt = useCallback( (regionId: string, prompt: string) => { - localEditPromptDraftRefs.current[regionId] = prompt; + updateLocalEditPromptDraft(regionId, prompt); commitLocalEditRegions( - localEditRegions.map((region) => + localEditRegionsRef.current.map((region) => region.id === regionId ? { ...region, prompt } : region ) ); }, - [commitLocalEditRegions, localEditRegions] + [commitLocalEditRegions, updateLocalEditPromptDraft] ); const deleteLocalEditRegion = useCallback( (regionId: string) => { delete localEditPromptDraftRefs.current[regionId]; + setLocalEditPromptDrafts((current) => { + if (!(regionId in current)) return current; + const next = { ...current }; + delete next[regionId]; + return next; + }); delete localEditPromptComposingRefs.current[regionId]; commitLocalEditRegions( localEditRegionsRef.current.filter((region) => region.id !== regionId) @@ -3764,6 +3806,7 @@ export function ImageNode({ const clearLocalEditRegions = useCallback(() => { localEditPromptDraftRefs.current = {}; + setLocalEditPromptDrafts({}); commitLocalEditRegions([]); setEditingLocalEditRegionId(null); }, [commitLocalEditRegions]); @@ -7996,45 +8039,31 @@ export function ImageNode({ className="nodrag nopan nowheel h-9 min-w-0 flex-1 resize-none overflow-hidden rounded-full border border-slate-300 bg-white/95 px-3 py-2 text-[12px] font-medium leading-5 text-slate-950 caret-slate-950 shadow-[0_10px_24px_rgba(15,23,42,0.14)] outline-none ring-2 ring-transparent transition placeholder:text-slate-400 focus:border-slate-950 focus:ring-slate-950/15 dark:border-white/20 dark:bg-slate-950/88 dark:text-white dark:caret-white dark:placeholder:text-white/42 dark:focus:border-white/40 dark:focus:ring-white/18" data-canvas-no-drag="true" data-local-edit-control="true" - defaultValue={ + autoComplete="off" + enterKeyHint="done" + inputMode="text" + lang="zh-CN" + value={ + localEditPromptDrafts[region.id] ?? localEditPromptDraftRefs.current[region.id] ?? region.prompt } - autoComplete="off" - enterKeyHint="done" - lang="zh-CN" onBlur={(event) => { const nextPrompt = event.currentTarget.value; - localEditPromptDraftRefs.current[region.id] = - nextPrompt; + updateLocalEditPromptDraft(region.id, nextPrompt); persistLocalEditRegionPrompt(region.id, nextPrompt); - if (isLocalEditControlTarget(event.relatedTarget)) { - return; - } - window.setTimeout(() => { - if ( - isLocalEditControlTarget(document.activeElement) - ) { - return; - } - setEditingLocalEditRegionId((current) => { - if (current === region.id) { - focusLocalEditPromptInput(region.id); - } - return current; - }); - }, 80); }} onClick={(event) => { event.stopPropagation(); - event.currentTarget.focus({ preventScroll: true }); }} onCompositionEnd={(event) => { stopLocalEditPromptTextEvent(event); localEditPromptComposingRefs.current[region.id] = false; - localEditPromptDraftRefs.current[region.id] = - event.currentTarget.value; + updateLocalEditPromptDraft( + region.id, + event.currentTarget.value + ); }} onCompositionStart={(event) => { stopLocalEditPromptTextEvent(event); @@ -8052,13 +8081,17 @@ export function ImageNode({ }} onInput={(event) => { stopLocalEditPromptTextEvent(event); - localEditPromptDraftRefs.current[region.id] = - event.currentTarget.value; + updateLocalEditPromptDraft( + region.id, + event.currentTarget.value + ); }} onChange={(event) => { stopLocalEditPromptTextEvent(event); - localEditPromptDraftRefs.current[region.id] = - event.currentTarget.value; + updateLocalEditPromptDraft( + region.id, + event.currentTarget.value + ); }} onKeyDown={(event) => { stopLocalEditPromptTextEvent(event); @@ -8095,17 +8128,9 @@ export function ImageNode({ onMouseDown={(event) => { event.stopPropagation(); }} - onMouseDownCapture={(event) => { - event.stopPropagation(); - event.currentTarget.focus({ preventScroll: true }); - }} onPointerDown={(event) => { event.stopPropagation(); }} - onPointerDownCapture={(event) => { - event.stopPropagation(); - event.currentTarget.focus({ preventScroll: true }); - }} placeholder={`区域 ${region.index} 提示词`} ref={(input) => { localEditPromptInputRefs.current[region.id] = input;