| 1 |
import { |
| 2 |
ImagePicker, |
| 3 |
PickerActions, |
| 4 |
PickerButton, |
| 5 |
} from '@agent/components/ImagePicker'; |
| 6 |
import { targetFor } from '@agent/hooks/useImageAcquisition'; |
| 7 |
import { useChatStore } from '@agent/state/chat'; |
| 8 |
import { useCallback, useEffect, useRef } from '@wordpress/element'; |
| 9 |
import { __ } from '@wordpress/i18n'; |
| 10 |
|
| 11 |
// The block's own image — never one belonging to a nested tagged block. |
| 12 |
const findBlockImage = (blockId) => { |
| 13 |
const scope = document.querySelector( |
| 14 |
`[data-extendify-agent-block-id="${blockId}"]`, |
| 15 |
); |
| 16 |
if (!scope) return null; |
| 17 |
return ( |
| 18 |
[...scope.querySelectorAll('img')].find( |
| 19 |
(img) => img.closest('[data-extendify-agent-block-id]') === scope, |
| 20 |
) ?? null |
| 21 |
); |
| 22 |
}; |
| 23 |
|
| 24 |
export const ReplaceImageConfirm = ({ inputs, onConfirm, onCancel }) => { |
| 25 |
// The backend caps a patch at one image; a second would have no picker. |
| 26 |
const operation = ( |
| 27 |
Array.isArray(inputs.operations) ? inputs.operations : [] |
| 28 |
).find(({ op }) => op === 'replace-image'); |
| 29 |
const blockId = operation?.blockId; |
| 30 |
const original = useRef(null); |
| 31 |
const confirmed = useRef(false); |
| 32 |
const addMessage = useChatStore((state) => state.addMessage); |
| 33 |
const updateMessage = useChatStore((state) => state.updateMessage); |
| 34 |
|
| 35 |
const restoreOriginal = useCallback(() => { |
| 36 |
const saved = original.current; |
| 37 |
if (!saved) return; |
| 38 |
saved.el.src = saved.src; |
| 39 |
if (saved.srcset) saved.el.setAttribute('srcset', saved.srcset); |
| 40 |
else saved.el.removeAttribute('srcset'); |
| 41 |
}, []); |
| 42 |
|
| 43 |
const previewOnPage = useCallback( |
| 44 |
(url) => { |
| 45 |
const img = findBlockImage(blockId); |
| 46 |
if (!img) return; |
| 47 |
original.current ??= { |
| 48 |
el: img, |
| 49 |
src: img.getAttribute('src') ?? '', |
| 50 |
srcset: img.getAttribute('srcset') ?? '', |
| 51 |
}; |
| 52 |
if (!url) { |
| 53 |
restoreOriginal(); |
| 54 |
return; |
| 55 |
} |
| 56 |
img.removeAttribute('srcset'); |
| 57 |
img.src = url; |
| 58 |
}, |
| 59 |
[blockId, restoreOriginal], |
| 60 |
); |
| 61 |
|
| 62 |
// Nothing on the page means there is nothing to replace. |
| 63 |
const receipt = useRef(null); |
| 64 |
useEffect(() => { |
| 65 |
if (!findBlockImage(blockId)) return onCancel(); |
| 66 |
receipt.current ??= addMessage('image', {}); |
| 67 |
}, [blockId, onCancel, addMessage]); |
| 68 |
|
| 69 |
useEffect(() => { |
| 70 |
return () => { |
| 71 |
if (confirmed.current) return; |
| 72 |
restoreOriginal(); |
| 73 |
}; |
| 74 |
}, [restoreOriginal]); |
| 75 |
|
| 76 |
if (!operation) return null; |
| 77 |
|
| 78 |
return ( |
| 79 |
<ImagePicker |
| 80 |
prompt={operation.prompt ?? ''} |
| 81 |
search={operation.prompt ?? ''} |
| 82 |
tab={operation.source === 'generate' ? 'generate' : 'media'} |
| 83 |
autoGenerate={operation.source === 'generate'} |
| 84 |
target={() => targetFor(findBlockImage(blockId))} |
| 85 |
onSelect={previewOnPage} |
| 86 |
onSubmit={async (image) => { |
| 87 |
confirmed.current = true; |
| 88 |
const url = image?.source_url || image?.url; |
| 89 |
if (url) updateMessage(receipt.current, { url }); |
| 90 |
// The full attachment is ~800 tokens replayed every turn. |
| 91 |
// swapBlockImage builds the block's alt from alt_text. |
| 92 |
const picked = image && { |
| 93 |
id: image.id, |
| 94 |
url, |
| 95 |
alt_text: image.alt_text ?? '', |
| 96 |
}; |
| 97 |
await onConfirm({ |
| 98 |
// No image resolved — the tool reports it as no-change. |
| 99 |
data: { |
| 100 |
...inputs, |
| 101 |
operations: [picked ? { ...operation, image: picked } : operation], |
| 102 |
}, |
| 103 |
shouldRefreshPage: true, |
| 104 |
}); |
| 105 |
}} |
| 106 |
footer={({ ready, busy, submit }) => ( |
| 107 |
<PickerActions> |
| 108 |
<PickerButton disabled={busy} onClick={onCancel}> |
| 109 |
{__('Cancel', 'extendify-local')} |
| 110 |
</PickerButton> |
| 111 |
<PickerButton primary disabled={!ready || busy} onClick={submit}> |
| 112 |
{busy |
| 113 |
? __('Saving...', 'extendify-local') |
| 114 |
: __('Save', 'extendify-local')} |
| 115 |
</PickerButton> |
| 116 |
</PickerActions> |
| 117 |
)} |
| 118 |
/> |
| 119 |
); |
| 120 |
}; |
| 121 |
|