@automattic/jetpack-ai-client 0.16.4 → 0.18.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 (43) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/build/components/ai-control/extension-ai-control.d.ts +2 -1
  3. package/build/components/ai-control/extension-ai-control.js +5 -2
  4. package/build/components/message/index.d.ts +6 -0
  5. package/build/components/message/index.js +13 -0
  6. package/build/jwt/index.js +1 -1
  7. package/build/logo-generator/components/fair-usage-notice.d.ts +11 -0
  8. package/build/logo-generator/components/fair-usage-notice.js +19 -0
  9. package/build/logo-generator/components/generator-modal.js +13 -6
  10. package/build/logo-generator/components/history-carousel.js +6 -5
  11. package/build/logo-generator/components/logo-presenter.js +8 -3
  12. package/build/logo-generator/components/prompt.js +5 -4
  13. package/build/logo-generator/hooks/use-checkout.js +3 -2
  14. package/build/logo-generator/hooks/use-fair-usage-notice-message.d.ts +3 -0
  15. package/build/logo-generator/hooks/use-fair-usage-notice-message.js +43 -0
  16. package/build/logo-generator/hooks/use-logo-generator.d.ts +3 -0
  17. package/build/logo-generator/hooks/use-logo-generator.js +7 -2
  18. package/build/logo-generator/store/actions.d.ts +4 -0
  19. package/build/logo-generator/store/actions.js +8 -1
  20. package/build/logo-generator/store/constants.d.ts +1 -0
  21. package/build/logo-generator/store/constants.js +1 -0
  22. package/build/logo-generator/store/reducer.d.ts +37 -0
  23. package/build/logo-generator/store/reducer.js +10 -1
  24. package/build/logo-generator/store/selectors.d.ts +14 -0
  25. package/build/logo-generator/store/selectors.js +21 -0
  26. package/build/logo-generator/store/types.d.ts +13 -0
  27. package/package.json +13 -13
  28. package/src/components/ai-control/extension-ai-control.tsx +10 -1
  29. package/src/components/message/index.tsx +20 -0
  30. package/src/jwt/index.ts +1 -2
  31. package/src/logo-generator/components/fair-usage-notice.tsx +38 -0
  32. package/src/logo-generator/components/generator-modal.tsx +24 -7
  33. package/src/logo-generator/components/history-carousel.tsx +8 -1
  34. package/src/logo-generator/components/logo-presenter.tsx +16 -5
  35. package/src/logo-generator/components/prompt.tsx +8 -4
  36. package/src/logo-generator/hooks/use-checkout.ts +7 -2
  37. package/src/logo-generator/hooks/use-fair-usage-notice-message.tsx +68 -0
  38. package/src/logo-generator/hooks/use-logo-generator.ts +8 -0
  39. package/src/logo-generator/store/actions.ts +9 -0
  40. package/src/logo-generator/store/constants.ts +1 -0
  41. package/src/logo-generator/store/reducer.ts +12 -0
  42. package/src/logo-generator/store/selectors.ts +24 -0
  43. package/src/logo-generator/store/types.ts +13 -0
@@ -24,6 +24,7 @@ import type { SiteDetails } from '../types.js';
24
24
  * @param {Array< { url: string; description: string; mediaId?: number } >} action.history - The logo history
25
25
  * @param {RequestError} action.error - The error to set
26
26
  * @param {string} action.context - The context where the tool is being used
27
+ * @param {boolean} action.isLoadingHistory - Whether the history is being loaded
27
28
  * @return {LogoGeneratorStateProp} The new state
28
29
  */
29
30
  export default function reducer(state: LogoGeneratorStateProp, action: {
@@ -51,6 +52,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
51
52
  }>;
52
53
  error?: RequestError;
53
54
  context?: string;
55
+ isLoadingHistory?: boolean;
54
56
  }): {
55
57
  features: {
56
58
  aiAssistantFeature: {
@@ -77,6 +79,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
77
79
  logo: number;
78
80
  };
79
81
  };
82
+ featuresControl?: import("./types.js").FeaturesControl;
80
83
  _meta?: {
81
84
  isRequesting: boolean;
82
85
  asyncRequestCountdown: number;
@@ -97,6 +100,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
97
100
  saveToLibraryError?: RequestError;
98
101
  logoUpdateError?: RequestError;
99
102
  context: string;
103
+ isLoadingHistory: boolean;
100
104
  };
101
105
  siteDetails?: SiteDetails | Record<string, never>;
102
106
  history: import("./types.js").Logo[];
@@ -119,6 +123,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
119
123
  saveToLibraryError?: RequestError;
120
124
  logoUpdateError?: RequestError;
121
125
  context: string;
126
+ isLoadingHistory: boolean;
122
127
  };
