@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
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ 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.0] - 2024-09-09
9
+ ### Added
10
+ - AI Client: add placeholders for Logo Generator modal commponents [#39244]
11
+
12
+ ### Changed
13
+ - AI Logo generator: add over quota notice, handle disabling tiers on checkout [#39149]
14
+ - Updated package dependencies. [#39176]
15
+
16
+ ## [0.17.0] - 2024-09-02
17
+ ### Added
18
+ - AI Client: Add FeaturesControl to ai-assistant-feature response parsing. [#39168]
19
+ - Jetpack AI: Support fair usage messaging on the Extension AI Control component. [#39103]
20
+
8
21
  ## [0.16.4] - 2024-08-26
9
22
  ### Changed
10
23
  - Updated package dependencies. [#39004]
@@ -392,6 +405,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
392
405
  - Updated package dependencies. [#31659]
393
406
  - Updated package dependencies. [#31785]
394
407
 
408
+ [0.18.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.17.0...v0.18.0
409
+ [0.17.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.4...v0.17.0
395
410
  [0.16.4]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.3...v0.16.4
396
411
  [0.16.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.2...v0.16.3
397
412
  [0.16.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.1...v0.16.2
@@ -17,6 +17,7 @@ type ExtensionAIControlProps = {
17
17
  error?: RequestingErrorProps;
18
18
  requestsRemaining?: number;
19
19
  showUpgradeMessage?: boolean;
20
+ showFairUsageMessage?: boolean;
20
21
  upgradeUrl?: string;
21
22
  wrapperRef?: React.MutableRefObject<HTMLDivElement | null>;
22
23
  onChange?: (newValue: string) => void;
@@ -34,6 +35,6 @@ type ExtensionAIControlProps = {
34
35
  * @param {React.MutableRefObject} ref - Ref to the component
35
36
  * @return {ReactElement} Rendered component
36
37
  */
37
- export declare function ExtensionAIControl({ className, disabled, value, placeholder, showButtonLabels, isTransparent, state, showGuideLine, error, requestsRemaining, showUpgradeMessage, upgradeUrl, wrapperRef, onChange, onSend, onStop, onClose, onUndo, onUpgrade, onTryAgain, }: ExtensionAIControlProps, ref: React.MutableRefObject<HTMLInputElement>): ReactElement;
38
+ export declare function ExtensionAIControl({ className, disabled, value, placeholder, showButtonLabels, isTransparent, state, showGuideLine, error, requestsRemaining, showUpgradeMessage, showFairUsageMessage, upgradeUrl, wrapperRef, onChange, onSend, onStop, onClose, onUndo, onUpgrade, onTryAgain, }: ExtensionAIControlProps, ref: React.MutableRefObject<HTMLInputElement>): ReactElement;
38
39
  declare const _default: React.ForwardRefExoticComponent<ExtensionAIControlProps & React.RefAttributes<HTMLInputElement>>;
39
40
  export default _default;
@@ -11,7 +11,7 @@ import { forwardRef } from 'react';
11
11
  /**
12
12
  * Internal dependencies
13
13
  */
14
- import { GuidelineMessage, ErrorMessage, UpgradeMessage } from '../message/index.js';
14
+ import { GuidelineMessage, ErrorMessage, UpgradeMessage, FairUsageLimitMessage, } from '../message/index.js';
15
15
  import AIControl from './ai-control.js';
16
16
  import './style.scss';
17
17
  /**
@@ -21,7 +21,7 @@ import './style.scss';
21
21
  * @param {React.MutableRefObject} ref - Ref to the component
22
22
  * @return {ReactElement} Rendered component
23
23
  */
24
- export function ExtensionAIControl({ className, disabled = false, value = '', placeholder = '', showButtonLabels = true, isTransparent = false, state = 'init', showGuideLine = false, error, requestsRemaining, showUpgradeMessage = false, upgradeUrl, wrapperRef, onChange, onSend, onStop, onClose, onUndo, onUpgrade, onTryAgain, }, ref) {
24
+ export function ExtensionAIControl({ className, disabled = false, value = '', placeholder = '', showButtonLabels = true, isTransparent = false, state = 'init', showGuideLine = false, error, requestsRemaining, showUpgradeMessage = false, showFairUsageMessage = false, upgradeUrl, wrapperRef, onChange, onSend, onStop, onClose, onUndo, onUpgrade, onTryAgain, }, ref) {
25
25
  const loading = state === 'requesting' || state === 'suggesting';
26
26
  const [editRequest, setEditRequest] = useState(false);
27
27
  const [lastValue, setLastValue] = useState(value || null);
@@ -78,6 +78,9 @@ export function ExtensionAIControl({ className, disabled = false, value = '', pl
78
78
  if (error?.message) {
79
79
  message = (_jsx(ErrorMessage, { error: error.message, code: error.code, onTryAgainClick: tryAgainHandler, onUpgradeClick: upgradeHandler, upgradeUrl: upgradeUrl }));
80
80
  }
81
+ else if (showFairUsageMessage) {
82
+ message = _jsx(FairUsageLimitMessage, {});
83
+ }
81
84
  else if (showUpgradeMessage) {
82
85
  message = (_jsx(UpgradeMessage, { requestsRemaining: requestsRemaining, onUpgradeClick: upgradeHandler, upgradeUrl: upgradeUrl }));
83
86
  }
@@ -47,6 +47,12 @@ export default function Message({ severity, icon, showSidebarIcon, onSidebarIcon
47
47
  * @return {React.ReactElement } - Message component.
48
48
  */
49
49
  export declare function GuidelineMessage(): React.ReactElement;
50
+ /**
51
+ * React component to render a fair usage limit message.
52
+ *
53
+ * @return {React.ReactElement } - Message component.
54
+ */
55
+ export declare function FairUsageLimitMessage(): React.ReactElement;
50
56
  /**
51
57
  * React component to render an upgrade message for free tier users
52
58
  *
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  * External dependencies
4
4
  */
5
5
  import { ExternalLink, Button } from '@wordpress/components';
6
+ import { createInterpolateElement } from '@wordpress/element';
6
7
  import { __, sprintf } from '@wordpress/i18n';
7
8
  import { Icon, check, arrowRight } from '@wordpress/icons';
8
9
  import clsx from 'clsx';
@@ -45,6 +46,18 @@ export default function Message({ severity = MESSAGE_SEVERITY_INFO, icon = null,
45
46
  export function GuidelineMessage() {
46
47
  return (_jsxs(Message, { children: [_jsx("span", { children: __('AI-generated content could be inaccurate or biased.', 'jetpack-ai-client') }), _jsx(ExternalLink, { href: "https://automattic.com/ai-guidelines", children: __('Learn more', 'jetpack-ai-client') })] }));
47
48
  }
49
+ /**
50
+ * React component to render a fair usage limit message.
51
+ *
52
+ * @return {React.ReactElement } - Message component.
53
+ */
54
+ export function FairUsageLimitMessage() {
55
+ const message = __("You've reached this month's request limit, per our <link>fair usage policy</link>", 'jetpack-ai-client');
56
+ const element = createInterpolateElement(message, {
57
+ link: (_jsx(ExternalLink, { href: "https://jetpack.com/redirect/?source=ai-assistant-fair-usage-policy" })),
58
+ });
59
+ return _jsx(Message, { severity: MESSAGE_SEVERITY_WARNING, children: element });
60
+ }
48
61
  /**
49
62
  * React component to render an upgrade message for free tier users
50
63
  *
@@ -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.
@@ -32,6 +32,7 @@ export function mapAiFeatureResponseToAiFeatureProps(response) {
32
32
  nextTier: response['next-tier'],
33
33
  tierPlansEnabled: !!response['tier-plans-enabled'],
34
34
  costs: response.costs,
35
+ featuresControl: response['features-control'],
35
36
  };
36
37
  }
37
38
  const actions = {
@@ -189,5 +190,11 @@ const actions = {
189
190
  context,
190
191
  };
191
192
  },
193
+ setIsLoadingHistory(isLoadingHistory) {
194
+ return {
195
+ type: ACTION_SET_IS_LOADING_HISTORY,
196
+ isLoadingHistory,
197
+ };
198
+ },
192
199
  };
193
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
  */