PluginProbe
Extendify / 2.2.0
Extendify v2.2.0
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 / GenerateImageConfirm.jsx

GenerateImageConfirm.jsx in Extendify 2.2.0, at src/Agent/workflows/block-selector/components/GenerateImageConfirm.jsx

233 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { registerCoreBlocks } from '@wordpress/block-library';
2 import { Spinner } from '@wordpress/components';
3 import { humanTimeDiff } from '@wordpress/date';
4 import { useEffect, useState } from '@wordpress/element';
5 import { __, sprintf } from '@wordpress/i18n';
6 import { generateImage } from '@shared/api/DataApi';
7 import { downloadImage } from '@shared/api/wp';
8 import { useImageGenerationStore } from '@shared/state/generate-images';
9 import { motion } from 'framer-motion';
10 import { ErrorMessage } from '@agent/components/ErrorMessage';
11 import { walkAndUpdateImageDetails } from '@agent/lib/blocks';
12
13 const openButton = __('Generate Image', 'extendify-local');
14 const preload = (src) =>
15 new Promise((resolve, reject) => {
16 const img = new Image();
17 img.onload = () => resolve(src);
18 img.onerror = reject;
19 img.src = src;
20 });
21
22 export const GenerateImageConfirm = ({ inputs, onConfirm, onCancel }) => {
23 const [generatedImage, setGeneratedImage] = useState(null);
24 const [generating, setGenerating] = useState(false);
25 const [error, setError] = useState(null);
26 const [refreshCheck, setRefreshCheck] = useState(0);
27 const [importing, setImporting] = useState(false);
28 const {
29 imageCredits,
30 updateImageCredits,
31 subtractOneCredit,
32 resetImageCredits,
33 } = useImageGenerationStore();
34 const noCredits = Number(imageCredits.remaining) === 0;
35
36 const handleConfirm = async () => {
37 if (importing) return;
38 if (!generatedImage) {
39 setGenerating(true);
40 subtractOneCredit();
41 try {
42 const { imageCredits, images } = await generateImage({
43 prompt: inputs.prompt,
44 });
45 updateImageCredits(imageCredits);
46 const url = images?.[0]?.url;
47 if (!url) throw new Error(__('No image returned', 'extendify-local'));
48 await preload(url);
49 setGeneratedImage(url);
50 setGenerating(false);
51 } catch (e) {
52 setError(
53 e?.message || __('An unknown error occurred.', 'extendify-local'),
54 );
55 if (e?.imageCredits) updateImageCredits(e.imageCredits);
56 setGenerating(false);
57 }
58 return;
59 }
60 setImporting(true);
61 const importedImage = await downloadImage(
62 null,
63 generatedImage,
64 'ai-generated',
65 );
66 await onConfirm({
67 data: {
68 previousContent: inputs.previousContent,
69 newContent: walkAndUpdateImageDetails(inputs, importedImage),
70 },
71 });
72 setTimeout(() => window.location.reload(), 1000);
73 };
74
75 useEffect(() => {
76 // rawHandler does not work on the frontend, so we need to register the
77 // core blocks again to get it working.
78 registerCoreBlocks();
79 }, []);
80
81 // Copied from Draft. Maybe not the best way to do this.
82 useEffect(() => {
83 const handle = () => {
84 setRefreshCheck((prev) => prev + 1);
85 if (!imageCredits.refresh) return;
86 if (new Date(Number(imageCredits.refresh)) > new Date()) return;
87 resetImageCredits();
88 };
89 if (refreshCheck === 0) handle(); // First run
90 const id = setTimeout(handle, 1000);
91 return () => clearTimeout(id);
92 }, [imageCredits, resetImageCredits, refreshCheck]);
93
94 useEffect(() => {
95 if (!error) return;
96 const timer = setTimeout(() => onCancel(), 5000);
97 return () => clearTimeout(timer);
98 }, [error, onCancel]);
99
100 if (error) {
101 return (
102 <ErrorMessage>
103 <div className="text-sm">
104 <div className="font-semibold">
105 {__('Error generating image', 'extendify-local')}
106 </div>
107 <div>{error}</div>
108 </div>
109 </ErrorMessage>
110 );
111 }
112
113 if (generating) {
114 return (
115 <Wrapper>
116 <Content>
117 <motion.div
118 initial={{ opacity: 1 }}
119 exit={{ opacity: 0 }}
120 className="flex aspect-square w-full items-center justify-center"
121 style={{
122 background:
123 'linear-gradient(135deg, #E8E8E8 47.92%, #F3F3F3 60.42%, #E8E8E8 72.92%)',
124 }}>
125 <Spinner style={{ height: '48px', width: '48px' }} />
126 </motion.div>
127 </Content>
128 </Wrapper>
129 );
130 }
131
132 if (generatedImage) {
133 return (
134 <Wrapper>
135 <Content>
136 <p className="m-0 p-0 text-sm text-gray-900">
137 {__('Image generated successfully!', 'extendify-local')}
138 </p>
139 <div className="my-2 flex justify-center">
140 <img
141 src={generatedImage}
142 alt={inputs.prompt}
143 style={{ maxWidth: '100%', height: 'auto' }}
144 />
145 </div>
146 </Content>
147 <div className="flex justify-start gap-2 p-3">
148 <button
149 type="button"
150 className="w-full rounded border border-gray-300 bg-white p-2 text-sm text-gray-700"
151 disabled={importing}
152 onClick={onCancel}>
153 {__('Cancel', 'extendify-local')}
154 </button>
155 <button
156 type="button"
157 className="w-full rounded border border-design-main bg-design-main p-2 text-sm text-white"
158 disabled={importing}
159 onClick={handleConfirm}>
160 {importing
161 ? __('Inserting Image...', 'extendify-local')
162 : __('Insert Image', 'extendify-local')}
163 </button>
164 </div>
165 </Wrapper>
166 );
167 }
168
169 return (
170 <Wrapper>
171 <Content>
172 <p className="m-0 p-0 text-sm text-gray-900">
173 {noCredits
174 ? sprintf(
175 __(
176 "You've used all your image credits for today. Your credits will reset in %s.",
177 'extendify-local',
178 ),
179 humanTimeDiff(new Date(Number(imageCredits.refresh))),
180 )
181 : sprintf(
182 __(
183 'The agent has asked to generate an image. Press "%s" to continue.',
184 'extendify-local',
185 ),
186 openButton,
187 )}
188 </p>
189 </Content>
190 <div className="flex justify-start gap-2 p-3">
191 <button
192 type="button"
193 className="w-full rounded border border-gray-300 bg-white p-2 text-sm text-gray-700"
194 onClick={onCancel}>
195 {__('Cancel', 'extendify-local')}
196 </button>
197 {noCredits ? null : (
198 <button
199 type="button"
200 className="w-full rounded border border-design-main bg-design-main p-2 text-sm text-white"
201 disabled={noCredits}
202 onClick={handleConfirm}>
203 {openButton}
204 </button>
205 )}
206 </div>
207 <div className="text-pretty px-4 pb-2 text-center text-xss leading-none text-gray-700">
208 {sprintf(
209 // translators: %1$s is the number of credits remaining, %2$s is the total credits
210 __(
211 'You have %1$s of %2$s daily image credits remaining.',
212 'extendify-local',
213 ),
214 imageCredits.remaining,
215 imageCredits.total,
216 )}
217 </div>
218 </Wrapper>
219 );
220 };
221
222 const Wrapper = ({ children }) => (
223 <div className="mb-4 ml-10 mr-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50 rtl:ml-2 rtl:mr-10">
224 {children}
225 </div>
226 );
227
228 const Content = ({ children }) => (
229 <div className="rounded-lg border-b border-gray-300 bg-white">
230 <div className="p-3">{children}</div>
231 </div>
232 );
233