123
128
  siteDetails?: SiteDetails | Record<string, never>;
124
129
  features: {
@@ -137,6 +142,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
137
142
  saveToLibraryError?: RequestError;
138
143
  logoUpdateError?: RequestError;
139
144
  context?: string;
145
+ isLoadingHistory?: boolean;
140
146
  };
141
147
  siteDetails?: SiteDetails | Record<string, never>;
142
148
  features: {
@@ -157,6 +163,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
157
163
  saveToLibraryError?: RequestError;
158
164
  logoUpdateError?: RequestError;
159
165
  context?: string;
166
+ isLoadingHistory?: boolean;
160
167
  };
161
168
  siteDetails?: SiteDetails | Record<string, never>;
162
169
  features: {
@@ -177,6 +184,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
177
184
  saveToLibraryError?: RequestError;
178
185
  logoUpdateError?: RequestError;
179
186
  context?: string;
187
+ isLoadingHistory?: boolean;
180
188
  };
181
189
  siteDetails?: SiteDetails | Record<string, never>;
182
190
  features: {
@@ -197,6 +205,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
197
205
  saveToLibraryError?: RequestError;
198
206
  logoUpdateError?: RequestError;
199
207
  context?: string;
208
+ isLoadingHistory?: boolean;
200
209
  };
201
210
  siteDetails?: SiteDetails | Record<string, never>;
202
211
  features: {
@@ -217,6 +226,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
217
226
  saveToLibraryError?: RequestError;
218
227
  logoUpdateError?: RequestError;
219
228
  context?: string;
229
+ isLoadingHistory?: boolean;
220
230
  };
221
231
  siteDetails?: SiteDetails | Record<string, never>;
222
232
  features: {
@@ -237,6 +247,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
237
247
  saveToLibraryError?: RequestError;
238
248
  logoUpdateError?: RequestError;
239
249
  context?: string;
250
+ isLoadingHistory?: boolean;
240
251
  };
241
252
  siteDetails?: SiteDetails | Record<string, never>;
242
253
  features: {
@@ -257,6 +268,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
257
268
  saveToLibraryError?: RequestError;
258
269
  logoUpdateError?: RequestError;
259
270
  context?: string;
271
+ isLoadingHistory?: boolean;
260
272
  };
261
273
  siteDetails?: SiteDetails | Record<string, never>;
262
274
  features: {
@@ -277,6 +289,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
277
289
  saveToLibraryError?: RequestError;
278
290
  logoUpdateError?: RequestError;
279
291
  context?: string;
292
+ isLoadingHistory?: boolean;
280
293
  };
281
294
  siteDetails?: SiteDetails | Record<string, never>;
282
295
  features: {
@@ -297,6 +310,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
297
310
  logoFetchError?: RequestError;
298
311
  logoUpdateError?: RequestError;
299
312
  context?: string;
313
+ isLoadingHistory?: boolean;
300
314
  };
301
315
  siteDetails?: SiteDetails | Record<string, never>;
302
316
  features: {
@@ -317,6 +331,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
317
331
  logoFetchError?: RequestError;
318
332
  saveToLibraryError?: RequestError;
319
333
  context?: string;
334
+ isLoadingHistory?: boolean;
320
335
  };
321
336
  siteDetails?: SiteDetails | Record<string, never>;
