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 / hooks / useImageAcquisition.js

useImageAcquisition.js in Extendify 3.1.6, at src/Agent/hooks/useImageAcquisition.js

176 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { generateImage } from '@shared/api/DataApi';
2 import { downloadImage } from '@shared/api/wp';
3 import {
4 DEFAULT_IMAGE_SEARCH,
5 resolveImageSearch,
6 } from '@shared/lib/image-search-defaults';
7 import { fetchImages } from '@shared/lib/unsplash';
8 import { useImageGenerationStore } from '@shared/state/generate-images';
9 import { useUnsplashCacheStore } from '@shared/state/unsplash-cache';
10 import { useCallback, useRef, useState } from '@wordpress/element';
11 import { __ } from '@wordpress/i18n';
12 import useSWRImmutable from 'swr/immutable';
13
14 const SIZES = [
15 ['1024x1024', 1],
16 ['1536x1024', 3 / 2],
17 ['1024x1536', 2 / 3],
18 ['1792x1024', 16 / 9],
19 ['1024x1792', 9 / 16],
20 ];
21
22 // The model renders one of five ratios; the wrong one gets cropped by
23 // object-cover, and 2k costs seconds a slot this size can't show.
24 export const targetFor = (el) => {
25 const { width, height } = el?.getBoundingClientRect?.() ?? {};
26 if (!width || !height) return {};
27 const ratio = width / height;
28 const [size] = SIZES.reduce((best, entry) =>
29 Math.abs(entry[1] - ratio) < Math.abs(best[1] - ratio) ? entry : best,
30 );
31 return { size, quality: width > 1024 ? 'medium' : 'low' };
32 };
33
34 // onUrlReady is awaited so surfaces can preload/preview before an url turns ready.
35 export const useImageAcquisition = ({ source, onUrlReady } = {}) => {
36 const [states, setStates] = useState({});
37 const { imageCredits, updateImageCredits, subtractOneCredit } =
38 useImageGenerationStore();
39 const statesRef = useRef(states);
40 statesRef.current = states;
41 const onUrlReadyRef = useRef(onUrlReady);
42 onUrlReadyRef.current = onUrlReady;
43
44 const setItemState = useCallback(
45 (key, state) => setStates((states) => ({ ...states, [key]: state })),
46 [],
47 );
48
49 const generate = useCallback(
50 async (key, prompt, target = {}) => {
51 if (
52 Number(useImageGenerationStore.getState().imageCredits.remaining) === 0
53 ) {
54 setItemState(key, {
55 status: 'error',
56 message: __(
57 "You've run out of daily image credits.",
58 'extendify-local',
59 ),
60 });
61 return;
62 }
63 setItemState(key, { status: 'generating' });
64 subtractOneCredit();
65 try {
66 const { imageCredits: credits, images } = await generateImage({
67 prompt,
68 source,
69 quality: 'low',
70 ...target,
71 });
72 updateImageCredits(credits);
73 const url = images?.[0]?.url;
74 if (!url) throw new Error(__('No image returned', 'extendify-local'));
75 await onUrlReadyRef.current?.(key, url);
76 setItemState(key, {
77 status: 'ready',
78 url,
79 alt: images[0].alt ?? prompt,
80 });
81 } catch (error) {
82 if (error?.imageCredits) updateImageCredits(error.imageCredits);
83 setItemState(key, {
84 status: 'error',
85 message:
86 error?.message ||
87 __('An unknown error occurred.', 'extendify-local'),
88 });
89 }
90 },
91 [setItemState, subtractOneCredit, updateImageCredits, source],
92 );
93
94 const attachImage = useCallback(
95 (key, image) => setItemState(key, { status: 'ready', image }),
96 [setItemState],
97 );
98
99 const pickUnsplash = useCallback(
100 async (key, photo) => {
101 const url = photo?.urls?.regular;
102 if (!url) return;
103 try {
104 await onUrlReadyRef.current?.(key, url);
105 } catch (error) {
106 setItemState(key, {
107 status: 'error',
108 message:
109 error?.message ||
110 __('An unknown error occurred.', 'extendify-local'),
111 });
112 return;
113 }
114 setItemState(key, {
115 status: 'ready',
116 url,
117 photoId: photo.id,
118 requestId: photo.requestMetadata?.id ?? null,
119 });
120 },
121 [setItemState],
122 );
123
124 const markAwaiting = useCallback(
125 (key) => setItemState(key, { status: 'awaiting' }),
126 [setItemState],
127 );
128
129 // Media picks are already attachments; generated/Unsplash urls are off-site until imported.
130 const resolveImage = useCallback(async (key, { disclose = false } = {}) => {
131 const state = statesRef.current[key];
132 if (state?.image) return state.image;
133 if (!state?.url) return null;
134 if (state.photoId) {
135 return await downloadImage(
136 state.requestId,
137 state.url,
138 'unsplash',
139 state.photoId,
140 );
141 }
142 return await downloadImage(null, state.url, 'ai-generated', null, {
143 alt: state.alt,
144 disclose,
145 });
146 }, []);
147
148 return {
149 states,
150 imageCredits,
151 generate,
152 attachImage,
153 pickUnsplash,
154 markAwaiting,
155 resolveImage,
156 };
157 };
158
159 const searchImages = async (search, source) => {
160 const cache = useUnsplashCacheStore.getState();
161 if (
162 search === DEFAULT_IMAGE_SEARCH &&
163 !cache.isEmpty() &&
164 !cache.hasExpired()
165 ) {
166 return cache.images;
167 }
168 return await fetchImages(resolveImageSearch(search), source);
169 };
170
171 export const useUnsplashSearch = (search, source = null) => {
172 const key = search || DEFAULT_IMAGE_SEARCH;
173 const { data, error } = useSWRImmutable(key, () => searchImages(key, source));
174 return { data, error, loading: !data && !error };
175 };
176