@automattic/jetpack-ai-client 0.15.0 → 0.16.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 +21 -0
- package/build/logo-generator/components/feature-fetch-failure-screen.js +1 -1
- package/build/logo-generator/components/generator-modal.js +24 -26
- package/build/logo-generator/components/logo-presenter.js +8 -14
- package/build/logo-generator/components/upgrade-screen.js +2 -2
- package/build/logo-generator/components/visit-site-banner.d.ts +1 -2
- package/build/logo-generator/components/visit-site-banner.js +2 -2
- package/build/logo-generator/hooks/use-checkout.js +16 -6
- package/build/logo-generator/hooks/use-logo-generator.js +1 -1
- package/build/logo-generator/lib/logo-storage.js +3 -1
- package/build/logo-generator/lib/wpcom-limited-request.js +0 -8
- package/build/logo-generator/types.d.ts +4 -1
- package/package.json +2 -2
- package/src/logo-generator/components/feature-fetch-failure-screen.tsx +1 -1
- package/src/logo-generator/components/generator-modal.tsx +32 -35
- package/src/logo-generator/components/logo-presenter.tsx +17 -32
- package/src/logo-generator/components/upgrade-screen.tsx +2 -2
- package/src/logo-generator/components/visit-site-banner.tsx +9 -5
- package/src/logo-generator/hooks/use-checkout.ts +23 -8
- package/src/logo-generator/hooks/use-logo-generator.ts +1 -1
- package/src/logo-generator/lib/logo-storage.ts +3 -1
- package/src/logo-generator/lib/wpcom-limited-request.ts +0 -11
- package/src/logo-generator/types.ts +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.16.1] - 2024-08-05
|
|
9
|
+
### Changed
|
|
10
|
+
- AI Logo Generator: fix UI issues. [#38590]
|
|
11
|
+
- Fixup versions [#38612]
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- AI Logo Generator: fix multiple feature requests error + retry handling. [#38630]
|
|
15
|
+
- AI Logo Generator: fix small UI issues. [#38676]
|
|
16
|
+
- AI Logo Generator: fix upgrade URLs so they work on any site type. [#38598]
|
|
17
|
+
- AI Logo Generator: update upgrade message. [#38690]
|
|
18
|
+
|
|
19
|
+
## [0.16.0] - 2024-07-29
|
|
20
|
+
### Added
|
|
21
|
+
- AI Logo Generator: support placement property on the generator modal, for tracking purposes. [#38574]
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- AI Logo Generator: make the initial prompt update when the site name and description are fully laoded from store. [#38491]
|
|
25
|
+
- AI Logo Generator: provide the saved media ID on the save logo callback. [#38552]
|
|
26
|
+
|
|
8
27
|
## [0.15.0] - 2024-07-22
|
|
9
28
|
### Added
|
|
10
29
|
- Jetpack AI: Add logo generator codebase to the ai-client package. [#38391]
|
|
@@ -358,6 +377,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
358
377
|
- Updated package dependencies. [#31659]
|
|
359
378
|
- Updated package dependencies. [#31785]
|
|
360
379
|
|
|
380
|
+
[0.16.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.0...v0.16.1
|
|
381
|
+
[0.16.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.15.0...v0.16.0
|
|
361
382
|
[0.15.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.6...v0.15.0
|
|
362
383
|
[0.14.6]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.5...v0.14.6
|
|
363
384
|
[0.14.5]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.4...v0.14.5
|
|
@@ -5,6 +5,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
import { Button } from '@wordpress/components';
|
|
6
6
|
import { __ } from '@wordpress/i18n';
|
|
7
7
|
export const FeatureFetchFailureScreen = ({ onCancel, onRetry }) => {
|
|
8
|
-
const errorMessage = __('We are sorry. There was an error loading your Jetpack AI
|
|
8
|
+
const errorMessage = __('We are sorry. There was an error loading your Jetpack AI plan data. Please, try again.', 'jetpack-ai-client');
|
|
9
9
|
return (_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message-wrapper", children: [_jsx("div", { className: "jetpack-ai-logo-generator-modal__notice-message", children: _jsx("span", { className: "jetpack-ai-logo-generator-modal__loading-message", children: errorMessage }) }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-actions", children: [_jsx(Button, { variant: "tertiary", onClick: onCancel, children: __('Cancel', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", onClick: onRetry, children: __('Try again', 'jetpack-ai-client') })] })] }));
|
|
10
10
|
};
|
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
*/
|
|
5
5
|
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
|
|
6
6
|
import { Modal, Button } from '@wordpress/components';
|
|
7
|
-
import { useDispatch } from '@wordpress/data';
|
|
7
|
+
import { useDispatch, select } from '@wordpress/data';
|
|
8
8
|
import { __ } from '@wordpress/i18n';
|
|
9
9
|
import { external, Icon } from '@wordpress/icons';
|
|
10
10
|
import clsx from 'clsx';
|
|
@@ -13,7 +13,8 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
13
13
|
/**
|
|
14
14
|
* Internal dependencies
|
|
15
15
|
*/
|
|
16
|
-
import { DEFAULT_LOGO_COST, EVENT_MODAL_OPEN, EVENT_FEEDBACK, EVENT_MODAL_CLOSE,
|
|
16
|
+
import { DEFAULT_LOGO_COST, EVENT_MODAL_OPEN, EVENT_FEEDBACK, EVENT_MODAL_CLOSE, EVENT_GENERATE, } from '../constants.js';
|
|
17
|
+
import { useCheckout } from '../hooks/use-checkout.js';
|
|
17
18
|
import useLogoGenerator from '../hooks/use-logo-generator.js';
|
|
18
19
|
import useRequestErrors from '../hooks/use-request-errors.js';
|
|
19
20
|
import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
|
|
@@ -27,22 +28,22 @@ import { UpgradeScreen } from './upgrade-screen.js';
|
|
|
27
28
|
import { VisitSiteBanner } from './visit-site-banner.js';
|
|
28
29
|
import './generator-modal.scss';
|
|
29
30
|
const debug = debugFactory('jetpack-ai-calypso:generator-modal');
|
|
30
|
-
export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
|
|
31
|
+
export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDetails, context, placement, }) => {
|
|
31
32
|
const { tracks } = useAnalytics();
|
|
32
33
|
const { recordEvent: recordTracksEvent } = tracks;
|
|
33
34
|
const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch(STORE_NAME);
|
|
35
|
+
const { getIsRequestingAiAssistantFeature } = select(STORE_NAME);
|
|
34
36
|
const [loadingState, setLoadingState] = useState(null);
|
|
35
37
|
const [initialPrompt, setInitialPrompt] = useState();
|
|
36
38
|
const needsToHandleModalOpen = useRef(true);
|
|
37
39
|
const requestedFeatureData = useRef(false);
|
|
38
40
|
const [needsFeature, setNeedsFeature] = useState(false);
|
|
39
41
|
const [needsMoreRequests, setNeedsMoreRequests] = useState(false);
|
|
40
|
-
const [upgradeURL, setUpgradeURL] = useState('');
|
|
41
42
|
const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } = useLogoGenerator();
|
|
42
43
|
const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
|
|
43
44
|
const siteId = siteDetails?.ID;
|
|
44
|
-
const siteURL = siteDetails?.URL;
|
|
45
45
|
const [logoAccepted, setLogoAccepted] = useState(false);
|
|
46
|
+
const { nextTierCheckoutURL: upgradeURL } = useCheckout();
|
|
46
47
|
// First fetch the feature data so we have the most up-to-date info from the backend.
|
|
47
48
|
const feature = getAiAssistantFeature();
|
|
48
49
|
const generateFirstLogo = useCallback(async () => {
|
|
@@ -80,13 +81,10 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
|
|
|
80
81
|
!hasNoNextTier &&
|
|
81
82
|
!hasHistory &&
|
|
82
83
|
currentLimit - currentUsage < logoCost + promptCreationCost;
|
|
83
|
-
// If the site requires an upgrade,
|
|
84
|
+
// If the site requires an upgrade, show the upgrade screen immediately.
|
|
84
85
|
setNeedsFeature(!feature?.hasFeature ?? true);
|
|
85
86
|
setNeedsMoreRequests(siteNeedsMoreRequests);
|
|
86
87
|
if (!feature?.hasFeature || siteNeedsMoreRequests) {
|
|
87
|
-
const siteUpgradeURL = new URL(`${location.origin}/checkout/${siteDetails?.domain}/${feature?.nextTier?.slug}`);
|
|
88
|
-
siteUpgradeURL.searchParams.set('redirect_to', location.href);
|
|
89
|
-
setUpgradeURL(siteUpgradeURL.toString());
|
|
90
88
|
setLoadingState(null);
|
|
91
89
|
return;
|
|
92
90
|
}
|
|
@@ -115,9 +113,9 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
|
|
|
115
113
|
]);
|
|
116
114
|
const handleModalOpen = useCallback(async () => {
|
|
117
115
|
setContext(context);
|
|
118
|
-
recordTracksEvent(EVENT_MODAL_OPEN, { context, placement
|
|
116
|
+
recordTracksEvent(EVENT_MODAL_OPEN, { context, placement });
|
|
119
117
|
initializeModal();
|
|
120
|
-
}, [setContext, context, initializeModal]);
|
|
118
|
+
}, [setContext, context, placement, initializeModal]);
|
|
121
119
|
const closeModal = () => {
|
|
122
120
|
// Reset the state when the modal is closed, so we trigger the modal initialization again when it's opened.
|
|
123
121
|
needsToHandleModalOpen.current = true;
|
|
@@ -127,17 +125,11 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
|
|
|
127
125
|
setNeedsMoreRequests(false);
|
|
128
126
|
clearErrors();
|
|
129
127
|
setLogoAccepted(false);
|
|
130
|
-
recordTracksEvent(EVENT_MODAL_CLOSE, { context, placement
|
|
128
|
+
recordTracksEvent(EVENT_MODAL_CLOSE, { context, placement });
|
|
131
129
|
};
|
|
132
|
-
const handleApplyLogo = () => {
|
|
130
|
+
const handleApplyLogo = (mediaId) => {
|
|
133
131
|
setLogoAccepted(true);
|
|
134
|
-
|
|
135
|
-
const handleCloseAndReload = () => {
|
|
136
|
-
closeModal();
|
|
137
|
-
setTimeout(() => {
|
|
138
|
-
// Reload the page to update the logo.
|
|
139
|
-
window.location.reload();
|
|
140
|
-
}, 1000);
|
|
132
|
+
onApplyLogo?.(mediaId);
|
|
141
133
|
};
|
|
142
134
|
const handleFeedbackClick = () => {
|
|
143
135
|
recordTracksEvent(EVENT_FEEDBACK, { context });
|
|
@@ -149,10 +141,13 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
|
|
|
149
141
|
}
|
|
150
142
|
// When the site details are set, we need to fetch the feature data.
|
|
151
143
|
if (!requestedFeatureData.current) {
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
const isRequestingFeature = getIsRequestingAiAssistantFeature();
|
|
145
|
+
if (!isRequestingFeature) {
|
|
146
|
+
requestedFeatureData.current = true;
|
|
147
|
+
fetchAiAssistantFeature();
|
|
148
|
+
}
|
|
154
149
|
}
|
|
155
|
-
}, [siteId, siteDetails, setSiteDetails]);
|
|
150
|
+
}, [siteId, siteDetails, setSiteDetails, getIsRequestingAiAssistantFeature]);
|
|
156
151
|
// Handles modal opening logic
|
|
157
152
|
useEffect(() => {
|
|
158
153
|
// While the modal is not open, the siteId is not set, or the feature data is not available, do nothing.
|
|
@@ -170,15 +165,18 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
|
|
|
170
165
|
body = _jsx(FirstLoadScreen, { state: loadingState });
|
|
171
166
|
}
|
|
172
167
|
else if (featureFetchError || firstLogoPromptFetchError) {
|
|
173
|
-
body = _jsx(FeatureFetchFailureScreen, { onCancel: closeModal, onRetry:
|
|
168
|
+
body = (_jsx(FeatureFetchFailureScreen, { onCancel: closeModal, onRetry: () => {
|
|
169
|
+
closeModal();
|
|
170
|
+
onReload?.();
|
|
171
|
+
} }));
|
|
174
172
|
}
|
|
175
173
|
else if (needsFeature || needsMoreRequests) {
|
|
176
174
|
body = (_jsx(UpgradeScreen, { onCancel: closeModal, upgradeURL: upgradeURL, reason: needsFeature ? 'feature' : 'requests' }));
|
|
177
175
|
}
|
|
178
176
|
else {
|
|
179
|
-
body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {
|
|
177
|
+
body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {}), _jsx("div", { className: "jetpack-ai-logo-generator__accept-actions", children: _jsx(Button, { variant: "primary", onClick: closeModal, children: __('Close', 'jetpack-ai-client') }) })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsxs(Button, { variant: "link", className: "jetpack-ai-logo-generator__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: [_jsx("span", { children: __('Provide feedback', 'jetpack-ai-client') }), _jsx(Icon, { icon: external, className: "icon" })] }) })] }))] }));
|
|
180
178
|
}
|
|
181
|
-
return (_jsx(_Fragment, { children: isOpen && (_jsx(Modal, { className: "jetpack-ai-logo-generator-modal", onRequestClose:
|
|
179
|
+
return (_jsx(_Fragment, { children: isOpen && (_jsx(Modal, { className: "jetpack-ai-logo-generator-modal", onRequestClose: closeModal, shouldCloseOnClickOutside: false, shouldCloseOnEsc: false, title: __('Jetpack AI Logo Generator', 'jetpack-ai-client'), children: _jsx("div", { className: clsx('jetpack-ai-logo-generator-modal__body', {
|
|
182
180
|
'notice-modal': needsFeature || needsMoreRequests || featureFetchError || firstLogoPromptFetchError,
|
|
183
181
|
}), children: body }) })) }));
|
|
184
182
|
};
|
|
@@ -53,38 +53,32 @@ const SaveInLibraryButton = ({ siteId }) => {
|
|
|
53
53
|
};
|
|
54
54
|
const savingLabel = __('Saving…', 'jetpack-ai-client');
|
|
55
55
|
const savedLabel = __('Saved', 'jetpack-ai-client');
|
|
56
|
-
return !saving && !saved ? (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, children: [_jsx(Icon, { icon: _jsx(MediaIcon, {}) }), _jsx("span", { className: "action-text", children: __('Save in Library', 'jetpack-ai-client') })] })) : (_jsxs(
|
|
56
|
+
return !saving && !saved ? (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, children: [_jsx(Icon, { icon: _jsx(MediaIcon, {}) }), _jsx("span", { className: "action-text", children: __('Save in Library', 'jetpack-ai-client') })] })) : (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", children: [_jsx(Icon, { icon: saving ? _jsx(MediaIcon, {}) : _jsx(CheckIcon, {}) }), _jsx("span", { className: "action-text", children: saving ? savingLabel : savedLabel })] }));
|
|
57
57
|
};
|
|
58
|
-
const UseOnSiteButton = ({ onApplyLogo }) => {
|
|
58
|
+
const UseOnSiteButton = ({ onApplyLogo, }) => {
|
|
59
59
|
const { tracks } = useAnalytics();
|
|
60
60
|
const { recordEvent: recordTracksEvent } = tracks;
|
|
61
|
-
const {
|
|
61
|
+
const { isSavingLogoToLibrary, selectedLogo, logos, selectedLogoIndex, context } = useLogoGenerator();
|
|
62
62
|
const handleClick = async () => {
|
|
63
|
-
if (!
|
|
63
|
+
if (!isSavingLogoToLibrary) {
|
|
64
64
|
recordTracksEvent(EVENT_USE, {
|
|
65
65
|
context,
|
|
66
66
|
logos_count: logos.length,
|
|
67
67
|
selected_logo: selectedLogoIndex != null ? selectedLogoIndex + 1 : 0,
|
|
68
68
|
});
|
|
69
|
-
|
|
70
|
-
await applyLogo();
|
|
71
|
-
onApplyLogo();
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
debug('Error applying logo', error);
|
|
75
|
-
}
|
|
69
|
+
onApplyLogo?.(selectedLogo?.mediaId);
|
|
76
70
|
}
|
|
77
71
|
};
|
|
78
|
-
return
|
|
72
|
+
return (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, disabled: isSavingLogoToLibrary || !selectedLogo?.mediaId, children: [_jsx(Icon, { icon: _jsx(LogoIcon, {}) }), _jsx("span", { className: "action-text", children: __('Use on block', 'jetpack-ai-client') })] }));
|
|
79
73
|
};
|
|
80
74
|
const LogoLoading = () => {
|
|
81
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') })] }));
|
|
82
76
|
};
|
|
83
|
-
const LogoReady = ({ siteId, logo, onApplyLogo
|
|
77
|
+
const LogoReady = ({ siteId, logo, onApplyLogo }) => {
|
|
84
78
|
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 })] })] })] }));
|
|
85
79
|
};
|
|
86
80
|
const LogoUpdated = ({ logo }) => {
|
|
87
|
-
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 logo
|
|
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__success-wrapper", children: [_jsx(Icon, { icon: _jsx(CheckIcon, {}) }), _jsx("span", { children: __('Your new logo was set to the block!', 'jetpack-ai-client') })] })] }));
|
|
88
82
|
};
|
|
89
83
|
export const LogoPresenter = ({ logo = null, loading = false, onApplyLogo, logoAccepted = false, siteId, }) => {
|
|
90
84
|
const { isRequestingImage } = useLogoGenerator();
|
|
@@ -13,12 +13,12 @@ import useLogoGenerator from '../hooks/use-logo-generator.js';
|
|
|
13
13
|
export const UpgradeScreen = ({ onCancel, upgradeURL, reason }) => {
|
|
14
14
|
const { tracks } = useAnalytics();
|
|
15
15
|
const { recordEvent: recordTracksEvent } = tracks;
|
|
16
|
-
const upgradeMessageFeature = __('
|
|
16
|
+
const upgradeMessageFeature = __('The logo generator requires a paid Jetpack AI plan. Upgrade your plan to access exclusive features, including logo generation. The upgrade will also increase the amount of requests you can use in all AI-powered features.', 'jetpack-ai-client');
|
|
17
17
|
const upgradeMessageRequests = __('Not enough requests left to generate a logo. Upgrade your Jetpack AI to increase the amount of requests you can use in all AI-powered features.', 'jetpack-ai-client');
|
|
18
18
|
const { context } = useLogoGenerator();
|
|
19
19
|
const handleUpgradeClick = () => {
|
|
20
20
|
recordTracksEvent(EVENT_UPGRADE, { context, placement: EVENT_PLACEMENT_FREE_USER_SCREEN });
|
|
21
21
|
onCancel();
|
|
22
22
|
};
|
|
23
|
-
return (_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message-wrapper", children: [_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal__loading-message", children: reason === 'feature' ? upgradeMessageFeature : upgradeMessageRequests }), "\u00A0", _jsx(Button, { variant: "link", href: "https://jetpack.com/ai/", target: "_blank", children: __('Learn more', 'jetpack-ai-client') })] }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-actions", children: [_jsx(Button, { variant: "tertiary", onClick: onCancel, children: __('Cancel', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", href: upgradeURL, target: "_blank", onClick: handleUpgradeClick, children: __('Upgrade', 'jetpack-ai-client') })] })] }));
|
|
23
|
+
return (_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message-wrapper", children: [_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal__loading-message", children: reason === 'feature' ? upgradeMessageFeature : upgradeMessageRequests }), "\u00A0", _jsx(Button, { variant: "link", href: "https://jetpack.com/ai/", target: "_blank", children: __('Learn more about Jetpack AI.', 'jetpack-ai-client') })] }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-actions", children: [_jsx(Button, { variant: "tertiary", onClick: onCancel, children: __('Cancel', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", href: upgradeURL, target: "_blank", onClick: handleUpgradeClick, children: __('Upgrade', 'jetpack-ai-client') })] })] }));
|
|
24
24
|
};
|
|
@@ -11,6 +11,6 @@ import clsx from 'clsx';
|
|
|
11
11
|
*/
|
|
12
12
|
import jetpackLogo from '../assets/images/jetpack-logo.svg';
|
|
13
13
|
import './visit-site-banner.scss';
|
|
14
|
-
export const VisitSiteBanner = ({ className = null,
|
|
15
|
-
return (_jsxs("div", { className: clsx('jetpack-ai-logo-generator-modal-visit-site-banner', className), children: [_jsx("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo", children: _jsx("img", { src: jetpackLogo, alt: "Jetpack" }) }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__content", children: [_jsx("strong", { children: __('Do you want to know all the amazing things you can do with Jetpack AI?', 'jetpack-ai-client') }), _jsx("span", { children: __('Generate and tweak content, create forms, get feedback and much more.', 'jetpack-ai-client') }), _jsx("div", { children: _jsxs(Button, { variant: "link", href:
|
|
14
|
+
export const VisitSiteBanner = ({ className = null, onVisitBlankTarget }) => {
|
|
15
|
+
return (_jsxs("div", { className: clsx('jetpack-ai-logo-generator-modal-visit-site-banner', className), children: [_jsx("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo", children: _jsx("img", { src: jetpackLogo, alt: "Jetpack" }) }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__content", children: [_jsx("strong", { children: __('Do you want to know all the amazing things you can do with Jetpack AI?', 'jetpack-ai-client') }), _jsx("span", { children: __('Generate and tweak content, create forms, get feedback and much more.', 'jetpack-ai-client') }), _jsx("div", { children: _jsxs(Button, { variant: "link", href: "https://jetpack.com/redirect/?source=logo_generator_learn_more_about_jetpack_ai", target: "_blank", onClick: onVisitBlankTarget ? onVisitBlankTarget : null, children: [__('Learn more about Jetpack AI', 'jetpack-ai-client'), _jsx(Icon, { icon: external, size: 20 })] }) })] })] }));
|
|
16
16
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
import { isAtomicSite, isSimpleSite, getSiteFragment, } from '@automattic/jetpack-shared-extension-utils';
|
|
4
5
|
import { useSelect } from '@wordpress/data';
|
|
5
6
|
import debugFactory from 'debug';
|
|
6
7
|
/**
|
|
@@ -9,18 +10,27 @@ import debugFactory from 'debug';
|
|
|
9
10
|
import { STORE_NAME } from '../store/index.js';
|
|
10
11
|
const debug = debugFactory('ai-client:logo-generator:use-checkout');
|
|
11
12
|
export const useCheckout = () => {
|
|
12
|
-
const { nextTier
|
|
13
|
+
const { nextTier } = useSelect(select => {
|
|
13
14
|
const selectors = select(STORE_NAME);
|
|
14
15
|
return {
|
|
15
16
|
nextTier: selectors.getAiAssistantFeature().nextTier,
|
|
16
|
-
siteDetails: selectors.getSiteDetails(),
|
|
17
17
|
};
|
|
18
18
|
}, []);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Use the Jetpack redirect URL to open the checkout page
|
|
21
|
+
*/
|
|
22
|
+
const wpcomCheckoutUrl = new URL(`https://jetpack.com/redirect/`);
|
|
23
|
+
wpcomCheckoutUrl.searchParams.set('source', 'jetpack-ai-yearly-tier-upgrade-nudge');
|
|
24
|
+
wpcomCheckoutUrl.searchParams.set('site', getSiteFragment());
|
|
25
|
+
wpcomCheckoutUrl.searchParams.set('path', `jetpack_ai_yearly:-q-${nextTier?.limit}`);
|
|
26
|
+
/**
|
|
27
|
+
* Open the product interstitial page
|
|
28
|
+
*/
|
|
29
|
+
const jetpackCheckoutUrl = `${window?.Jetpack_Editor_Initial_State?.adminUrl}admin.php?redirect_to_referrer=1&page=my-jetpack#/add-jetpack-ai`;
|
|
30
|
+
const nextTierCheckoutURL = isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl.toString() : jetpackCheckoutUrl;
|
|
31
|
+
debug('Next tier checkout URL: ', nextTierCheckoutURL);
|
|
22
32
|
return {
|
|
23
|
-
nextTierCheckoutURL
|
|
33
|
+
nextTierCheckoutURL,
|
|
24
34
|
hasNextTier: !!nextTier,
|
|
25
35
|
};
|
|
26
36
|
};
|
|
@@ -78,7 +78,7 @@ Site description: ${description}`;
|
|
|
78
78
|
setFirstLogoPromptFetchError(error);
|
|
79
79
|
throw error;
|
|
80
80
|
}
|
|
81
|
-
}, [setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount]);
|
|
81
|
+
}, [setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount, name, description]);
|
|
82
82
|
const enhancePrompt = async function ({ prompt }) {
|
|
83
83
|
setEnhancePromptFetchError(null);
|
|
84
84
|
increaseAiAssistantRequestsCount();
|
|
@@ -119,5 +119,7 @@ export async function clearDeletedMedia(siteId) {
|
|
|
119
119
|
.filter(({ exists }) => !exists)
|
|
120
120
|
.forEach(({ mediaId }) => removeLogo({ siteId, mediaId }));
|
|
121
121
|
}
|
|
122
|
-
catch (error) {
|
|
122
|
+
catch (error) {
|
|
123
|
+
// Assume that the media exists if there was a network error and do nothing to avoid data loss.
|
|
124
|
+
}
|
|
123
125
|
}
|
|
@@ -7,7 +7,6 @@ import apiFetch from '../../api-fetch/index.js';
|
|
|
7
7
|
*/
|
|
8
8
|
const MAX_CONCURRENT_REQUESTS = 5;
|
|
9
9
|
let concurrentCounter = 0;
|
|
10
|
-
let lastCallTimestamp = null;
|
|
11
10
|
/**
|
|
12
11
|
* Concurrency-limited request to wpcom-proxy-request.
|
|
13
12
|
* @param { object } params - The request params, as expected by apiFetch.
|
|
@@ -20,13 +19,6 @@ export default async function wpcomLimitedRequest(params) {
|
|
|
20
19
|
concurrentCounter -= 1;
|
|
21
20
|
throw new Error('Too many requests');
|
|
22
21
|
}
|
|
23
|
-
const now = Date.now();
|
|
24
|
-
// Check if the last call was made less than 100 milliseconds ago
|
|
25
|
-
if (lastCallTimestamp && now - lastCallTimestamp < 100) {
|
|
26
|
-
concurrentCounter -= 1;
|
|
27
|
-
throw new Error('Too many requests');
|
|
28
|
-
}
|
|
29
|
-
lastCallTimestamp = now; // Update the timestamp
|
|
30
22
|
return apiFetch(params).finally(() => {
|
|
31
23
|
concurrentCounter -= 1;
|
|
32
24
|
});
|
|
@@ -13,12 +13,15 @@ export interface GeneratorModalProps {
|
|
|
13
13
|
siteDetails?: SiteDetails;
|
|
14
14
|
isOpen: boolean;
|
|
15
15
|
onClose: () => void;
|
|
16
|
+
onApplyLogo: (mediaId: number) => void;
|
|
17
|
+
onReload: () => void;
|
|
16
18
|
context: string;
|
|
19
|
+
placement: string;
|
|
17
20
|
}
|
|
18
21
|
export interface LogoPresenterProps {
|
|
19
22
|
logo?: Logo;
|
|
20
23
|
loading?: boolean;
|
|
21
|
-
onApplyLogo: () => void;
|
|
24
|
+
onApplyLogo: (mediaId: number) => void;
|
|
22
25
|
logoAccepted?: boolean;
|
|
23
26
|
siteId: string | number;
|
|
24
27
|
}
|
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.16.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": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@automattic/jetpack-base-styles": "^0.6.28",
|
|
46
46
|
"@automattic/jetpack-connection": "^0.34.1",
|
|
47
|
-
"@automattic/jetpack-shared-extension-utils": "^0.
|
|
47
|
+
"@automattic/jetpack-shared-extension-utils": "^0.15.1",
|
|
48
48
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
49
49
|
"@types/react": "18.3.1",
|
|
50
50
|
"@types/wordpress__block-editor": "11.5.14",
|
|
@@ -13,7 +13,7 @@ export const FeatureFetchFailureScreen: React.FC< {
|
|
|
13
13
|
onRetry: () => void;
|
|
14
14
|
} > = ( { onCancel, onRetry } ) => {
|
|
15
15
|
const errorMessage = __(
|
|
16
|
-
'We are sorry. There was an error loading your Jetpack AI
|
|
16
|
+
'We are sorry. There was an error loading your Jetpack AI plan data. Please, try again.',
|
|
17
17
|
'jetpack-ai-client'
|
|
18
18
|
);
|
|
19
19
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
|
|
5
5
|
import { Modal, Button } from '@wordpress/components';
|
|
6
|
-
import { useDispatch } from '@wordpress/data';
|
|
6
|
+
import { useDispatch, select } from '@wordpress/data';
|
|
7
7
|
import { __ } from '@wordpress/i18n';
|
|
8
8
|
import { external, Icon } from '@wordpress/icons';
|
|
9
9
|
import clsx from 'clsx';
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
EVENT_MODAL_OPEN,
|
|
18
18
|
EVENT_FEEDBACK,
|
|
19
19
|
EVENT_MODAL_CLOSE,
|
|
20
|
-
EVENT_PLACEMENT_QUICK_LINKS,
|
|
21
20
|
EVENT_GENERATE,
|
|
22
21
|
} from '../constants.js';
|
|
22
|
+
import { useCheckout } from '../hooks/use-checkout.js';
|
|
23
23
|
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';
|
|
@@ -43,12 +43,16 @@ const debug = debugFactory( 'jetpack-ai-calypso:generator-modal' );
|
|
|
43
43
|
export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
44
44
|
isOpen,
|
|
45
45
|
onClose,
|
|
46
|
+
onApplyLogo,
|
|
47
|
+
onReload,
|
|
46
48
|
siteDetails,
|
|
47
49
|
context,
|
|
50
|
+
placement,
|
|
48
51
|
} ) => {
|
|
49
52
|
const { tracks } = useAnalytics();
|
|
50
53
|
const { recordEvent: recordTracksEvent } = tracks;
|
|
51
54
|
const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch( STORE_NAME );
|
|
55
|
+
const { getIsRequestingAiAssistantFeature } = select( STORE_NAME );
|
|
52
56
|
const [ loadingState, setLoadingState ] = useState<
|
|
53
57
|
'loadingFeature' | 'analyzing' | 'generating' | null
|
|
54
58
|
>( null );
|
|
@@ -57,13 +61,12 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
57
61
|
const requestedFeatureData = useRef< boolean >( false );
|
|
58
62
|
const [ needsFeature, setNeedsFeature ] = useState( false );
|
|
59
63
|
const [ needsMoreRequests, setNeedsMoreRequests ] = useState( false );
|
|
60
|
-
const [ upgradeURL, setUpgradeURL ] = useState( '' );
|
|
61
64
|
const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } =
|
|
62
65
|
useLogoGenerator();
|
|
63
66
|
const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
|
|
64
67
|
const siteId = siteDetails?.ID;
|
|
65
|
-
const siteURL = siteDetails?.URL;
|
|
66
68
|
const [ logoAccepted, setLogoAccepted ] = useState( false );
|
|
69
|
+
const { nextTierCheckoutURL: upgradeURL } = useCheckout();
|
|
67
70
|
|
|
68
71
|
// First fetch the feature data so we have the most up-to-date info from the backend.
|
|
69
72
|
const feature = getAiAssistantFeature();
|
|
@@ -107,16 +110,10 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
107
110
|
! hasHistory &&
|
|
108
111
|
currentLimit - currentUsage < logoCost + promptCreationCost;
|
|
109
112
|
|
|
110
|
-
// If the site requires an upgrade,
|
|
113
|
+
// If the site requires an upgrade, show the upgrade screen immediately.
|
|
111
114
|
setNeedsFeature( ! feature?.hasFeature ?? true );
|
|
112
115
|
setNeedsMoreRequests( siteNeedsMoreRequests );
|
|
113
|
-
|
|
114
116
|
if ( ! feature?.hasFeature || siteNeedsMoreRequests ) {
|
|
115
|
-
const siteUpgradeURL = new URL(
|
|
116
|
-
`${ location.origin }/checkout/${ siteDetails?.domain }/${ feature?.nextTier?.slug }`
|
|
117
|
-
);
|
|
118
|
-
siteUpgradeURL.searchParams.set( 'redirect_to', location.href );
|
|
119
|
-
setUpgradeURL( siteUpgradeURL.toString() );
|
|
120
117
|
setLoadingState( null );
|
|
121
118
|
return;
|
|
122
119
|
}
|
|
@@ -148,10 +145,10 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
148
145
|
|
|
149
146
|
const handleModalOpen = useCallback( async () => {
|
|
150
147
|
setContext( context );
|
|
151
|
-
recordTracksEvent( EVENT_MODAL_OPEN, { context, placement
|
|
148
|
+
recordTracksEvent( EVENT_MODAL_OPEN, { context, placement } );
|
|
152
149
|
|
|
153
150
|
initializeModal();
|
|
154
|
-
}, [ setContext, context, initializeModal ] );
|
|
151
|
+
}, [ setContext, context, placement, initializeModal ] );
|
|
155
152
|
|
|
156
153
|
const closeModal = () => {
|
|
157
154
|
// Reset the state when the modal is closed, so we trigger the modal initialization again when it's opened.
|
|
@@ -162,20 +159,12 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
162
159
|
setNeedsMoreRequests( false );
|
|
163
160
|
clearErrors();
|
|
164
161
|
setLogoAccepted( false );
|
|
165
|
-
recordTracksEvent( EVENT_MODAL_CLOSE, { context, placement
|
|
162
|
+
recordTracksEvent( EVENT_MODAL_CLOSE, { context, placement } );
|
|
166
163
|
};
|
|
167
164
|
|
|
168
|
-
const handleApplyLogo = () => {
|
|
165
|
+
const handleApplyLogo = ( mediaId: number ) => {
|
|
169
166
|
setLogoAccepted( true );
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const handleCloseAndReload = () => {
|
|
173
|
-
closeModal();
|
|
174
|
-
|
|
175
|
-
setTimeout( () => {
|
|
176
|
-
// Reload the page to update the logo.
|
|
177
|
-
window.location.reload();
|
|
178
|
-
}, 1000 );
|
|
167
|
+
onApplyLogo?.( mediaId );
|
|
179
168
|
};
|
|
180
169
|
|
|
181
170
|
const handleFeedbackClick = () => {
|
|
@@ -190,10 +179,13 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
190
179
|
|
|
191
180
|
// When the site details are set, we need to fetch the feature data.
|
|
192
181
|
if ( ! requestedFeatureData.current ) {
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
const isRequestingFeature = getIsRequestingAiAssistantFeature();
|
|
183
|
+
if ( ! isRequestingFeature ) {
|
|
184
|
+
requestedFeatureData.current = true;
|
|
185
|
+
fetchAiAssistantFeature();
|
|
186
|
+
}
|
|
195
187
|
}
|
|
196
|
-
}, [ siteId, siteDetails, setSiteDetails ] );
|
|
188
|
+
}, [ siteId, siteDetails, setSiteDetails, getIsRequestingAiAssistantFeature ] );
|
|
197
189
|
|
|
198
190
|
// Handles modal opening logic
|
|
199
191
|
useEffect( () => {
|
|
@@ -214,7 +206,15 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
214
206
|
if ( loadingState ) {
|
|
215
207
|
body = <FirstLoadScreen state={ loadingState } />;
|
|
216
208
|
} else if ( featureFetchError || firstLogoPromptFetchError ) {
|
|
217
|
-
body =
|
|
209
|
+
body = (
|
|
210
|
+
<FeatureFetchFailureScreen
|
|
211
|
+
onCancel={ closeModal }
|
|
212
|
+
onRetry={ () => {
|
|
213
|
+
closeModal();
|
|
214
|
+
onReload?.();
|
|
215
|
+
} }
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
218
|
} else if ( needsFeature || needsMoreRequests ) {
|
|
219
219
|
body = (
|
|
220
220
|
<UpgradeScreen
|
|
@@ -235,13 +235,10 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
235
235
|
/>
|
|
236
236
|
{ logoAccepted ? (
|
|
237
237
|
<div className="jetpack-ai-logo-generator__accept">
|
|
238
|
-
<VisitSiteBanner
|
|
238
|
+
<VisitSiteBanner />
|
|
239
239
|
<div className="jetpack-ai-logo-generator__accept-actions">
|
|
240
|
-
<Button variant="
|
|
241
|
-
{ __( 'Close
|
|
242
|
-
</Button>
|
|
243
|
-
<Button href={ siteURL } variant="primary">
|
|
244
|
-
{ __( 'Visit site', 'jetpack-ai-client' ) }
|
|
240
|
+
<Button variant="primary" onClick={ closeModal }>
|
|
241
|
+
{ __( 'Close', 'jetpack-ai-client' ) }
|
|
245
242
|
</Button>
|
|
246
243
|
</div>
|
|
247
244
|
</div>
|
|
@@ -271,7 +268,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
|
|
|
271
268
|
{ isOpen && (
|
|
272
269
|
<Modal
|
|
273
270
|
className="jetpack-ai-logo-generator-modal"
|
|
274
|
-
onRequestClose={
|
|
271
|
+
onRequestClose={ closeModal }
|
|
275
272
|
shouldCloseOnClickOutside={ false }
|
|
276
273
|
shouldCloseOnEsc={ false }
|
|
277
274
|
title={ __( 'Jetpack AI Logo Generator', 'jetpack-ai-client' ) }
|
|
@@ -79,56 +79,41 @@ const SaveInLibraryButton: React.FC< { siteId: string } > = ( { siteId } ) => {
|
|
|
79
79
|
<span className="action-text">{ __( 'Save in Library', 'jetpack-ai-client' ) }</span>
|
|
80
80
|
</Button>
|
|
81
81
|
) : (
|
|
82
|
-
<
|
|
82
|
+
<Button className="jetpack-ai-logo-generator-modal-presenter__action">
|
|
83
83
|
<Icon icon={ saving ? <MediaIcon /> : <CheckIcon /> } />
|
|
84
84
|
<span className="action-text">{ saving ? savingLabel : savedLabel }</span>
|
|
85
|
-
</
|
|
85
|
+
</Button>
|
|
86
86
|
);
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
const UseOnSiteButton: React.FC< { onApplyLogo: () => void } > = ( {
|
|
89
|
+
const UseOnSiteButton: React.FC< { onApplyLogo: ( mediaId: number ) => void } > = ( {
|
|
90
|
+
onApplyLogo,
|
|
91
|
+
} ) => {
|
|
90
92
|
const { tracks } = useAnalytics();
|
|
91
93
|
const { recordEvent: recordTracksEvent } = tracks;
|
|
92
|
-
const {
|
|
93
|
-
|
|
94
|
-
isSavingLogoToLibrary,
|
|
95
|
-
isApplyingLogo,
|
|
96
|
-
selectedLogo,
|
|
97
|
-
logos,
|
|
98
|
-
selectedLogoIndex,
|
|
99
|
-
context,
|
|
100
|
-
} = useLogoGenerator();
|
|
94
|
+
const { isSavingLogoToLibrary, selectedLogo, logos, selectedLogoIndex, context } =
|
|
95
|
+
useLogoGenerator();
|
|
101
96
|
|
|
102
97
|
const handleClick = async () => {
|
|
103
|
-
if ( !
|
|
98
|
+
if ( ! isSavingLogoToLibrary ) {
|
|
104
99
|
recordTracksEvent( EVENT_USE, {
|
|
105
100
|
context,
|
|
106
101
|
logos_count: logos.length,
|
|
107
102
|
selected_logo: selectedLogoIndex != null ? selectedLogoIndex + 1 : 0,
|
|
108
103
|
} );
|
|
109
104
|
|
|
110
|
-
|
|
111
|
-
await applyLogo();
|
|
112
|
-
onApplyLogo();
|
|
113
|
-
} catch ( error ) {
|
|
114
|
-
debug( 'Error applying logo', error );
|
|
115
|
-
}
|
|
105
|
+
onApplyLogo?.( selectedLogo?.mediaId );
|
|
116
106
|
}
|
|
117
107
|
};
|
|
118
108
|
|
|
119
|
-
return
|
|
120
|
-
<button className="jetpack-ai-logo-generator-modal-presenter__action">
|
|
121
|
-
<Icon icon={ <LogoIcon /> } />
|
|
122
|
-
<span className="action-text">{ __( 'Applying logo…', 'jetpack-ai-client' ) }</span>
|
|
123
|
-
</button>
|
|
124
|
-
) : (
|
|
109
|
+
return (
|
|
125
110
|
<Button
|
|
126
111
|
className="jetpack-ai-logo-generator-modal-presenter__action"
|
|
127
112
|
onClick={ handleClick }
|
|
128
113
|
disabled={ isSavingLogoToLibrary || ! selectedLogo?.mediaId }
|
|
129
114
|
>
|
|
130
115
|
<Icon icon={ <LogoIcon /> } />
|
|
131
|
-
<span className="action-text">{ __( 'Use on
|
|
116
|
+
<span className="action-text">{ __( 'Use on block', 'jetpack-ai-client' ) }</span>
|
|
132
117
|
</Button>
|
|
133
118
|
);
|
|
134
119
|
};
|
|
@@ -144,11 +129,11 @@ const LogoLoading: React.FC = () => {
|
|
|
144
129
|
);
|
|
145
130
|
};
|
|
146
131
|
|
|
147
|
-
const LogoReady: React.FC< {
|
|
148
|
-
siteId
|
|
149
|
-
logo
|
|
150
|
-
onApplyLogo
|
|
151
|
-
} ) => {
|
|
132
|
+
const LogoReady: React.FC< {
|
|
133
|
+
siteId: string;
|
|
134
|
+
logo: Logo;
|
|
135
|
+
onApplyLogo: ( mediaId: number ) => void;
|
|
136
|
+
} > = ( { siteId, logo, onApplyLogo } ) => {
|
|
152
137
|
return (
|
|
153
138
|
<>
|
|
154
139
|
<img
|
|
@@ -179,7 +164,7 @@ const LogoUpdated: React.FC< { logo: Logo } > = ( { logo } ) => {
|
|
|
179
164
|
/>
|
|
180
165
|
<div className="jetpack-ai-logo-generator-modal-presenter__success-wrapper">
|
|
181
166
|
<Icon icon={ <CheckIcon /> } />
|
|
182
|
-
<span>{ __( 'Your logo
|
|
167
|
+
<span>{ __( 'Your new logo was set to the block!', 'jetpack-ai-client' ) }</span>
|
|
183
168
|
</div>
|
|
184
169
|
</>
|
|
185
170
|
);
|
|
@@ -22,7 +22,7 @@ export const UpgradeScreen: React.FC< {
|
|
|
22
22
|
const { tracks } = useAnalytics();
|
|
23
23
|
const { recordEvent: recordTracksEvent } = tracks;
|
|
24
24
|
const upgradeMessageFeature = __(
|
|
25
|
-
'
|
|
25
|
+
'The logo generator requires a paid Jetpack AI plan. Upgrade your plan to access exclusive features, including logo generation. The upgrade will also increase the amount of requests you can use in all AI-powered features.',
|
|
26
26
|
'jetpack-ai-client'
|
|
27
27
|
);
|
|
28
28
|
|
|
@@ -46,7 +46,7 @@ export const UpgradeScreen: React.FC< {
|
|
|
46
46
|
</span>
|
|
47
47
|
|
|
48
48
|
<Button variant="link" href="https://jetpack.com/ai/" target="_blank">
|
|
49
|
-
{ __( 'Learn more', 'jetpack-ai-client' ) }
|
|
49
|
+
{ __( 'Learn more about Jetpack AI.', 'jetpack-ai-client' ) }
|
|
50
50
|
</Button>
|
|
51
51
|
</div>
|
|
52
52
|
<div className="jetpack-ai-logo-generator-modal__notice-actions">
|
|
@@ -17,9 +17,8 @@ import type React from 'react';
|
|
|
17
17
|
|
|
18
18
|
export const VisitSiteBanner: React.FC< {
|
|
19
19
|
className?: string;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} > = ( { className = null, siteURL = '#', onVisitBlankTarget } ) => {
|
|
20
|
+
onVisitBlankTarget?: () => void;
|
|
21
|
+
} > = ( { className = null, onVisitBlankTarget } ) => {
|
|
23
22
|
return (
|
|
24
23
|
<div className={ clsx( 'jetpack-ai-logo-generator-modal-visit-site-banner', className ) }>
|
|
25
24
|
<div className="jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo">
|
|
@@ -39,8 +38,13 @@ export const VisitSiteBanner: React.FC< {
|
|
|
39
38
|
) }
|
|
40
39
|
</span>
|
|
41
40
|
<div>
|
|
42
|
-
<Button
|
|
43
|
-
|
|
41
|
+
<Button
|
|
42
|
+
variant="link"
|
|
43
|
+
href="https://jetpack.com/redirect/?source=logo_generator_learn_more_about_jetpack_ai"
|
|
44
|
+
target="_blank"
|
|
45
|
+
onClick={ onVisitBlankTarget ? onVisitBlankTarget : null }
|
|
46
|
+
>
|
|
47
|
+
{ __( 'Learn more about Jetpack AI', 'jetpack-ai-client' ) }
|
|
44
48
|
<Icon icon={ external } size={ 20 } />
|
|
45
49
|
</Button>
|
|
46
50
|
</div>
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
import {
|
|
5
|
+
isAtomicSite,
|
|
6
|
+
isSimpleSite,
|
|
7
|
+
getSiteFragment,
|
|
8
|
+
} from '@automattic/jetpack-shared-extension-utils';
|
|
4
9
|
import { useSelect } from '@wordpress/data';
|
|
5
10
|
import debugFactory from 'debug';
|
|
6
11
|
/**
|
|
@@ -15,23 +20,33 @@ import type { Selectors } from '../store/types.js';
|
|
|
15
20
|
const debug = debugFactory( 'ai-client:logo-generator:use-checkout' );
|
|
16
21
|
|
|
17
22
|
export const useCheckout = () => {
|
|
18
|
-
const { nextTier
|
|
23
|
+
const { nextTier } = useSelect( select => {
|
|
19
24
|
const selectors: Selectors = select( STORE_NAME );
|
|
20
25
|
return {
|
|
21
26
|
nextTier: selectors.getAiAssistantFeature().nextTier,
|
|
22
|
-
siteDetails: selectors.getSiteDetails(),
|
|
23
27
|
};
|
|
24
28
|
}, [] );
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Use the Jetpack redirect URL to open the checkout page
|
|
32
|
+
*/
|
|
33
|
+
const wpcomCheckoutUrl = new URL( `https://jetpack.com/redirect/` );
|
|
34
|
+
wpcomCheckoutUrl.searchParams.set( 'source', 'jetpack-ai-yearly-tier-upgrade-nudge' );
|
|
35
|
+
wpcomCheckoutUrl.searchParams.set( 'site', getSiteFragment() as string );
|
|
36
|
+
wpcomCheckoutUrl.searchParams.set( 'path', `jetpack_ai_yearly:-q-${ nextTier?.limit }` );
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Open the product interstitial page
|
|
40
|
+
*/
|
|
41
|
+
const jetpackCheckoutUrl = `${ window?.Jetpack_Editor_Initial_State?.adminUrl }admin.php?redirect_to_referrer=1&page=my-jetpack#/add-jetpack-ai`;
|
|
42
|
+
|
|
43
|
+
const nextTierCheckoutURL =
|
|
44
|
+
isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl.toString() : jetpackCheckoutUrl;
|
|
45
|
+
|
|
46
|
+
debug( 'Next tier checkout URL: ', nextTierCheckoutURL );
|
|
32
47
|
|
|
33
48
|
return {
|
|
34
|
-
nextTierCheckoutURL
|
|
49
|
+
nextTierCheckoutURL,
|
|
35
50
|
hasNextTier: !! nextTier,
|
|
36
51
|
};
|
|
37
52
|
};
|
|
@@ -128,7 +128,7 @@ Site description: ${ description }`;
|
|
|
128
128
|
throw error;
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
|
-
[ setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount ]
|
|
131
|
+
[ setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount, name, description ]
|
|
132
132
|
);
|
|
133
133
|
|
|
134
134
|
const enhancePrompt = async function ( { prompt }: { prompt: string } ): Promise< string > {
|
|
@@ -162,5 +162,7 @@ export async function clearDeletedMedia( siteId: string ) {
|
|
|
162
162
|
responses
|
|
163
163
|
.filter( ( { exists } ) => ! exists )
|
|
164
164
|
.forEach( ( { mediaId } ) => removeLogo( { siteId, mediaId } ) );
|
|
165
|
-
} catch ( error ) {
|
|
165
|
+
} catch ( error ) {
|
|
166
|
+
// Assume that the media exists if there was a network error and do nothing to avoid data loss.
|
|
167
|
+
}
|
|
166
168
|
}
|
|
@@ -9,7 +9,6 @@ import apiFetch from '../../api-fetch/index.js';
|
|
|
9
9
|
const MAX_CONCURRENT_REQUESTS = 5;
|
|
10
10
|
|
|
11
11
|
let concurrentCounter = 0;
|
|
12
|
-
let lastCallTimestamp: number | null = null;
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Concurrency-limited request to wpcom-proxy-request.
|
|
@@ -25,16 +24,6 @@ export default async function wpcomLimitedRequest< T >( params: object ): Promis
|
|
|
25
24
|
throw new Error( 'Too many requests' );
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
const now = Date.now();
|
|
29
|
-
|
|
30
|
-
// Check if the last call was made less than 100 milliseconds ago
|
|
31
|
-
if ( lastCallTimestamp && now - lastCallTimestamp < 100 ) {
|
|
32
|
-
concurrentCounter -= 1;
|
|
33
|
-
throw new Error( 'Too many requests' );
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
lastCallTimestamp = now; // Update the timestamp
|
|
37
|
-
|
|
38
27
|
return apiFetch< T >( params ).finally( () => {
|
|
39
28
|
concurrentCounter -= 1;
|
|
40
29
|
} );
|
|
@@ -15,13 +15,16 @@ export interface GeneratorModalProps {
|
|
|
15
15
|
siteDetails?: SiteDetails;
|
|
16
16
|
isOpen: boolean;
|
|
17
17
|
onClose: () => void;
|
|
18
|
+
onApplyLogo: ( mediaId: number ) => void;
|
|
19
|
+
onReload: () => void;
|
|
18
20
|
context: string;
|
|
21
|
+
placement: string;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export interface LogoPresenterProps {
|
|
22
25
|
logo?: Logo;
|
|
23
26
|
loading?: boolean;
|
|
24
|
-
onApplyLogo: () => void;
|
|
27
|
+
onApplyLogo: ( mediaId: number ) => void;
|
|
25
28
|
logoAccepted?: boolean;
|
|
26
29
|
siteId: string | number;
|
|
27
30
|
}
|