@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.
- package/CHANGELOG.md +14 -0
- package/build/jwt/index.js +1 -1
- package/build/logo-generator/components/fair-usage-notice.d.ts +11 -0
- package/build/logo-generator/components/fair-usage-notice.js +19 -0
- package/build/logo-generator/components/generator-modal.js +13 -6
- package/build/logo-generator/components/history-carousel.js +6 -5
- package/build/logo-generator/components/logo-presenter.js +8 -3
- package/build/logo-generator/components/prompt.js +5 -4
- package/build/logo-generator/hooks/use-checkout.js +3 -2
- package/build/logo-generator/hooks/use-fair-usage-notice-message.d.ts +3 -0
- package/build/logo-generator/hooks/use-fair-usage-notice-message.js +43 -0
- package/build/logo-generator/hooks/use-logo-generator.d.ts +3 -0
- package/build/logo-generator/hooks/use-logo-generator.js +7 -2
- package/build/logo-generator/store/actions.d.ts +4 -0
- package/build/logo-generator/store/actions.js +7 -1
- package/build/logo-generator/store/constants.d.ts +1 -0
- package/build/logo-generator/store/constants.js +1 -0
- package/build/logo-generator/store/reducer.d.ts +36 -0
- package/build/logo-generator/store/reducer.js +10 -1
- package/build/logo-generator/store/selectors.d.ts +14 -0
- package/build/logo-generator/store/selectors.js +21 -0
- package/build/logo-generator/store/types.d.ts +3 -0
- package/package.json +13 -13
- package/src/jwt/index.ts +1 -2
- package/src/logo-generator/components/fair-usage-notice.tsx +38 -0
- package/src/logo-generator/components/generator-modal.tsx +24 -7
- package/src/logo-generator/components/history-carousel.tsx +8 -1
- package/src/logo-generator/components/logo-presenter.tsx +16 -5
- package/src/logo-generator/components/prompt.tsx +8 -4
- package/src/logo-generator/hooks/use-checkout.ts +7 -2
- package/src/logo-generator/hooks/use-fair-usage-notice-message.tsx +68 -0
- package/src/logo-generator/hooks/use-logo-generator.ts +8 -0
- package/src/logo-generator/store/actions.ts +8 -0
- package/src/logo-generator/store/constants.ts +1 -0
- package/src/logo-generator/store/reducer.ts +12 -0
- package/src/logo-generator/store/selectors.ts +24 -0
- 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
|
package/build/jwt/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
88
|
+
setNeedsFeature(currentLimit === 0);
|
|
86
89
|
setNeedsMoreRequests(siteNeedsMoreRequests);
|
|
87
|
-
if (
|
|
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 (
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
92
|
+
debug('No logo provided, history still loading or logo being generated');
|
|
93
|
+
logoContent = _jsx(LogoFetching, {});
|
|
88
94
|
}
|
|
89
|
-
|
|
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 }) })] })),
|
|
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,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;
|
|
@@ -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.
|
|
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.
|
|
47
|
-
"@automattic/jetpack-connection": "^0.35.
|
|
48
|
-
"@automattic/jetpack-shared-extension-utils": "^0.15.
|
|
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.
|
|
53
|
-
"@wordpress/blob": "4.
|
|
54
|
-
"@wordpress/block-editor": "14.
|
|
55
|
-
"@wordpress/components": "28.
|
|
56
|
-
"@wordpress/compose": "7.
|
|
57
|
-
"@wordpress/data": "10.
|
|
58
|
-
"@wordpress/element": "6.
|
|
59
|
-
"@wordpress/i18n": "5.
|
|
60
|
-
"@wordpress/icons": "10.
|
|
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 } =
|
|
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 {
|
|
65
|
-
|
|
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
|
-
|
|
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(
|
|
125
|
+
setNeedsFeature( currentLimit === 0 );
|
|
115
126
|
setNeedsMoreRequests( siteNeedsMoreRequests );
|
|
116
|
-
|
|
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 } =
|
|
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 (
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
/*
|