@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.
Files changed (128) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build/ask-question/sync.d.ts +2 -8
  3. package/build/ask-question/sync.js +20 -19
  4. package/build/hooks/use-image-generator/index.js +1 -1
  5. package/build/hooks/use-save-to-media-library/index.d.ts +12 -0
  6. package/build/hooks/use-save-to-media-library/index.js +74 -0
  7. package/build/index.d.ts +2 -0
  8. package/build/index.js +5 -0
  9. package/build/libs/index.d.ts +1 -1
  10. package/build/libs/index.js +1 -1
  11. package/build/libs/markdown/index.d.ts +2 -2
  12. package/build/libs/markdown/index.js +2 -2
  13. package/build/libs/markdown/markdown-to-html.d.ts +8 -1
  14. package/build/libs/markdown/markdown-to-html.js +10 -1
  15. package/build/logo-generator/assets/icons/ai.d.ts +6 -0
  16. package/build/logo-generator/assets/icons/ai.js +8 -0
  17. package/build/logo-generator/assets/icons/check.d.ts +6 -0
  18. package/build/logo-generator/assets/icons/check.js +8 -0
  19. package/build/logo-generator/assets/icons/logo.d.ts +6 -0
  20. package/build/logo-generator/assets/icons/logo.js +8 -0
  21. package/build/logo-generator/assets/icons/media.d.ts +6 -0
  22. package/build/logo-generator/assets/icons/media.js +8 -0
  23. package/build/logo-generator/components/feature-fetch-failure-screen.d.ts +8 -0
  24. package/build/logo-generator/components/feature-fetch-failure-screen.js +10 -0
  25. package/build/logo-generator/components/first-load-screen.d.ts +5 -0
  26. package/build/logo-generator/components/first-load-screen.js +16 -0
  27. package/build/logo-generator/components/generator-modal.d.ts +7 -0
  28. package/build/logo-generator/components/generator-modal.js +184 -0
  29. package/build/logo-generator/components/history-carousel.d.ts +6 -0
  30. package/build/logo-generator/components/history-carousel.js +36 -0
  31. package/build/logo-generator/components/image-loader.d.ts +7 -0
  32. package/build/logo-generator/components/image-loader.js +12 -0
  33. package/build/logo-generator/components/logo-presenter.d.ts +4 -0
  34. package/build/logo-generator/components/logo-presenter.js +106 -0
  35. package/build/logo-generator/components/prompt.d.ts +5 -0
  36. package/build/logo-generator/components/prompt.js +96 -0
  37. package/build/logo-generator/components/upgrade-nudge.d.ts +2 -0
  38. package/build/logo-generator/components/upgrade-nudge.js +30 -0
  39. package/build/logo-generator/components/upgrade-screen.d.ts +9 -0
  40. package/build/logo-generator/components/upgrade-screen.js +24 -0
  41. package/build/logo-generator/components/visit-site-banner.d.ts +10 -0
  42. package/build/logo-generator/components/visit-site-banner.js +16 -0
  43. package/build/logo-generator/constants.d.ts +16 -0
  44. package/build/logo-generator/constants.js +19 -0
  45. package/build/logo-generator/hooks/use-checkout.d.ts +4 -0
  46. package/build/logo-generator/hooks/use-checkout.js +26 -0
  47. package/build/logo-generator/hooks/use-logo-generator.d.ts +46 -0
  48. package/build/logo-generator/hooks/use-logo-generator.js +286 -0
  49. package/build/logo-generator/hooks/use-request-errors.d.ts +16 -0
  50. package/build/logo-generator/hooks/use-request-errors.js +46 -0
  51. package/build/logo-generator/index.d.ts +1 -0
  52. package/build/logo-generator/index.js +1 -0
  53. package/build/logo-generator/lib/logo-storage.d.ts +58 -0
  54. package/build/logo-generator/lib/logo-storage.js +123 -0
  55. package/build/logo-generator/lib/media-exists.d.ts +12 -0
  56. package/build/logo-generator/lib/media-exists.js +33 -0
  57. package/build/logo-generator/lib/set-site-logo.d.ts +13 -0
  58. package/build/logo-generator/lib/set-site-logo.js +26 -0
  59. package/build/logo-generator/lib/wpcom-limited-request.d.ts +7 -0
  60. package/build/logo-generator/lib/wpcom-limited-request.js +33 -0
  61. package/build/logo-generator/store/actions.d.ts +105 -0
  62. package/build/logo-generator/store/actions.js +193 -0
  63. package/build/logo-generator/store/constants.d.ts +44 -0
  64. package/build/logo-generator/store/constants.js +44 -0
  65. package/build/logo-generator/store/index.d.ts +1 -0
  66. package/build/logo-generator/store/index.js +19 -0
  67. package/build/logo-generator/store/initial-state.d.ts +3 -0
  68. package/build/logo-generator/store/initial-state.js +40 -0
  69. package/build/logo-generator/store/reducer.d.ts +347 -0
  70. package/build/logo-generator/store/reducer.js +293 -0
  71. package/build/logo-generator/store/selectors.d.ts +119 -0
  72. package/build/logo-generator/store/selectors.js +173 -0
  73. package/build/logo-generator/store/types.d.ts +164 -0
  74. package/build/logo-generator/store/types.js +1 -0
  75. package/build/logo-generator/types.d.ts +82 -0
  76. package/build/logo-generator/types.js +1 -0
  77. package/build/types.d.ts +6 -0
  78. package/package.json +5 -3
  79. package/src/ask-question/sync.ts +22 -27
  80. package/src/hooks/use-image-generator/index.ts +1 -1
  81. package/src/hooks/use-save-to-media-library/index.ts +95 -0
  82. package/src/index.ts +6 -0
  83. package/src/libs/index.ts +1 -0
  84. package/src/libs/markdown/index.ts +2 -2
  85. package/src/libs/markdown/markdown-to-html.ts +20 -3
  86. package/src/logo-generator/assets/icons/ai.tsx +21 -0
  87. package/src/logo-generator/assets/icons/check.tsx +23 -0
  88. package/src/logo-generator/assets/icons/icons.scss +5 -0
  89. package/src/logo-generator/assets/icons/logo.tsx +23 -0
  90. package/src/logo-generator/assets/icons/media.tsx +24 -0
  91. package/src/logo-generator/assets/images/jetpack-logo.svg +4 -0
  92. package/src/logo-generator/assets/images/loader.gif +0 -0
  93. package/src/logo-generator/assets/index.d.ts +3 -0
  94. package/src/logo-generator/components/feature-fetch-failure-screen.tsx +35 -0
  95. package/src/logo-generator/components/first-load-screen.scss +12 -0
  96. package/src/logo-generator/components/first-load-screen.tsx +32 -0
  97. package/src/logo-generator/components/generator-modal.scss +92 -0
  98. package/src/logo-generator/components/generator-modal.tsx +291 -0
  99. package/src/logo-generator/components/history-carousel.scss +36 -0
  100. package/src/logo-generator/components/history-carousel.tsx +57 -0
  101. package/src/logo-generator/components/image-loader.tsx +22 -0
  102. package/src/logo-generator/components/logo-presenter.scss +116 -0
  103. package/src/logo-generator/components/logo-presenter.tsx +234 -0
  104. package/src/logo-generator/components/prompt.scss +102 -0
  105. package/src/logo-generator/components/prompt.tsx +211 -0
  106. package/src/logo-generator/components/upgrade-nudge.scss +43 -0
  107. package/src/logo-generator/components/upgrade-nudge.tsx +58 -0
  108. package/src/logo-generator/components/upgrade-screen.tsx +67 -0
  109. package/src/logo-generator/components/visit-site-banner.scss +29 -0
  110. package/src/logo-generator/components/visit-site-banner.tsx +50 -0
  111. package/src/logo-generator/constants.ts +22 -0
  112. package/src/logo-generator/hooks/use-checkout.ts +37 -0
  113. package/src/logo-generator/hooks/use-logo-generator.ts +389 -0
  114. package/src/logo-generator/hooks/use-request-errors.ts +70 -0
  115. package/src/logo-generator/index.ts +1 -0
  116. package/src/logo-generator/lib/logo-storage.ts +166 -0
  117. package/src/logo-generator/lib/media-exists.ts +42 -0
  118. package/src/logo-generator/lib/set-site-logo.ts +32 -0
  119. package/src/logo-generator/lib/wpcom-limited-request.ts +41 -0
  120. package/src/logo-generator/store/actions.ts +251 -0
  121. package/src/logo-generator/store/constants.ts +49 -0
  122. package/src/logo-generator/store/index.ts +25 -0
  123. package/src/logo-generator/store/initial-state.ts +43 -0
  124. package/src/logo-generator/store/reducer.ts +387 -0
  125. package/src/logo-generator/store/selectors.ts +201 -0
  126. package/src/logo-generator/store/types.ts +207 -0
  127. package/src/logo-generator/types.ts +97 -0
  128. 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
+ }