@automattic/jetpack-ai-client 0.17.0 → 0.18.1

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 (37) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/build/jwt/index.js +1 -1
  3. package/build/logo-generator/components/fair-usage-notice.d.ts +11 -0
  4. package/build/logo-generator/components/fair-usage-notice.js +19 -0
  5. package/build/logo-generator/components/generator-modal.js +13 -6
  6. package/build/logo-generator/components/history-carousel.js +6 -5
  7. package/build/logo-generator/components/logo-presenter.js +8 -3
  8. package/build/logo-generator/components/prompt.js +5 -4
  9. package/build/logo-generator/hooks/use-checkout.js +3 -2
  10. package/build/logo-generator/hooks/use-fair-usage-notice-message.d.ts +3 -0
  11. package/build/logo-generator/hooks/use-fair-usage-notice-message.js +43 -0
  12. package/build/logo-generator/hooks/use-logo-generator.d.ts +3 -0
  13. package/build/logo-generator/hooks/use-logo-generator.js +7 -2
  14. package/build/logo-generator/store/actions.d.ts +4 -0
  15. package/build/logo-generator/store/actions.js +7 -1
  16. package/build/logo-generator/store/constants.d.ts +1 -0
  17. package/build/logo-generator/store/constants.js +1 -0
  18. package/build/logo-generator/store/reducer.d.ts +36 -0
  19. package/build/logo-generator/store/reducer.js +10 -1
  20. package/build/logo-generator/store/selectors.d.ts +14 -0
  21. package/build/logo-generator/store/selectors.js +21 -0
  22. package/build/logo-generator/store/types.d.ts +3 -0
  23. package/package.json +13 -13
  24. package/src/jwt/index.ts +1 -2
  25. package/src/logo-generator/components/fair-usage-notice.tsx +38 -0
  26. package/src/logo-generator/components/generator-modal.tsx +24 -7
  27. package/src/logo-generator/components/history-carousel.tsx +8 -1
  28. package/src/logo-generator/components/logo-presenter.tsx +16 -5
  29. package/src/logo-generator/components/prompt.tsx +8 -4
  30. package/src/logo-generator/hooks/use-checkout.ts +7 -2
  31. package/src/logo-generator/hooks/use-fair-usage-notice-message.tsx +68 -0
  32. package/src/logo-generator/hooks/use-logo-generator.ts +8 -0
  33. package/src/logo-generator/store/actions.ts +8 -0
  34. package/src/logo-generator/store/constants.ts +1 -0
  35. package/src/logo-generator/store/reducer.ts +12 -0
  36. package/src/logo-generator/store/selectors.ts +24 -0
  37. package/src/logo-generator/store/types.ts +3 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.18.1] - 2024-09-10