322
337
  features: {
@@ -337,6 +352,28 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
337
352
  logoFetchError?: RequestError;
338
353
  saveToLibraryError?: RequestError;
339
354
  logoUpdateError?: RequestError;
355
+ isLoadingHistory?: boolean;
356
+ };
357
+ siteDetails?: SiteDetails | Record<string, never>;
358
+ features: {
359
+ aiAssistantFeature?: AiFeatureStateProps;
360
+ };
361
+ history: import("./types.js").Logo[];
362
+ selectedLogoIndex: number;
363
+ } | {
364
+ _meta: {
365
+ isLoadingHistory: boolean;
366
+ isSavingLogoToLibrary?: boolean;
367
+ isApplyingLogo?: boolean;
368
+ isRequestingImage?: boolean;
369
+ isEnhancingPrompt?: boolean;
370
+ featureFetchError?: RequestError;
371
+ firstLogoPromptFetchError?: RequestError;
372
+ enhancePromptFetchError?: RequestError;
373
+ logoFetchError?: RequestError;
374
+ saveToLibraryError?: RequestError;
375
+ logoUpdateError?: RequestError;
376
+ context?: string;
340
377
  };
341
378
  siteDetails?: SiteDetails | Record<string, never>;
342
379
  features: {
@@ -2,7 +2,7 @@
2
2
  * Types & Constants
3
3
  */
4
4
  import { DEFAULT_LOGO_COST } from '../constants.js';
5
- import { ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT, ACTION_REQUEST_AI_ASSISTANT_FEATURE, ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE, ACTION_STORE_AI_ASSISTANT_FEATURE, ASYNC_REQUEST_COUNTDOWN_INIT_VALUE, FREE_PLAN_REQUESTS_LIMIT, UNLIMITED_PLAN_REQUESTS_LIMIT, ACTION_SET_TIER_PLANS_ENABLED, ACTION_SET_SITE_DETAILS, ACTION_SET_SELECTED_LOGO_INDEX, ACTION_ADD_LOGO_TO_HISTORY, ACTION_SAVE_SELECTED_LOGO, ACTION_SET_IS_SAVING_LOGO_TO_LIBRARY, ACTION_SET_IS_REQUESTING_IMAGE, ACTION_SET_IS_APPLYING_LOGO, ACTION_SET_IS_ENHANCING_PROMPT, ACTION_SET_SITE_HISTORY, ACTION_SET_FEATURE_FETCH_ERROR, ACTION_SET_FIRST_LOGO_PROMPT_FETCH_ERROR, ACTION_SET_ENHANCE_PROMPT_FETCH_ERROR, ACTION_SET_LOGO_FETCH_ERROR, ACTION_SET_SAVE_TO_LIBRARY_ERROR, ACTION_SET_LOGO_UPDATE_ERROR, ACTION_SET_CONTEXT, } from './constants.js';
5
+ import { ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT, ACTION_REQUEST_AI_ASSISTANT_FEATURE, ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE, ACTION_STORE_AI_ASSISTANT_FEATURE, ASYNC_REQUEST_COUNTDOWN_INIT_VALUE, FREE_PLAN_REQUESTS_LIMIT, UNLIMITED_PLAN_REQUESTS_LIMIT, ACTION_SET_TIER_PLANS_ENABLED, ACTION_SET_SITE_DETAILS, ACTION_SET_SELECTED_LOGO_INDEX, ACTION_ADD_LOGO_TO_HISTORY, ACTION_SAVE_SELECTED_LOGO, ACTION_SET_IS_SAVING_LOGO_TO_LIBRARY, ACTION_SET_IS_REQUESTING_IMAGE, ACTION_SET_IS_APPLYING_LOGO, ACTION_SET_IS_ENHANCING_PROMPT, ACTION_SET_SITE_HISTORY, ACTION_SET_FEATURE_FETCH_ERROR, ACTION_SET_FIRST_LOGO_PROMPT_FETCH_ERROR, ACTION_SET_ENHANCE_PROMPT_FETCH_ERROR, ACTION_SET_LOGO_FETCH_ERROR, ACTION_SET_SAVE_TO_LIBRARY_ERROR, ACTION_SET_LOGO_UPDATE_ERROR, ACTION_SET_CONTEXT, ACTION_SET_IS_LOADING_HISTORY, } from './constants.js';
6
6
  import INITIAL_STATE from './initial-state.js';
7
7
  /**
8
8
  * Reducer for the Logo Generator store.
@@ -28,6 +28,7 @@ import INITIAL_STATE from './initial-state.js';
28
28
  * @param {Array< { url: string; description: string; mediaId?: number } >} action.history - The logo history
29
29
  * @param {RequestError} action.error - The error to set
30
30
  * @param {string} action.context - The context where the tool is being used
31
+ * @param {boolean} action.isLoadingHistory - Whether the history is being loaded
31
32
  * @return {LogoGeneratorStateProp} The new state
32
33
  */
33
34
  export default function reducer(state = INITIAL_STATE, action) {
@@ -288,6 +289,14 @@ export default function reducer(state = INITIAL_STATE, action) {
288
289
  context: action.context,
289
290
  },
290
291
  };
292
+ case ACTION_SET_IS_LOADING_HISTORY:
293
+ return {
294
+ ...state,
295
+ _meta: {
296
+ ...(state._meta ?? {}),
297
+ isLoadingHistory: action.isLoadingHistory,
298
+ },
299
+ };
291
300
  }
292
301
  return state;
293
302
  }
