PluginProbe
Extendify / 3.1.6
Extendify v3.1.6
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Agent / workflows / block-selector / components / ReplaceImageConfirm.jsx

ReplaceImageConfirm.jsx in Extendify 3.1.6, at src/Agent/workflows/block-selector/components/ReplaceImageConfirm.jsx

121 lines 3.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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