9
+ ### Changed
10
+ - Updated package dependencies. [#39302]
11
+
12
+ ## [0.18.0] - 2024-09-09
13
+ ### Added
14
+ - AI Client: add placeholders for Logo Generator modal commponents [#39244]
15
+
16
+ ### Changed
17
+ - AI Logo generator: add over quota notice, handle disabling tiers on checkout [#39149]
18
+ - Updated package dependencies. [#39176]
19
+
8
20
  ## [0.17.0] - 2024-09-02
9
21
  ### Added
10
22
  - AI Client: Add FeaturesControl to ai-assistant-feature response parsing. [#39168]
@@ -397,6 +409,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
397
409
  - Updated package dependencies. [#31659]
398
410
  - Updated package dependencies. [#31785]
399
411
 
412
+ [0.18.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.18.0...v0.18.1
413
+ [0.18.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.17.0...v0.18.0
400
414
  [0.17.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.4...v0.17.0
401
415
  [0.16.4]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.3...v0.16.4
402
416
  [0.16.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.2...v0.16.3
@@ -21,7 +21,6 @@ export default async function requestJwt({ apiNonce, siteId, expirationTime, } =
21
21
  apiNonce = apiNonce || window.JP_CONNECTION_INITIAL_STATE.apiNonce;
22
22
  siteId = siteId || window.JP_CONNECTION_INITIAL_STATE.siteSuffix;
23
23
  expirationTime = expirationTime || JWT_TOKEN_EXPIRATION_TIME;
24
- const isSimple = isSimpleSite();
25
24
  // Trying to pick the token from localStorage
26
25
  const token = localStorage.getItem(JWT_TOKEN_ID);
27
26
  let tokenData = null;
@@ -38,6 +37,7 @@ export default async function requestJwt({ apiNonce, siteId, expirationTime, } =
38
37
  return tokenData;
39
38
  }
40
39
  let data;
40
+ const isSimple = isSimpleSite();
41
41
  if (!isSimple) {
42
42
  data = await apiFetch({
43
43
  /*
@@ -0,0 +1,11 @@
1
+ type FairUsageNoticeProps = {
2
+ variant?: 'error' | 'muted';
3
+ };
4
+ /**
5
+ * The fair usage notice component.
6
+ * @param {FairUsageNoticeProps} props - Fair usage notice component props.
7
+ * @param {FairUsageNoticeProps.variant} props.variant - The variant of the notice to render.
8
+ * @return {ReactElement} the Notice component with the fair usage message.
9
+ */
10
+ export declare const FairUsageNotice: ({ variant }: FairUsageNoticeProps) => import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Notice } from '@wordpress/components';
3
+ import useFairUsageNoticeMessage from '../hooks/use-fair-usage-notice-message.js';
4
+ /**
5
+ * The fair usage notice component.
6
+ * @param {FairUsageNoticeProps} props - Fair usage notice component props.
7
+ * @param {FairUsageNoticeProps.variant} props.variant - The variant of the notice to render.
8
+ * @return {ReactElement} the Notice component with the fair usage message.
9
+ */
10
+ export const FairUsageNotice = ({ variant = 'error' }) => {
11
+ const useFairUsageNoticeMessageElement = useFairUsageNoticeMessage();
12
+ if (variant === 'muted') {
13
+ return (_jsx("span", { className: "jetpack-ai-fair-usage-notice-muted-variant", children: useFairUsageNoticeMessageElement }));
14
+ }
15
+ if (variant === 'error') {
16
+ return (_jsx(Notice, { status: "error", isDismissible: false, className: "jetpack-ai-fair-usage-notice", children: useFairUsageNoticeMessageElement }));
17
+ }
18
+ return null;
19
+ };
@@ -19,6 +19,7 @@ import useLogoGenerator from '../hooks/use-logo-generator.js';
19
19
  import useRequestErrors from '../hooks/use-request-errors.js';
20
20
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
21
21
  import { STORE_NAME } from '../store/index.js';
22
+ // import { FairUsageNotice } from './fair-usage-notice.js';
22
23
  import { FeatureFetchFailureScreen } from './feature-fetch-failure-screen.js';
23
24
  import { FirstLoadScreen } from './first-load-screen.js';
24
25
  import { HistoryCarousel } from './history-carousel.js';
@@ -31,7 +32,7 @@ const debug = debugFactory('jetpack-ai-calypso:generator-modal');
31
32
  export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDetails, context, placement, }) => {
32
33
  const { tracks } = useAnalytics();
33
34
  const { recordEvent: recordTracksEvent } = tracks;
34
- const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch(STORE_NAME);
35
+ const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory, setIsLoadingHistory } = useDispatch(STORE_NAME);
35
36
  const { getIsRequestingAiAssistantFeature } = select(STORE_NAME);
36
37
  const [loadingState, setLoadingState] = useState(null);
37
38
  const [initialPrompt, setInitialPrompt] = useState();
@@ -39,7 +40,7 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDet
39
40
  const requestedFeatureData = useRef(false);
40
41
  const [needsFeature, setNeedsFeature] = useState(false);
41
42
  const [needsMoreRequests, setNeedsMoreRequests] = useState(false);
42
- const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } = useLogoGenerator();
43
+ const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext, tierPlansEnabled, } = useLogoGenerator();
43
44
  const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
44
45
  const siteId = siteDetails?.ID;
45
46
  const [logoAccepted, setLogoAccepted] = useState(false);
@@ -74,26 +75,30 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDet
74
75
  const promptCreationCost = 1;
75
76
  const currentLimit = feature?.currentTier?.value || 0;
76
77
  const currentUsage = feature?.usagePeriod?.requestsCount || 0;
77
- const isUnlimited = currentLimit === 1;
78
+ const isUnlimited = !tierPlansEnabled ? currentLimit > 0 : currentLimit === 1;
78
79
  const hasNoNextTier = !feature?.nextTier; // If there is no next tier, the user cannot upgrade.
79
80
  // The user needs an upgrade immediately if they have no logos and not enough requests remaining for one prompt and one logo generation.
80
81
  const siteNeedsMoreRequests = !isUnlimited &&
81
82
  !hasNoNextTier &&
82
83
  !hasHistory &&
83
- currentLimit - currentUsage < logoCost + promptCreationCost;
84
+ (tierPlansEnabled
85
+ ? currentLimit - currentUsage < logoCost + promptCreationCost
86
+ : currentLimit < currentUsage);
84
87
  // If the site requires an upgrade, show the upgrade screen immediately.
85
- setNeedsFeature(!feature?.hasFeature ?? true);
88
+ setNeedsFeature(currentLimit === 0);
86
89
  setNeedsMoreRequests(siteNeedsMoreRequests);
87
- if (!feature?.hasFeature || siteNeedsMoreRequests) {
90
+ if (currentLimit === 0 || siteNeedsMoreRequests) {
88
91
  setLoadingState(null);
89
92
  return;
90
93
  }
94
+ setIsLoadingHistory(true);
91
95
  // Load the logo history and clear any deleted media.
92
96
  await clearDeletedMedia(String(siteId));
93
97
  loadLogoHistory(siteId);
94
98
  // If there is any logo, we do not need to generate a first logo again.
95
99
  if (!isLogoHistoryEmpty(String(siteId))) {
96
100
  setLoadingState(null);
101
+ setIsLoadingHistory(false);
97
102
  return;
98
103
  }
99
104
  // If the site does not require an upgrade and has no logos stored, generate the first prompt based on the site's data.
@@ -102,6 +107,7 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDet
102
107
  catch (error) {
103
108
  debug('Error fetching feature', error);
104
109
  setLoadingState(null);
110
+ setIsLoadingHistory(false);
105
111
  }
106
112
  }, [
107
113
  feature,
@@ -125,6 +131,7 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDet
125
131
  setNeedsMoreRequests(false);
126
132
  clearErrors();
127
133
  setLogoAccepted(false);
134
+ setIsLoadingHistory(false);
128
135
  recordTracksEvent(EVENT_MODAL_CLOSE, { context, placement });
129
136
  };
130
137
  const handleApplyLogo = (mediaId) => {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * External dependencies
4
4
  */
@@ -8,13 +8,14 @@ import clsx from 'clsx';
8
8
  /**
9
9
  * Internal dependencies
10
10
  */
11
+ import loader from '../assets/images/loader.gif';
11
12
  import { EVENT_NAVIGATE } from '../constants.js';
12
13
  import useLogoGenerator from '../hooks/use-logo-generator.js';
13
14
  import './history-carousel.scss';
14
15
  export const HistoryCarousel = () => {
15
16
  const { tracks } = useAnalytics();
16
17
  const { recordEvent: recordTracksEvent } = tracks;
17
- const { logos, selectedLogo, setSelectedLogoIndex, context } = useLogoGenerator();
18
+ const { logos, selectedLogo, setSelectedLogoIndex, context, isLoadingHistory } = useLogoGenerator();
18
19
  const handleClick = (index) => {
19
20
  recordTracksEvent(EVENT_NAVIGATE, {
20
21
  context,
@@ -30,7 +31,7 @@ export const HistoryCarousel = () => {
30
31
  }
31
32
  return thumbnailURL.toString();
32
33
  };
33
- return (_jsx("div", { className: "jetpack-ai-logo-generator__carousel", children: logos.map((logo, index) => (_jsx(Button, { className: clsx('jetpack-ai-logo-generator__carousel-logo', {
34
- 'is-selected': logo.url === selectedLogo.url,
35
- }), onClick: () => handleClick(index), children: _jsx("img", { src: thumbnailFrom(logo.url), alt: logo.description }) }, logo.url))) }));
34
+ return (_jsxs("div", { className: "jetpack-ai-logo-generator__carousel", children: [!logos.length && isLoadingHistory && (_jsx(Button, { disabled: true, className: clsx('jetpack-ai-logo-generator__carousel-logo'), children: _jsx("img", { height: "48", width: "48", src: loader, alt: 'loading' }) })), logos.map((logo, index) => (_jsx(Button, { className: clsx('jetpack-ai-logo-generator__carousel-logo', {
35
+ 'is-selected': logo.url === selectedLogo.url,
36
+ }), onClick: () => handleClick(index), children: _jsx("img", { src: thumbnailFrom(logo.url), alt: logo.description }) }, logo.url)))] }));
36
37
  };
@@ -74,6 +74,9 @@ const UseOnSiteButton = ({ onApplyLogo, }) => {
74
74
  const LogoLoading = () => {
75
75
  return (_jsxs(_Fragment, { children: [_jsx(ImageLoader, { className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__loading-text", children: __('Generating new logo…', 'jetpack-ai-client') })] }));
76
76
  };
77
+ const LogoFetching = () => {
78
+ return (_jsxs(_Fragment, { children: [_jsx(ImageLoader, { className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__loading-text", children: __('Fetching previous logos…', 'jetpack-ai-client') })] }));
79
+ };
77
80
  const LogoReady = ({ siteId, logo, onApplyLogo }) => {
78
81
  return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__action-wrapper", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__description", children: logo.description }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__actions", children: [_jsx(SaveInLibraryButton, { siteId: siteId }), _jsx(UseOnSiteButton, { onApplyLogo: onApplyLogo })] })] })] }));
79
82
  };
@@ -81,13 +84,15 @@ const LogoUpdated = ({ logo }) => {
81
84
  return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__success-wrapper", children: [_jsx(Icon, { icon: _jsx(CheckIcon, {}) }), _jsx("span", { children: __('Your new logo was set to the block!', 'jetpack-ai-client') })] })] }));
82
85
  };
83
86
  export const LogoPresenter = ({ logo = null, loading = false, onApplyLogo, logoAccepted = false, siteId, }) => {
87
+ // 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.
84
88
  const { isRequestingImage } = useLogoGenerator();
85
89
  const { saveToLibraryError, logoUpdateError } = useRequestErrors();
90
+ let logoContent;
86
91
  if (!logo) {
87
- return null;
92
+ debug('No logo provided, history still loading or logo being generated');
93
+ logoContent = _jsx(LogoFetching, {});
88
94
  }
89
- let logoContent;
90
- if (loading || isRequestingImage) {
95
+ else if (loading || isRequestingImage) {
91
96
  logoContent = _jsx(LogoLoading, {});
92
97
  }
93
98
  else if (logoAccepted) {
@@ -16,6 +16,7 @@ import { EVENT_GENERATE, MINIMUM_PROMPT_LENGTH, EVENT_UPGRADE, EVENT_PLACEMENT_I
16
16
  import { useCheckout } from '../hooks/use-checkout.js';
17
17
  import useLogoGenerator from '../hooks/use-logo-generator.js';
18
18
  import useRequestErrors from '../hooks/use-request-errors.js';
19
+ import { FairUsageNotice } from './fair-usage-notice.js';
19
20
  import { UpgradeNudge } from './upgrade-nudge.js';
20
21
  import './prompt.scss';
21
22
  const debug = debugFactory('jetpack-ai-calypso:prompt-box');
@@ -27,7 +28,7 @@ export const Prompt = ({ initialPrompt = '' }) => {
27
28
  const { enhancePromptFetchError, logoFetchError } = useRequestErrors();
28
29
  const { nextTierCheckoutURL: checkoutUrl, hasNextTier } = useCheckout();
29
30
  const hasPrompt = prompt?.length >= MINIMUM_PROMPT_LENGTH;
30
- const { generateLogo, enhancePrompt, setIsEnhancingPrompt, isBusy, isEnhancingPrompt, site, getAiAssistantFeature, requireUpgrade, context, } = useLogoGenerator();
31
+ const { generateLogo, enhancePrompt, setIsEnhancingPrompt, isBusy, isEnhancingPrompt, site, getAiAssistantFeature, requireUpgrade, context, tierPlansEnabled, } = useLogoGenerator();
31
32
  const enhancingLabel = __('Enhancing…', 'jetpack-ai-client');
32
33
  const enhanceLabel = __('Enhance prompt', 'jetpack-ai-client');
33
34
  const enhanceButtonLabel = isEnhancingPrompt ? enhancingLabel : enhanceLabel;
@@ -73,12 +74,12 @@ export const Prompt = ({ initialPrompt = '' }) => {
73
74
  };
74
75
  const onPromptPaste = (event) => {
75
76
  event.preventDefault();
76
- // Paste plain text only
77
- const text = event.clipboardData.getData('text/plain');
78
77
  const selection = window.getSelection();
79
78
  if (!selection || !selection.rangeCount) {
80
79
  return;
81
80
  }
81
+ // Paste plain text only
82
+ const text = event.clipboardData.getData('text/plain');
82
83
  selection.deleteFromDocument();
83
84
  const range = selection.getRangeAt(0);
84
85
  range.insertNode(document.createTextNode(text));
@@ -92,5 +93,5 @@ export const Prompt = ({ initialPrompt = '' }) => {
92
93
  // The content editable div is expected to be updated by the enhance prompt, so warnings are suppressed
93
94
  suppressContentEditableWarning: true, className: "prompt-query__input", onInput: onPromptInput, onPaste: onPromptPaste, "data-placeholder": __('Describe your site or simply ask for a logo specifying some details about it', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", className: "jetpack-ai-logo-generator__prompt-submit", onClick: onGenerate, disabled: isBusy || requireUpgrade || !hasPrompt, children: __('Generate', 'jetpack-ai-client') })] }), _jsxs("div", { className: "jetpack-ai-logo-generator__prompt-footer", children: [!isUnlimited && !requireUpgrade && (_jsxs("div", { className: "jetpack-ai-logo-generator__prompt-requests", children: [_jsx("div", { children: sprintf(
94
95
  // translators: %u is the number of requests
95
- __('%u requests remaining.', 'jetpack-ai-client'), requestsRemaining) }), hasNextTier && (_jsxs(_Fragment, { children: ["\u00A0", _jsx(Button, { variant: "link", href: checkoutUrl, target: "_blank", onClick: onUpgradeClick, children: __('Upgrade', 'jetpack-ai-client') })] })), "\u00A0", _jsx(Tooltip, { text: __('Logo generation costs 10 requests; prompt enhancement costs 1 request each', 'jetpack-ai-client'), placement: "bottom", children: _jsx(Icon, { className: "prompt-footer__icon", icon: info }) })] })), !isUnlimited && requireUpgrade && _jsx(UpgradeNudge, {}), enhancePromptFetchError && (_jsx("div", { className: "jetpack-ai-logo-generator__prompt-error", children: __('Error enhancing prompt. Please try again.', 'jetpack-ai-client') })), logoFetchError && (_jsx("div", { className: "jetpack-ai-logo-generator__prompt-error", children: __('Error generating logo. Please try again.', 'jetpack-ai-client') }))] })] }));
96
+ __('%u requests remaining.', 'jetpack-ai-client'), requestsRemaining) }), hasNextTier && (_jsxs(_Fragment, { children: ["\u00A0", _jsx(Button, { variant: "link", href: checkoutUrl, target: "_blank", onClick: onUpgradeClick, children: __('Upgrade', 'jetpack-ai-client') })] })), "\u00A0", _jsx(Tooltip, { text: __('Logo generation costs 10 requests; prompt enhancement costs 1 request each', 'jetpack-ai-client'), placement: "bottom", children: _jsx(Icon, { className: "prompt-footer__icon", icon: info }) })] })), requireUpgrade && tierPlansEnabled && _jsx(UpgradeNudge, {}), requireUpgrade && !tierPlansEnabled && _jsx(FairUsageNotice, {}), enhancePromptFetchError && (_jsx("div", { className: "jetpack-ai-logo-generator__prompt-error", children: __('Error enhancing prompt. Please try again.', 'jetpack-ai-client') })), logoFetchError && (_jsx("div", { className: "jetpack-ai-logo-generator__prompt-error", children: __('Error generating logo. Please try again.', 'jetpack-ai-client') }))] })] }));
96
97
  };
@@ -10,10 +10,11 @@ import debugFactory from 'debug';
10
10
  import { STORE_NAME } from '../store/index.js';
11
11
  const debug = debugFactory('ai-client:logo-generator:use-checkout');
12
12
  export const useCheckout = () => {
13
- const { nextTier } = useSelect(select => {
13
+ const { nextTier, tierPlansEnabled } = useSelect(select => {
14
14
  const selectors = select(STORE_NAME);
15
15
  return {
16
16
  nextTier: selectors.getAiAssistantFeature().nextTier,
17
+ tierPlansEnabled: selectors.getAiAssistantFeature().tierPlansEnabled,
17
18
  };
18
19
  }, []);
19
20
  /**
@@ -22,7 +23,7 @@ export const useCheckout = () => {
22
23
  const wpcomCheckoutUrl = new URL(`https://jetpack.com/redirect/`);
23
24
  wpcomCheckoutUrl.searchParams.set('source', 'jetpack-ai-yearly-tier-upgrade-nudge');
24
25
  wpcomCheckoutUrl.searchParams.set('site', getSiteFragment());
25
- wpcomCheckoutUrl.searchParams.set('path', `jetpack_ai_yearly:-q-${nextTier?.limit}`);
26
+ wpcomCheckoutUrl.searchParams.set('path', tierPlansEnabled ? `jetpack_ai_yearly:-q-${nextTier?.limit}` : 'jetpack_ai_yearly');
26
27
  /**
27
28
  * Open the product interstitial page
28
29
  */
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" resolution-mode="require"/>
2
+ declare const useFairUsageNoticeMessage: () => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
3
+ export default useFairUsageNoticeMessage;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useSelect } from '@wordpress/data';
3
+ import { createInterpolateElement } from '@wordpress/element';
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ /**
6
+ * Internal dependencies
7
+ */
8
+ import { STORE_NAME } from '../store/index.js';
9
+ const useFairUsageNoticeMessage = () => {
10
+ const { usagePeriod } = useSelect(select => {
11
+ const selectors = select(STORE_NAME);
12
+ return {
13
+ usagePeriod: selectors.getAiAssistantFeature().nextTier,
14
+ };
15
+ }, []);
16
+ const getFormattedUsagePeriodStartDate = planUsagePeriod => {
17
+ if (!planUsagePeriod?.nextStart) {
18
+ return null;
19
+ }
20
+ const nextUsagePeriodStartDate = new Date(planUsagePeriod.nextStart);
21
+ return (nextUsagePeriodStartDate.toLocaleString('default', { month: 'long' }) +
22
+ ' ' +
23
+ nextUsagePeriodStartDate.getDate());
24
+ };
25
+ const getFairUsageNoticeMessage = resetDateString => {
26
+ const fairUsageMessage = __("You've reached this month's request limit, per our <link>fair usage policy</link>.", 'jetpack-ai-client');
27
+ if (!resetDateString) {
28
+ return fairUsageMessage;
29
+ }
30
+ // Translators: %s is the date when the requests will reset.
31
+ const dateMessage = __('Requests will reset on %s.', 'jetpack-ai-client');
32
+ const formattedDateMessage = sprintf(dateMessage, resetDateString);
33
+ return `${fairUsageMessage} ${formattedDateMessage}`;
34
+ };
35
+ const nextUsagePeriodStartDateString = getFormattedUsagePeriodStartDate(usagePeriod);
36
+ // Get the proper template based on the presence of the next usage period start date.
37
+ const fairUsageNoticeMessage = getFairUsageNoticeMessage(nextUsagePeriodStartDateString);
38
+ const fairUsageNoticeMessageElement = createInterpolateElement(fairUsageNoticeMessage, {
39
+ link: (_jsx("a", { href: "https://jetpack.com/redirect/?source=ai-logo-generator-fair-usage-policy", target: "_blank", rel: "noreferrer" })),
40
+ });
41
+ return fairUsageNoticeMessageElement;
42
+ };
43
+ export default useFairUsageNoticeMessage;
@@ -42,5 +42,8 @@ declare const useLogoGenerator: () => {
42
42
  getAiAssistantFeature: (siteId?: string) => Partial<import("../store/types.js").AiFeatureProps>;
43
43
  requireUpgrade: boolean;
44
44
  context: string;
45
+ tierPlansEnabled: boolean;
46
+ isLoadingHistory: boolean;
47
+ setIsLoadingHistory: any;
45
48
  };
46
49
  export default useLogoGenerator;
@@ -16,8 +16,8 @@ import { STORE_NAME } from '../store/index.js';
16
16
  import useRequestErrors from './use-request-errors.js';
17
17
  const debug = debugFactory('jetpack-ai-calypso:use-logo-generator');
18
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 => {
19
+ const { setSelectedLogoIndex, setIsSavingLogoToLibrary, setIsApplyingLogo, setIsRequestingImage, setIsEnhancingPrompt, increaseAiAssistantRequestsCount, addLogoToHistory, setContext, setIsLoadingHistory, } = useDispatch(STORE_NAME);
20
+ const { logos, selectedLogoIndex, selectedLogo, siteDetails, isSavingLogoToLibrary, isApplyingLogo, isEnhancingPrompt, isBusy, isRequestingImage, getAiAssistantFeature, requireUpgrade, context, tierPlansEnabled, isLoadingHistory, } = useSelect(select => {
21
21
  const selectors = select(STORE_NAME);
22
22
  return {
23
23
  logos: selectors.getLogos(),
@@ -32,6 +32,8 @@ const useLogoGenerator = () => {
32
32
  getAiAssistantFeature: selectors.getAiAssistantFeature,
33
33
  requireUpgrade: selectors.getRequireUpgrade(),
34
34
  context: selectors.getContext(),
35
+ tierPlansEnabled: selectors.getTierPlansEnabled(),
36
+ isLoadingHistory: selectors.getIsLoadingHistory(),
35
37
  };
36
38
  }, []);
37
39
  const { setFirstLogoPromptFetchError, setEnhancePromptFetchError, setLogoFetchError, setSaveToLibraryError, setLogoUpdateError, } = useRequestErrors();
@@ -281,6 +283,9 @@ User request:${prompt}`;
281
283
  getAiAssistantFeature,
282
284
  requireUpgrade,
283
285
  context,
286
+ tierPlansEnabled,
287
+ isLoadingHistory,
288
+ setIsLoadingHistory,
284
289
  };
285
290
  };
286
291
  export default useLogoGenerator;
@@ -101,5 +101,9 @@ declare const actions: {
101
101
  type: string;
102
102
  context: string;
103
103
  };
104
+ setIsLoadingHistory(isLoadingHistory: boolean): {
105
+ type: string;
106
+ isLoadingHistory: boolean;
107
+ };
104
108
  };
105
109
  export default actions;
@@ -6,7 +6,7 @@ import wpcomLimitedRequest from '../lib/wpcom-limited-request.js';
6
6
  /**
7
7
  * Types & Constants
8
8
  */
9
- import { ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT, ACTION_REQUEST_AI_ASSISTANT_FEATURE, ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE, ACTION_SET_SITE_DETAILS, ACTION_STORE_AI_ASSISTANT_FEATURE, ACTION_SET_TIER_PLANS_ENABLED, ACTION_SET_SELECTED_LOGO_INDEX, ACTION_ADD_LOGO_TO_HISTORY, ACTION_SET_IS_SAVING_LOGO_TO_LIBRARY, ACTION_SAVE_SELECTED_LOGO, 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_LOGO_UPDATE_ERROR, ACTION_SET_SAVE_TO_LIBRARY_ERROR, ACTION_SET_CONTEXT, } from './constants.js';
9
+ import { ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT, ACTION_REQUEST_AI_ASSISTANT_FEATURE, ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE, ACTION_SET_SITE_DETAILS, ACTION_STORE_AI_ASSISTANT_FEATURE, ACTION_SET_TIER_PLANS_ENABLED, ACTION_SET_SELECTED_LOGO_INDEX, ACTION_ADD_LOGO_TO_HISTORY, ACTION_SET_IS_SAVING_LOGO_TO_LIBRARY, ACTION_SAVE_SELECTED_LOGO, 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_LOGO_UPDATE_ERROR, ACTION_SET_SAVE_TO_LIBRARY_ERROR, ACTION_SET_CONTEXT, ACTION_SET_IS_LOADING_HISTORY, } from './constants.js';
10
10
  /**
11
11
  * Map the response from the `sites/$site/ai-assistant-feature`
12
12
  * endpoint to the AI Assistant feature props.
@@ -190,5 +190,11 @@ const actions = {
190
190
  context,
191
191
  };
192
192
  },
193
+ setIsLoadingHistory(isLoadingHistory) {
194
+ return {
195
+ type: ACTION_SET_IS_LOADING_HISTORY,
196
+ isLoadingHistory,
197
+ };
198
+ },
193
199
  };
194
200
  export default actions;
@@ -33,6 +33,7 @@ export declare const ACTION_SAVE_SELECTED_LOGO = "SAVE_SELECTED_LOGO";
33
33
  export declare const ACTION_SET_IS_REQUESTING_IMAGE = "SET_IS_REQUESTING_IMAGE";
34
34
  export declare const ACTION_SET_IS_ENHANCING_PROMPT = "SET_IS_ENHANCING_PROMPT";
35
35
  export declare const ACTION_SET_SITE_HISTORY = "SET_SITE_HISTORY";
36
+ export declare const ACTION_SET_IS_LOADING_HISTORY = "SET_IS_LOADING_HISTORY";
36
37
  /**
37
38
  * Logo generator error actions
38
39
  */
@@ -33,6 +33,7 @@ export const ACTION_SAVE_SELECTED_LOGO = 'SAVE_SELECTED_LOGO';
33
33
  export const ACTION_SET_IS_REQUESTING_IMAGE = 'SET_IS_REQUESTING_IMAGE';
34
34
  export const ACTION_SET_IS_ENHANCING_PROMPT = 'SET_IS_ENHANCING_PROMPT';
35
35
  export const ACTION_SET_SITE_HISTORY = 'SET_SITE_HISTORY';
36
+ export const ACTION_SET_IS_LOADING_HISTORY = 'SET_IS_LOADING_HISTORY';
36
37
  /**
37
38
  * Logo generator error actions
38
39
  */
@@ -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: {
@@ -98,6 +100,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
98
100
  saveToLibraryError?: RequestError;
99
101
  logoUpdateError?: RequestError;
100
102
  context: string;
103
+ isLoadingHistory: boolean;
101
104
  };
102
105
  siteDetails?: SiteDetails | Record<string, never>;
103
106
  history: import("./types.js").Logo[];
@@ -120,6 +123,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
120
123
  saveToLibraryError?: RequestError;
121
124
  logoUpdateError?: RequestError;
122
125
  context: string;
126
+ isLoadingHistory: boolean;
123
127
  };
124
128
  siteDetails?: SiteDetails | Record<string, never>;
125
129
  features: {
@@ -138,6 +142,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
138
142
  saveToLibraryError?: RequestError;
139
143
  logoUpdateError?: RequestError;
140
144
  context?: string;
145
+ isLoadingHistory?: boolean;
141
146
  };
142
147
  siteDetails?: SiteDetails | Record<string, never>;
143
148
  features: {
@@ -158,6 +163,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
158
163
  saveToLibraryError?: RequestError;
159
164
  logoUpdateError?: RequestError;
160
165
  context?: string;
166
+ isLoadingHistory?: boolean;
161
167
  };
162
168
  siteDetails?: SiteDetails | Record<string, never>;
163
169
  features: {
@@ -178,6 +184,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
178
184
  saveToLibraryError?: RequestError;
179
185
  logoUpdateError?: RequestError;
180
186
  context?: string;
187
+ isLoadingHistory?: boolean;
181
188
  };
182
189
  siteDetails?: SiteDetails | Record<string, never>;
183
190
  features: {
@@ -198,6 +205,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
198
205
  saveToLibraryError?: RequestError;
199
206
  logoUpdateError?: RequestError;
200
207
  context?: string;
208
+ isLoadingHistory?: boolean;
201
209
  };
202
210
  siteDetails?: SiteDetails | Record<string, never>;
203
211
  features: {
@@ -218,6 +226,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
218
226
  saveToLibraryError?: RequestError;
219
227
  logoUpdateError?: RequestError;
220
228
  context?: string;
229
+ isLoadingHistory?: boolean;
221
230
  };
222
231
  siteDetails?: SiteDetails | Record<string, never>;
223
232
  features: {
@@ -238,6 +247,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
238
247
  saveToLibraryError?: RequestError;
239
248
  logoUpdateError?: RequestError;
240
249
  context?: string;
250
+ isLoadingHistory?: boolean;
241
251
  };
242
252
  siteDetails?: SiteDetails | Record<string, never>;
243
253
  features: {
@@ -258,6 +268,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
258
268
  saveToLibraryError?: RequestError;
259
269
  logoUpdateError?: RequestError;
260
270
  context?: string;
271
+ isLoadingHistory?: boolean;
261
272
  };
262
273
  siteDetails?: SiteDetails | Record<string, never>;
263
274
  features: {
@@ -278,6 +289,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
278
289
  saveToLibraryError?: RequestError;
279
290
  logoUpdateError?: RequestError;
280
291
  context?: string;
292
+ isLoadingHistory?: boolean;
281
293
  };
282
294
  siteDetails?: SiteDetails | Record<string, never>;
283
295
  features: {
@@ -298,6 +310,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
298
310
  logoFetchError?: RequestError;
299
311
  logoUpdateError?: RequestError;
300
312
  context?: string;
313
+ isLoadingHistory?: boolean;
301
314
  };
302
315
  siteDetails?: SiteDetails | Record<string, never>;
303
316
  features: {
@@ -318,6 +331,7 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
318
331
  logoFetchError?: RequestError;
319
332
  saveToLibraryError?: RequestError;
320
333
  context?: string;
334
+ isLoadingHistory?: boolean;
321
335
  };
322
336
  siteDetails?: SiteDetails | Record<string, never>;
323
337
  features: {
@@ -338,6 +352,28 @@ export default function reducer(state: LogoGeneratorStateProp, action: {
338
352
  logoFetchError?: RequestError;
339
353
  saveToLibraryError?: RequestError;
340
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;
341
377
  };
342
378
  siteDetails?: SiteDetails | Record<string, never>;
343
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;
@@ -112,6 +112,7 @@ export type LogoGeneratorStateProp = {
112
112
  saveToLibraryError?: RequestError;
113
113
  logoUpdateError?: RequestError;
114
114
  context: string;
115
+ isLoadingHistory: boolean;
115
116
  };
116
117
  siteDetails?: SiteDetails | Record<string, never>;
117
118
  features: {
@@ -140,6 +141,8 @@ export type Selectors = {
140
141
  getSaveToLibraryError(): RequestError;
141
142
  getLogoUpdateError(): RequestError;
142
143
  getContext(): string;
144
+ getTierPlansEnabled(): boolean;
145
+ getIsLoadingHistory(): boolean;
143
146
  };
144
147
  export type AiAssistantFeatureEndpointResponseProps = {
145
148
  'is-enabled': boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.17.0",
4
+ "version": "0.18.1",
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.32",
47
+ "@automattic/jetpack-connection": "^0.35.7",
48
+ "@automattic/jetpack-shared-extension-utils": "^0.15.9",
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.7.0",
53
+ "@wordpress/blob": "4.7.0",
54
+ "@wordpress/block-editor": "14.2.0",
55
+ "@wordpress/components": "28.7.0",
56
+ "@wordpress/compose": "7.7.0",
57
+ "@wordpress/data": "10.7.0",
58
+ "@wordpress/element": "6.7.0",
59
+ "@wordpress/i18n": "5.7.0",
60
+ "@wordpress/icons": "10.7.0",
61
61
  "clsx": "2.1.1",
62
62
  "debug": "4.3.4",
63
63
  "markdown-it": "14.0.0",
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' ) }
@@ -20,10 +20,11 @@ import type { Selectors } from '../store/types.js';
20
20
  const debug = debugFactory( 'ai-client:logo-generator:use-checkout' );
21
21
 
22
22
  export const useCheckout = () => {
23
- const { nextTier } = useSelect( select => {
23
+ const { nextTier, tierPlansEnabled } = useSelect( select => {
24
24
  const selectors: Selectors = select( STORE_NAME );
25
25
  return {
26
26
  nextTier: selectors.getAiAssistantFeature().nextTier,
27
+ tierPlansEnabled: selectors.getAiAssistantFeature().tierPlansEnabled,
27
28
  };
28
29
  }, [] );
29
30
 
@@ -33,7 +34,11 @@ export const useCheckout = () => {
33
34
  const wpcomCheckoutUrl = new URL( `https://jetpack.com/redirect/` );
34
35
  wpcomCheckoutUrl.searchParams.set( 'source', 'jetpack-ai-yearly-tier-upgrade-nudge' );
35
36
  wpcomCheckoutUrl.searchParams.set( 'site', getSiteFragment() as string );
36
- wpcomCheckoutUrl.searchParams.set( 'path', `jetpack_ai_yearly:-q-${ nextTier?.limit }` );
37
+
38
+ wpcomCheckoutUrl.searchParams.set(
39
+ 'path',
40
+ tierPlansEnabled ? `jetpack_ai_yearly:-q-${ nextTier?.limit }` : 'jetpack_ai_yearly'
41
+ );
37
42
 
38
43
  /**
39
44
  * Open the product interstitial page
@@ -0,0 +1,68 @@
1
+ import { useSelect } from '@wordpress/data';
2
+ import { createInterpolateElement } from '@wordpress/element';
3
+ import { __, sprintf } from '@wordpress/i18n';
4
+ /**
5
+ * Internal dependencies
6
+ */
7
+ import { STORE_NAME } from '../store/index.js';
8
+ /**
9
+ * Types
10
+ */
11
+ import type { Selectors } from '../store/types.js';
12
+
13
+ const useFairUsageNoticeMessage = () => {
14
+ const { usagePeriod } = useSelect( select => {
15
+ const selectors: Selectors = select( STORE_NAME );
16
+ return {
17
+ usagePeriod: selectors.getAiAssistantFeature().nextTier,
18
+ };
19
+ }, [] );
20
+ const getFormattedUsagePeriodStartDate = planUsagePeriod => {
21
+ if ( ! planUsagePeriod?.nextStart ) {
22
+ return null;
23
+ }
24
+
25
+ const nextUsagePeriodStartDate = new Date( planUsagePeriod.nextStart );
26
+ return (
27
+ nextUsagePeriodStartDate.toLocaleString( 'default', { month: 'long' } ) +
28
+ ' ' +
29
+ nextUsagePeriodStartDate.getDate()
30
+ );
31
+ };
32
+
33
+ const getFairUsageNoticeMessage = resetDateString => {
34
+ const fairUsageMessage = __(
35
+ "You've reached this month's request limit, per our <link>fair usage policy</link>.",
36
+ 'jetpack-ai-client'
37
+ );
38
+
39
+ if ( ! resetDateString ) {
40
+ return fairUsageMessage;
41
+ }
42
+
43
+ // Translators: %s is the date when the requests will reset.
44
+ const dateMessage = __( 'Requests will reset on %s.', 'jetpack-ai-client' );
45
+ const formattedDateMessage = sprintf( dateMessage, resetDateString );
46
+
47
+ return `${ fairUsageMessage } ${ formattedDateMessage }`;
48
+ };
49
+
50
+ const nextUsagePeriodStartDateString = getFormattedUsagePeriodStartDate( usagePeriod );
51
+
52
+ // Get the proper template based on the presence of the next usage period start date.
53
+ const fairUsageNoticeMessage = getFairUsageNoticeMessage( nextUsagePeriodStartDateString );
54
+
55
+ const fairUsageNoticeMessageElement = createInterpolateElement( fairUsageNoticeMessage, {
56
+ link: (
57
+ <a
58
+ href="https://jetpack.com/redirect/?source=ai-logo-generator-fair-usage-policy"
59
+ target="_blank"
60
+ rel="noreferrer"
61
+ />
62
+ ),
63
+ } );
64
+
65
+ return fairUsageNoticeMessageElement;
66
+ };
67
+
68
+ export default useFairUsageNoticeMessage;
@@ -31,6 +31,7 @@ const useLogoGenerator = () => {
31
31
  increaseAiAssistantRequestsCount,
32
32
  addLogoToHistory,
33
33
  setContext,
34
+ setIsLoadingHistory,
34
35
  } = useDispatch( STORE_NAME );
35
36
 
36
37
  const {
@@ -46,6 +47,8 @@ const useLogoGenerator = () => {
46
47
  getAiAssistantFeature,
47
48
  requireUpgrade,
48
49
  context,
50
+ tierPlansEnabled,
51
+ isLoadingHistory,
49
52
  } = useSelect( select => {
50
53
  const selectors: Selectors = select( STORE_NAME );
51
54
 
@@ -62,6 +65,8 @@ const useLogoGenerator = () => {
62
65
  getAiAssistantFeature: selectors.getAiAssistantFeature,
63
66
  requireUpgrade: selectors.getRequireUpgrade(),
64
67
  context: selectors.getContext(),
68
+ tierPlansEnabled: selectors.getTierPlansEnabled(),
69
+ isLoadingHistory: selectors.getIsLoadingHistory(),
65
70
  };
66
71
  }, [] );
67
72
 
@@ -383,6 +388,9 @@ User request:${ prompt }`;
383
388
  getAiAssistantFeature,
384
389
  requireUpgrade,
385
390
  context,
391
+ tierPlansEnabled,
392
+ isLoadingHistory,
393
+ setIsLoadingHistory,
386
394
  };
387
395
  };
388
396
 
@@ -28,6 +28,7 @@ import {
28
28
  ACTION_SET_LOGO_UPDATE_ERROR,
29
29
  ACTION_SET_SAVE_TO_LIBRARY_ERROR,
30
30
  ACTION_SET_CONTEXT,
31
+ ACTION_SET_IS_LOADING_HISTORY,
31
32
  } from './constants.js';
32
33
  import type {
33
34
  AiFeatureProps,
@@ -247,6 +248,13 @@ const actions = {
247
248
  context,
248
249
  };
249
250
  },
251
+
252
+ setIsLoadingHistory( isLoadingHistory: boolean ) {
253
+ return {
254
+ type: ACTION_SET_IS_LOADING_HISTORY,
255
+ isLoadingHistory,
256
+ };
257
+ },
250
258
  };
251
259
 
252
260
  export default actions;
@@ -37,6 +37,7 @@ export const ACTION_SAVE_SELECTED_LOGO = 'SAVE_SELECTED_LOGO';
37
37
  export const ACTION_SET_IS_REQUESTING_IMAGE = 'SET_IS_REQUESTING_IMAGE';
38
38
  export const ACTION_SET_IS_ENHANCING_PROMPT = 'SET_IS_ENHANCING_PROMPT';
39
39
  export const ACTION_SET_SITE_HISTORY = 'SET_SITE_HISTORY';
40
+ export const ACTION_SET_IS_LOADING_HISTORY = 'SET_IS_LOADING_HISTORY';
40
41
 
41
42
  /**
42
43
  * Logo generator error actions
@@ -27,6 +27,7 @@ import {
27
27
  ACTION_SET_SAVE_TO_LIBRARY_ERROR,
28
28
  ACTION_SET_LOGO_UPDATE_ERROR,
29
29
  ACTION_SET_CONTEXT,
30
+ ACTION_SET_IS_LOADING_HISTORY,
30
31
  } from './constants.js';
31
32
  import INITIAL_STATE from './initial-state.js';
32
33
  import type {
@@ -61,6 +62,7 @@ import type { SiteDetails } from '../types.js';
61
62
  * @param {Array< { url: string; description: string; mediaId?: number } >} action.history - The logo history
62
63
  * @param {RequestError} action.error - The error to set
63
64
  * @param {string} action.context - The context where the tool is being used
65
+ * @param {boolean} action.isLoadingHistory - Whether the history is being loaded
64
66
  * @return {LogoGeneratorStateProp} The new state
65
67
  */
66
68
  export default function reducer(
@@ -83,6 +85,7 @@ export default function reducer(
83
85
  history?: Array< { url: string; description: string; mediaId?: number } >;
84
86
  error?: RequestError;
85
87
  context?: string;
88
+ isLoadingHistory?: boolean;
86
89
  }
87
90
  ) {
88
91
  switch ( action.type ) {
@@ -381,6 +384,15 @@ export default function reducer(
381
384
  context: action.context,
382
385
  },
383
386
  };
387
+
388
+ case ACTION_SET_IS_LOADING_HISTORY:
389
+ return {
390
+ ...state,
391
+ _meta: {
392
+ ...( state._meta ?? {} ),
393
+ isLoadingHistory: action.isLoadingHistory,
394
+ },
395
+ };
384
396
  }
385
397
 
386
398
  return state;
@@ -121,6 +121,10 @@ const selectors = {
121
121
  */
122
122
  getRequireUpgrade( state: LogoGeneratorStateProp ): boolean {
123
123
  const feature = state.features.aiAssistantFeature;
124
+
125
+ if ( ! feature?.tierPlansEnabled ) {
126
+ return feature?.requireUpgrade;
127
+ }
124
128
  const logoCost = feature?.costs?.[ 'jetpack-ai-logo-generator' ]?.logo ?? DEFAULT_LOGO_COST;
125
129
  const currentLimit = feature?.currentTier?.value || 0;
126
130
  const currentUsage = feature?.usagePeriod?.requestsCount || 0;
@@ -196,6 +200,26 @@ const selectors = {
196
200
  getContext( state: LogoGeneratorStateProp ): string {
197
201
  return state._meta?.context ?? '';
198
202
  },
203
+
204
+ /**
205
+ * Get tier plans enabled status.
206
+ *
207
+ * @param {LogoGeneratorStateProp} state - The app state tree.
208
+ * @return {boolean} The tier plans enabled status.
209
+ */
210
+ getTierPlansEnabled( state: LogoGeneratorStateProp ): boolean {
211
+ return state.features.aiAssistantFeature?.tierPlansEnabled ?? false;
212
+ },
213
+
214
+ /**
215
+ * Get tier plans enabled status.
216
+ *
217
+ * @param {LogoGeneratorStateProp} state - The app state tree.
218
+ * @return {boolean} The loading logo history status.
219
+ */
220
+ getIsLoadingHistory( state: LogoGeneratorStateProp ): boolean {
221
+ return state._meta?.isLoadingHistory ?? false;
222
+ },
199
223
  };
200
224
 
201
225
  export default selectors;
@@ -152,6 +152,7 @@ export type LogoGeneratorStateProp = {
152
152
  saveToLibraryError?: RequestError;
153
153
  logoUpdateError?: RequestError;
154
154
  context: string;
155
+ isLoadingHistory: boolean;
155
156
  };
156
157
  siteDetails?: SiteDetails | Record< string, never >;
157
158
  features: {
@@ -181,6 +182,8 @@ export type Selectors = {
181
182
  getSaveToLibraryError(): RequestError;
182
183
  getLogoUpdateError(): RequestError;
183
184
  getContext(): string;
185
+ getTierPlansEnabled(): boolean;
186
+ getIsLoadingHistory(): boolean;
184
187
  };
185
188
 
186
189
  /*