@@ -115,5 +115,19 @@ declare const selectors: {
115
115
  * @return {string} The context value.
116
116
  */
117
117
  getContext(state: LogoGeneratorStateProp): string;
118
+ /**
119
+ * Get tier plans enabled status.
120
+ *
121
+ * @param {LogoGeneratorStateProp} state - The app state tree.
122
+ * @return {boolean} The tier plans enabled status.
123
+ */
124
+ getTierPlansEnabled(state: LogoGeneratorStateProp): boolean;
125
+ /**
126
+ * Get tier plans enabled status.
127
+ *
128
+ * @param {LogoGeneratorStateProp} state - The app state tree.
129
+ * @return {boolean} The loading logo history status.
130
+ */
131
+ getIsLoadingHistory(state: LogoGeneratorStateProp): boolean;
118
132
  };
119
133
  export default selectors;
@@ -104,6 +104,9 @@ const selectors = {
104
104
  */
105
105
  getRequireUpgrade(state) {
106
106
  const feature = state.features.aiAssistantFeature;
107
+ if (!feature?.tierPlansEnabled) {
108
+ return feature?.requireUpgrade;
109
+ }
107
110
  const logoCost = feature?.costs?.['jetpack-ai-logo-generator']?.logo ?? DEFAULT_LOGO_COST;
108
111
  const currentLimit = feature?.currentTier?.value || 0;
109
112
  const currentUsage = feature?.usagePeriod?.requestsCount || 0;
@@ -169,5 +172,23 @@ const selectors = {
169
172
  getContext(state) {
170
173
  return state._meta?.context ?? '';
171
174
  },
175
+ /**
176
+ * Get tier plans enabled status.
177
+ *
178
+ * @param {LogoGeneratorStateProp} state - The app state tree.
179
+ * @return {boolean} The tier plans enabled status.
180
+ */
181
+ getTierPlansEnabled(state) {
182
+ return state.features.aiAssistantFeature?.tierPlansEnabled ?? false;
183
+ },
184
+ /**
185
+ * Get tier plans enabled status.
186
+ *
187
+ * @param {LogoGeneratorStateProp} state - The app state tree.
188
+ * @return {boolean} The loading logo history status.
189
+ */
190
+ getIsLoadingHistory(state) {
191
+ return state._meta?.isLoadingHistory ?? false;
192
+ },
172
193
  };
173
194
  export default selectors;
@@ -53,6 +53,14 @@ export type TierProp = {
53
53
  export type TierLimitProp = TierUnlimitedProps['limit'] | TierFreeProps['limit'] | Tier100Props['limit'] | Tier200Props['limit'] | Tier500Props['limit'] | Tier750Props['limit'] | Tier1000Props['limit'];
54
54
  export type TierSlugProp = TierUnlimitedProps['slug'] | TierFreeProps['slug'] | Tier100Props['slug'] | Tier200Props['slug'] | Tier500Props['slug'] | Tier750Props['slug'] | Tier1000Props['slug'];
55
55
  export type TierValueProp = TierUnlimitedProps['value'] | TierFreeProps['value'] | Tier100Props['value'] | Tier200Props['value'] | Tier500Props['value'] | Tier750Props['value'] | Tier1000Props['value'];
56
+ export type FeatureControl = {
57
+ enabled: boolean;
58
+ 'min-jetpack-version': string;
59
+ [key: string]: FeatureControl | boolean | string;
60
+ };
61
+ export type FeaturesControl = {
62
+ [key: string]: FeatureControl;
63
+ };
56
64
  export type AiFeatureProps = {
57
65
  hasFeature: boolean;
58
66
  isOverLimit: boolean;
@@ -75,6 +83,7 @@ export type AiFeatureProps = {
75
83
  logo: number;
76
84
  };
77
85
  };
86
+ featuresControl?: FeaturesControl;
78
87
  };
79
88
  export type AiFeatureStateProps = AiFeatureProps & {
80
89
  _meta?: {
@@ -103,6 +112,7 @@ export type LogoGeneratorStateProp = {
103
112
  saveToLibraryError?: RequestError;
104
113
  logoUpdateError?: RequestError;
105
114
  context: string;
115
+ isLoadingHistory: boolean;
106
116
  };
107
117
  siteDetails?: SiteDetails | Record<string, never>;
108
118
  features: {
@@ -131,6 +141,8 @@ export type Selectors = {
131
141
  getSaveToLibraryError(): RequestError;
132
142
  getLogoUpdateError(): RequestError;
133
143
  getContext(): string;
144
+ getTierPlansEnabled(): boolean;
145
+ getIsLoadingHistory(): boolean;
134
146
  };
135
147
  export type AiAssistantFeatureEndpointResponseProps = {
136
148
  'is-enabled': boolean;
@@ -157,6 +169,7 @@ export type AiAssistantFeatureEndpointResponseProps = {
157
169
  logo: number;
158
170
  };
159
171
  };
172
+ 'features-control'?: FeaturesControl;
160
173
  };
161
174
  export type SaveLogo = (logo: Logo) => Promise<{
162
175
  mediaId: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.16.4",
4
+ "version": "0.18.0",
5
5
  "description": "A JS client for consuming Jetpack AI services",
6
6
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme",
7
7
  "bugs": {
@@ -43,21 +43,21 @@
43
43
  "main": "./build/index.js",
44
44
  "types": "./build/index.d.ts",
45
45
  "dependencies": {
46
- "@automattic/jetpack-base-styles": "^0.6.30",
47
- "@automattic/jetpack-connection": "^0.35.3",
48
- "@automattic/jetpack-shared-extension-utils": "^0.15.5",
46
+ "@automattic/jetpack-base-styles": "^0.6.31",
47
+ "@automattic/jetpack-connection": "^0.35.6",
48
+ "@automattic/jetpack-shared-extension-utils": "^0.15.8",
49
49
  "@microsoft/fetch-event-source": "2.0.1",
50
50
  "@types/react": "18.3.3",
51
51
  "@types/wordpress__block-editor": "11.5.15",
52
- "@wordpress/api-fetch": "7.5.0",
53
- "@wordpress/blob": "4.5.0",
54
- "@wordpress/block-editor": "14.0.0",
55
- "@wordpress/components": "28.5.0",
56
- "@wordpress/compose": "7.5.0",
57
- "@wordpress/data": "10.5.0",
58
- "@wordpress/element": "6.5.0",
59
- "@wordpress/i18n": "5.5.0",
60
- "@wordpress/icons": "10.5.0",
52
+ "@wordpress/api-fetch": "7.6.0",
53
+ "@wordpress/blob": "4.6.0",
54
+ "@wordpress/block-editor": "14.1.0",
55
+ "@wordpress/components": "28.6.0",
56
+ "@wordpress/compose": "7.6.0",
57
+ "@wordpress/data": "10.6.0",
58
+ "@wordpress/element": "6.6.0",
59
+ "@wordpress/i18n": "5.6.0",
60
+ "@wordpress/icons": "10.6.0",
61
61
  "clsx": "2.1.1",
62
62
  "debug": "4.3.4",
63
63
  "markdown-it": "14.0.0",
@@ -10,7 +10,12 @@ import React, { forwardRef } from 'react';
10
10
  /**
11
11
  * Internal dependencies
12
12
  */
13
- import { GuidelineMessage, ErrorMessage, UpgradeMessage } from '../message/index.js';
13
+ import {
14
+ GuidelineMessage,
15
+ ErrorMessage,
16
+ UpgradeMessage,
17
+ FairUsageLimitMessage,
18
+ } from '../message/index.js';
14
19
  import AIControl from './ai-control.js';
15
20
  import './style.scss';
16
21
  /**
@@ -31,6 +36,7 @@ type ExtensionAIControlProps = {
31
36
  error?: RequestingErrorProps;
32
37
  requestsRemaining?: number;
33
38
  showUpgradeMessage?: boolean;
39
+ showFairUsageMessage?: boolean;
34
40
  upgradeUrl?: string;
35
41
  wrapperRef?: React.MutableRefObject< HTMLDivElement | null >;
36
42
  onChange?: ( newValue: string ) => void;
@@ -62,6 +68,7 @@ export function ExtensionAIControl(
62
68
  error,
63
69
  requestsRemaining,
64
70
  showUpgradeMessage = false,
71
+ showFairUsageMessage = false,
65
72
  upgradeUrl,
66
73
  wrapperRef,
67
74
  onChange,
@@ -215,6 +222,8 @@ export function ExtensionAIControl(
215
222
  upgradeUrl={ upgradeUrl }
216
223
  />
217
224
  );
225
+ } else if ( showFairUsageMessage ) {
226
+ message = <FairUsageLimitMessage />;
218
227
  } else if ( showUpgradeMessage ) {
219
228
  message = (
220
229
  <UpgradeMessage
@@ -2,6 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { ExternalLink, Button } from '@wordpress/components';
5
+ import { createInterpolateElement } from '@wordpress/element';
5
6
  import { __, sprintf } from '@wordpress/i18n';
6
7
  import { Icon, check, arrowRight } from '@wordpress/icons';
7
8
  import clsx from 'clsx';
@@ -114,6 +115,25 @@ export function GuidelineMessage(): React.ReactElement {
114
115
  );
115
116
  }
116
117
 
118
+ /**
119
+ * React component to render a fair usage limit message.
120
+ *
121
+ * @return {React.ReactElement } - Message component.
122
+ */
123
+ export function FairUsageLimitMessage(): React.ReactElement {
124
+ const message = __(
125
+ "You've reached this month's request limit, per our <link>fair usage policy</link>",
126
+ 'jetpack-ai-client'
127
+ );
128
+ const element = createInterpolateElement( message, {
129
+ link: (
130
+ <ExternalLink href="https://jetpack.com/redirect/?source=ai-assistant-fair-usage-policy" />
131
+ ),
132
+ } );
133
+
134
+ return <Message severity={ MESSAGE_SEVERITY_WARNING }>{ element }</Message>;
135
+ }
136
+
117
137
  /**
118
138
  * React component to render an upgrade message for free tier users
119
139
  *
package/src/jwt/index.ts CHANGED
@@ -49,8 +49,6 @@ export default async function requestJwt( {
49
49
  siteId = siteId || window.JP_CONNECTION_INITIAL_STATE.siteSuffix;
50
50
  expirationTime = expirationTime || JWT_TOKEN_EXPIRATION_TIME;
51
51
 
52
- const isSimple = isSimpleSite();
53
-
54
52
  // Trying to pick the token from localStorage
55
53
  const token = localStorage.getItem( JWT_TOKEN_ID );
56
54
  let tokenData: TokenDataProps | null = null;
@@ -70,6 +68,7 @@ export default async function requestJwt( {
70
68
 
71
69
  let data: TokenDataEndpointResponseProps;
72
70
 
71
+ const isSimple = isSimpleSite();
73
72
  if ( ! isSimple ) {
74
73
  data = await apiFetch( {
75
74
  /*
@@ -0,0 +1,38 @@
1
+ import { Notice } from '@wordpress/components';
2
+ import useFairUsageNoticeMessage from '../hooks/use-fair-usage-notice-message.js';
3
+ /**
4
+ * Types
5
+ */
6
+ import type { ReactElement } from 'react';
7
+
8
+ type FairUsageNoticeProps = {
9
+ variant?: 'error' | 'muted';
10
+ };
11
+
12
+ /**
13
+ * The fair usage notice component.
14
+ * @param {FairUsageNoticeProps} props - Fair usage notice component props.
15
+ * @param {FairUsageNoticeProps.variant} props.variant - The variant of the notice to render.
16
+ * @return {ReactElement} the Notice component with the fair usage message.
17
+ */
18
+ export const FairUsageNotice = ( { variant = 'error' }: FairUsageNoticeProps ) => {
19
+ const useFairUsageNoticeMessageElement = useFairUsageNoticeMessage();
20
+
21
+ if ( variant === 'muted' ) {
22
+ return (
23
+ <span className="jetpack-ai-fair-usage-notice-muted-variant">
24
+ { useFairUsageNoticeMessageElement }
25
+ </span>
26
+ );
27
+ }
28
+
29
+ if ( variant === 'error' ) {
30
+ return (
31
+ <Notice status="error" isDismissible={ false } className="jetpack-ai-fair-usage-notice">
32
+ { useFairUsageNoticeMessageElement }
33
+ </Notice>
34
+ );
35
+ }
36
+
37
+ return null;
38
+ };
@@ -24,6 +24,7 @@ import useLogoGenerator from '../hooks/use-logo-generator.js';
24
24
  import useRequestErrors from '../hooks/use-request-errors.js';
25
25
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
26
26
  import { STORE_NAME } from '../store/index.js';
27
+ // import { FairUsageNotice } from './fair-usage-notice.js';
27
28
  import { FeatureFetchFailureScreen } from './feature-fetch-failure-screen.js';
28
29
  import { FirstLoadScreen } from './first-load-screen.js';
29
30
  import { HistoryCarousel } from './history-carousel.js';
@@ -51,7 +52,8 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
51
52
  } ) => {
52
53
  const { tracks } = useAnalytics();
53
54
  const { recordEvent: recordTracksEvent } = tracks;
54
- const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch( STORE_NAME );
55
+ const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory, setIsLoadingHistory } =
56
+ useDispatch( STORE_NAME );
55
57
  const { getIsRequestingAiAssistantFeature } = select( STORE_NAME );
56
58
  const [ loadingState, setLoadingState ] = useState<
57
59
  'loadingFeature' | 'analyzing' | 'generating' | null
@@ -61,8 +63,14 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
61
63
  const requestedFeatureData = useRef< boolean >( false );
62
64
  const [ needsFeature, setNeedsFeature ] = useState( false );
63
65
  const [ needsMoreRequests, setNeedsMoreRequests ] = useState( false );
64
- const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } =
65
- useLogoGenerator();
66
+ const {
67
+ selectedLogo,
68
+ getAiAssistantFeature,
69
+ generateFirstPrompt,
70
+ generateLogo,
71
+ setContext,
72
+ tierPlansEnabled,
73
+ } = useLogoGenerator();
66
74
  const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
67
75
  const siteId = siteDetails?.ID;
68
76
  const [ logoAccepted, setLogoAccepted ] = useState( false );
@@ -96,11 +104,12 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
96
104
  const initializeModal = useCallback( async () => {
97
105
  try {
98
106
  const hasHistory = ! isLogoHistoryEmpty( String( siteId ) );
107
+
99
108
  const logoCost = feature?.costs?.[ 'jetpack-ai-logo-generator' ]?.logo ?? DEFAULT_LOGO_COST;
100
109
  const promptCreationCost = 1;
101
110
  const currentLimit = feature?.currentTier?.value || 0;
102
111
  const currentUsage = feature?.usagePeriod?.requestsCount || 0;
103
- const isUnlimited = currentLimit === 1;
112
+ const isUnlimited = ! tierPlansEnabled ? currentLimit > 0 : currentLimit === 1;
104
113
  const hasNoNextTier = ! feature?.nextTier; // If there is no next tier, the user cannot upgrade.
105
114
 
106
115
  // The user needs an upgrade immediately if they have no logos and not enough requests remaining for one prompt and one logo generation.
@@ -108,16 +117,20 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
108
117
  ! isUnlimited &&
109
118
  ! hasNoNextTier &&
110
119
  ! hasHistory &&
111
- currentLimit - currentUsage < logoCost + promptCreationCost;
120
+ ( tierPlansEnabled
121
+ ? currentLimit - currentUsage < logoCost + promptCreationCost
122
+ : currentLimit < currentUsage );
112
123
 
113
124
  // If the site requires an upgrade, show the upgrade screen immediately.
114
- setNeedsFeature( ! feature?.hasFeature ?? true );
125
+ setNeedsFeature( currentLimit === 0 );
115
126
  setNeedsMoreRequests( siteNeedsMoreRequests );
116
- if ( ! feature?.hasFeature || siteNeedsMoreRequests ) {
127
+
128
+ if ( currentLimit === 0 || siteNeedsMoreRequests ) {
117
129
  setLoadingState( null );
118
130
  return;
119
131
  }
120
132
 
133
+ setIsLoadingHistory( true );
121
134
  // Load the logo history and clear any deleted media.
122
135
  await clearDeletedMedia( String( siteId ) );
123
136
  loadLogoHistory( siteId );
@@ -125,6 +138,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
125
138
  // If there is any logo, we do not need to generate a first logo again.
126
139
  if ( ! isLogoHistoryEmpty( String( siteId ) ) ) {
127
140
  setLoadingState( null );
141
+ setIsLoadingHistory( false );
128
142
  return;
129
143
  }
130
144
 
@@ -133,6 +147,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
133
147
  } catch ( error ) {
134
148
  debug( 'Error fetching feature', error );
135
149
  setLoadingState( null );
150
+ setIsLoadingHistory( false );
136
151
  }
137
152
  }, [
138
153
  feature,
@@ -159,6 +174,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
159
174
  setNeedsMoreRequests( false );
160
175
  clearErrors();
161
176
  setLogoAccepted( false );
177
+ setIsLoadingHistory( false );
162
178
  recordTracksEvent( EVENT_MODAL_CLOSE, { context, placement } );
163
179
  };
164
180
 
@@ -227,6 +243,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
227
243
  body = (
228
244
  <>
229
245
  { ! logoAccepted && <Prompt initialPrompt={ initialPrompt } /> }
246
+
230
247
  <LogoPresenter
231
248
  logo={ selectedLogo }
232
249
  onApplyLogo={ handleApplyLogo }
@@ -7,6 +7,7 @@ import clsx from 'clsx';
7
7
  /**
8
8
  * Internal dependencies
9
9
  */
10
+ import loader from '../assets/images/loader.gif';
10
11
  import { EVENT_NAVIGATE } from '../constants.js';
11
12
  import useLogoGenerator from '../hooks/use-logo-generator.js';
12
13
  import './history-carousel.scss';
@@ -18,7 +19,8 @@ import type React from 'react';
18
19
  export const HistoryCarousel: React.FC = () => {
19
20
  const { tracks } = useAnalytics();
20
21
  const { recordEvent: recordTracksEvent } = tracks;
21
- const { logos, selectedLogo, setSelectedLogoIndex, context } = useLogoGenerator();
22
+ const { logos, selectedLogo, setSelectedLogoIndex, context, isLoadingHistory } =
23
+ useLogoGenerator();
22
24
 
23
25
  const handleClick = ( index: number ) => {
24
26
  recordTracksEvent( EVENT_NAVIGATE, {
@@ -41,6 +43,11 @@ export const HistoryCarousel: React.FC = () => {
41
43
 
42
44
  return (
43
45
  <div className="jetpack-ai-logo-generator__carousel">
46
+ { ! logos.length && isLoadingHistory && (
47
+ <Button disabled className={ clsx( 'jetpack-ai-logo-generator__carousel-logo' ) }>
48
+ <img height="48" width="48" src={ loader } alt={ 'loading' } />
49
+ </Button>
50
+ ) }
44
51
  { logos.map( ( logo, index ) => (
45
52
  <Button
46
53
  key={ logo.url }
@@ -129,6 +129,17 @@ const LogoLoading: React.FC = () => {
129
129
  );
130
130
  };
131
131
 
132
+ const LogoFetching: React.FC = () => {
133
+ return (
134
+ <>
135
+ <ImageLoader className="jetpack-ai-logo-generator-modal-presenter__logo" />
136
+ <span className="jetpack-ai-logo-generator-modal-presenter__loading-text">
137
+ { __( 'Fetching previous logos…', 'jetpack-ai-client' ) }
138
+ </span>
139
+ </>
140
+ );
141
+ };
142
+
132
143
  const LogoReady: React.FC< {
133
144
  siteId: string;
134
145
  logo: Logo;
@@ -177,16 +188,16 @@ export const LogoPresenter: React.FC< LogoPresenterProps > = ( {
177
188
  logoAccepted = false,
178
189
  siteId,
179
190
  } ) => {
191
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return -- @todo Start extending jetpack-js-tools/eslintrc/react in eslintrc, then we can remove this disable comment.
180
192
  const { isRequestingImage } = useLogoGenerator();
181
193
  const { saveToLibraryError, logoUpdateError } = useRequestErrors();
182
194
 
183
- if ( ! logo ) {
184
- return null;
185
- }
186
-
187
195
  let logoContent: React.ReactNode;
188
196
 
189
- if ( loading || isRequestingImage ) {
197
+ if ( ! logo ) {
198
+ debug( 'No logo provided, history still loading or logo being generated' );
199
+ logoContent = <LogoFetching />;
200
+ } else if ( loading || isRequestingImage ) {
190
201
  logoContent = <LogoLoading />;
191
202
  } else if ( logoAccepted ) {
192
203
  logoContent = <LogoUpdated logo={ logo } />;
@@ -20,6 +20,7 @@ import {
20
20
  import { useCheckout } from '../hooks/use-checkout.js';
21
21
  import useLogoGenerator from '../hooks/use-logo-generator.js';
22
22
  import useRequestErrors from '../hooks/use-request-errors.js';
23
+ import { FairUsageNotice } from './fair-usage-notice.js';
23
24
  import { UpgradeNudge } from './upgrade-nudge.js';
24
25
  import './prompt.scss';
25
26
 
@@ -44,6 +45,7 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
44
45
  getAiAssistantFeature,
45
46
  requireUpgrade,
46
47
  context,
48
+ tierPlansEnabled,
47
49
  } = useLogoGenerator();
48
50
 
49
51
  const enhancingLabel = __( 'Enhancing…', 'jetpack-ai-client' );
@@ -100,13 +102,14 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
100
102
  const onPromptPaste = ( event: React.ClipboardEvent< HTMLInputElement > ) => {
101
103
  event.preventDefault();
102
104
 
103
- // Paste plain text only
104
- const text = event.clipboardData.getData( 'text/plain' );
105
-
106
105
  const selection = window.getSelection();
107
106
  if ( ! selection || ! selection.rangeCount ) {
108
107
  return;
109
108
  }
109
+
110
+ // Paste plain text only
111
+ const text = event.clipboardData.getData( 'text/plain' );
112
+
110
113
  selection.deleteFromDocument();
111
114
  const range = selection.getRangeAt( 0 );
112
115
  range.insertNode( document.createTextNode( text ) );
@@ -194,7 +197,8 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
194
197
  </Tooltip>
195
198
  </div>
196
199
  ) }
197
- { ! isUnlimited && requireUpgrade && <UpgradeNudge /> }
200
+ { requireUpgrade && tierPlansEnabled && <UpgradeNudge /> }
201
+ { requireUpgrade && ! tierPlansEnabled && <FairUsageNotice /> }
198
202
  { enhancePromptFetchError && (
199
203
  <div className="jetpack-ai-logo-generator__prompt-error">
200
204
  { __( 'Error enhancing prompt. Please try again.', 'jetpack-ai-client' ) }