← All changes
|
src/QuickEdit/components/modals/AiImagePickerModal.jsx
+34
-17
3.1.3
→
3.2.2
View file →
| @@ -1,10 +1,13 @@ | ||
| 1 | 1 | import { generateImage } from '@shared/api/DataApi'; |
| 2 | -import { importImage, importImageServer } from '@shared/api/wp'; | |
| 2 | +import { downloadImage } from '@shared/api/wp'; | |
| 3 | +import { useImageCreditsSync } from '@shared/hooks/useImageCreditsSync'; | |
| 4 | +import { useStampedPreview } from '@shared/hooks/useStampedPreview'; | |
| 3 | 5 | import { track } from '@shared/lib/track'; |
| 4 | 6 | import { useImageGenerationStore } from '@shared/state/generate-images'; |
| 5 | 7 | import { |
| 6 | 8 | Button, |
| 9 | + CheckboxControl, | |
| 7 | 10 | Modal, |
| 8 | 11 | Notice, |
| 9 | 12 | Spinner, |
| 10 | 13 | TextareaControl, |
| @@ -47,14 +50,18 @@ | ||
| 47 | 50 | subtractOneCredit, |
| 48 | 51 | aiImageOptions, |
| 49 | 52 | setAiImageOption, |
| 50 | 53 | } = useImageGenerationStore(); |
| 54 | + const [disclose, setDisclose] = useState(false); | |
| 51 | 55 | const [generating, setGenerating] = useState(false); |
| 52 | 56 | const [applying, setApplying] = useState(false); |
| 53 | 57 | const [error, setError] = useState(''); |
| 54 | 58 | const [preview, setPreview] = useState(null); // { src, id } |
| 55 | 59 | const abortRef = useRef(null); |
| 60 | + const previewSrc = useStampedPreview(preview?.src, disclose); | |
| 56 | 61 | |
| 62 | + useImageCreditsSync(); | |
| 63 | + | |
| 57 | 64 | const noCredits = imageCredits.remaining === 0; |
| 58 | 65 | const usedCredits = imageCredits.total - imageCredits.remaining; |
| 59 | 66 | |
| 60 | 67 | const onGenerate = async (e) => { |
| @@ -70,9 +77,13 @@ | ||
| 70 | 77 | images, |
| 71 | 78 | id: gid, |
| 72 | 79 | } = await generateImage(aiImageOptions, abortRef.current.signal); |
| 73 | 80 | updateImageCredits(newCredits); |
| 74 | - setPreview({ src: images[0].url, id: gid }); | |
| 81 | + setPreview({ | |
| 82 | + src: images[0].url, | |
| 83 | + id: gid, | |
| 84 | + alt: images[0].alt ?? aiImageOptions.prompt, | |
| 85 | + }); | |
| 75 | 86 | track('ai_image_generated', { size: aiImageOptions.size }); |
| 76 | 87 | } catch (err) { |
| 77 | 88 | if (err?.code === 20) return; // aborted |
| 78 | 89 | if (!err?.imageCredits) { |
| @@ -95,21 +106,19 @@ | ||
| 95 | 106 | if (!preview?.src || applying) return; |
| 96 | 107 | setError(''); |
| 97 | 108 | setApplying(true); |
| 98 | 109 | try { |
| 99 | - let attachment; | |
| 100 | - try { | |
| 101 | - attachment = await importImage(preview.src, { | |
| 102 | - alt: aiImageOptions.prompt, | |
| 103 | - filename: 'ai-image.jpg', | |
| 110 | + const attachment = await downloadImage( | |
| 111 | + preview.id, | |
| 112 | + preview.src, | |
| 113 | + 'ai-generated', | |
| 114 | + null, | |
| 115 | + { | |
| 116 | + alt: preview.alt, | |
| 104 | 117 | caption: '', |
| 105 | - }); | |
| 106 | - } catch (_e) { | |
| 107 | - attachment = await importImageServer(preview.src, { | |
| 108 | - alt: aiImageOptions.prompt, | |
| 109 | - caption: '', | |
| 110 | - }); | |
| 111 | - } | |
| 118 | + disclose, | |
| 119 | + }, | |
| 120 | + ); | |
| 112 | 121 | const mediaId = attachment?.id; |
| 113 | 122 | if (!mediaId) throw new Error('No media id returned'); |
| 114 | 123 | |
| 115 | 124 | // Product image: write the featured image via WC's save |
| @@ -152,9 +161,9 @@ | ||
| 152 | 161 | fieldKey: field, |
| 153 | 162 | value: { |
| 154 | 163 | url: attachment.url || attachment.source_url, |
| 155 | 164 | id: mediaId, |
| 156 | - alt: aiImageOptions.prompt || '', | |
| 165 | + alt: attachment.alt_text ?? '', | |
| 157 | 166 | }, |
| 158 | 167 | }, |
| 159 | 168 | ], |
| 160 | 169 | }); |
| @@ -212,15 +221,15 @@ | ||
| 212 | 221 | </Notice> |
| 213 | 222 | ) : null} |
| 214 | 223 | {preview?.src ? ( |
| 215 | 224 | <div className="extendify-quick-edit-ai-preview"> |
| 216 | - <img src={preview.src} alt={aiImageOptions.prompt} /> | |
| 225 | + <img src={previewSrc} alt={preview.alt} /> | |
| 217 | 226 | </div> |
| 218 | 227 | ) : ( |
| 219 | 228 | <form onSubmit={onGenerate} className="extendify-quick-edit-ai-form"> |
| 220 | 229 | <TextareaControl |
| 221 | 230 | autoFocus |
| 222 | - label={__('Image prompt', 'extendify-local')} | |
| 231 | + label={__('Image description', 'extendify-local')} | |
| 223 | 232 | placeholder={__( |
| 224 | 233 | 'Describe the image you want to create', |
| 225 | 234 | 'extendify-local', |
| 226 | 235 | )} |
| @@ -258,8 +267,16 @@ | ||
| 258 | 267 | __('Portrait', 'extendify-local') |
| 259 | 268 | } |
| 260 | 269 | /> |
| 261 | 270 | </ToggleGroupControl> |
| 271 | + <CheckboxControl | |
| 272 | + __nextHasNoMarginBottom | |
| 273 | + // translators: Checkbox that adds a visible "AI Generated" mark onto the image. | |
| 274 | + label={__('Label image as AI-generated', 'extendify-local')} | |
| 275 | + checked={disclose} | |
| 276 | + onChange={setDisclose} | |
| 277 | + disabled={generating} | |
| 278 | + /> | |
| 262 | 279 | {generating ? ( |
| 263 | 280 | // biome-ignore lint/a11y/useSemanticElements: deliberate live region; <output> changes display + semantics |
| 264 | 281 | <div className="extendify-quick-edit-ai-generating" role="status"> |
| 265 | 282 | <Spinner /> |