@automattic/jetpack-ai-client 0.14.6 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/build/ask-question/sync.d.ts +2 -8
- package/build/ask-question/sync.js +20 -19
- package/build/hooks/use-image-generator/index.js +1 -1
- package/build/hooks/use-save-to-media-library/index.d.ts +12 -0
- package/build/hooks/use-save-to-media-library/index.js +74 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +5 -0
- package/build/libs/index.d.ts +1 -1
- package/build/libs/index.js +1 -1
- package/build/libs/markdown/index.d.ts +2 -2
- package/build/libs/markdown/index.js +2 -2
- package/build/libs/markdown/markdown-to-html.d.ts +8 -1
- package/build/libs/markdown/markdown-to-html.js +10 -1
- package/build/logo-generator/assets/icons/ai.d.ts +6 -0
- package/build/logo-generator/assets/icons/ai.js +8 -0
- package/build/logo-generator/assets/icons/check.d.ts +6 -0
- package/build/logo-generator/assets/icons/check.js +8 -0
- package/build/logo-generator/assets/icons/logo.d.ts +6 -0
- package/build/logo-generator/assets/icons/logo.js +8 -0
- package/build/logo-generator/assets/icons/media.d.ts +6 -0
- package/build/logo-generator/assets/icons/media.js +8 -0
- package/build/logo-generator/components/feature-fetch-failure-screen.d.ts +8 -0
- package/build/logo-generator/components/feature-fetch-failure-screen.js +10 -0
- package/build/logo-generator/components/first-load-screen.d.ts +5 -0
- package/build/logo-generator/components/first-load-screen.js +16 -0
- package/build/logo-generator/components/generator-modal.d.ts +7 -0
- package/build/logo-generator/components/generator-modal.js +184 -0
- package/build/logo-generator/components/history-carousel.d.ts +6 -0
- package/build/logo-generator/components/history-carousel.js +36 -0
- package/build/logo-generator/components/image-loader.d.ts +7 -0
- package/build/logo-generator/components/image-loader.js +12 -0
- package/build/logo-generator/components/logo-presenter.d.ts +4 -0
- package/build/logo-generator/components/logo-presenter.js +106 -0
- package/build/logo-generator/components/prompt.d.ts +5 -0
- package/build/logo-generator/components/prompt.js +96 -0
- package/build/logo-generator/components/upgrade-nudge.d.ts +2 -0
- package/build/logo-generator/components/upgrade-nudge.js +30 -0
- package/build/logo-generator/components/upgrade-screen.d.ts +9 -0
- package/build/logo-generator/components/upgrade-screen.js +24 -0
- package/build/logo-generator/components/visit-site-banner.d.ts +10 -0
- package/build/logo-generator/components/visit-site-banner.js +16 -0
- package/build/logo-generator/constants.d.ts +16 -0
- package/build/logo-generator/constants.js +19 -0
- package/build/logo-generator/hooks/use-checkout.d.ts +4 -0
- package/build/logo-generator/hooks/use-checkout.js +26 -0
- package/build/logo-generator/hooks/use-logo-generator.d.ts +46 -0
- package/build/logo-generator/hooks/use-logo-generator.js +286 -0
- package/build/logo-generator/hooks/use-request-errors.d.ts +16 -0
- package/build/logo-generator/hooks/use-request-errors.js +46 -0
- package/build/logo-generator/index.d.ts +1 -0
- package/build/logo-generator/index.js +1 -0
- package/build/logo-generator/lib/logo-storage.d.ts +58 -0
- package/build/logo-generator/lib/logo-storage.js +123 -0
- package/build/logo-generator/lib/media-exists.d.ts +12 -0
- package/build/logo-generator/lib/media-exists.js +33 -0
- package/build/logo-generator/lib/set-site-logo.d.ts +13 -0
- package/build/logo-generator/lib/set-site-logo.js +26 -0
- package/build/logo-generator/lib/wpcom-limited-request.d.ts +7 -0
- package/build/logo-generator/lib/wpcom-limited-request.js +33 -0
- package/build/logo-generator/store/actions.d.ts +105 -0
- package/build/logo-generator/store/actions.js +193 -0
- package/build/logo-generator/store/constants.d.ts +44 -0
- package/build/logo-generator/store/constants.js +44 -0
- package/build/logo-generator/store/index.d.ts +1 -0
- package/build/logo-generator/store/index.js +19 -0
- package/build/logo-generator/store/initial-state.d.ts +3 -0
- package/build/logo-generator/store/initial-state.js +40 -0
- package/build/logo-generator/store/reducer.d.ts +347 -0
- package/build/logo-generator/store/reducer.js +293 -0
- package/build/logo-generator/store/selectors.d.ts +119 -0
- package/build/logo-generator/store/selectors.js +173 -0
- package/build/logo-generator/store/types.d.ts +164 -0
- package/build/logo-generator/store/types.js +1 -0
- package/build/logo-generator/types.d.ts +82 -0
- package/build/logo-generator/types.js +1 -0
- package/build/types.d.ts +6 -0
- package/package.json +5 -3
- package/src/ask-question/sync.ts +22 -27
- package/src/hooks/use-image-generator/index.ts +1 -1
- package/src/hooks/use-save-to-media-library/index.ts +95 -0
- package/src/index.ts +6 -0
- package/src/libs/index.ts +1 -0
- package/src/libs/markdown/index.ts +2 -2
- package/src/libs/markdown/markdown-to-html.ts +20 -3
- package/src/logo-generator/assets/icons/ai.tsx +21 -0
- package/src/logo-generator/assets/icons/check.tsx +23 -0
- package/src/logo-generator/assets/icons/icons.scss +5 -0
- package/src/logo-generator/assets/icons/logo.tsx +23 -0
- package/src/logo-generator/assets/icons/media.tsx +24 -0
- package/src/logo-generator/assets/images/jetpack-logo.svg +4 -0
- package/src/logo-generator/assets/images/loader.gif +0 -0
- package/src/logo-generator/assets/index.d.ts +3 -0
- package/src/logo-generator/components/feature-fetch-failure-screen.tsx +35 -0
- package/src/logo-generator/components/first-load-screen.scss +12 -0
- package/src/logo-generator/components/first-load-screen.tsx +32 -0
- package/src/logo-generator/components/generator-modal.scss +92 -0
- package/src/logo-generator/components/generator-modal.tsx +291 -0
- package/src/logo-generator/components/history-carousel.scss +36 -0
- package/src/logo-generator/components/history-carousel.tsx +57 -0
- package/src/logo-generator/components/image-loader.tsx +22 -0
- package/src/logo-generator/components/logo-presenter.scss +116 -0
- package/src/logo-generator/components/logo-presenter.tsx +234 -0
- package/src/logo-generator/components/prompt.scss +102 -0
- package/src/logo-generator/components/prompt.tsx +211 -0
- package/src/logo-generator/components/upgrade-nudge.scss +43 -0
- package/src/logo-generator/components/upgrade-nudge.tsx +58 -0
- package/src/logo-generator/components/upgrade-screen.tsx +67 -0
- package/src/logo-generator/components/visit-site-banner.scss +29 -0
- package/src/logo-generator/components/visit-site-banner.tsx +50 -0
- package/src/logo-generator/constants.ts +22 -0
- package/src/logo-generator/hooks/use-checkout.ts +37 -0
- package/src/logo-generator/hooks/use-logo-generator.ts +389 -0
- package/src/logo-generator/hooks/use-request-errors.ts +70 -0
- package/src/logo-generator/index.ts +1 -0
- package/src/logo-generator/lib/logo-storage.ts +166 -0
- package/src/logo-generator/lib/media-exists.ts +42 -0
- package/src/logo-generator/lib/set-site-logo.ts +32 -0
- package/src/logo-generator/lib/wpcom-limited-request.ts +41 -0
- package/src/logo-generator/store/actions.ts +251 -0
- package/src/logo-generator/store/constants.ts +49 -0
- package/src/logo-generator/store/index.ts +25 -0
- package/src/logo-generator/store/initial-state.ts +43 -0
- package/src/logo-generator/store/reducer.ts +387 -0
- package/src/logo-generator/store/selectors.ts +201 -0
- package/src/logo-generator/store/types.ts +207 -0
- package/src/logo-generator/types.ts +97 -0
- package/src/types.ts +8 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useDispatch, useSelect } from '@wordpress/data';
|
|
5
|
+
import debugFactory from 'debug';
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import useImageGenerator from '../../hooks/use-image-generator/index.js';
|
|
11
|
+
import useSaveToMediaLibrary from '../../hooks/use-save-to-media-library/index.js';
|
|
12
|
+
import requestJwt from '../../jwt/index.js';
|
|
13
|
+
import { stashLogo } from '../lib/logo-storage.js';
|
|
14
|
+
import { setSiteLogo } from '../lib/set-site-logo.js';
|
|
15
|
+
import { STORE_NAME } from '../store/index.js';
|
|
16
|
+
import useRequestErrors from './use-request-errors.js';
|
|
17
|
+
const debug = debugFactory('jetpack-ai-calypso:use-logo-generator');
|
|
18
|
+
const useLogoGenerator = () => {
|
|
19
|
+
const { setSelectedLogoIndex, setIsSavingLogoToLibrary, setIsApplyingLogo, setIsRequestingImage, setIsEnhancingPrompt, increaseAiAssistantRequestsCount, addLogoToHistory, setContext, } = useDispatch(STORE_NAME);
|
|
20
|
+
const { logos, selectedLogoIndex, selectedLogo, siteDetails, isSavingLogoToLibrary, isApplyingLogo, isEnhancingPrompt, isBusy, isRequestingImage, getAiAssistantFeature, requireUpgrade, context, } = useSelect(select => {
|
|
21
|
+
const selectors = select(STORE_NAME);
|
|
22
|
+
return {
|
|
23
|
+
logos: selectors.getLogos(),
|
|
24
|
+
selectedLogoIndex: selectors.getSelectedLogoIndex(),
|
|
25
|
+
selectedLogo: selectors.getSelectedLogo(),
|
|
26
|
+
siteDetails: selectors.getSiteDetails(),
|
|
27
|
+
isSavingLogoToLibrary: selectors.getIsSavingLogoToLibrary(),
|
|
28
|
+
isApplyingLogo: selectors.getIsApplyingLogo(),
|
|
29
|
+
isRequestingImage: selectors.getIsRequestingImage(),
|
|
30
|
+
isEnhancingPrompt: selectors.getIsEnhancingPrompt(),
|
|
31
|
+
isBusy: selectors.getIsBusy(),
|
|
32
|
+
getAiAssistantFeature: selectors.getAiAssistantFeature,
|
|
33
|
+
requireUpgrade: selectors.getRequireUpgrade(),
|
|
34
|
+
context: selectors.getContext(),
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
const { setFirstLogoPromptFetchError, setEnhancePromptFetchError, setLogoFetchError, setSaveToLibraryError, setLogoUpdateError, } = useRequestErrors();
|
|
38
|
+
const { generateImageWithParameters } = useImageGenerator();
|
|
39
|
+
const { saveToMediaLibrary } = useSaveToMediaLibrary();
|
|
40
|
+
const { ID = null, name = null, description = null } = siteDetails || {};
|
|
41
|
+
const siteId = ID ? String(ID) : null;
|
|
42
|
+
const aiAssistantFeatureData = getAiAssistantFeature(siteId);
|
|
43
|
+
const logoGenerationCost = aiAssistantFeatureData?.costs?.['jetpack-ai-logo-generator']?.logo;
|
|
44
|
+
const generateFirstPrompt = useCallback(async function () {
|
|
45
|
+
setFirstLogoPromptFetchError(null);
|
|
46
|
+
increaseAiAssistantRequestsCount();
|
|
47
|
+
try {
|
|
48
|
+
const tokenData = await requestJwt();
|
|
49
|
+
if (!tokenData || !tokenData.token) {
|
|
50
|
+
throw new Error('No token provided');
|
|
51
|
+
}
|
|
52
|
+
debug('Generating first prompt for site');
|
|
53
|
+
const firstPromptGenerationPrompt = `Generate a simple and short prompt asking for a logo based on the site's name and description, keeping the same language.
|
|
54
|
+
Example for a site named "The minimalist fashion blog", described as "Daily inspiration for all things fashion": A logo for a minimalist fashion site focused on daily sartorial inspiration with a clean and modern aesthetic that is sleek and sophisticated.
|
|
55
|
+
Another example, now for a site called "El observatorio de aves", described as "Un sitio dedicado a nuestros compañeros y compañeras entusiastas de la observación de aves.": Un logo para un sitio web dedicado a la observación de aves, capturando la esencia de la naturaleza y la pasión por la avifauna en un diseño elegante y representativo, reflejando una estética natural y apasionada por la vida silvestre.
|
|
56
|
+
|
|
57
|
+
Site name: ${name}
|
|
58
|
+
Site description: ${description}`;
|
|
59
|
+
const body = {
|
|
60
|
+
question: firstPromptGenerationPrompt,
|
|
61
|
+
feature: 'jetpack-ai-logo-generator',
|
|
62
|
+
stream: false,
|
|
63
|
+
};
|
|
64
|
+
const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-query';
|
|
65
|
+
const headers = {
|
|
66
|
+
Authorization: `Bearer ${tokenData.token}`,
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
};
|
|
69
|
+
const data = await fetch(URL, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers,
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
}).then(response => response.json());
|
|
74
|
+
return data?.choices?.[0]?.message?.content;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
increaseAiAssistantRequestsCount(-1);
|
|
78
|
+
setFirstLogoPromptFetchError(error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}, [setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount]);
|
|
82
|
+
const enhancePrompt = async function ({ prompt }) {
|
|
83
|
+
setEnhancePromptFetchError(null);
|
|
84
|
+
increaseAiAssistantRequestsCount();
|
|
85
|
+
try {
|
|
86
|
+
const tokenData = await requestJwt();
|
|
87
|
+
if (!tokenData || !tokenData.token) {
|
|
88
|
+
throw new Error('No token provided');
|
|
89
|
+
}
|
|
90
|
+
debug('Enhancing prompt', prompt);
|
|
91
|
+
const systemMessage = `Enhance the prompt you receive.
|
|
92
|
+
The prompt is meant for generating a logo. Return the same prompt enhanced, and make each enhancement wrapped in brackets.
|
|
93
|
+
Do not add any mention to text, letters, typography or the name of the site in the prompt.
|
|
94
|
+
For example: user's prompt: A logo for an ice cream shop. Returned prompt: A logo for an ice cream shop [that is pink] [and vibrant].`;
|
|
95
|
+
const messages = [
|
|
96
|
+
{
|
|
97
|
+
role: 'system',
|
|
98
|
+
content: systemMessage,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
role: 'user',
|
|
102
|
+
content: prompt,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
const body = {
|
|
106
|
+
messages,
|
|
107
|
+
feature: 'jetpack-ai-logo-generator',
|
|
108
|
+
stream: false,
|
|
109
|
+
};
|
|
110
|
+
const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-query';
|
|
111
|
+
const headers = {
|
|
112
|
+
Authorization: `Bearer ${tokenData.token}`,
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
};
|
|
115
|
+
const data = await fetch(URL, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers,
|
|
118
|
+
body: JSON.stringify(body),
|
|
119
|
+
}).then(response => response.json());
|
|
120
|
+
return data?.choices?.[0]?.message?.content;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
increaseAiAssistantRequestsCount(-1);
|
|
124
|
+
setEnhancePromptFetchError(error);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const generateImage = useCallback(async function ({ prompt, }) {
|
|
129
|
+
setLogoFetchError(null);
|
|
130
|
+
try {
|
|
131
|
+
const tokenData = await requestJwt();
|
|
132
|
+
if (!tokenData || !tokenData.token) {
|
|
133
|
+
throw new Error('No token provided');
|
|
134
|
+
}
|
|
135
|
+
debug('Generating image with prompt', prompt);
|
|
136
|
+
const imageGenerationPrompt = `I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS-IS:
|
|
137
|
+
Create a single text-free iconic vector logo that symbolically represents the user request, using abstract or symbolic imagery.
|
|
138
|
+
The design should be modern, with either a vivid color scheme full of gradients or a color scheme that's monochromatic. Use any of those styles based on the user request mood.
|
|
139
|
+
Ensure the logo is set against a clean solid background.
|
|
140
|
+
Ensure the logo works in small sizes.
|
|
141
|
+
The imagery in the logo should subtly hint at the mood of the user request but DO NOT use any text, letters, or the name of the site on the imagery.
|
|
142
|
+
The image should contain a single icon, without variations, color palettes or different versions.
|
|
143
|
+
|
|
144
|
+
User request:${prompt}`;
|
|
145
|
+
const body = {
|
|
146
|
+
prompt: imageGenerationPrompt,
|
|
147
|
+
feature: 'jetpack-ai-logo-generator',
|
|
148
|
+
response_format: 'b64_json',
|
|
149
|
+
};
|
|
150
|
+
const data = await generateImageWithParameters(body);
|
|
151
|
+
return data;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
setLogoFetchError(error);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}, []);
|
|
158
|
+
const saveLogo = useCallback(async (logo) => {
|
|
159
|
+
setSaveToLibraryError(null);
|
|
160
|
+
try {
|
|
161
|
+
debug('Saving logo for site');
|
|
162
|
+
// If the logo is already saved, return its mediaId and mediaURL.
|
|
163
|
+
if (logo.mediaId) {
|
|
164
|
+
return { mediaId: logo.mediaId, mediaURL: logo.url };
|
|
165
|
+
}
|
|
166
|
+
const savedLogo = {
|
|
167
|
+
mediaId: 0,
|
|
168
|
+
mediaURL: '',
|
|
169
|
+
};
|
|
170
|
+
setIsSavingLogoToLibrary(true);
|
|
171
|
+
const { id: mediaId, url: mediaURL } = await saveToMediaLibrary(logo.url, 'site-logo.png');
|
|
172
|
+
savedLogo.mediaId = parseInt(mediaId);
|
|
173
|
+
savedLogo.mediaURL = mediaURL;
|
|
174
|
+
return savedLogo;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
setSaveToLibraryError(error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
setIsSavingLogoToLibrary(false);
|
|
182
|
+
}
|
|
183
|
+
}, [setIsSavingLogoToLibrary, setSaveToLibraryError]);
|
|
184
|
+
const applyLogo = useCallback(async () => {
|
|
185
|
+
setLogoUpdateError(null);
|
|
186
|
+
try {
|
|
187
|
+
if (!siteId || !selectedLogo) {
|
|
188
|
+
throw new Error('Missing siteId or logo');
|
|
189
|
+
}
|
|
190
|
+
debug('Applying logo for site', siteId);
|
|
191
|
+
setIsApplyingLogo(true);
|
|
192
|
+
const { mediaId } = selectedLogo;
|
|
193
|
+
if (!mediaId) {
|
|
194
|
+
throw new Error('Missing mediaId');
|
|
195
|
+
}
|
|
196
|
+
await setSiteLogo({
|
|
197
|
+
siteId: siteId,
|
|
198
|
+
imageId: String(mediaId),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
setLogoUpdateError(error);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
setIsApplyingLogo(false);
|
|
207
|
+
}
|
|
208
|
+
}, [selectedLogo, setIsApplyingLogo, setLogoUpdateError, siteId]);
|
|
209
|
+
const storeLogo = useCallback((logo) => {
|
|
210
|
+
addLogoToHistory(logo);
|
|
211
|
+
stashLogo({ ...logo, siteId: String(siteId) });
|
|
212
|
+
}, [siteId, addLogoToHistory, stashLogo]);
|
|
213
|
+
const generateLogo = useCallback(async function ({ prompt }) {
|
|
214
|
+
debug('Generating logo for site');
|
|
215
|
+
setIsRequestingImage(true);
|
|
216
|
+
try {
|
|
217
|
+
if (!logoGenerationCost) {
|
|
218
|
+
throw new Error('Missing cost information');
|
|
219
|
+
}
|
|
220
|
+
increaseAiAssistantRequestsCount(logoGenerationCost);
|
|
221
|
+
let image;
|
|
222
|
+
try {
|
|
223
|
+
image = await generateImage({ prompt });
|
|
224
|
+
if (!image || !image.data.length) {
|
|
225
|
+
throw new Error('No image returned');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
increaseAiAssistantRequestsCount(-logoGenerationCost);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
// response_format=url returns object with url, otherwise b64_json
|
|
233
|
+
const logo = {
|
|
234
|
+
url: 'data:image/png;base64,' + image.data[0].b64_json,
|
|
235
|
+
description: prompt,
|
|
236
|
+
};
|
|
237
|
+
try {
|
|
238
|
+
const savedLogo = await saveLogo(logo);
|
|
239
|
+
storeLogo({
|
|
240
|
+
url: savedLogo.mediaURL,
|
|
241
|
+
description: prompt,
|
|
242
|
+
mediaId: savedLogo.mediaId,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
storeLogo(logo);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
setIsRequestingImage(false);
|
|
252
|
+
}
|
|
253
|
+
}, [logoGenerationCost, increaseAiAssistantRequestsCount, saveLogo, storeLogo, generateImage]);
|
|
254
|
+
return {
|
|
255
|
+
logos,
|
|
256
|
+
selectedLogoIndex,
|
|
257
|
+
selectedLogo,
|
|
258
|
+
setSelectedLogoIndex,
|
|
259
|
+
site: {
|
|
260
|
+
id: siteId,
|
|
261
|
+
name,
|
|
262
|
+
description,
|
|
263
|
+
},
|
|
264
|
+
generateFirstPrompt,
|
|
265
|
+
saveLogo,
|
|
266
|
+
applyLogo,
|
|
267
|
+
generateImage,
|
|
268
|
+
enhancePrompt,
|
|
269
|
+
storeLogo,
|
|
270
|
+
generateLogo,
|
|
271
|
+
setIsEnhancingPrompt,
|
|
272
|
+
setIsRequestingImage,
|
|
273
|
+
setIsSavingLogoToLibrary,
|
|
274
|
+
setIsApplyingLogo,
|
|
275
|
+
setContext,
|
|
276
|
+
isEnhancingPrompt,
|
|
277
|
+
isRequestingImage,
|
|
278
|
+
isSavingLogoToLibrary,
|
|
279
|
+
isApplyingLogo,
|
|
280
|
+
isBusy,
|
|
281
|
+
getAiAssistantFeature,
|
|
282
|
+
requireUpgrade,
|
|
283
|
+
context,
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
export default useLogoGenerator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const useRequestErrors: () => {
|
|
2
|
+
setFeatureFetchError: any;
|
|
3
|
+
setFirstLogoPromptFetchError: any;
|
|
4
|
+
setEnhancePromptFetchError: any;
|
|
5
|
+
setLogoFetchError: any;
|
|
6
|
+
setSaveToLibraryError: any;
|
|
7
|
+
setLogoUpdateError: any;
|
|
8
|
+
clearErrors: () => void;
|
|
9
|
+
featureFetchError: import("../store/types.js").RequestError;
|
|
10
|
+
firstLogoPromptFetchError: import("../store/types.js").RequestError;
|
|
11
|
+
enhancePromptFetchError: import("../store/types.js").RequestError;
|
|
12
|
+
logoFetchError: import("../store/types.js").RequestError;
|
|
13
|
+
saveToLibraryError: import("../store/types.js").RequestError;
|
|
14
|
+
logoUpdateError: import("../store/types.js").RequestError;
|
|
15
|
+
};
|
|
16
|
+
export default useRequestErrors;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useDispatch, useSelect } from '@wordpress/data';
|
|
5
|
+
/**
|
|
6
|
+
* Internal dependencies
|
|
7
|
+
*/
|
|
8
|
+
import { STORE_NAME } from '../store/index.js';
|
|
9
|
+
const useRequestErrors = () => {
|
|
10
|
+
const { setFeatureFetchError, setFirstLogoPromptFetchError, setEnhancePromptFetchError, setLogoFetchError, setSaveToLibraryError, setLogoUpdateError, } = useDispatch(STORE_NAME);
|
|
11
|
+
const { featureFetchError, firstLogoPromptFetchError, enhancePromptFetchError, logoFetchError, saveToLibraryError, logoUpdateError, } = useSelect(select => {
|
|
12
|
+
const selectors = select(STORE_NAME);
|
|
13
|
+
return {
|
|
14
|
+
featureFetchError: selectors.getFeatureFetchError(),
|
|
15
|
+
firstLogoPromptFetchError: selectors.getFirstLogoPromptFetchError(),
|
|
16
|
+
enhancePromptFetchError: selectors.getEnhancePromptFetchError(),
|
|
17
|
+
logoFetchError: selectors.getLogoFetchError(),
|
|
18
|
+
saveToLibraryError: selectors.getSaveToLibraryError(),
|
|
19
|
+
logoUpdateError: selectors.getLogoUpdateError(),
|
|
20
|
+
};
|
|
21
|
+
}, []);
|
|
22
|
+
const clearErrors = () => {
|
|
23
|
+
setFeatureFetchError(null);
|
|
24
|
+
setFirstLogoPromptFetchError(null);
|
|
25
|
+
setEnhancePromptFetchError(null);
|
|
26
|
+
setLogoFetchError(null);
|
|
27
|
+
setSaveToLibraryError(null);
|
|
28
|
+
setLogoUpdateError(null);
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
setFeatureFetchError,
|
|
32
|
+
setFirstLogoPromptFetchError,
|
|
33
|
+
setEnhancePromptFetchError,
|
|
34
|
+
setLogoFetchError,
|
|
35
|
+
setSaveToLibraryError,
|
|
36
|
+
setLogoUpdateError,
|
|
37
|
+
clearErrors,
|
|
38
|
+
featureFetchError,
|
|
39
|
+
firstLogoPromptFetchError,
|
|
40
|
+
enhancePromptFetchError,
|
|
41
|
+
logoFetchError,
|
|
42
|
+
saveToLibraryError,
|
|
43
|
+
logoUpdateError,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export default useRequestErrors;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/generator-modal.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/generator-modal.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types
|
|
3
|
+
*/
|
|
4
|
+
import { Logo } from '../store/types.js';
|
|
5
|
+
import { RemoveFromStorageProps, SaveToStorageProps, UpdateInStorageProps } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Add an entry to the site's logo history.
|
|
8
|
+
*
|
|
9
|
+
* @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage
|
|
10
|
+
* @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID
|
|
11
|
+
* @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo
|
|
12
|
+
* @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it
|
|
13
|
+
* @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend
|
|
14
|
+
*
|
|
15
|
+
* @returns {Logo} The logo that was saved
|
|
16
|
+
*/
|
|
17
|
+
export declare function stashLogo({ siteId, url, description, mediaId }: SaveToStorageProps): Logo;
|
|
18
|
+
/**
|
|
19
|
+
* Update an entry in the site's logo history.
|
|
20
|
+
*
|
|
21
|
+
* @param {UpdateInStorageProps} updateInStorageProps - The properties to update in storage
|
|
22
|
+
* @param {UpdateInStorageProps.siteId} updateInStorageProps.siteId - The site ID
|
|
23
|
+
* @param {UpdateInStorageProps.url} updateInStorageProps.url - The URL of the logo to update
|
|
24
|
+
* @param {UpdateInStorageProps.newUrl} updateInStorageProps.newUrl - The new URL of the logo
|
|
25
|
+
* @param {UpdateInStorageProps.mediaId} updateInStorageProps.mediaId - The new media ID of the logo
|
|
26
|
+
* @returns {Logo} The logo that was updated
|
|
27
|
+
*/
|
|
28
|
+
export declare function updateLogo({ siteId, url, newUrl, mediaId }: UpdateInStorageProps): Logo;
|
|
29
|
+
/**
|
|
30
|
+
* Get the logo history for a site.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} siteId - The site ID to get the logo history for
|
|
33
|
+
* @returns {Logo[]} The logo history for the site
|
|
34
|
+
*/
|
|
35
|
+
export declare function getSiteLogoHistory(siteId: string): Logo[];
|
|
36
|
+
/**
|
|
37
|
+
* Check if the logo history for a site is empty.
|
|
38
|
+
*
|
|
39
|
+
* @param {string }siteId - The site ID to check the logo history for
|
|
40
|
+
* @returns {boolean} Whether the logo history for the site is empty
|
|
41
|
+
*/
|
|
42
|
+
export declare function isLogoHistoryEmpty(siteId: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Remove an entry from the site's logo history.
|
|
45
|
+
*
|
|
46
|
+
* @param {RemoveFromStorageProps} removeFromStorageProps - The properties to remove from storage
|
|
47
|
+
* @param {RemoveFromStorageProps.siteId} removeFromStorageProps.siteId - The site ID
|
|
48
|
+
* @param {RemoveFromStorageProps.mediaId} removeFromStorageProps.mediaId - The media ID of the logo to remove
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
export declare function removeLogo({ siteId, mediaId }: RemoveFromStorageProps): void;
|
|
52
|
+
/**
|
|
53
|
+
* Clear deleted media from the site's logo history, checking if the media still exists on the backend.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} siteId - The site ID to clear deleted media for
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
export declare function clearDeletedMedia(siteId: string): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mediaExists } from './media-exists.js';
|
|
2
|
+
const MAX_LOGOS = 10;
|
|
3
|
+
/**
|
|
4
|
+
* Add an entry to the site's logo history.
|
|
5
|
+
*
|
|
6
|
+
* @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage
|
|
7
|
+
* @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID
|
|
8
|
+
* @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo
|
|
9
|
+
* @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it
|
|
10
|
+
* @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend
|
|
11
|
+
*
|
|
12
|
+
* @returns {Logo} The logo that was saved
|
|
13
|
+
*/
|
|
14
|
+
export function stashLogo({ siteId, url, description, mediaId }) {
|
|
15
|
+
const storedContent = getSiteLogoHistory(siteId);
|
|
16
|
+
const logo = {
|
|
17
|
+
url,
|
|
18
|
+
description,
|
|
19
|
+
mediaId,
|
|
20
|
+
};
|
|
21
|
+
storedContent.push(logo);
|
|
22
|
+
localStorage.setItem(`logo-history-${siteId}`, JSON.stringify(storedContent.slice(-MAX_LOGOS)));
|
|
23
|
+
return logo;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Update an entry in the site's logo history.
|
|
27
|
+
*
|
|
28
|
+
* @param {UpdateInStorageProps} updateInStorageProps - The properties to update in storage
|
|
29
|
+
* @param {UpdateInStorageProps.siteId} updateInStorageProps.siteId - The site ID
|
|
30
|
+
* @param {UpdateInStorageProps.url} updateInStorageProps.url - The URL of the logo to update
|
|
31
|
+
* @param {UpdateInStorageProps.newUrl} updateInStorageProps.newUrl - The new URL of the logo
|
|
32
|
+
* @param {UpdateInStorageProps.mediaId} updateInStorageProps.mediaId - The new media ID of the logo
|
|
33
|
+
* @returns {Logo} The logo that was updated
|
|
34
|
+
*/
|
|
35
|
+
export function updateLogo({ siteId, url, newUrl, mediaId }) {
|
|
36
|
+
const storedContent = getSiteLogoHistory(siteId);
|
|
37
|
+
const index = storedContent.findIndex(logo => logo.url === url);
|
|
38
|
+
if (index > -1) {
|
|
39
|
+
storedContent[index].url = newUrl;
|
|
40
|
+
storedContent[index].mediaId = mediaId;
|
|
41
|
+
}
|
|
42
|
+
localStorage.setItem(`logo-history-${siteId}`, JSON.stringify(storedContent.slice(-MAX_LOGOS)));
|
|
43
|
+
return storedContent[index];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the logo history for a site.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} siteId - The site ID to get the logo history for
|
|
49
|
+
* @returns {Logo[]} The logo history for the site
|
|
50
|
+
*/
|
|
51
|
+
export function getSiteLogoHistory(siteId) {
|
|
52
|
+
const storedString = localStorage.getItem(`logo-history-${siteId}`);
|
|
53
|
+
let storedContent = storedString ? JSON.parse(storedString) : [];
|
|
54
|
+
// Ensure that the stored content is an array
|
|
55
|
+
if (!Array.isArray(storedContent)) {
|
|
56
|
+
storedContent = [];
|
|
57
|
+
}
|
|
58
|
+
// Ensure a maximum of 10 logos are stored
|
|
59
|
+
storedContent = storedContent.slice(-MAX_LOGOS);
|
|
60
|
+
// Ensure that the stored content is an array of Logo objects
|
|
61
|
+
storedContent = storedContent
|
|
62
|
+
.filter(logo => {
|
|
63
|
+
return (typeof logo === 'object' &&
|
|
64
|
+
typeof logo.url === 'string' &&
|
|
65
|
+
typeof logo.description === 'string');
|
|
66
|
+
})
|
|
67
|
+
.map(logo => ({
|
|
68
|
+
url: logo.url,
|
|
69
|
+
description: logo.description,
|
|
70
|
+
mediaId: logo.mediaId,
|
|
71
|
+
}));
|
|
72
|
+
return storedContent;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if the logo history for a site is empty.
|
|
76
|
+
*
|
|
77
|
+
* @param {string }siteId - The site ID to check the logo history for
|
|
78
|
+
* @returns {boolean} Whether the logo history for the site is empty
|
|
79
|
+
*/
|
|
80
|
+
export function isLogoHistoryEmpty(siteId) {
|
|
81
|
+
const storedContent = getSiteLogoHistory(siteId);
|
|
82
|
+
return storedContent.length === 0;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Remove an entry from the site's logo history.
|
|
86
|
+
*
|
|
87
|
+
* @param {RemoveFromStorageProps} removeFromStorageProps - The properties to remove from storage
|
|
88
|
+
* @param {RemoveFromStorageProps.siteId} removeFromStorageProps.siteId - The site ID
|
|
89
|
+
* @param {RemoveFromStorageProps.mediaId} removeFromStorageProps.mediaId - The media ID of the logo to remove
|
|
90
|
+
* @returns {void}
|
|
91
|
+
*/
|
|
92
|
+
export function removeLogo({ siteId, mediaId }) {
|
|
93
|
+
const storedContent = getSiteLogoHistory(siteId);
|
|
94
|
+
const index = storedContent.findIndex(logo => logo.mediaId === mediaId);
|
|
95
|
+
if (index === -1) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
storedContent.splice(index, 1);
|
|
99
|
+
localStorage.setItem(`logo-history-${siteId}`, JSON.stringify(storedContent));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Clear deleted media from the site's logo history, checking if the media still exists on the backend.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} siteId - The site ID to clear deleted media for
|
|
105
|
+
* @returns {Promise<void>}
|
|
106
|
+
*/
|
|
107
|
+
export async function clearDeletedMedia(siteId) {
|
|
108
|
+
const storedContent = getSiteLogoHistory(siteId);
|
|
109
|
+
const checks = storedContent
|
|
110
|
+
.filter(({ mediaId }) => mediaId !== undefined)
|
|
111
|
+
.map(({ mediaId }) => new Promise((resolve, reject) => {
|
|
112
|
+
mediaExists({ siteId, mediaId })
|
|
113
|
+
.then(exists => resolve({ mediaId, exists }))
|
|
114
|
+
.catch(error => reject(error));
|
|
115
|
+
}));
|
|
116
|
+
try {
|
|
117
|
+
const responses = (await Promise.all(checks));
|
|
118
|
+
responses
|
|
119
|
+
.filter(({ exists }) => !exists)
|
|
120
|
+
.forEach(({ mediaId }) => removeLogo({ siteId, mediaId }));
|
|
121
|
+
}
|
|
122
|
+
catch (error) { } // Assume that the media exists if there was a network error and do nothing to avoid data loss.
|
|
123
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types
|
|
3
|
+
*/
|
|
4
|
+
import type { CheckMediaProps } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Uses the media information to confirm it exists or not on the server.
|
|
7
|
+
*
|
|
8
|
+
* @param {CheckMediaProps} checkMediaProps - the media details to check
|
|
9
|
+
* @param {CheckMediaProps.mediaId} checkMediaProps.mediaId - the id of the media to check
|
|
10
|
+
* @returns {Promise<boolean>} - true if the media exists, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
export declare function mediaExists({ mediaId }: CheckMediaProps): Promise<boolean>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import apiFetch from '../../api-fetch/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Uses the media information to confirm it exists or not on the server.
|
|
7
|
+
*
|
|
8
|
+
* @param {CheckMediaProps} checkMediaProps - the media details to check
|
|
9
|
+
* @param {CheckMediaProps.mediaId} checkMediaProps.mediaId - the id of the media to check
|
|
10
|
+
* @returns {Promise<boolean>} - true if the media exists, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
export async function mediaExists({ mediaId }) {
|
|
13
|
+
const id = Number(mediaId);
|
|
14
|
+
if (Number.isNaN(id)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Using apiFetch directly here because we don't want to limit the number of concurrent media checks
|
|
19
|
+
// We store at most 10 logos in the local storage, so the number of concurrent requests should be limited
|
|
20
|
+
await apiFetch({
|
|
21
|
+
path: `/wp/v2/media/${Number(mediaId)}`,
|
|
22
|
+
method: 'GET',
|
|
23
|
+
});
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const status = error?.data?.status;
|
|
28
|
+
if (status === 404) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types
|
|
3
|
+
*/
|
|
4
|
+
import type { SetSiteLogoProps, SetSiteLogoResponseProps } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Set the site logo using a backend request.
|
|
7
|
+
*
|
|
8
|
+
* @param {SetSiteLogoProps} setSiteLogoProps - The properties to set the site logo
|
|
9
|
+
* @param {SetSiteLogoProps.siteId} setSiteLogoProps.siteId - The site ID
|
|
10
|
+
* @param {SetSiteLogoProps.imageId} setSiteLogoProps.imageId - The image ID to set as the site logo
|
|
11
|
+
* @returns {Promise<SetSiteLogoResponseProps>} The response from the request
|
|
12
|
+
*/
|
|
13
|
+
export declare function setSiteLogo({ siteId, imageId }: SetSiteLogoProps): Promise<SetSiteLogoResponseProps>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import wpcomLimitedRequest from './wpcom-limited-request.js';
|
|
5
|
+
/**
|
|
6
|
+
* Set the site logo using a backend request.
|
|
7
|
+
*
|
|
8
|
+
* @param {SetSiteLogoProps} setSiteLogoProps - The properties to set the site logo
|
|
9
|
+
* @param {SetSiteLogoProps.siteId} setSiteLogoProps.siteId - The site ID
|
|
10
|
+
* @param {SetSiteLogoProps.imageId} setSiteLogoProps.imageId - The image ID to set as the site logo
|
|
11
|
+
* @returns {Promise<SetSiteLogoResponseProps>} The response from the request
|
|
12
|
+
*/
|
|
13
|
+
export async function setSiteLogo({ siteId, imageId }) {
|
|
14
|
+
const body = {
|
|
15
|
+
site_logo: imageId,
|
|
16
|
+
site_icon: imageId,
|
|
17
|
+
};
|
|
18
|
+
return wpcomLimitedRequest({
|
|
19
|
+
path: `/sites/${String(siteId)}/settings`,
|
|
20
|
+
apiVersion: 'v2',
|
|
21
|
+
apiNamespace: 'wp/v2',
|
|
22
|
+
body,
|
|
23
|
+
query: 'source=jetpack-ai',
|
|
24
|
+
method: 'POST',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concurrency-limited request to wpcom-proxy-request.
|
|
3
|
+
* @param { object } params - The request params, as expected by apiFetch.
|
|
4
|
+
* @returns { Promise } The response.
|
|
5
|
+
* @throws { Error } If there are too many concurrent requests.
|
|
6
|
+
*/
|
|
7
|
+
export default function wpcomLimitedRequest<T>(params: object): Promise<T>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import apiFetch from '../../api-fetch/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Types
|
|
7
|
+
*/
|
|
8
|
+
const MAX_CONCURRENT_REQUESTS = 5;
|
|
9
|
+
let concurrentCounter = 0;
|
|
10
|
+
let lastCallTimestamp = null;
|
|
11
|
+
/**
|
|
12
|
+
* Concurrency-limited request to wpcom-proxy-request.
|
|
13
|
+
* @param { object } params - The request params, as expected by apiFetch.
|
|
14
|
+
* @returns { Promise } The response.
|
|
15
|
+
* @throws { Error } If there are too many concurrent requests.
|
|
16
|
+
*/
|
|
17
|
+
export default async function wpcomLimitedRequest(params) {
|
|
18
|
+
concurrentCounter += 1;
|
|
19
|
+
if (concurrentCounter > MAX_CONCURRENT_REQUESTS) {
|
|
20
|
+
concurrentCounter -= 1;
|
|
21
|
+
throw new Error('Too many requests');
|
|
22
|
+
}
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
// Check if the last call was made less than 100 milliseconds ago
|
|
25
|
+
if (lastCallTimestamp && now - lastCallTimestamp < 100) {
|
|
26
|
+
concurrentCounter -= 1;
|
|
27
|
+
throw new Error('Too many requests');
|
|
28
|
+
}
|
|
29
|
+
lastCallTimestamp = now; // Update the timestamp
|
|
30
|
+
return apiFetch(params).finally(() => {
|
|
31
|
+
concurrentCounter -= 1;
|
|
32
|
+
});
|
|
33
|
+
}
|