@automattic/jetpack-shared-extension-utils 0.16.4 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/index.js +5 -1
- package/package.json +26 -15
- package/src/block-icons.js +25 -0
- package/src/components/index.js +2 -0
- package/src/components/upgrade-nudge/index.jsx +59 -0
- package/src/components/upgrade-nudge/style.scss +42 -0
- package/src/hooks/use-autosave-and-redirect/README.md +54 -0
- package/src/hooks/use-autosave-and-redirect/index.js +103 -0
- package/src/hooks/use-plan-type/index.ts +27 -0
- package/src/hooks/use-ref-interval.ts +67 -0
- package/src/icons.js +525 -0
- package/src/icons.native.scss +19 -0
- package/src/icons.scss +21 -0
- package/src/is-current-user-connected.js +8 -1
- package/src/libs/connection/index.ts +59 -0
- package/src/libs/index.js +1 -0
- package/src/store/wordpress-com/actions.ts +194 -0
- package/src/store/wordpress-com/constants.ts +31 -0
- package/src/store/wordpress-com/index.ts +108 -0
- package/src/store/wordpress-com/reducer.ts +224 -0
- package/src/store/wordpress-com/types.ts +161 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './connection';
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
5
|
+
/**
|
|
6
|
+
* Types & Constants
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN,
|
|
10
|
+
ACTION_DEQUEUE_ASYNC_REQUEST,
|
|
11
|
+
ACTION_ENQUEUE_ASYNC_REQUEST,
|
|
12
|
+
ACTION_FETCH_FROM_API,
|
|
13
|
+
ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT,
|
|
14
|
+
ACTION_REQUEST_AI_ASSISTANT_FEATURE,
|
|
15
|
+
ACTION_SET_PLANS,
|
|
16
|
+
ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE,
|
|
17
|
+
ACTION_STORE_AI_ASSISTANT_FEATURE,
|
|
18
|
+
ENDPOINT_AI_ASSISTANT_FEATURE,
|
|
19
|
+
NEW_ASYNC_REQUEST_TIMER_INTERVAL,
|
|
20
|
+
ACTION_SET_TIER_PLANS_ENABLED,
|
|
21
|
+
} from './constants.js';
|
|
22
|
+
import type { Plan, AiFeatureProps, SiteAIAssistantFeatureEndpointResponseProps } from './types.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Map the response from the `sites/$site/ai-assistant-feature`
|
|
26
|
+
* endpoint to the AI Assistant feature props.
|
|
27
|
+
* @param { SiteAIAssistantFeatureEndpointResponseProps } response - The response from the endpoint.
|
|
28
|
+
* @return { AiFeatureProps } The AI Assistant feature props.
|
|
29
|
+
*/
|
|
30
|
+
export function mapAiFeatureResponseToAiFeatureProps(
|
|
31
|
+
response: SiteAIAssistantFeatureEndpointResponseProps
|
|
32
|
+
): AiFeatureProps {
|
|
33
|
+
return {
|
|
34
|
+
hasFeature: !! response[ 'has-feature' ],
|
|
35
|
+
isOverLimit: !! response[ 'is-over-limit' ],
|
|
36
|
+
requestsCount: response[ 'requests-count' ],
|
|
37
|
+
requestsLimit: response[ 'requests-limit' ],
|
|
38
|
+
requireUpgrade: !! response[ 'site-require-upgrade' ],
|
|
39
|
+
errorMessage: response[ 'error-message' ],
|
|
40
|
+
errorCode: response[ 'error-code' ],
|
|
41
|
+
upgradeType: response[ 'upgrade-type' ],
|
|
42
|
+
usagePeriod: {
|
|
43
|
+
currentStart: response[ 'usage-period' ]?.[ 'current-start' ],
|
|
44
|
+
nextStart: response[ 'usage-period' ]?.[ 'next-start' ],
|
|
45
|
+
requestsCount: response[ 'usage-period' ]?.[ 'requests-count' ] || 0,
|
|
46
|
+
},
|
|
47
|
+
currentTier: response[ 'current-tier' ],
|
|
48
|
+
nextTier: response[ 'next-tier' ],
|
|
49
|
+
tierPlansEnabled: !! response[ 'tier-plans-enabled' ],
|
|
50
|
+
costs: response.costs,
|
|
51
|
+
featuresControl: response[ 'features-control' ],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const actions = {
|
|
56
|
+
setPlans( plans: Array< Plan > ) {
|
|
57
|
+
return {
|
|
58
|
+
type: ACTION_SET_PLANS,
|
|
59
|
+
plans,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
fetchFromAPI( url: string ) {
|
|
64
|
+
return {
|
|
65
|
+
type: ACTION_FETCH_FROM_API,
|
|
66
|
+
url,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
storeAiAssistantFeature( feature: AiFeatureProps ) {
|
|
71
|
+
return {
|
|
72
|
+
type: ACTION_STORE_AI_ASSISTANT_FEATURE,
|
|
73
|
+
feature,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Thunk action to fetch the AI Assistant feature from the API.
|
|
79
|
+
*
|
|
80
|
+
* @return {Function} The thunk action.
|
|
81
|
+
*/
|
|
82
|
+
fetchAiAssistantFeature() {
|
|
83
|
+
return async ( { dispatch } ) => {
|
|
84
|
+
// Dispatch isFetching action.
|
|
85
|
+
dispatch( { type: ACTION_REQUEST_AI_ASSISTANT_FEATURE } );
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const response: SiteAIAssistantFeatureEndpointResponseProps = await apiFetch( {
|
|
89
|
+
path: ENDPOINT_AI_ASSISTANT_FEATURE,
|
|
90
|
+
} );
|
|
91
|
+
|
|
92
|
+
// Store the feature in the store.
|
|
93
|
+
dispatch(
|
|
94
|
+
actions.storeAiAssistantFeature( mapAiFeatureResponseToAiFeatureProps( response ) )
|
|
95
|
+
);
|
|
96
|
+
} catch ( err ) {
|
|
97
|
+
// @todo: Handle error.
|
|
98
|
+
console.error( err ); // eslint-disable-line no-console
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* This thunk action is used to increase
|
|
105
|
+
* the requests count for the current usage period.
|
|
106
|
+
* @param {number} count - The number of requests to increase. Default is 1.
|
|
107
|
+
* @return {Function} The thunk action.
|
|
108
|
+
*/
|
|
109
|
+
increaseAiAssistantRequestsCount( count: number = 1 ) {
|
|
110
|
+
return ( { dispatch } ) => {
|
|
111
|
+
dispatch( {
|
|
112
|
+
type: ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT,
|
|
113
|
+
count,
|
|
114
|
+
} );
|
|
115
|
+
|
|
116
|
+
// Every time the requests count is increased, decrease the countdown
|
|
117
|
+
dispatch( actions.decreaseAsyncRequestCountdownValue() );
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* This thunk action is used to decrease
|
|
123
|
+
* the countdown value for the new async request.
|
|
124
|
+
* When the countdown reaches 0, enqueue a new async request.
|
|
125
|
+
*
|
|
126
|
+
* @return {Function} The thunk action.
|
|
127
|
+
*/
|
|
128
|
+
decreaseAsyncRequestCountdownValue() {
|
|
129
|
+
return async ( { dispatch, select } ) => {
|
|
130
|
+
dispatch( { type: ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN } );
|
|
131
|
+
|
|
132
|
+
const asyncCoundown = select.getAsyncRequestCountdownValue();
|
|
133
|
+
if ( asyncCoundown <= 0 ) {
|
|
134
|
+
dispatch( actions.enqueueAiAssistantFeatureAsyncRequest() );
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* This thunk action is used to enqueue a new async request.
|
|
141
|
+
* If already exist an enqueue request, clear it and enqueue a new one.
|
|
142
|
+
*
|
|
143
|
+
* @return {Function} The thunk action.
|
|
144
|
+
*/
|
|
145
|
+
enqueueAiAssistantFeatureAsyncRequest() {
|
|
146
|
+
return ( { dispatch } ) => {
|
|
147
|
+
// Check if there is already a timer running
|
|
148
|
+
dispatch.dequeueAiAssistantFeatureAsyncRequest();
|
|
149
|
+
|
|
150
|
+
const contdownTimerId = setTimeout( () => {
|
|
151
|
+
dispatch( actions.fetchAiAssistantFeature() );
|
|
152
|
+
}, NEW_ASYNC_REQUEST_TIMER_INTERVAL ); // backend process requires a delay to be able to see the new value
|
|
153
|
+
|
|
154
|
+
dispatch( { type: ACTION_ENQUEUE_ASYNC_REQUEST, timerId: contdownTimerId } );
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* This thunk action is used to dequeue a new async request.
|
|
160
|
+
* It will clear the timer if there is one,
|
|
161
|
+
* canceling the enqueue async request.
|
|
162
|
+
*
|
|
163
|
+
* @return {Function} The thunk action.
|
|
164
|
+
*/
|
|
165
|
+
dequeueAiAssistantFeatureAsyncRequest() {
|
|
166
|
+
return ( { dispatch, select } ) => {
|
|
167
|
+
dispatch( { type: ACTION_DEQUEUE_ASYNC_REQUEST, timerId: 0 } );
|
|
168
|
+
|
|
169
|
+
const timerId = select.getAsyncRequestCountdownTimerId();
|
|
170
|
+
// If there is no timer, there is nothing to clear
|
|
171
|
+
if ( ! timerId ) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
window?.clearTimeout( timerId );
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
setAiAssistantFeatureRequireUpgrade( requireUpgrade: boolean = true ) {
|
|
180
|
+
return {
|
|
181
|
+
type: ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE,
|
|
182
|
+
requireUpgrade,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
setTierPlansEnabled( tierPlansEnabled: boolean = true ) {
|
|
187
|
+
return {
|
|
188
|
+
type: ACTION_SET_TIER_PLANS_ENABLED,
|
|
189
|
+
tierPlansEnabled,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export default actions;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plans actions
|
|
3
|
+
*/
|
|
4
|
+
export const ACTION_SET_PLANS = 'SET_PLANS';
|
|
5
|
+
export const ACTION_FETCH_FROM_API = 'FETCH_FROM_API';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AI Assistant feature Actions
|
|
9
|
+
*/
|
|
10
|
+
export const ACTION_STORE_AI_ASSISTANT_FEATURE = 'STORE_AI_ASSISTANT_FEATURE';
|
|
11
|
+
export const ACTION_REQUEST_AI_ASSISTANT_FEATURE = 'REQUEST_AI_ASSISTANT_FEATURE';
|
|
12
|
+
export const ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT = 'INCREASE_AI_ASSISTANT_REQUESTS_COUNT';
|
|
13
|
+
export const ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE =
|
|
14
|
+
'SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE';
|
|
15
|
+
export const ACTION_SET_TIER_PLANS_ENABLED = 'SET_TIER_PLANS_ENABLED';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Endpoints
|
|
19
|
+
*/
|
|
20
|
+
export const ENDPOINT_AI_ASSISTANT_FEATURE = '/wpcom/v2/jetpack-ai/ai-assistant-feature';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* New AI Assistant feature async request
|
|
24
|
+
*/
|
|
25
|
+
export const FREE_PLAN_REQUESTS_LIMIT = 20;
|
|
26
|
+
export const UNLIMITED_PLAN_REQUESTS_LIMIT = 3000;
|
|
27
|
+
export const ASYNC_REQUEST_COUNTDOWN_INIT_VALUE = 3;
|
|
28
|
+
export const NEW_ASYNC_REQUEST_TIMER_INTERVAL = 5000;
|
|
29
|
+
export const ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN = 'DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN';
|
|
30
|
+
export const ACTION_ENQUEUE_ASYNC_REQUEST = 'ENQUEUE_ASYNC_COUNTDOWN_REQUEST';
|
|
31
|
+
export const ACTION_DEQUEUE_ASYNC_REQUEST = 'DEQUEUE_ASYNC_COUNTDOWN_REQUEST';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { createReduxStore, register } from '@wordpress/data';
|
|
5
|
+
/**
|
|
6
|
+
* Internal dependencies
|
|
7
|
+
*/
|
|
8
|
+
import actions from './actions.js';
|
|
9
|
+
import reducer from './reducer.js';
|
|
10
|
+
/**
|
|
11
|
+
* Types
|
|
12
|
+
*/
|
|
13
|
+
import type { AiFeatureProps, PlanStateProps } from './types.js';
|
|
14
|
+
|
|
15
|
+
const store = 'wordpress-com/plans';
|
|
16
|
+
|
|
17
|
+
export const selectors = {
|
|
18
|
+
/*
|
|
19
|
+
* Return the plan with the given slug.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} state - The Plans state tree.
|
|
22
|
+
* @param {string} planSlug - The plan slug to find.
|
|
23
|
+
* @return {Object} The plan.
|
|
24
|
+
*/
|
|
25
|
+
getPlan( state: PlanStateProps, planSlug: string ) {
|
|
26
|
+
return state.plans.find( plan => plan.product_slug === planSlug );
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return the AI Assistant feature.
|
|
31
|
+
*
|
|
32
|
+
* @param {PlanStateProps} state - The Plans state tree.
|
|
33
|
+
* @return {AiFeatureProps} The AI Assistant feature data.
|
|
34
|
+
*/
|
|
35
|
+
getAiAssistantFeature( state: PlanStateProps ): AiFeatureProps {
|
|
36
|
+
// Clean up the _meta property.
|
|
37
|
+
const data = { ...state.features.aiAssistant };
|
|
38
|
+
delete data._meta;
|
|
39
|
+
|
|
40
|
+
return data;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the isRequesting flag for the AI Assistant feature.
|
|
45
|
+
*
|
|
46
|
+
* @param {PlanStateProps} state - The Plans state tree.
|
|
47
|
+
* @return {boolean} The isRequesting flag.
|
|
48
|
+
*/
|
|
49
|
+
getIsRequestingAiAssistantFeature( state: PlanStateProps ): boolean {
|
|
50
|
+
return state.features.aiAssistant?._meta?.isRequesting;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
getAsyncRequestCountdownValue( state: PlanStateProps ): number {
|
|
54
|
+
return state.features.aiAssistant?._meta?.asyncRequestCountdown;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
getAsyncRequestCountdownTimerId( state: PlanStateProps ): number {
|
|
58
|
+
return state.features.aiAssistant?._meta?.asyncRequestTimerId;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const wordpressPlansStore = createReduxStore( store, {
|
|
63
|
+
actions,
|
|
64
|
+
|
|
65
|
+
reducer,
|
|
66
|
+
|
|
67
|
+
selectors,
|
|
68
|
+
|
|
69
|
+
controls: {
|
|
70
|
+
FETCH_FROM_API( { url } ) {
|
|
71
|
+
// We cannot use `@wordpress/api-fetch` here since it unconditionally sends
|
|
72
|
+
// the `X-WP-Nonce` header, which is disallowed by WordPress.com.
|
|
73
|
+
// (To reproduce, note that you need to call `apiFetch` with `
|
|
74
|
+
// `{ credentials: 'same-origin', mode: 'cors' }`, since its defaults are
|
|
75
|
+
// different from `fetch`'s.)
|
|
76
|
+
return fetch( url ).then( response => response.json() );
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
resolvers: {
|
|
81
|
+
*getPlan() {
|
|
82
|
+
const url = 'https://public-api.wordpress.com/rest/v1.5/plans';
|
|
83
|
+
const plans = yield actions.fetchFromAPI( url );
|
|
84
|
+
return actions.setPlans( plans );
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
getAiAssistantFeature: ( state: PlanStateProps ) => {
|
|
88
|
+
if ( state?.features?.aiAssistant ) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return actions.fetchAiAssistantFeature();
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
} );
|
|
96
|
+
|
|
97
|
+
register( wordpressPlansStore );
|
|
98
|
+
|
|
99
|
+
// Types
|
|
100
|
+
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
type OmitFirstArg< F > = F extends ( _: any, ...args: infer P ) => infer R
|
|
103
|
+
? ( ...args: P ) => R
|
|
104
|
+
: never;
|
|
105
|
+
|
|
106
|
+
export type WordPressPlansSelectors = {
|
|
107
|
+
[ key in keyof typeof selectors ]: OmitFirstArg< ( typeof selectors )[ key ] >;
|
|
108
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types & Constants
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN,
|
|
6
|
+
ACTION_ENQUEUE_ASYNC_REQUEST,
|
|
7
|
+
ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT,
|
|
8
|
+
ACTION_REQUEST_AI_ASSISTANT_FEATURE,
|
|
9
|
+
ACTION_SET_PLANS,
|
|
10
|
+
ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE,
|
|
11
|
+
ACTION_STORE_AI_ASSISTANT_FEATURE,
|
|
12
|
+
ASYNC_REQUEST_COUNTDOWN_INIT_VALUE,
|
|
13
|
+
FREE_PLAN_REQUESTS_LIMIT,
|
|
14
|
+
UNLIMITED_PLAN_REQUESTS_LIMIT,
|
|
15
|
+
ACTION_SET_TIER_PLANS_ENABLED,
|
|
16
|
+
} from './constants.js';
|
|
17
|
+
import type { PlanStateProps, TierLimitProp } from './types.js';
|
|
18
|
+
|
|
19
|
+
const INITIAL_STATE: PlanStateProps = {
|
|
20
|
+
plans: [],
|
|
21
|
+
features: {
|
|
22
|
+
aiAssistant: {
|
|
23
|
+
hasFeature: true,
|
|
24
|
+
isOverLimit: false,
|
|
25
|
+
requestsCount: 0,
|
|
26
|
+
requestsLimit: FREE_PLAN_REQUESTS_LIMIT,
|
|
27
|
+
requireUpgrade: false,
|
|
28
|
+
errorMessage: '',
|
|
29
|
+
errorCode: '',
|
|
30
|
+
upgradeType: 'default',
|
|
31
|
+
currentTier: {
|
|
32
|
+
slug: 'ai-assistant-tier-free',
|
|
33
|
+
value: 0,
|
|
34
|
+
limit: 20,
|
|
35
|
+
},
|
|
36
|
+
usagePeriod: {
|
|
37
|
+
currentStart: '',
|
|
38
|
+
nextStart: '',
|
|
39
|
+
requestsCount: 0,
|
|
40
|
+
},
|
|
41
|
+
nextTier: null,
|
|
42
|
+
tierPlansEnabled: false,
|
|
43
|
+
_meta: {
|
|
44
|
+
isRequesting: false,
|
|
45
|
+
asyncRequestCountdown: ASYNC_REQUEST_COUNTDOWN_INIT_VALUE,
|
|
46
|
+
asyncRequestTimerId: 0,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The reducer of the plan state
|
|
54
|
+
* @param {PlanStateProps} state - The plan state.
|
|
55
|
+
* @param {object} action - The action.
|
|
56
|
+
* @return {PlanStateProps} - The plan state.
|
|
57
|
+
*/
|
|
58
|
+
export default function reducer( state = INITIAL_STATE, action ) {
|
|
59
|
+
switch ( action.type ) {
|
|
60
|
+
case ACTION_SET_PLANS:
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
plans: action.plans,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
case ACTION_REQUEST_AI_ASSISTANT_FEATURE:
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
features: {
|
|
70
|
+
...state.features,
|
|
71
|
+
aiAssistant: {
|
|
72
|
+
...state.features.aiAssistant,
|
|
73
|
+
_meta: {
|
|
74
|
+
...state.features.aiAssistant._meta,
|
|
75
|
+
isRequesting: true,
|
|
76
|
+
asyncRequestCountdown: ASYNC_REQUEST_COUNTDOWN_INIT_VALUE, // restore the countdown
|
|
77
|
+
asyncRequestTimerId: 0, // reset the timer id
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
case ACTION_STORE_AI_ASSISTANT_FEATURE: {
|
|
84
|
+
return {
|
|
85
|
+
...state,
|
|
86
|
+
features: {
|
|
87
|
+
...state.features,
|
|
88
|
+
aiAssistant: {
|
|
89
|
+
...action.feature,
|
|
90
|
+
_meta: {
|
|
91
|
+
...state.features.aiAssistant._meta,
|
|
92
|
+
isRequesting: false,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case ACTION_INCREASE_AI_ASSISTANT_REQUESTS_COUNT: {
|
|
100
|
+
// Usage Period data
|
|
101
|
+
const usagePeriod = state.features.aiAssistant.usagePeriod || { requestsCount: 0 };
|
|
102
|
+
|
|
103
|
+
// Increase requests counters
|
|
104
|
+
const requestsCount = state.features.aiAssistant.requestsCount + action.count;
|
|
105
|
+
usagePeriod.requestsCount += action.count;
|
|
106
|
+
|
|
107
|
+
// Current tier value
|
|
108
|
+
const currentTierValue = state.features.aiAssistant.currentTier?.value;
|
|
109
|
+
|
|
110
|
+
const isFreeTierPlan =
|
|
111
|
+
( typeof currentTierValue === 'undefined' && ! state.features.aiAssistant.hasFeature ) ||
|
|
112
|
+
currentTierValue === 0;
|
|
113
|
+
|
|
114
|
+
const isUnlimitedTierPlan =
|
|
115
|
+
( typeof currentTierValue === 'undefined' && state.features.aiAssistant.hasFeature ) ||
|
|
116
|
+
currentTierValue === 1;
|
|
117
|
+
|
|
118
|
+
// Request limit defined with the current tier limit by default.
|
|
119
|
+
let requestsLimit = state.features.aiAssistant.currentTier?.limit;
|
|
120
|
+
|
|
121
|
+
if ( isUnlimitedTierPlan ) {
|
|
122
|
+
requestsLimit = UNLIMITED_PLAN_REQUESTS_LIMIT;
|
|
123
|
+
} else if ( isFreeTierPlan ) {
|
|
124
|
+
requestsLimit = state.features.aiAssistant.requestsLimit as TierLimitProp;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const currentCount = isFreeTierPlan
|
|
128
|
+
? requestsCount // Free tier plan counts all time requests
|
|
129
|
+
: state.features.aiAssistant.usagePeriod?.requestsCount; // Unlimited tier plan counts usage period requests
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Compute the AI Assistant Feature data optimistically,
|
|
133
|
+
* based on the Jetpack_AI_Helper::get_ai_assistance_feature() helper.
|
|
134
|
+
*
|
|
135
|
+
* @see _inc/lib/class-jetpack-ai-helper.php
|
|
136
|
+
*/
|
|
137
|
+
const isOverLimit = currentCount >= requestsLimit;
|
|
138
|
+
|
|
139
|
+
// highest tier holds a soft limit so requireUpgrade is false on that case (nextTier null means highest tier)
|
|
140
|
+
const requireUpgrade = isOverLimit && state.features.aiAssistant.nextTier !== null;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...state,
|
|
144
|
+
features: {
|
|
145
|
+
...state.features,
|
|
146
|
+
aiAssistant: {
|
|
147
|
+
...state.features.aiAssistant,
|
|
148
|
+
isOverLimit,
|
|
149
|
+
requestsCount,
|
|
150
|
+
requireUpgrade,
|
|
151
|
+
usagePeriod: { ...usagePeriod },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
case ACTION_DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN: {
|
|
158
|
+
return {
|
|
159
|
+
...state,
|
|
160
|
+
features: {
|
|
161
|
+
...state.features,
|
|
162
|
+
aiAssistant: {
|
|
163
|
+
...state.features.aiAssistant,
|
|
164
|
+
_meta: {
|
|
165
|
+
...state.features.aiAssistant._meta,
|
|
166
|
+
asyncRequestCountdown: state.features.aiAssistant._meta.asyncRequestCountdown - 1,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case ACTION_ENQUEUE_ASYNC_REQUEST: {
|
|
174
|
+
return {
|
|
175
|
+
...state,
|
|
176
|
+
features: {
|
|
177
|
+
...state.features,
|
|
178
|
+
aiAssistant: {
|
|
179
|
+
...state.features.aiAssistant,
|
|
180
|
+
_meta: {
|
|
181
|
+
...state.features.aiAssistant._meta,
|
|
182
|
+
asyncRequestTimerId: action.timerId,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case ACTION_SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE: {
|
|
190
|
+
/*
|
|
191
|
+
* If we require an upgrade, we are also over the limit;
|
|
192
|
+
* The opposite is not true, we can be over the limit without
|
|
193
|
+
* requiring an upgrade, for example when we are on the highest tier.
|
|
194
|
+
* In this case, we don't want to set isOverLimit to false.
|
|
195
|
+
*/
|
|
196
|
+
return {
|
|
197
|
+
...state,
|
|
198
|
+
features: {
|
|
199
|
+
...state.features,
|
|
200
|
+
aiAssistant: {
|
|
201
|
+
...state.features.aiAssistant,
|
|
202
|
+
requireUpgrade: action.requireUpgrade,
|
|
203
|
+
...( action.requireUpgrade ? { isOverLimit: true } : {} ),
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case ACTION_SET_TIER_PLANS_ENABLED: {
|
|
210
|
+
return {
|
|
211
|
+
...state,
|
|
212
|
+
features: {
|
|
213
|
+
...state.features,
|
|
214
|
+
aiAssistant: {
|
|
215
|
+
...state.features.aiAssistant,
|
|
216
|
+
tierPlansEnabled: action.tierPlansEnabled,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return state;
|
|
224
|
+
}
|