@automattic/jetpack-ai-client 0.1.2 → 0.1.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 +35 -5
- package/index.ts +1 -1
- package/package.json +11 -10
- package/src/ask-question/index.ts +0 -4
- package/src/components/ai-control/Readme.md +2 -2
- package/src/components/ai-control/index.tsx +92 -76
- package/src/components/ai-control/message.tsx +86 -0
- package/src/components/ai-control/style.scss +52 -26
- package/src/components/ai-status-indicator/style.scss +2 -0
- package/src/components/index.ts +2 -0
- package/src/data-flow/context.tsx +5 -0
- package/src/data-flow/use-ai-context.ts +2 -2
- package/src/data-flow/with-ai-assistant-data.tsx +10 -1
- package/src/hooks/use-ai-suggestions/index.ts +53 -43
- package/src/suggestions-event-source/index.ts +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,15 +5,43 @@ 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.1.4] - 2023-08-14
|
|
9
|
+
### Added
|
|
10
|
+
- AI Client: Add border-box in AIControl. [#32419]
|
|
11
|
+
- AI Client: Export AiStatusIndicator. [#32397]
|
|
12
|
+
- AI Client: Import base styles in the AI status indicator component. [#32396]
|
|
13
|
+
- AI Control: Forward ref to consumer. [#32400]
|
|
14
|
+
- AI Control: Import jetpack-base-styles. [#32376]
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- AI Client: Expose stopSuggestion function on useAiSuggestions hook so the consumer can stop a suggestion in the middle. [#32382]
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
- AI Client: Remove redundant switch case [#32405]
|
|
21
|
+
|
|
22
|
+
## [0.1.3] - 2023-08-09
|
|
23
|
+
### Added
|
|
24
|
+
- AI Client: Introduce disabled prop in AI Control. [#32326]
|
|
25
|
+
- AI Control: Add guideline message. [#32358]
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- AI Client: handle token fetching errors by dispatching an event from the SuggestionsEventSource class. [#32350]
|
|
29
|
+
- AI Client: tweak layout and styles to make AI Control mobile friendly. [#32362]
|
|
30
|
+
- AI Control: clean up props. [#32360]
|
|
31
|
+
- Updated package dependencies. [#32166]
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- AI Client: fix TS type definition issue [#32330]
|
|
35
|
+
|
|
8
36
|
## [0.1.2] - 2023-08-07
|
|
9
37
|
### Added
|
|
10
|
-
- AI Assistant:
|
|
38
|
+
- AI Assistant: Add options parameter to request function on useAiSuggestions hook [#32198]
|
|
11
39
|
- AI Client: add @wordpress/compose dependency [#32228]
|
|
12
|
-
- AI Client:
|
|
13
|
-
- AI Client:
|
|
40
|
+
- AI Client: Add clear button in AI Control component [#32274]
|
|
41
|
+
- AI Client: Add keyboard shortcut to AIControl [#32239]
|
|
14
42
|
- AI Client: add onError() response support [#32223]
|
|
15
|
-
- AI Client:
|
|
16
|
-
- AI Client:
|
|
43
|
+
- AI Client: Export types [#32209]
|
|
44
|
+
- AI Client: Start supporting request options on requestSuggestion callback. [#32303]
|
|
17
45
|
- AI Control: introduce AiStatusIndicator component [#32258]
|
|
18
46
|
|
|
19
47
|
### Changed
|
|
@@ -54,5 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
54
82
|
- Updated package dependencies. [#31659]
|
|
55
83
|
- Updated package dependencies. [#31785]
|
|
56
84
|
|
|
85
|
+
[0.1.4]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.3...v0.1.4
|
|
86
|
+
[0.1.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.2...v0.1.3
|
|
57
87
|
[0.1.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.1...v0.1.2
|
|
58
88
|
[0.1.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.0...v0.1.1
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@automattic/jetpack-ai-client",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.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": {
|
|
@@ -32,17 +32,18 @@
|
|
|
32
32
|
".": "./index.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@automattic/jetpack-base-styles": "^0.6.5",
|
|
35
36
|
"@automattic/jetpack-connection": "workspace:*",
|
|
36
|
-
"@automattic/jetpack-shared-extension-utils": "^0.11.
|
|
37
|
+
"@automattic/jetpack-shared-extension-utils": "^0.11.1",
|
|
37
38
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
38
|
-
"@wordpress/api-fetch": "6.
|
|
39
|
-
"@wordpress/block-editor": "12.
|
|
40
|
-
"@wordpress/components": "25.
|
|
41
|
-
"@wordpress/compose": "6.
|
|
42
|
-
"@wordpress/data": "9.
|
|
43
|
-
"@wordpress/element": "5.
|
|
44
|
-
"@wordpress/i18n": "4.
|
|
45
|
-
"@wordpress/icons": "9.
|
|
39
|
+
"@wordpress/api-fetch": "6.35.0",
|
|
40
|
+
"@wordpress/block-editor": "12.6.0",
|
|
41
|
+
"@wordpress/components": "25.4.0",
|
|
42
|
+
"@wordpress/compose": "6.15.0",
|
|
43
|
+
"@wordpress/data": "9.8.0",
|
|
44
|
+
"@wordpress/element": "5.15.0",
|
|
45
|
+
"@wordpress/i18n": "4.38.0",
|
|
46
|
+
"@wordpress/icons": "9.29.0",
|
|
46
47
|
"classnames": "2.3.2",
|
|
47
48
|
"debug": "4.3.4",
|
|
48
49
|
"react": "18.2.0",
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import debugFactory from 'debug';
|
|
5
|
-
import requestJwt from '../jwt';
|
|
6
5
|
import SuggestionsEventSource from '../suggestions-event-source';
|
|
7
6
|
/*
|
|
8
7
|
* Types & constants
|
|
@@ -62,11 +61,8 @@ export default async function askQuestion(
|
|
|
62
61
|
): Promise< SuggestionsEventSource > {
|
|
63
62
|
debug( 'Asking question: %o. options: %o', question, { postId, fromCache, feature, functions } );
|
|
64
63
|
|
|
65
|
-
const { token } = await requestJwt();
|
|
66
|
-
|
|
67
64
|
return new SuggestionsEventSource( {
|
|
68
65
|
question,
|
|
69
|
-
token,
|
|
70
66
|
options: { postId, feature, fromCache, functions },
|
|
71
67
|
} );
|
|
72
68
|
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
#### Properties
|
|
4
4
|
|
|
5
|
-
- `
|
|
5
|
+
- `disabled` (**boolean**) (Optional): Disables the ai control. Default value is `false`.
|
|
6
6
|
- `value` (**string**): Current input value. Default value is `''`.
|
|
7
7
|
- `placeholder` (**string**) (Optional): Placeholder text for the input field. Default value is `''`.
|
|
8
8
|
- `showAccept` (**boolean**) (Optional): Determines if the accept button is shown. Default value is `false`.
|
|
9
9
|
- `acceptLabel` (**string**) (Optional): Label text for the accept button. Default value is `'Accept'`.
|
|
10
10
|
- `showButtonsLabel` (**boolean**) (Optional): Determines if button labels are shown. Default value is `true`.
|
|
11
11
|
- `isOpaque` (**boolean**) (Optional): Controls the opacity of the component. Default value is `false`.
|
|
12
|
-
- `
|
|
12
|
+
- `state` (**RequestingStateProp**) (Optional): Determines the state of the component. Default value is `'init'`.
|
|
13
13
|
- `onChange` (**Function**) (Optional): Handler for input change. Default action is no operation.
|
|
14
14
|
- `onSend` (**Function**) (Optional): Handler to send a request. Default action is no operation.
|
|
15
15
|
- `onStop` (**Function**) (Optional): Handler to stop a request. Default action is no operation.
|
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
import { PlainText } from '@wordpress/block-editor';
|
|
5
5
|
import { Button } from '@wordpress/components';
|
|
6
6
|
import { useKeyboardShortcut } from '@wordpress/compose';
|
|
7
|
-
import { useRef } from '@wordpress/element';
|
|
7
|
+
import { forwardRef, useImperativeHandle, useRef } from '@wordpress/element';
|
|
8
8
|
import { __ } from '@wordpress/i18n';
|
|
9
9
|
import { Icon, closeSmall, check, arrowUp } from '@wordpress/icons';
|
|
10
10
|
import classNames from 'classnames';
|
|
11
|
+
import React from 'react';
|
|
11
12
|
/**
|
|
12
13
|
* Internal dependencies
|
|
13
14
|
*/
|
|
14
15
|
import './style.scss';
|
|
15
16
|
import AiStatusIndicator from '../ai-status-indicator';
|
|
17
|
+
import { GuidelineMessage } from './message';
|
|
16
18
|
/**
|
|
17
19
|
* Types
|
|
18
20
|
*/
|
|
@@ -25,48 +27,57 @@ const noop = () => {};
|
|
|
25
27
|
* AI Control component.
|
|
26
28
|
*
|
|
27
29
|
* @param {object} props - component props
|
|
28
|
-
* @param {boolean} props.
|
|
30
|
+
* @param {boolean} props.disabled - is disabled
|
|
29
31
|
* @param {string} props.value - input value
|
|
30
32
|
* @param {string} props.placeholder - input placeholder
|
|
31
33
|
* @param {boolean} props.showAccept - show accept button
|
|
32
34
|
* @param {string} props.acceptLabel - accept button label
|
|
33
35
|
* @param {boolean} props.showButtonsLabel - show buttons label
|
|
34
36
|
* @param {boolean} props.isOpaque - is opaque
|
|
37
|
+
* @param {string} props.state - requesting state
|
|
35
38
|
* @param {Function} props.onChange - input change handler
|
|
36
39
|
* @param {Function} props.onSend - send request handler
|
|
37
40
|
* @param {Function} props.onStop - stop request handler
|
|
38
41
|
* @param {Function} props.onAccept - accept handler
|
|
39
|
-
* @param {
|
|
42
|
+
* @param {object} ref - Auto injected ref from react
|
|
40
43
|
* @returns {object} - AI Control component
|
|
41
44
|
*/
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
45
|
+
export function AIControl(
|
|
46
|
+
{
|
|
47
|
+
disabled = false,
|
|
48
|
+
value = '',
|
|
49
|
+
placeholder = '',
|
|
50
|
+
showAccept = false,
|
|
51
|
+
acceptLabel = __( 'Accept', 'jetpack-ai-client' ),
|
|
52
|
+
showButtonsLabel = true,
|
|
53
|
+
isOpaque = false,
|
|
54
|
+
state = 'init',
|
|
55
|
+
onChange = noop,
|
|
56
|
+
onSend = noop,
|
|
57
|
+
onStop = noop,
|
|
58
|
+
onAccept = noop,
|
|
59
|
+
}: {
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
value: string;
|
|
62
|
+
placeholder?: string;
|
|
63
|
+
showAccept?: boolean;
|
|
64
|
+
acceptLabel?: string;
|
|
65
|
+
showButtonsLabel?: boolean;
|
|
66
|
+
isOpaque?: boolean;
|
|
67
|
+
state?: RequestingStateProp;
|
|
68
|
+
onChange?: ( newValue: string ) => void;
|
|
69
|
+
onSend?: ( currentValue: string ) => void;
|
|
70
|
+
onStop?: () => void;
|
|
71
|
+
onAccept?: () => void;
|
|
72
|
+
},
|
|
73
|
+
ref
|
|
74
|
+
) {
|
|
69
75
|
const promptUserInputRef = useRef( null );
|
|
76
|
+
const loading = state === 'requesting' || state === 'suggesting';
|
|
77
|
+
const showGuideLine = ! ( loading || disabled || value?.length || isOpaque );
|
|
78
|
+
|
|
79
|
+
// Pass the ref to forwardRef.
|
|
80
|
+
useImperativeHandle( ref, () => promptUserInputRef.current );
|
|
70
81
|
|
|
71
82
|
useKeyboardShortcut(
|
|
72
83
|
'mod+enter',
|
|
@@ -91,6 +102,10 @@ export default function AIControl( {
|
|
|
91
102
|
}
|
|
92
103
|
);
|
|
93
104
|
|
|
105
|
+
const actionButtonClasses = classNames( 'jetpack-components-ai-control__controls-prompt_button', {
|
|
106
|
+
'has-label': showButtonsLabel,
|
|
107
|
+
} );
|
|
108
|
+
|
|
94
109
|
return (
|
|
95
110
|
<div className="jetpack-components-ai-control__container">
|
|
96
111
|
<div
|
|
@@ -98,7 +113,7 @@ export default function AIControl( {
|
|
|
98
113
|
'is-opaque': isOpaque,
|
|
99
114
|
} ) }
|
|
100
115
|
>
|
|
101
|
-
<AiStatusIndicator state={
|
|
116
|
+
<AiStatusIndicator state={ state } />
|
|
102
117
|
|
|
103
118
|
<div className="jetpack-components-ai-control__input-wrapper">
|
|
104
119
|
<PlainText
|
|
@@ -106,60 +121,61 @@ export default function AIControl( {
|
|
|
106
121
|
onChange={ onChange }
|
|
107
122
|
placeholder={ placeholder }
|
|
108
123
|
className="jetpack-components-ai-control__input"
|
|
109
|
-
disabled={ loading }
|
|
124
|
+
disabled={ loading || disabled }
|
|
110
125
|
ref={ promptUserInputRef }
|
|
111
126
|
/>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{ value?.length > 0 && (
|
|
130
|
+
<Button
|
|
131
|
+
icon={ closeSmall }
|
|
132
|
+
className="jetpack-components-ai-control__clear"
|
|
133
|
+
onClick={ () => onChange( '' ) }
|
|
134
|
+
/>
|
|
135
|
+
) }
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
className=
|
|
117
|
-
onClick={ () =>
|
|
118
|
-
|
|
137
|
+
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
138
|
+
{ ! loading ? (
|
|
139
|
+
<Button
|
|
140
|
+
className={ actionButtonClasses }
|
|
141
|
+
onClick={ () => onSend( value ) }
|
|
142
|
+
isSmall={ true }
|
|
143
|
+
disabled={ ! value?.length || disabled }
|
|
144
|
+
label={ __( 'Send request', 'jetpack-ai-client' ) }
|
|
145
|
+
>
|
|
146
|
+
<Icon icon={ arrowUp } />
|
|
147
|
+
{ showButtonsLabel && __( 'Send', 'jetpack-ai-client' ) }
|
|
148
|
+
</Button>
|
|
149
|
+
) : (
|
|
150
|
+
<Button
|
|
151
|
+
className={ actionButtonClasses }
|
|
152
|
+
onClick={ onStop }
|
|
153
|
+
isSmall={ true }
|
|
154
|
+
label={ __( 'Stop request', 'jetpack-ai-client' ) }
|
|
155
|
+
>
|
|
156
|
+
<Icon icon={ closeSmall } />
|
|
157
|
+
{ showButtonsLabel && __( 'Stop', 'jetpack-ai-client' ) }
|
|
158
|
+
</Button>
|
|
119
159
|
) }
|
|
120
160
|
</div>
|
|
121
161
|
|
|
122
|
-
|
|
162
|
+
{ showAccept && (
|
|
123
163
|
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{ showButtonsLabel && __( 'Send', 'jetpack-ai-client' ) }
|
|
134
|
-
</Button>
|
|
135
|
-
) : (
|
|
136
|
-
<Button
|
|
137
|
-
className="jetpack-components-ai-control__controls-prompt_button"
|
|
138
|
-
onClick={ onStop }
|
|
139
|
-
isSmall={ true }
|
|
140
|
-
label={ __( 'Stop request', 'jetpack-ai-client' ) }
|
|
141
|
-
>
|
|
142
|
-
<Icon icon={ closeSmall } />
|
|
143
|
-
{ showButtonsLabel && __( 'Stop', 'jetpack-ai-client' ) }
|
|
144
|
-
</Button>
|
|
145
|
-
) }
|
|
164
|
+
<Button
|
|
165
|
+
className={ actionButtonClasses }
|
|
166
|
+
onClick={ onAccept }
|
|
167
|
+
isSmall={ true }
|
|
168
|
+
label={ acceptLabel }
|
|
169
|
+
>
|
|
170
|
+
<Icon icon={ check } />
|
|
171
|
+
{ showButtonsLabel && acceptLabel }
|
|
172
|
+
</Button>
|
|
146
173
|
</div>
|
|
147
|
-
|
|
148
|
-
{ showAccept && (
|
|
149
|
-
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
150
|
-
<Button
|
|
151
|
-
className="jetpack-components-ai-control__controls-prompt_button"
|
|
152
|
-
onClick={ onAccept }
|
|
153
|
-
isSmall={ true }
|
|
154
|
-
label={ acceptLabel }
|
|
155
|
-
>
|
|
156
|
-
<Icon icon={ check } />
|
|
157
|
-
{ acceptLabel }
|
|
158
|
-
</Button>
|
|
159
|
-
</div>
|
|
160
|
-
) }
|
|
161
|
-
</div>
|
|
174
|
+
) }
|
|
162
175
|
</div>
|
|
176
|
+
{ showGuideLine && <GuidelineMessage /> }
|
|
163
177
|
</div>
|
|
164
178
|
);
|
|
165
179
|
}
|
|
180
|
+
|
|
181
|
+
export default forwardRef( AIControl );
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { ExternalLink } from '@wordpress/components';
|
|
5
|
+
import { createInterpolateElement } from '@wordpress/element';
|
|
6
|
+
import { __ } from '@wordpress/i18n';
|
|
7
|
+
import {
|
|
8
|
+
Icon,
|
|
9
|
+
warning,
|
|
10
|
+
info,
|
|
11
|
+
cancelCircleFilled as error,
|
|
12
|
+
check as success,
|
|
13
|
+
} from '@wordpress/icons';
|
|
14
|
+
/**
|
|
15
|
+
* Types
|
|
16
|
+
*/
|
|
17
|
+
import type React from 'react';
|
|
18
|
+
|
|
19
|
+
import './style.scss';
|
|
20
|
+
|
|
21
|
+
export const MESSAGE_SEVERITY_WARNING = 'warning';
|
|
22
|
+
export const MESSAGE_SEVERITY_ERROR = 'error';
|
|
23
|
+
export const MESSAGE_SEVERITY_SUCCESS = 'success';
|
|
24
|
+
export const MESSAGE_SEVERITY_INFO = 'info';
|
|
25
|
+
|
|
26
|
+
const messageSeverityTypes = [
|
|
27
|
+
MESSAGE_SEVERITY_WARNING,
|
|
28
|
+
MESSAGE_SEVERITY_ERROR,
|
|
29
|
+
MESSAGE_SEVERITY_SUCCESS,
|
|
30
|
+
MESSAGE_SEVERITY_INFO,
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export type MessageSeverityProp = ( typeof messageSeverityTypes )[ number ] | null;
|
|
34
|
+
|
|
35
|
+
export type MessageProps = {
|
|
36
|
+
icon?: React.ReactNode;
|
|
37
|
+
children: React.ReactNode;
|
|
38
|
+
severity: MessageSeverityProp;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const messageIconsMap = {
|
|
42
|
+
[ MESSAGE_SEVERITY_WARNING ]: warning,
|
|
43
|
+
[ MESSAGE_SEVERITY_ERROR ]: error,
|
|
44
|
+
[ MESSAGE_SEVERITY_SUCCESS ]: success,
|
|
45
|
+
[ MESSAGE_SEVERITY_INFO ]: info,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* React component to render a block message.
|
|
50
|
+
*
|
|
51
|
+
* @param {MessageProps} props - Component props.
|
|
52
|
+
* @returns {React.ReactElement } Banner component.
|
|
53
|
+
*/
|
|
54
|
+
export default function Message( {
|
|
55
|
+
severity = null,
|
|
56
|
+
icon = null,
|
|
57
|
+
children,
|
|
58
|
+
}: MessageProps ): React.ReactElement {
|
|
59
|
+
return (
|
|
60
|
+
<div className="jetpack-ai-assistant__message">
|
|
61
|
+
{ ( severity || icon ) && <Icon icon={ messageIconsMap[ severity ] || icon } /> }
|
|
62
|
+
<div className="jetpack-ai-assistant__message-content">{ children }</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* React component to render a guideline message.
|
|
69
|
+
*
|
|
70
|
+
* @returns {React.ReactElement } - Message component.
|
|
71
|
+
*/
|
|
72
|
+
export function GuidelineMessage(): React.ReactElement {
|
|
73
|
+
return (
|
|
74
|
+
<Message severity={ MESSAGE_SEVERITY_INFO }>
|
|
75
|
+
{ createInterpolateElement(
|
|
76
|
+
__(
|
|
77
|
+
'AI-generated content could be inaccurate or biased. <link>Learn more</link>',
|
|
78
|
+
'jetpack-ai-client'
|
|
79
|
+
),
|
|
80
|
+
{
|
|
81
|
+
link: <ExternalLink href="https://automattic.com/ai-guidelines" />,
|
|
82
|
+
}
|
|
83
|
+
) }
|
|
84
|
+
</Message>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
@import '@automattic/jetpack-base-styles/root-variables';
|
|
2
|
+
|
|
3
|
+
// AI CONTROL
|
|
4
|
+
|
|
1
5
|
.jetpack-components-ai-control__container {
|
|
2
6
|
color: var( --jp-gray-80 );
|
|
3
7
|
background-color: var( --jp-white );
|
|
@@ -11,6 +15,7 @@
|
|
|
11
15
|
padding: 12px 14px;
|
|
12
16
|
gap: 8px;
|
|
13
17
|
align-items: center;
|
|
18
|
+
box-sizing: border-box;
|
|
14
19
|
|
|
15
20
|
&.is-opaque {
|
|
16
21
|
opacity: 0.4;
|
|
@@ -23,26 +28,11 @@
|
|
|
23
28
|
flex-grow: 1;
|
|
24
29
|
width: 100%;
|
|
25
30
|
|
|
26
|
-
.jetpack-components-ai-control__clear {
|
|
27
|
-
position: absolute;
|
|
28
|
-
right: 8px;
|
|
29
|
-
top: calc( 50% - 12px );
|
|
30
|
-
border-radius: 50%;
|
|
31
|
-
background-color: black;
|
|
32
|
-
opacity: 0.2;
|
|
33
|
-
fill: white;
|
|
34
|
-
cursor: pointer;
|
|
35
|
-
|
|
36
|
-
&:hover {
|
|
37
|
-
opacity: 0.4;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
31
|
textarea.jetpack-components-ai-control__input {
|
|
42
32
|
width: 100%;
|
|
43
33
|
min-height: 20px;
|
|
44
34
|
border-radius: 2px;
|
|
45
|
-
padding: 6px
|
|
35
|
+
padding: 6px 8px;
|
|
46
36
|
|
|
47
37
|
resize: none !important;
|
|
48
38
|
border: none;
|
|
@@ -72,6 +62,21 @@
|
|
|
72
62
|
}
|
|
73
63
|
}
|
|
74
64
|
|
|
65
|
+
.jetpack-components-ai-control__clear.components-button.has-icon {
|
|
66
|
+
width: 24px;
|
|
67
|
+
min-width: 24px;
|
|
68
|
+
height: 24px;
|
|
69
|
+
padding: 0;
|
|
70
|
+
border-radius: 50%;
|
|
71
|
+
background-color: black;
|
|
72
|
+
opacity: 0.2;
|
|
73
|
+
color: white;
|
|
74
|
+
|
|
75
|
+
&:hover {
|
|
76
|
+
opacity: 0.4;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
.jetpack-components-ai-controlton__icon {
|
|
76
81
|
flex-shrink: 0;
|
|
77
82
|
display: flex;
|
|
@@ -94,11 +99,6 @@
|
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
.jetpack-components-ai-control__controls {
|
|
98
|
-
display: flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
102
|
.jetpack-components-ai-control__controls-prompt_button_wrapper {
|
|
103
103
|
text-transform: uppercase;
|
|
104
104
|
font-size: 11px;
|
|
@@ -108,16 +108,16 @@
|
|
|
108
108
|
white-space: nowrap;
|
|
109
109
|
display: flex;
|
|
110
110
|
align-items: center;
|
|
111
|
+
|
|
112
|
+
.components-button.is-small:not(.has-label) {
|
|
113
|
+
padding: 0;
|
|
114
|
+
}
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
.jetpack-components-ai-control__controls-prompt_button {
|
|
114
118
|
color: var( --jp-gray-80 );
|
|
115
119
|
text-transform: uppercase;
|
|
116
120
|
|
|
117
|
-
> SVG {
|
|
118
|
-
margin-right: 4px;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
121
|
&:hover,
|
|
122
122
|
&:active {
|
|
123
123
|
color: var( --wp-components-color-accent, var( --wp-admin-theme-color ) );
|
|
@@ -127,4 +127,30 @@
|
|
|
127
127
|
opacity: 0.6;
|
|
128
128
|
cursor: not-allowed;
|
|
129
129
|
}
|
|
130
|
-
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MESSAGE
|
|
133
|
+
|
|
134
|
+
.jetpack-ai-assistant__message {
|
|
135
|
+
display: flex;
|
|
136
|
+
line-height: 28px;
|
|
137
|
+
font-size: 12px;
|
|
138
|
+
align-self: center;
|
|
139
|
+
align-items: center;
|
|
140
|
+
background-color: var( --jp-white-off );
|
|
141
|
+
padding: 0 12px;
|
|
142
|
+
|
|
143
|
+
> svg {
|
|
144
|
+
fill: var( --jp-gray-40 );
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.jetpack-ai-assistant__message-content {
|
|
148
|
+
flex-grow: 2;
|
|
149
|
+
margin: 0 8px;
|
|
150
|
+
color: var( --jp-gray-70 );
|
|
151
|
+
|
|
152
|
+
.components-external-link {
|
|
153
|
+
color: var( --jp-gray-50 );
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -33,6 +33,11 @@ export type AiDataContextProps = {
|
|
|
33
33
|
*/
|
|
34
34
|
requestSuggestion: ( prompt: PromptProp, options?: AskQuestionOptionsArgProps ) => void;
|
|
35
35
|
|
|
36
|
+
/*
|
|
37
|
+
* Stop suggestion function
|
|
38
|
+
*/
|
|
39
|
+
stopSuggestion: () => void;
|
|
40
|
+
|
|
36
41
|
/*
|
|
37
42
|
* The Suggestions Event Source instance
|
|
38
43
|
*/
|
|
@@ -5,7 +5,7 @@ import { useCallback, useContext, useEffect } from '@wordpress/element';
|
|
|
5
5
|
/**
|
|
6
6
|
* Internal dependencies
|
|
7
7
|
*/
|
|
8
|
-
import { ERROR_RESPONSE } from '../types';
|
|
8
|
+
import { ERROR_RESPONSE, RequestingErrorProps } from '../types';
|
|
9
9
|
import { AiDataContext } from '.';
|
|
10
10
|
/**
|
|
11
11
|
* Types & constants
|
|
@@ -32,7 +32,7 @@ export type UseAiContextOptions = {
|
|
|
32
32
|
/*
|
|
33
33
|
* onError callback.
|
|
34
34
|
*/
|
|
35
|
-
onError?: ( error:
|
|
35
|
+
onError?: ( error: RequestingErrorProps ) => void;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -25,6 +25,7 @@ const withAiDataProvider = createHigherOrderComponent( ( WrappedComponent: React
|
|
|
25
25
|
error: requestingError,
|
|
26
26
|
requestingState,
|
|
27
27
|
request: requestSuggestion,
|
|
28
|
+
stopSuggestion,
|
|
28
29
|
eventSource,
|
|
29
30
|
} = useAiSuggestions();
|
|
30
31
|
|
|
@@ -37,8 +38,16 @@ const withAiDataProvider = createHigherOrderComponent( ( WrappedComponent: React
|
|
|
37
38
|
eventSource,
|
|
38
39
|
|
|
39
40
|
requestSuggestion,
|
|
41
|
+
stopSuggestion,
|
|
40
42
|
} ),
|
|
41
|
-
[
|
|
43
|
+
[
|
|
44
|
+
suggestion,
|
|
45
|
+
requestingError,
|
|
46
|
+
requestingState,
|
|
47
|
+
eventSource,
|
|
48
|
+
requestSuggestion,
|
|
49
|
+
stopSuggestion,
|
|
50
|
+
]
|
|
42
51
|
);
|
|
43
52
|
|
|
44
53
|
return (
|
|
@@ -97,6 +97,11 @@ type useAiSuggestionsProps = {
|
|
|
97
97
|
* The request handler.
|
|
98
98
|
*/
|
|
99
99
|
request: ( prompt: PromptProp, options?: AskQuestionOptionsArgProps ) => Promise< void >;
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* The handler to stop a suggestion.
|
|
103
|
+
*/
|
|
104
|
+
stopSuggestion: () => void;
|
|
100
105
|
};
|
|
101
106
|
|
|
102
107
|
const debug = debugFactory( 'jetpack-ai-client:use-suggestion' );
|
|
@@ -140,14 +145,6 @@ export function getErrorData( errorCode: SuggestionErrorCode ): RequestingErrorP
|
|
|
140
145
|
severity: 'info',
|
|
141
146
|
};
|
|
142
147
|
case ERROR_NETWORK:
|
|
143
|
-
return {
|
|
144
|
-
code: ERROR_NETWORK,
|
|
145
|
-
message: __(
|
|
146
|
-
'It was not possible to process your request. Mind trying again?',
|
|
147
|
-
'jetpack-ai-client'
|
|
148
|
-
),
|
|
149
|
-
severity: 'info',
|
|
150
|
-
};
|
|
151
148
|
default:
|
|
152
149
|
return {
|
|
153
150
|
code: ERROR_NETWORK,
|
|
@@ -234,7 +231,7 @@ export default function useAiSuggestions( {
|
|
|
234
231
|
|
|
235
232
|
const handleModerationError = useCallback( () => handleError( ERROR_MODERATION ), [] );
|
|
236
233
|
|
|
237
|
-
const
|
|
234
|
+
const handleNetworkError = useCallback( () => handleError( ERROR_NETWORK ), [] );
|
|
238
235
|
|
|
239
236
|
/**
|
|
240
237
|
* Request handler.
|
|
@@ -278,7 +275,7 @@ export default function useAiSuggestions( {
|
|
|
278
275
|
eventSource.addEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
|
|
279
276
|
eventSource.addEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
|
|
280
277
|
eventSource.addEventListener( ERROR_MODERATION, handleModerationError );
|
|
281
|
-
eventSource.addEventListener( ERROR_NETWORK,
|
|
278
|
+
eventSource.addEventListener( ERROR_NETWORK, handleNetworkError );
|
|
282
279
|
|
|
283
280
|
eventSource.addEventListener( 'done', handleDone );
|
|
284
281
|
} catch ( e ) {
|
|
@@ -292,11 +289,51 @@ export default function useAiSuggestions( {
|
|
|
292
289
|
handleUnclearPromptError,
|
|
293
290
|
handleServiceUnavailableError,
|
|
294
291
|
handleModerationError,
|
|
295
|
-
|
|
292
|
+
handleNetworkError,
|
|
296
293
|
handleSuggestion,
|
|
297
294
|
]
|
|
298
295
|
);
|
|
299
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Stop suggestion handler.
|
|
299
|
+
*
|
|
300
|
+
* @returns {void}
|
|
301
|
+
*/
|
|
302
|
+
const stopSuggestion = useCallback( () => {
|
|
303
|
+
if ( ! eventSourceRef?.current ) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Alias
|
|
308
|
+
const eventSource = eventSourceRef?.current;
|
|
309
|
+
|
|
310
|
+
// Close the connection.
|
|
311
|
+
eventSource.close();
|
|
312
|
+
|
|
313
|
+
// Clean up the event listeners.
|
|
314
|
+
eventSource.removeEventListener( 'suggestion', handleSuggestion );
|
|
315
|
+
|
|
316
|
+
eventSource.removeEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
|
|
317
|
+
eventSource.removeEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
|
|
318
|
+
eventSource.removeEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
|
|
319
|
+
eventSource.removeEventListener( ERROR_MODERATION, handleModerationError );
|
|
320
|
+
eventSource.removeEventListener( ERROR_NETWORK, handleNetworkError );
|
|
321
|
+
|
|
322
|
+
eventSource.removeEventListener( 'done', handleDone );
|
|
323
|
+
|
|
324
|
+
// Set requesting state to done since the suggestion stopped.
|
|
325
|
+
setRequestingState( 'done' );
|
|
326
|
+
}, [
|
|
327
|
+
eventSourceRef,
|
|
328
|
+
handleSuggestion,
|
|
329
|
+
handleErrorQuotaExceededError,
|
|
330
|
+
handleUnclearPromptError,
|
|
331
|
+
handleServiceUnavailableError,
|
|
332
|
+
handleModerationError,
|
|
333
|
+
handleNetworkError,
|
|
334
|
+
handleDone,
|
|
335
|
+
] );
|
|
336
|
+
|
|
300
337
|
// Request suggestions automatically when ready.
|
|
301
338
|
useEffect( () => {
|
|
302
339
|
// Check if there is a prompt to request.
|
|
@@ -310,38 +347,10 @@ export default function useAiSuggestions( {
|
|
|
310
347
|
}
|
|
311
348
|
|
|
312
349
|
return () => {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Alias
|
|
318
|
-
const eventSource = eventSourceRef.current;
|
|
319
|
-
|
|
320
|
-
// Close the connection.
|
|
321
|
-
eventSource.close();
|
|
322
|
-
|
|
323
|
-
// Clean up the event listeners.
|
|
324
|
-
eventSource.removeEventListener( 'suggestion', handleSuggestion );
|
|
325
|
-
|
|
326
|
-
eventSource.removeEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
|
|
327
|
-
eventSource.removeEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
|
|
328
|
-
eventSource.removeEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
|
|
329
|
-
eventSource.removeEventListener( ERROR_MODERATION, handleModerationError );
|
|
330
|
-
eventSource.removeEventListener( ERROR_NETWORK, handleNetwotkError );
|
|
331
|
-
|
|
332
|
-
eventSource.removeEventListener( 'done', handleDone );
|
|
350
|
+
// Stop the suggestion if the component unmounts.
|
|
351
|
+
stopSuggestion();
|
|
333
352
|
};
|
|
334
|
-
}, [
|
|
335
|
-
autoRequest,
|
|
336
|
-
handleDone,
|
|
337
|
-
handleErrorQuotaExceededError,
|
|
338
|
-
handleModerationError,
|
|
339
|
-
handleServiceUnavailableError,
|
|
340
|
-
handleSuggestion,
|
|
341
|
-
handleUnclearPromptError,
|
|
342
|
-
prompt,
|
|
343
|
-
request,
|
|
344
|
-
] );
|
|
353
|
+
}, [ autoRequest, prompt, request, stopSuggestion ] );
|
|
345
354
|
|
|
346
355
|
return {
|
|
347
356
|
// Data
|
|
@@ -349,8 +358,9 @@ export default function useAiSuggestions( {
|
|
|
349
358
|
error,
|
|
350
359
|
requestingState,
|
|
351
360
|
|
|
352
|
-
// Request
|
|
361
|
+
// Request/stop handlers
|
|
353
362
|
request,
|
|
363
|
+
stopSuggestion,
|
|
354
364
|
|
|
355
365
|
// SuggestionsEventSource
|
|
356
366
|
eventSource: eventSourceRef.current,
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { EventSourceMessage, fetchEventSource } from '@microsoft/fetch-event-source';
|
|
5
5
|
import debugFactory from 'debug';
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { getErrorData } from '../hooks/use-ai-suggestions';
|
|
10
|
+
import requestJwt from '../jwt';
|
|
6
11
|
/*
|
|
7
12
|
* Types & constants
|
|
8
13
|
*/
|
|
9
|
-
import { getErrorData } from '../hooks/use-ai-suggestions';
|
|
10
14
|
import {
|
|
11
15
|
ERROR_MODERATION,
|
|
12
16
|
ERROR_NETWORK,
|
|
@@ -20,7 +24,7 @@ import type { PromptMessagesProp, PromptProp, SuggestionErrorCode } from '../typ
|
|
|
20
24
|
type SuggestionsEventSourceConstructorArgs = {
|
|
21
25
|
url?: string;
|
|
22
26
|
question: PromptProp;
|
|
23
|
-
token
|
|
27
|
+
token?: string;
|
|
24
28
|
options?: {
|
|
25
29
|
postId?: number;
|
|
26
30
|
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
@@ -81,6 +85,18 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
81
85
|
token,
|
|
82
86
|
options = {},
|
|
83
87
|
}: SuggestionsEventSourceConstructorArgs ) {
|
|
88
|
+
// If the token is not provided, try to get one
|
|
89
|
+
if ( ! token ) {
|
|
90
|
+
try {
|
|
91
|
+
debug( 'Token was not provided, requesting one...' );
|
|
92
|
+
token = ( await requestJwt() ).token;
|
|
93
|
+
} catch ( err ) {
|
|
94
|
+
this.processErrorEvent( err );
|
|
95
|
+
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
84
100
|
const bodyData: {
|
|
85
101
|
post_id?: number;
|
|
86
102
|
messages?: PromptMessagesProp;
|