@automattic/jetpack-ai-client 0.12.2 → 0.12.4
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 +10 -0
- package/build/components/ai-control/ai-control.d.ts +28 -0
- package/build/components/ai-control/ai-control.js +22 -0
- package/build/components/ai-control/block-ai-control.d.ts +37 -0
- package/build/components/ai-control/block-ai-control.js +82 -0
- package/build/components/ai-control/extension-ai-control.d.ts +35 -0
- package/build/components/ai-control/extension-ai-control.js +86 -0
- package/build/components/ai-control/index.d.ts +3 -40
- package/build/components/ai-control/index.js +3 -86
- package/build/components/index.d.ts +2 -2
- package/build/components/index.js +2 -2
- package/build/components/{ai-control/message.d.ts → message/index.d.ts} +25 -8
- package/build/components/message/index.js +69 -0
- package/build/icons/error-exclamation.d.ts +2 -0
- package/build/icons/error-exclamation.js +7 -0
- package/package.json +3 -2
- package/src/components/ai-control/ai-control.tsx +79 -0
- package/src/components/ai-control/block-ai-control.tsx +278 -0
- package/src/components/ai-control/extension-ai-control.tsx +217 -0
- package/src/components/ai-control/index.tsx +3 -281
- package/src/components/ai-control/style.scss +4 -42
- package/src/components/index.ts +3 -2
- package/src/components/message/index.tsx +157 -0
- package/src/components/message/style.scss +83 -0
- package/src/icons/error-exclamation.tsx +18 -0
- package/build/components/ai-control/message.js +0 -57
- package/src/components/ai-control/message.tsx +0 -118
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@automattic/jetpack-ai-client",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.4",
|
|
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": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@storybook/addon-actions": "8.0.6",
|
|
27
27
|
"@storybook/blocks": "8.0.6",
|
|
28
|
+
"@storybook/preview-api": "8.0.6",
|
|
28
29
|
"@storybook/react": "8.0.6",
|
|
29
30
|
"@types/markdown-it": "14.0.0",
|
|
30
31
|
"@types/turndown": "5.0.4",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"main": "./build/index.js",
|
|
42
43
|
"types": "./build/index.d.ts",
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@automattic/jetpack-base-styles": "^0.6.
|
|
45
|
+
"@automattic/jetpack-base-styles": "^0.6.23",
|
|
45
46
|
"@automattic/jetpack-connection": "^0.33.8",
|
|
46
47
|
"@automattic/jetpack-shared-extension-utils": "^0.14.10",
|
|
47
48
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { PlainText } from '@wordpress/block-editor';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import AiStatusIndicator from '../ai-status-indicator/index.js';
|
|
11
|
+
import './style.scss';
|
|
12
|
+
/**
|
|
13
|
+
* Types
|
|
14
|
+
*/
|
|
15
|
+
import type { RequestingStateProp } from '../../types.js';
|
|
16
|
+
import type { ReactElement } from 'react';
|
|
17
|
+
|
|
18
|
+
type AIControlProps = {
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
value: string;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
isTransparent?: boolean;
|
|
23
|
+
state?: RequestingStateProp;
|
|
24
|
+
onChange?: ( newValue: string ) => void;
|
|
25
|
+
banner?: ReactElement;
|
|
26
|
+
error?: ReactElement;
|
|
27
|
+
actions?: ReactElement;
|
|
28
|
+
message?: ReactElement;
|
|
29
|
+
promptUserInputRef?: React.MutableRefObject< HTMLInputElement >;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base AIControl component. Contains the main structure of the control component and slots for banner, error, actions and message.
|
|
34
|
+
*
|
|
35
|
+
* @param {AIControlProps} props - Component props
|
|
36
|
+
* @returns {ReactElement} Rendered component
|
|
37
|
+
*/
|
|
38
|
+
export default function AIControl( {
|
|
39
|
+
disabled = false,
|
|
40
|
+
value = '',
|
|
41
|
+
placeholder = '',
|
|
42
|
+
isTransparent = false,
|
|
43
|
+
state = 'init',
|
|
44
|
+
onChange,
|
|
45
|
+
banner = null,
|
|
46
|
+
error = null,
|
|
47
|
+
actions = null,
|
|
48
|
+
message = null,
|
|
49
|
+
promptUserInputRef = null,
|
|
50
|
+
}: AIControlProps ): ReactElement {
|
|
51
|
+
return (
|
|
52
|
+
<div className="jetpack-components-ai-control__container-wrapper">
|
|
53
|
+
{ error }
|
|
54
|
+
<div className="jetpack-components-ai-control__container">
|
|
55
|
+
{ banner }
|
|
56
|
+
<div
|
|
57
|
+
className={ classNames( 'jetpack-components-ai-control__wrapper', {
|
|
58
|
+
'is-transparent': isTransparent,
|
|
59
|
+
} ) }
|
|
60
|
+
>
|
|
61
|
+
<AiStatusIndicator state={ state } />
|
|
62
|
+
|
|
63
|
+
<div className="jetpack-components-ai-control__input-wrapper">
|
|
64
|
+
<PlainText
|
|
65
|
+
value={ value }
|
|
66
|
+
onChange={ onChange }
|
|
67
|
+
placeholder={ placeholder }
|
|
68
|
+
className="jetpack-components-ai-control__input"
|
|
69
|
+
disabled={ disabled }
|
|
70
|
+
ref={ promptUserInputRef }
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
{ actions }
|
|
74
|
+
</div>
|
|
75
|
+
{ message }
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Button, ButtonGroup } from '@wordpress/components';
|
|
5
|
+
import { useKeyboardShortcut } from '@wordpress/compose';
|
|
6
|
+
import { useImperativeHandle, useRef, useEffect, useCallback, useState } from '@wordpress/element';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
8
|
+
import {
|
|
9
|
+
Icon,
|
|
10
|
+
closeSmall,
|
|
11
|
+
check,
|
|
12
|
+
arrowUp,
|
|
13
|
+
trash,
|
|
14
|
+
reusableBlock as regenerate,
|
|
15
|
+
} from '@wordpress/icons';
|
|
16
|
+
import debugFactory from 'debug';
|
|
17
|
+
import React, { forwardRef } from 'react';
|
|
18
|
+
/**
|
|
19
|
+
* Internal dependencies
|
|
20
|
+
*/
|
|
21
|
+
import { GuidelineMessage } from '../message/index.js';
|
|
22
|
+
import AIControl from './ai-control.js';
|
|
23
|
+
import './style.scss';
|
|
24
|
+
/**
|
|
25
|
+
* Types
|
|
26
|
+
*/
|
|
27
|
+
import type { RequestingStateProp } from '../../types.js';
|
|
28
|
+
import type { ReactElement } from 'react';
|
|
29
|
+
|
|
30
|
+
type BlockAIControlProps = {
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
value: string;
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
showAccept?: boolean;
|
|
35
|
+
acceptLabel?: string;
|
|
36
|
+
showButtonLabels?: boolean;
|
|
37
|
+
isTransparent?: boolean;
|
|
38
|
+
state?: RequestingStateProp;
|
|
39
|
+
showGuideLine?: boolean;
|
|
40
|
+
customFooter?: ReactElement;
|
|
41
|
+
onChange?: ( newValue: string ) => void;
|
|
42
|
+
onSend?: ( currentValue: string ) => void;
|
|
43
|
+
onStop?: () => void;
|
|
44
|
+
onAccept?: () => void;
|
|
45
|
+
onDiscard?: () => void;
|
|
46
|
+
showRemove?: boolean;
|
|
47
|
+
banner?: ReactElement;
|
|
48
|
+
error?: ReactElement;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const debug = debugFactory( 'jetpack-ai-client:block-ai-control' );
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* BlockAIControl component. Used by the AI Assistant block, adding logic and components to the base AIControl component.
|
|
55
|
+
*
|
|
56
|
+
* @param {BlockAIControlProps} props - Component props
|
|
57
|
+
* @param {React.MutableRefObject} ref - Ref to the component
|
|
58
|
+
* @returns {ReactElement} Rendered component
|
|
59
|
+
*/
|
|
60
|
+
export function BlockAIControl(
|
|
61
|
+
{
|
|
62
|
+
disabled = false,
|
|
63
|
+
value = '',
|
|
64
|
+
placeholder = '',
|
|
65
|
+
showAccept = false,
|
|
66
|
+
acceptLabel = __( 'Accept', 'jetpack-ai-client' ),
|
|
67
|
+
showButtonLabels = true,
|
|
68
|
+
isTransparent = false,
|
|
69
|
+
state = 'init',
|
|
70
|
+
showGuideLine = false,
|
|
71
|
+
customFooter = null,
|
|
72
|
+
onChange,
|
|
73
|
+
onSend,
|
|
74
|
+
onStop,
|
|
75
|
+
onAccept,
|
|
76
|
+
onDiscard,
|
|
77
|
+
showRemove = false,
|
|
78
|
+
banner = null,
|
|
79
|
+
error = null,
|
|
80
|
+
}: BlockAIControlProps,
|
|
81
|
+
ref: React.MutableRefObject< HTMLInputElement >
|
|
82
|
+
): ReactElement {
|
|
83
|
+
const loading = state === 'requesting' || state === 'suggesting';
|
|
84
|
+
const [ editRequest, setEditRequest ] = useState( false );
|
|
85
|
+
const [ lastValue, setLastValue ] = useState( value || null );
|
|
86
|
+
const promptUserInputRef = useRef( null );
|
|
87
|
+
|
|
88
|
+
// Pass the ref to forwardRef.
|
|
89
|
+
useImperativeHandle( ref, () => promptUserInputRef.current );
|
|
90
|
+
|
|
91
|
+
useEffect( () => {
|
|
92
|
+
if ( editRequest ) {
|
|
93
|
+
promptUserInputRef?.current?.focus();
|
|
94
|
+
}
|
|
95
|
+
}, [ editRequest ] );
|
|
96
|
+
|
|
97
|
+
const sendHandler = useCallback( () => {
|
|
98
|
+
setLastValue( value );
|
|
99
|
+
setEditRequest( false );
|
|
100
|
+
onSend?.( value );
|
|
101
|
+
}, [ value ] );
|
|
102
|
+
|
|
103
|
+
const changeHandler = useCallback(
|
|
104
|
+
( newValue: string ) => {
|
|
105
|
+
onChange?.( newValue );
|
|
106
|
+
if ( state === 'init' ) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ( ! lastValue ) {
|
|
111
|
+
// here we're coming from a one-click action
|
|
112
|
+
setEditRequest( newValue.length > 0 );
|
|
113
|
+
} else {
|
|
114
|
+
// here we're coming from an edit action
|
|
115
|
+
setEditRequest( newValue !== lastValue );
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
[ lastValue, state ]
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const discardHandler = useCallback( () => {
|
|
122
|
+
onDiscard?.();
|
|
123
|
+
}, [] );
|
|
124
|
+
|
|
125
|
+
const cancelEdit = useCallback( () => {
|
|
126
|
+
debug( 'cancelEdit, revert to last value', lastValue );
|
|
127
|
+
onChange?.( lastValue || '' );
|
|
128
|
+
setEditRequest( false );
|
|
129
|
+
}, [ lastValue ] );
|
|
130
|
+
|
|
131
|
+
useKeyboardShortcut(
|
|
132
|
+
'mod+enter',
|
|
133
|
+
() => {
|
|
134
|
+
if ( showAccept ) {
|
|
135
|
+
onAccept?.();
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
target: promptUserInputRef,
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
useKeyboardShortcut(
|
|
144
|
+
'enter',
|
|
145
|
+
e => {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
sendHandler();
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
target: promptUserInputRef,
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const actions = (
|
|
155
|
+
<>
|
|
156
|
+
{ ( ! showAccept || editRequest ) && (
|
|
157
|
+
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
158
|
+
{ ! loading ? (
|
|
159
|
+
<>
|
|
160
|
+
{ editRequest && (
|
|
161
|
+
<Button
|
|
162
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
163
|
+
onClick={ cancelEdit }
|
|
164
|
+
variant="secondary"
|
|
165
|
+
label={ __( 'Cancel', 'jetpack-ai-client' ) }
|
|
166
|
+
>
|
|
167
|
+
{ showButtonLabels ? (
|
|
168
|
+
__( 'Cancel', 'jetpack-ai-client' )
|
|
169
|
+
) : (
|
|
170
|
+
<Icon icon={ closeSmall } />
|
|
171
|
+
) }
|
|
172
|
+
</Button>
|
|
173
|
+
) }
|
|
174
|
+
|
|
175
|
+
{ showRemove && ! editRequest && ! value?.length && onDiscard && (
|
|
176
|
+
<Button
|
|
177
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
178
|
+
onClick={ discardHandler }
|
|
179
|
+
variant="secondary"
|
|
180
|
+
label={ __( 'Cancel', 'jetpack-ai-client' ) }
|
|
181
|
+
>
|
|
182
|
+
{ showButtonLabels ? (
|
|
183
|
+
__( 'Cancel', 'jetpack-ai-client' )
|
|
184
|
+
) : (
|
|
185
|
+
<Icon icon={ closeSmall } />
|
|
186
|
+
) }
|
|
187
|
+
</Button>
|
|
188
|
+
) }
|
|
189
|
+
|
|
190
|
+
{ value?.length > 0 && (
|
|
191
|
+
<Button
|
|
192
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
193
|
+
onClick={ sendHandler }
|
|
194
|
+
variant="primary"
|
|
195
|
+
disabled={ ! value?.length || disabled }
|
|
196
|
+
label={ __( 'Send request', 'jetpack-ai-client' ) }
|
|
197
|
+
>
|
|
198
|
+
{ showButtonLabels ? (
|
|
199
|
+
__( 'Generate', 'jetpack-ai-client' )
|
|
200
|
+
) : (
|
|
201
|
+
<Icon icon={ arrowUp } />
|
|
202
|
+
) }
|
|
203
|
+
</Button>
|
|
204
|
+
) }
|
|
205
|
+
</>
|
|
206
|
+
) : (
|
|
207
|
+
<Button
|
|
208
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
209
|
+
onClick={ onStop }
|
|
210
|
+
variant="secondary"
|
|
211
|
+
label={ __( 'Stop request', 'jetpack-ai-client' ) }
|
|
212
|
+
>
|
|
213
|
+
{ showButtonLabels ? (
|
|
214
|
+
__( 'Stop', 'jetpack-ai-client' )
|
|
215
|
+
) : (
|
|
216
|
+
<Icon icon={ closeSmall } />
|
|
217
|
+
) }
|
|
218
|
+
</Button>
|
|
219
|
+
) }
|
|
220
|
+
</div>
|
|
221
|
+
) }
|
|
222
|
+
{ showAccept && ! editRequest && (
|
|
223
|
+
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
224
|
+
{ ( value?.length > 0 || lastValue === null ) && (
|
|
225
|
+
<ButtonGroup>
|
|
226
|
+
<Button
|
|
227
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
228
|
+
label={ __( 'Discard', 'jetpack-ai-client' ) }
|
|
229
|
+
onClick={ discardHandler }
|
|
230
|
+
tooltipPosition="top"
|
|
231
|
+
>
|
|
232
|
+
<Icon icon={ trash } />
|
|
233
|
+
</Button>
|
|
234
|
+
<Button
|
|
235
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
236
|
+
label={ __( 'Regenerate', 'jetpack-ai-client' ) }
|
|
237
|
+
onClick={ () => onSend?.( value ) }
|
|
238
|
+
tooltipPosition="top"
|
|
239
|
+
disabled={ ! value?.length || value === null || disabled }
|
|
240
|
+
>
|
|
241
|
+
<Icon icon={ regenerate } />
|
|
242
|
+
</Button>
|
|
243
|
+
</ButtonGroup>
|
|
244
|
+
) }
|
|
245
|
+
<Button
|
|
246
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
247
|
+
onClick={ onAccept }
|
|
248
|
+
variant="primary"
|
|
249
|
+
label={ acceptLabel }
|
|
250
|
+
>
|
|
251
|
+
{ showButtonLabels ? acceptLabel : <Icon icon={ check } /> }
|
|
252
|
+
</Button>
|
|
253
|
+
</div>
|
|
254
|
+
) }
|
|
255
|
+
</>
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const message =
|
|
259
|
+
showGuideLine && ! loading && ! editRequest && ( customFooter || <GuidelineMessage /> );
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<AIControl
|
|
263
|
+
disabled={ disabled || loading }
|
|
264
|
+
value={ value }
|
|
265
|
+
placeholder={ placeholder }
|
|
266
|
+
isTransparent={ isTransparent }
|
|
267
|
+
state={ state }
|
|
268
|
+
onChange={ changeHandler }
|
|
269
|
+
banner={ banner }
|
|
270
|
+
error={ error }
|
|
271
|
+
actions={ actions }
|
|
272
|
+
message={ message }
|
|
273
|
+
promptUserInputRef={ promptUserInputRef }
|
|
274
|
+
/>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export default forwardRef( BlockAIControl );
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Button, ButtonGroup } from '@wordpress/components';
|
|
5
|
+
import { useKeyboardShortcut } from '@wordpress/compose';
|
|
6
|
+
import { useImperativeHandle, useRef, useEffect, useCallback, useState } from '@wordpress/element';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
8
|
+
import { Icon, closeSmall, arrowUp, undo } from '@wordpress/icons';
|
|
9
|
+
import React, { forwardRef } from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* Internal dependencies
|
|
12
|
+
*/
|
|
13
|
+
import { GuidelineMessage, ErrorMessage, UpgradeMessage } from '../message/index.js';
|
|
14
|
+
import AIControl from './ai-control.js';
|
|
15
|
+
import './style.scss';
|
|
16
|
+
/**
|
|
17
|
+
* Types
|
|
18
|
+
*/
|
|
19
|
+
import type { RequestingStateProp } from '../../types.js';
|
|
20
|
+
import type { ReactElement } from 'react';
|
|
21
|
+
|
|
22
|
+
type ExtensionAIControlProps = {
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
value: string;
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
showButtonLabels?: boolean;
|
|
27
|
+
isTransparent?: boolean;
|
|
28
|
+
state?: RequestingStateProp;
|
|
29
|
+
showGuideLine?: boolean;
|
|
30
|
+
error?: string;
|
|
31
|
+
requestsRemaining?: number;
|
|
32
|
+
showUpgradeMessage?: boolean;
|
|
33
|
+
onChange?: ( newValue: string ) => void;
|
|
34
|
+
onSend?: ( currentValue: string ) => void;
|
|
35
|
+
onStop?: () => void;
|
|
36
|
+
onClose?: () => void;
|
|
37
|
+
onUndo?: () => void;
|
|
38
|
+
onUpgrade?: () => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* ExtensionAIControl component. Used by the AI Assistant inline extensions, adding logic and components to the base AIControl component.
|
|
43
|
+
*
|
|
44
|
+
* @param {ExtensionAIControlProps} props - Component props
|
|
45
|
+
* @param {React.MutableRefObject} ref - Ref to the component
|
|
46
|
+
* @returns {ReactElement} Rendered component
|
|
47
|
+
*/
|
|
48
|
+
export function ExtensionAIControl(
|
|
49
|
+
{
|
|
50
|
+
disabled = false,
|
|
51
|
+
value = '',
|
|
52
|
+
placeholder = '',
|
|
53
|
+
showButtonLabels = true,
|
|
54
|
+
isTransparent = false,
|
|
55
|
+
state = 'init',
|
|
56
|
+
showGuideLine = false,
|
|
57
|
+
error,
|
|
58
|
+
requestsRemaining,
|
|
59
|
+
showUpgradeMessage = false,
|
|
60
|
+
onChange,
|
|
61
|
+
onSend,
|
|
62
|
+
onStop,
|
|
63
|
+
onClose,
|
|
64
|
+
onUndo,
|
|
65
|
+
onUpgrade,
|
|
66
|
+
}: ExtensionAIControlProps,
|
|
67
|
+
ref: React.MutableRefObject< HTMLInputElement >
|
|
68
|
+
): ReactElement {
|
|
69
|
+
const loading = state === 'requesting' || state === 'suggesting';
|
|
70
|
+
const [ editRequest, setEditRequest ] = useState( false );
|
|
71
|
+
const [ lastValue, setLastValue ] = useState( value || null );
|
|
72
|
+
const promptUserInputRef = useRef( null );
|
|
73
|
+
|
|
74
|
+
// Pass the ref to forwardRef.
|
|
75
|
+
useImperativeHandle( ref, () => promptUserInputRef.current );
|
|
76
|
+
|
|
77
|
+
useEffect( () => {
|
|
78
|
+
if ( editRequest ) {
|
|
79
|
+
promptUserInputRef?.current?.focus();
|
|
80
|
+
}
|
|
81
|
+
}, [ editRequest ] );
|
|
82
|
+
|
|
83
|
+
const sendHandler = useCallback( () => {
|
|
84
|
+
setLastValue( value );
|
|
85
|
+
setEditRequest( false );
|
|
86
|
+
onSend?.( value );
|
|
87
|
+
}, [ onSend, value ] );
|
|
88
|
+
|
|
89
|
+
const changeHandler = useCallback(
|
|
90
|
+
( newValue: string ) => {
|
|
91
|
+
onChange?.( newValue );
|
|
92
|
+
if ( state === 'init' ) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ( ! lastValue ) {
|
|
97
|
+
// here we're coming from a one-click action
|
|
98
|
+
setEditRequest( newValue.length > 0 );
|
|
99
|
+
} else {
|
|
100
|
+
// here we're coming from an edit action
|
|
101
|
+
setEditRequest( newValue !== lastValue );
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[ onChange, lastValue, state ]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const stopHandler = useCallback( () => {
|
|
108
|
+
onStop?.();
|
|
109
|
+
}, [ onStop ] );
|
|
110
|
+
|
|
111
|
+
const closeHandler = useCallback( () => {
|
|
112
|
+
onClose?.();
|
|
113
|
+
}, [ onClose ] );
|
|
114
|
+
|
|
115
|
+
const undoHandler = useCallback( () => {
|
|
116
|
+
onUndo?.();
|
|
117
|
+
}, [ onUndo ] );
|
|
118
|
+
|
|
119
|
+
const upgradeHandler = useCallback( () => {
|
|
120
|
+
onUpgrade?.();
|
|
121
|
+
}, [ onUpgrade ] );
|
|
122
|
+
|
|
123
|
+
useKeyboardShortcut(
|
|
124
|
+
'enter',
|
|
125
|
+
e => {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
sendHandler();
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
target: promptUserInputRef,
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const actions = (
|
|
135
|
+
<>
|
|
136
|
+
{ loading ? (
|
|
137
|
+
<Button
|
|
138
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
139
|
+
onClick={ stopHandler }
|
|
140
|
+
variant="secondary"
|
|
141
|
+
label={ __( 'Stop request', 'jetpack-ai-client' ) }
|
|
142
|
+
>
|
|
143
|
+
{ showButtonLabels ? __( 'Stop', 'jetpack-ai-client' ) : <Icon icon={ closeSmall } /> }
|
|
144
|
+
</Button>
|
|
145
|
+
) : (
|
|
146
|
+
<>
|
|
147
|
+
{ value?.length > 0 && (
|
|
148
|
+
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
149
|
+
<Button
|
|
150
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
151
|
+
onClick={ sendHandler }
|
|
152
|
+
variant="primary"
|
|
153
|
+
disabled={ ! value?.length || disabled }
|
|
154
|
+
label={ __( 'Send request', 'jetpack-ai-client' ) }
|
|
155
|
+
>
|
|
156
|
+
{ showButtonLabels ? (
|
|
157
|
+
__( 'Generate', 'jetpack-ai-client' )
|
|
158
|
+
) : (
|
|
159
|
+
<Icon icon={ arrowUp } />
|
|
160
|
+
) }
|
|
161
|
+
</Button>
|
|
162
|
+
</div>
|
|
163
|
+
) }
|
|
164
|
+
{ value?.length <= 0 && state === 'done' && (
|
|
165
|
+
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
166
|
+
<ButtonGroup>
|
|
167
|
+
<Button
|
|
168
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
169
|
+
label={ __( 'Undo', 'jetpack-ai-client' ) }
|
|
170
|
+
onClick={ undoHandler }
|
|
171
|
+
tooltipPosition="top"
|
|
172
|
+
>
|
|
173
|
+
<Icon icon={ undo } />
|
|
174
|
+
</Button>
|
|
175
|
+
<Button
|
|
176
|
+
className="jetpack-components-ai-control__controls-prompt_button"
|
|
177
|
+
label={ __( 'Close', 'jetpack-ai-client' ) }
|
|
178
|
+
onClick={ closeHandler }
|
|
179
|
+
variant="tertiary"
|
|
180
|
+
>
|
|
181
|
+
{ __( 'Close', 'jetpack-ai-client' ) }
|
|
182
|
+
</Button>
|
|
183
|
+
</ButtonGroup>
|
|
184
|
+
</div>
|
|
185
|
+
) }
|
|
186
|
+
</>
|
|
187
|
+
) }
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
let message = null;
|
|
192
|
+
if ( error ) {
|
|
193
|
+
message = <ErrorMessage error={ error } onTryAgainClick={ sendHandler } />;
|
|
194
|
+
} else if ( showUpgradeMessage ) {
|
|
195
|
+
message = (
|
|
196
|
+
<UpgradeMessage requestsRemaining={ requestsRemaining } onUpgradeClick={ upgradeHandler } />
|
|
197
|
+
);
|
|
198
|
+
} else if ( showGuideLine ) {
|
|
199
|
+
message = <GuidelineMessage />;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<AIControl
|
|
204
|
+
disabled={ disabled || loading }
|
|
205
|
+
value={ value }
|
|
206
|
+
placeholder={ placeholder }
|
|
207
|
+
isTransparent={ isTransparent }
|
|
208
|
+
state={ state }
|
|
209
|
+
onChange={ changeHandler }
|
|
210
|
+
actions={ actions }
|
|
211
|
+
message={ message }
|
|
212
|
+
promptUserInputRef={ promptUserInputRef }
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default forwardRef( ExtensionAIControl );
|