@automattic/jetpack-ai-client 0.1.1 → 0.1.2
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 +19 -0
- package/README.md +18 -96
- package/index.ts +5 -0
- package/package.json +5 -2
- package/src/components/ai-control/Readme.md +29 -0
- package/src/components/ai-control/index.tsx +56 -21
- package/src/components/ai-control/stories/index.stories.tsx +27 -1
- package/src/components/ai-control/style.scss +26 -3
- package/src/components/ai-status-indicator/Readme.md +11 -0
- package/src/components/ai-status-indicator/index.tsx +43 -0
- package/src/components/ai-status-indicator/stories/index.mdx +25 -0
- package/src/components/ai-status-indicator/stories/index.stories.tsx +84 -0
- package/src/components/ai-status-indicator/style.scss +50 -0
- package/src/data-flow/Readme.md +2 -0
- package/src/data-flow/context.tsx +4 -2
- package/src/data-flow/use-ai-context.ts +18 -3
- package/src/hooks/use-ai-suggestions/Readme.md +1 -1
- package/src/hooks/use-ai-suggestions/index.ts +11 -7
- package/src/suggestions-event-source/index.ts +31 -1
- package/src/types.ts +29 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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.2] - 2023-08-07
|
|
9
|
+
### Added
|
|
10
|
+
- AI Assistant: add options parameter to request function on useAiSuggestions hook [#32198]
|
|
11
|
+
- AI Client: add @wordpress/compose dependency [#32228]
|
|
12
|
+
- AI Client: add clear button in AI Control component [#32274]
|
|
13
|
+
- AI Client: add keyboard shortcut to AIControl [#32239]
|
|
14
|
+
- AI Client: add onError() response support [#32223]
|
|
15
|
+
- AI Client: export types [#32209]
|
|
16
|
+
- AI Client: start supporting request options on requestSuggestion callback. [#32303]
|
|
17
|
+
- AI Control: introduce AiStatusIndicator component [#32258]
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- AI Client: complete/update/improve doc [#32311]
|
|
21
|
+
- AI Client: rename the prop name of the requesting state of the AiStatusIndicator component [#32279]
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- AI Client: Fix wrong disabled state condition. [#32210]
|
|
25
|
+
|
|
8
26
|
## [0.1.1] - 2023-08-01
|
|
9
27
|
### Added
|
|
10
28
|
- Add AI Client icon components [#32079]
|
|
@@ -36,4 +54,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
36
54
|
- Updated package dependencies. [#31659]
|
|
37
55
|
- Updated package dependencies. [#31785]
|
|
38
56
|
|
|
57
|
+
[0.1.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.1...v0.1.2
|
|
39
58
|
[0.1.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.0...v0.1.1
|
package/README.md
CHANGED
|
@@ -10,118 +10,40 @@ To install the Jetpack AI Client, clone the repository to your local machine and
|
|
|
10
10
|
npm install @automattic/jetpack-ai-client
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Libraries & Components
|
|
14
14
|
|
|
15
|
-
###
|
|
15
|
+
### Requesting
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const MyComp = ( props ) => {
|
|
21
|
-
const [ completion, setCompletion ] = useState( '' );
|
|
22
|
-
|
|
23
|
-
const newHaiku = async () => {
|
|
24
|
-
const eventSource = await requestCompletion( 'Write a haiku about WordPress' );
|
|
25
|
-
eventSource.addEventListener( 'suggestion', answer => setCompletion( answer.detail ) );
|
|
26
|
-
eventSource.addEventListener( 'done', event => {
|
|
27
|
-
console.log( "Full completion", event.detail );
|
|
28
|
-
} );
|
|
29
|
-
|
|
30
|
-
eventSource.addEventListener( 'suggestion', event => {
|
|
31
|
-
console.log( "Received so far", event.detail );
|
|
32
|
-
} );
|
|
33
|
-
|
|
34
|
-
eventSource.addEventListener( 'error_quota_exceeded', event => {
|
|
35
|
-
console.log( "You reached the AI query quota for your current plan.", event.detail );
|
|
36
|
-
} );
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div>
|
|
41
|
-
<div> { completion } </div>
|
|
42
|
-
<button onClick={ newHaiku }>Get new Haiku</button>
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
};
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Requesting a Completion from the Jetpack AI API
|
|
49
|
-
|
|
50
|
-
You can request a completion from the Jetpack AI API using the `requestCompletion` function. This function takes a prompt and optionally a post ID as parameters and returns an instance of `SuggestionsEventSource`.
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
import { requestCompletion } from '@automattic/jetpack-ai-client';
|
|
54
|
-
|
|
55
|
-
// postId is the post where the request is being triggered
|
|
56
|
-
// It's only used for loggin purposes and can be omitted.
|
|
57
|
-
const postId = 123;
|
|
58
|
-
const eventSource = requestCompletion( 'A haiku', postId ))
|
|
59
|
-
|
|
60
|
-
eventSource.addEventListener('done', event => {
|
|
61
|
-
console.log( "Full completion", event.detail );
|
|
62
|
-
} );
|
|
63
|
-
|
|
64
|
-
eventSource.addEventListener('suggestion', event => {
|
|
65
|
-
console.log( "Received so far", event.detail );
|
|
66
|
-
} );
|
|
67
|
-
```
|
|
17
|
+
#### [askQuestion](./src/ask-question/Readme.md) helper
|
|
18
|
+
Async function that sends a question and optional configurations, retrieves a JWT token, and returns a [SuggestionsEventSource](./src/suggestions-event-source/Readme.md) instance.
|
|
68
19
|
|
|
69
|
-
|
|
20
|
+
#### [SuggestionsEventSouce](./src/suggestions-event-source/Readme.md) Class
|
|
70
21
|
|
|
71
|
-
|
|
22
|
+
Class that connects to an AI model to receive and emit suggestion streams, using EventTarget for handling data chunks.
|
|
72
23
|
|
|
73
|
-
|
|
74
|
-
import { requestImages } from '@automattic/jetpack-ai-client';
|
|
75
|
-
|
|
76
|
-
requestImages( 'a flower', postId )
|
|
77
|
-
.then( images => {
|
|
78
|
-
document.getElementById("imgid").src= image[0]
|
|
79
|
-
} )
|
|
80
|
-
.catch( error => {
|
|
81
|
-
// Handle the error
|
|
82
|
-
} );
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Using the SuggestionsEventSource Class
|
|
86
|
-
|
|
87
|
-
The `SuggestionsEventSource` class is a wrapper around `EventSource` that emits events for each chunk of data received, when the stream is closed, and when a full suggestion has been received.
|
|
88
|
-
|
|
89
|
-
You shouldn't need to instantiate this class. You get one of these by calling `requestCompletion()`.
|
|
24
|
+
#### [useAiSuggestions](./src/hooks/use-ai-suggestions/Readme.md) hook
|
|
90
25
|
|
|
91
|
-
|
|
92
|
-
import { requestCompletion } from '@automattic/jetpack-ai-client';
|
|
26
|
+
A custom React hook that obtains suggestions from an AI by hitting a specific query endpoint.
|
|
93
27
|
|
|
94
|
-
|
|
28
|
+
#### [JWT](./src/jwt/Readme.md) helper
|
|
95
29
|
|
|
96
|
-
|
|
97
|
-
console.log( "Full completion", event.detail );
|
|
98
|
-
} );
|
|
30
|
+
Library to manage JWT tokens for Jetpack AI across various site types, handling acquisition, caching in localStorage, expiration, and customization of request options.
|
|
99
31
|
|
|
100
|
-
|
|
101
|
-
console.log( "Received so far", event.detail );
|
|
102
|
-
} );
|
|
103
|
-
```
|
|
32
|
+
#### [Data Flow](./src/data-flow/Readme.md) implementation
|
|
104
33
|
|
|
105
|
-
|
|
34
|
+
Data Flow offers a streamlined way to manage an AI Assistant's state and functionality within a React app, using React context, HOCs, and custom hooks to handle suggestions, errors, and requests.
|
|
106
35
|
|
|
107
|
-
|
|
36
|
+
### [Components](./src/components/)
|
|
108
37
|
|
|
109
|
-
|
|
38
|
+
#### [AIControl](./src/components/ai-control/Readme.md)
|
|
110
39
|
|
|
111
|
-
|
|
112
|
-
import { requestCompletionAuthToken } from '@automattic/jetpack-ai-client';
|
|
113
|
-
|
|
114
|
-
requestCompletionAuthToken()
|
|
115
|
-
.then(tokenData => {
|
|
116
|
-
// Do something with the token data
|
|
117
|
-
})
|
|
118
|
-
.catch(error => {
|
|
119
|
-
// Handle the error
|
|
120
|
-
});
|
|
121
|
-
```
|
|
40
|
+
#### [AiStatusIndicator](./src/components/ai-status-indicator/)
|
|
122
41
|
|
|
42
|
+
### [Icons](./src/icons/Readme.md)
|
|
123
43
|
## Contribute
|
|
124
44
|
|
|
45
|
+
React components useful for when you need to create some UI implementations.
|
|
46
|
+
|
|
125
47
|
We welcome contributions from the community. Please submit your pull requests on the GitHub repository.
|
|
126
48
|
|
|
127
49
|
## Get Help
|
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.2",
|
|
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": {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@storybook/addon-actions": "7.1.0",
|
|
25
|
+
"@storybook/blocks": "7.1.0",
|
|
24
26
|
"@storybook/react": "7.1.0",
|
|
25
27
|
"jest": "^29.6.2",
|
|
26
28
|
"jest-environment-jsdom": "29.5.0",
|
|
@@ -31,11 +33,12 @@
|
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
35
|
"@automattic/jetpack-connection": "workspace:*",
|
|
34
|
-
"@automattic/jetpack-shared-extension-utils": "^0.
|
|
36
|
+
"@automattic/jetpack-shared-extension-utils": "^0.11.0",
|
|
35
37
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
36
38
|
"@wordpress/api-fetch": "6.34.0",
|
|
37
39
|
"@wordpress/block-editor": "12.5.0",
|
|
38
40
|
"@wordpress/components": "25.3.0",
|
|
41
|
+
"@wordpress/compose": "6.14.0",
|
|
39
42
|
"@wordpress/data": "9.7.0",
|
|
40
43
|
"@wordpress/element": "5.14.0",
|
|
41
44
|
"@wordpress/i18n": "4.37.0",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
### AIControl
|
|
2
|
+
|
|
3
|
+
#### Properties
|
|
4
|
+
|
|
5
|
+
- `loading` (**boolean**) (Optional): Determines the loading state. Default value is `false`.
|
|
6
|
+
- `value` (**string**): Current input value. Default value is `''`.
|
|
7
|
+
- `placeholder` (**string**) (Optional): Placeholder text for the input field. Default value is `''`.
|
|
8
|
+
- `showAccept` (**boolean**) (Optional): Determines if the accept button is shown. Default value is `false`.
|
|
9
|
+
- `acceptLabel` (**string**) (Optional): Label text for the accept button. Default value is `'Accept'`.
|
|
10
|
+
- `showButtonsLabel` (**boolean**) (Optional): Determines if button labels are shown. Default value is `true`.
|
|
11
|
+
- `isOpaque` (**boolean**) (Optional): Controls the opacity of the component. Default value is `false`.
|
|
12
|
+
- `requestingState` (**RequestingStateProp**) (Optional): Determines the state of the request. Default value is `'init'`.
|
|
13
|
+
- `onChange` (**Function**) (Optional): Handler for input change. Default action is no operation.
|
|
14
|
+
- `onSend` (**Function**) (Optional): Handler to send a request. Default action is no operation.
|
|
15
|
+
- `onStop` (**Function**) (Optional): Handler to stop a request. Default action is no operation.
|
|
16
|
+
- `onAccept` (**Function**) (Optional): Handler to accept the input. Default action is no operation.
|
|
17
|
+
|
|
18
|
+
#### Example Usage
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
<AIControl
|
|
22
|
+
value="Type here"
|
|
23
|
+
placeholder="Placeholder text"
|
|
24
|
+
onChange={ handleChange }
|
|
25
|
+
onSend={ handleSend }
|
|
26
|
+
onStop={ handleStop }
|
|
27
|
+
onAccept={ handleAccept }
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { PlainText } from '@wordpress/block-editor';
|
|
5
|
-
import { Button
|
|
5
|
+
import { Button } from '@wordpress/components';
|
|
6
|
+
import { useKeyboardShortcut } from '@wordpress/compose';
|
|
6
7
|
import { useRef } from '@wordpress/element';
|
|
7
8
|
import { __ } from '@wordpress/i18n';
|
|
8
9
|
import { Icon, closeSmall, check, arrowUp } from '@wordpress/icons';
|
|
@@ -10,8 +11,12 @@ import classNames from 'classnames';
|
|
|
10
11
|
/**
|
|
11
12
|
* Internal dependencies
|
|
12
13
|
*/
|
|
13
|
-
import { aiAssistantIcon } from '../../icons';
|
|
14
14
|
import './style.scss';
|
|
15
|
+
import AiStatusIndicator from '../ai-status-indicator';
|
|
16
|
+
/**
|
|
17
|
+
* Types
|
|
18
|
+
*/
|
|
19
|
+
import type { RequestingStateProp } from '../../types';
|
|
15
20
|
|
|
16
21
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
17
22
|
const noop = () => {};
|
|
@@ -31,6 +36,7 @@ const noop = () => {};
|
|
|
31
36
|
* @param {Function} props.onSend - send request handler
|
|
32
37
|
* @param {Function} props.onStop - stop request handler
|
|
33
38
|
* @param {Function} props.onAccept - accept handler
|
|
39
|
+
* @param {string} props.requestingState - requesting state
|
|
34
40
|
* @returns {object} - AI Control component
|
|
35
41
|
*/
|
|
36
42
|
export default function AIControl( {
|
|
@@ -41,6 +47,7 @@ export default function AIControl( {
|
|
|
41
47
|
acceptLabel = __( 'Accept', 'jetpack-ai-client' ),
|
|
42
48
|
showButtonsLabel = true,
|
|
43
49
|
isOpaque = false,
|
|
50
|
+
requestingState = 'init',
|
|
44
51
|
onChange = noop,
|
|
45
52
|
onSend = noop,
|
|
46
53
|
onStop = noop,
|
|
@@ -53,13 +60,37 @@ export default function AIControl( {
|
|
|
53
60
|
acceptLabel?: string;
|
|
54
61
|
showButtonsLabel?: boolean;
|
|
55
62
|
isOpaque?: boolean;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
requestingState?: RequestingStateProp;
|
|
64
|
+
onChange?: ( newValue: string ) => void;
|
|
65
|
+
onSend?: ( currentValue: string ) => void;
|
|
66
|
+
onStop?: () => void;
|
|
67
|
+
onAccept?: () => void;
|
|
60
68
|
} ) {
|
|
61
69
|
const promptUserInputRef = useRef( null );
|
|
62
70
|
|
|
71
|
+
useKeyboardShortcut(
|
|
72
|
+
'mod+enter',
|
|
73
|
+
() => {
|
|
74
|
+
if ( showAccept ) {
|
|
75
|
+
onAccept?.();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
target: promptUserInputRef,
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
useKeyboardShortcut(
|
|
84
|
+
'enter',
|
|
85
|
+
e => {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
onSend?.( value );
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
target: promptUserInputRef,
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
63
94
|
return (
|
|
64
95
|
<div className="jetpack-components-ai-control__container">
|
|
65
96
|
<div
|
|
@@ -67,23 +98,27 @@ export default function AIControl( {
|
|
|
67
98
|
'is-opaque': isOpaque,
|
|
68
99
|
} ) }
|
|
69
100
|
>
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
<AiStatusIndicator state={ requestingState } />
|
|
102
|
+
|
|
103
|
+
<div className="jetpack-components-ai-control__input-wrapper">
|
|
104
|
+
<PlainText
|
|
105
|
+
value={ value }
|
|
106
|
+
onChange={ onChange }
|
|
107
|
+
placeholder={ placeholder }
|
|
108
|
+
className="jetpack-components-ai-control__input"
|
|
109
|
+
disabled={ loading }
|
|
110
|
+
ref={ promptUserInputRef }
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
{ value?.length > 0 && (
|
|
114
|
+
<Icon
|
|
115
|
+
icon={ closeSmall }
|
|
116
|
+
className="jetpack-components-ai-control__clear"
|
|
117
|
+
onClick={ () => onChange( '' ) }
|
|
118
|
+
/>
|
|
75
119
|
) }
|
|
76
120
|
</div>
|
|
77
121
|
|
|
78
|
-
<PlainText
|
|
79
|
-
value={ value }
|
|
80
|
-
onChange={ onChange }
|
|
81
|
-
placeholder={ placeholder }
|
|
82
|
-
className="jetpack-components-ai-control__input"
|
|
83
|
-
disabled={ loading }
|
|
84
|
-
ref={ promptUserInputRef }
|
|
85
|
-
/>
|
|
86
|
-
|
|
87
122
|
<div className="jetpack-components-ai-control__controls">
|
|
88
123
|
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
|
|
89
124
|
{ ! loading ? (
|
|
@@ -91,7 +126,7 @@ export default function AIControl( {
|
|
|
91
126
|
className="jetpack-components-ai-control__controls-prompt_button"
|
|
92
127
|
onClick={ () => onSend( value ) }
|
|
93
128
|
isSmall={ true }
|
|
94
|
-
disabled={ value?.length }
|
|
129
|
+
disabled={ ! value?.length }
|
|
95
130
|
label={ __( 'Send request', 'jetpack-ai-client' ) }
|
|
96
131
|
>
|
|
97
132
|
<Icon icon={ arrowUp } />
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
import { action } from '@storybook/addon-actions';
|
|
4
5
|
import { useState } from '@wordpress/element';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
/**
|
|
7
8
|
* Internal dependencies
|
|
8
9
|
*/
|
|
10
|
+
import { REQUESTING_STATES } from '../../../types';
|
|
9
11
|
import AIControl from '../index';
|
|
10
12
|
/**
|
|
11
13
|
* Types
|
|
@@ -15,7 +17,26 @@ import type { Meta } from '@storybook/react';
|
|
|
15
17
|
export default {
|
|
16
18
|
title: 'JS Packages/AI Client/AI Control',
|
|
17
19
|
component: AIControl,
|
|
18
|
-
|
|
20
|
+
decorators: [
|
|
21
|
+
Story => (
|
|
22
|
+
<div style={ { backgroundColor: 'white' } }>
|
|
23
|
+
<Story />
|
|
24
|
+
</div>
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
argTypes: {
|
|
28
|
+
requestingState: {
|
|
29
|
+
control: {
|
|
30
|
+
type: 'select',
|
|
31
|
+
},
|
|
32
|
+
options: REQUESTING_STATES,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
parameters: {
|
|
36
|
+
controls: {
|
|
37
|
+
exclude: /on[A-Z].*/,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
19
40
|
} as Meta< typeof AIControl >;
|
|
20
41
|
|
|
21
42
|
const Template = args => {
|
|
@@ -33,9 +54,14 @@ const DefaultArgs = {
|
|
|
33
54
|
loading: false,
|
|
34
55
|
isOpaque: false,
|
|
35
56
|
placeholder: '',
|
|
57
|
+
requestingState: 'init',
|
|
36
58
|
showButtonsLabel: true,
|
|
37
59
|
showAccept: false,
|
|
38
60
|
acceptLabel: 'Accept',
|
|
61
|
+
onChange: action( 'onChange' ),
|
|
62
|
+
onSend: action( 'onSend' ),
|
|
63
|
+
onStop: action( 'onStop' ),
|
|
64
|
+
onAccept: action( 'onAccept' ),
|
|
39
65
|
};
|
|
40
66
|
|
|
41
67
|
export const Default = Template.bind( {} );
|
|
@@ -3,23 +3,46 @@
|
|
|
3
3
|
background-color: var( --jp-white );
|
|
4
4
|
box-shadow: var( --wp--preset--color--cyan-bluish-gray ) 0px 0px 0px 1px, var( --wp--preset--color--cyan-bluish-gray ) 0px 0px 8px;
|
|
5
5
|
font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
6
|
+
width: 100%;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
.jetpack-components-ai-control__wrapper {
|
|
9
10
|
display: flex;
|
|
10
11
|
padding: 12px 14px;
|
|
11
12
|
gap: 8px;
|
|
13
|
+
align-items: center;
|
|
12
14
|
|
|
13
15
|
&.is-opaque {
|
|
14
16
|
opacity: 0.4;
|
|
15
17
|
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.jetpack-components-ai-control__input-wrapper {
|
|
21
|
+
position: relative;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-grow: 1;
|
|
24
|
+
width: 100%;
|
|
25
|
+
|
|
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
|
+
}
|
|
16
40
|
|
|
17
41
|
textarea.jetpack-components-ai-control__input {
|
|
18
42
|
width: 100%;
|
|
19
|
-
|
|
20
|
-
flex-grow: 1;
|
|
43
|
+
min-height: 20px;
|
|
21
44
|
border-radius: 2px;
|
|
22
|
-
padding: 6px 8px;
|
|
45
|
+
padding: 6px 38px 6px 8px;
|
|
23
46
|
|
|
24
47
|
resize: none !important;
|
|
25
48
|
border: none;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### AiStatusIndicator
|
|
2
|
+
|
|
3
|
+
#### Properties
|
|
4
|
+
|
|
5
|
+
- `state` (**RequestingStateProp**) (Optional): Determines the appearance of the icon.
|
|
6
|
+
- `size` (**AiStatusIndicatorIconSize** 24 | 32 | 48 | 64 pixels) (Optional): Defines the size of the icon. Default value is `24`.
|
|
7
|
+
|
|
8
|
+
#### Example Usage
|
|
9
|
+
```jsx
|
|
10
|
+
<AiStatusIndicator state="requesting" size={ 32 } />
|
|
11
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Icon } from '@wordpress/components';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
/*
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { aiAssistantIcon } from '../../icons';
|
|
10
|
+
/*
|
|
11
|
+
* Types
|
|
12
|
+
*/
|
|
13
|
+
import type { RequestingStateProp } from '../../types';
|
|
14
|
+
export type AiStatusIndicatorIconSize = 24 | 32 | 48 | 64;
|
|
15
|
+
import type React from 'react';
|
|
16
|
+
|
|
17
|
+
import './style.scss';
|
|
18
|
+
|
|
19
|
+
export type AiStatusIndicatorProps = {
|
|
20
|
+
state?: RequestingStateProp;
|
|
21
|
+
size?: AiStatusIndicatorIconSize;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* AiStatusIndicator component.
|
|
26
|
+
*
|
|
27
|
+
* @param {AiStatusIndicatorProps} props - component props.
|
|
28
|
+
* @returns {React.ReactElement} - rendered component.
|
|
29
|
+
*/
|
|
30
|
+
export default function AiStatusIndicator( {
|
|
31
|
+
state,
|
|
32
|
+
size = 24,
|
|
33
|
+
}: AiStatusIndicatorProps ): React.ReactElement {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={ classNames( 'jetpack-ai-status-indicator__icon-wrapper', {
|
|
37
|
+
[ `is-${ state }` ]: true,
|
|
38
|
+
} ) }
|
|
39
|
+
>
|
|
40
|
+
<Icon icon={ aiAssistantIcon } size={ size } />
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Meta, Story, Canvas } from '@storybook/blocks';
|
|
2
|
+
import AiStatusIndicator from '../index';
|
|
3
|
+
import * as AiStatusIndicatorStory from './index.stories';
|
|
4
|
+
|
|
5
|
+
<Meta of={AiStatusIndicatorStory} />
|
|
6
|
+
|
|
7
|
+
# AiStatusIndicator
|
|
8
|
+
|
|
9
|
+
## Requesting states
|
|
10
|
+
|
|
11
|
+
### Init
|
|
12
|
+
<Story id="js-packages-ai-client-aistatusindicator--init" />
|
|
13
|
+
|
|
14
|
+
### Requesting
|
|
15
|
+
<Story id="js-packages-ai-client-aistatusindicator--requesting" />
|
|
16
|
+
|
|
17
|
+
### Suggesting
|
|
18
|
+
<Story id="js-packages-ai-client-aistatusindicator--suggesting" />
|
|
19
|
+
|
|
20
|
+
### Done
|
|
21
|
+
<Story id="js-packages-ai-client-aistatusindicator--done" />
|
|
22
|
+
|
|
23
|
+
### Error
|
|
24
|
+
<Story id="js-packages-ai-client-aistatusindicator--error" />
|
|
25
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* External Dependencies
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
/*
|
|
6
|
+
* Internal Dependencies
|
|
7
|
+
*/
|
|
8
|
+
import AiStatusIndicator, { AiStatusIndicatorProps } from '..';
|
|
9
|
+
import { REQUESTING_STATES } from '../../../types';
|
|
10
|
+
|
|
11
|
+
type AiStatusIndicatoryStoryProps = AiStatusIndicatorProps & {
|
|
12
|
+
icon: string;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
title: 'JS Packages/AI Client/AiStatusIndicator',
|
|
18
|
+
component: AiStatusIndicator,
|
|
19
|
+
argTypes: {
|
|
20
|
+
state: {
|
|
21
|
+
control: {
|
|
22
|
+
type: 'select',
|
|
23
|
+
},
|
|
24
|
+
options: REQUESTING_STATES,
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
control: {
|
|
28
|
+
type: 'select',
|
|
29
|
+
},
|
|
30
|
+
options: [ 24, 32, 48, 64 ],
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
action: {
|
|
34
|
+
table: {
|
|
35
|
+
disable: true,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DefaultTemplate = ( args: AiStatusIndicatoryStoryProps ) => {
|
|
42
|
+
const props: AiStatusIndicatorProps = {
|
|
43
|
+
state: args.state,
|
|
44
|
+
size: args.size,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return <AiStatusIndicator { ...props } />;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const _default = DefaultTemplate.bind( {} );
|
|
51
|
+
_default.args = {
|
|
52
|
+
state: 'init',
|
|
53
|
+
size: 24,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Init = DefaultTemplate.bind( {} );
|
|
57
|
+
Init.args = {
|
|
58
|
+
state: 'init',
|
|
59
|
+
size: 48,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Requesting = DefaultTemplate.bind( {} );
|
|
63
|
+
Requesting.args = {
|
|
64
|
+
state: 'requesting',
|
|
65
|
+
size: 48,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Suggesting = DefaultTemplate.bind( {} );
|
|
69
|
+
Suggesting.args = {
|
|
70
|
+
state: 'suggesting',
|
|
71
|
+
size: 48,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const Error = DefaultTemplate.bind( {} );
|
|
75
|
+
Error.args = {
|
|
76
|
+
state: 'error',
|
|
77
|
+
size: 48,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Done = DefaultTemplate.bind( {} );
|
|
81
|
+
Done.args = {
|
|
82
|
+
state: 'done',
|
|
83
|
+
size: 48,
|
|
84
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.jetpack-ai-status-indicator__icon-wrapper {
|
|
2
|
+
color: var( --jp-green-60 );
|
|
3
|
+
height: 24px;
|
|
4
|
+
|
|
5
|
+
&.is-init {
|
|
6
|
+
color: var( --jp-green-60 );
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
&.is-requesting {
|
|
10
|
+
color: var( --jp-green-40 );
|
|
11
|
+
animation: fade 800ms linear infinite;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
&.is-suggesting {
|
|
15
|
+
color: var( --jp-green-30 );
|
|
16
|
+
.ai-assistant-icon > path {
|
|
17
|
+
animation: fadingSpark 300ms ease-in-out alternate infinite;
|
|
18
|
+
|
|
19
|
+
&.spark-first {
|
|
20
|
+
animation-delay: 0.2s;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&.spark-second {
|
|
24
|
+
animation-delay: 0.6s;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.is-done {
|
|
30
|
+
color: var( --jp-green-40 );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.is-error {
|
|
34
|
+
color: var( --jp-yellow-30 );
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@keyframes fadingSpark {
|
|
40
|
+
to {
|
|
41
|
+
opacity: 0.3;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes fade {
|
|
46
|
+
0% { opacity: 1; }
|
|
47
|
+
50% { opacity: 0.4; }
|
|
48
|
+
100% { opacity: 1; }
|
|
49
|
+
}
|
|
50
|
+
|
package/src/data-flow/Readme.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
# AI Assistant Data Flow
|
|
3
3
|
|
|
4
|
+
Data Flow is a seamless and robust implementation designed to manage the state and functionality of an AI Assistant within a React application. By leveraging a React context, Higher Order Components (HOCs), and custom hooks, this implementation streamlines the interaction with the AI, handles suggestions, manages error states, and efficiently controls request functionality.
|
|
5
|
+
|
|
4
6
|
```jsx
|
|
5
7
|
import { withAiAssistantData, useAiContext } from '@automattic/jetpack-ai-client';
|
|
6
8
|
|
|
@@ -7,8 +7,10 @@ import React from 'react';
|
|
|
7
7
|
* Types & Constants
|
|
8
8
|
*/
|
|
9
9
|
import SuggestionsEventSource from '../suggestions-event-source';
|
|
10
|
-
import type {
|
|
10
|
+
import type { AskQuestionOptionsArgProps } from '../ask-question';
|
|
11
|
+
import type { RequestingErrorProps } from '../hooks/use-ai-suggestions';
|
|
11
12
|
import type { PromptProp } from '../types';
|
|
13
|
+
import type { RequestingStateProp } from '../types';
|
|
12
14
|
|
|
13
15
|
export type AiDataContextProps = {
|
|
14
16
|
/*
|
|
@@ -29,7 +31,7 @@ export type AiDataContextProps = {
|
|
|
29
31
|
/*
|
|
30
32
|
* Request suggestion function
|
|
31
33
|
*/
|
|
32
|
-
requestSuggestion: ( prompt: PromptProp ) => void;
|
|
34
|
+
requestSuggestion: ( prompt: PromptProp, options?: AskQuestionOptionsArgProps ) => void;
|
|
33
35
|
|
|
34
36
|
/*
|
|
35
37
|
* The Suggestions Event Source instance
|
|
@@ -5,6 +5,7 @@ import { useCallback, useContext, useEffect } from '@wordpress/element';
|
|
|
5
5
|
/**
|
|
6
6
|
* Internal dependencies
|
|
7
7
|
*/
|
|
8
|
+
import { ERROR_RESPONSE } from '../types';
|
|
8
9
|
import { AiDataContext } from '.';
|
|
9
10
|
/**
|
|
10
11
|
* Types & constants
|
|
@@ -12,7 +13,7 @@ import { AiDataContext } from '.';
|
|
|
12
13
|
import type { AiDataContextProps } from './context';
|
|
13
14
|
import type { AskQuestionOptionsArgProps } from '../ask-question';
|
|
14
15
|
|
|
15
|
-
type
|
|
16
|
+
export type UseAiContextOptions = {
|
|
16
17
|
/*
|
|
17
18
|
* Ask question options.
|
|
18
19
|
*/
|
|
@@ -27,6 +28,11 @@ type useAiContextOptions = {
|
|
|
27
28
|
* onSuggestion callback.
|
|
28
29
|
*/
|
|
29
30
|
onSuggestion?: ( suggestion: string ) => void;
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* onError callback.
|
|
34
|
+
*/
|
|
35
|
+
onError?: ( error: Error ) => void;
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
/**
|
|
@@ -34,13 +40,14 @@ type useAiContextOptions = {
|
|
|
34
40
|
* the AI Assistant data (from context),
|
|
35
41
|
* and to subscribe to the request events (onDone, onSuggestion).
|
|
36
42
|
*
|
|
37
|
-
* @param {
|
|
43
|
+
* @param {UseAiContextOptions} options - the hook options.
|
|
38
44
|
* @returns {AiDataContextProps} the AI Assistant data context.
|
|
39
45
|
*/
|
|
40
46
|
export default function useAiContext( {
|
|
41
47
|
onDone,
|
|
42
48
|
onSuggestion,
|
|
43
|
-
|
|
49
|
+
onError,
|
|
50
|
+
}: UseAiContextOptions = {} ): AiDataContextProps {
|
|
44
51
|
const context = useContext( AiDataContext );
|
|
45
52
|
const { eventSource } = context;
|
|
46
53
|
|
|
@@ -49,6 +56,9 @@ export default function useAiContext( {
|
|
|
49
56
|
( event: CustomEvent ) => onSuggestion?.( event?.detail ),
|
|
50
57
|
[ onSuggestion ]
|
|
51
58
|
);
|
|
59
|
+
const error = useCallback( ( event: CustomEvent ) => {
|
|
60
|
+
onError?.( event?.detail );
|
|
61
|
+
}, [] );
|
|
52
62
|
|
|
53
63
|
useEffect( () => {
|
|
54
64
|
if ( ! eventSource ) {
|
|
@@ -63,9 +73,14 @@ export default function useAiContext( {
|
|
|
63
73
|
eventSource.addEventListener( 'suggestion', suggestion );
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
if ( onError ) {
|
|
77
|
+
eventSource.addEventListener( ERROR_RESPONSE, error );
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
return () => {
|
|
67
81
|
eventSource.removeEventListener( 'done', done );
|
|
68
82
|
eventSource.removeEventListener( 'suggestion', suggestion );
|
|
83
|
+
eventSource.removeEventListener( ERROR_RESPONSE, error );
|
|
69
84
|
};
|
|
70
85
|
}, [ eventSource ] );
|
|
71
86
|
|
|
@@ -35,7 +35,7 @@ An object with the following properties:
|
|
|
35
35
|
- `error: RequestingErrorProps | undefined`: An error object if an error occurs.
|
|
36
36
|
- `requestingState: RequestingStateProp`: The state of the request.
|
|
37
37
|
- `eventSource: SuggestionsEventSource | undefined`: The event source of the request.
|
|
38
|
-
- `request: ( prompt: Array< PromptItemProps
|
|
38
|
+
- `request: ( prompt: Array< PromptItemProps >, options: AskQuestionOptionsArgProps ) => Promise< void >`: The request handler.
|
|
39
39
|
|
|
40
40
|
### `PromptItemProps`
|
|
41
41
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import type { AskQuestionOptionsArgProps } from '../../ask-question';
|
|
22
22
|
import type SuggestionsEventSource from '../../suggestions-event-source';
|
|
23
23
|
import type { PromptProp, SuggestionErrorCode } from '../../types';
|
|
24
|
+
import type { RequestingStateProp } from '../../types';
|
|
24
25
|
|
|
25
26
|
export type RequestingErrorProps = {
|
|
26
27
|
/*
|
|
@@ -71,8 +72,6 @@ type useAiSuggestionsOptions = {
|
|
|
71
72
|
onError?: ( error: RequestingErrorProps ) => void;
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
export type RequestingStateProp = 'init' | 'requesting' | 'suggesting' | 'done' | 'error';
|
|
75
|
-
|
|
76
75
|
type useAiSuggestionsProps = {
|
|
77
76
|
/*
|
|
78
77
|
* The suggestion.
|
|
@@ -97,7 +96,7 @@ type useAiSuggestionsProps = {
|
|
|
97
96
|
/*
|
|
98
97
|
* The request handler.
|
|
99
98
|
*/
|
|
100
|
-
request: ( prompt: PromptProp ) => Promise< void >;
|
|
99
|
+
request: ( prompt: PromptProp, options?: AskQuestionOptionsArgProps ) => Promise< void >;
|
|
101
100
|
};
|
|
102
101
|
|
|
103
102
|
const debug = debugFactory( 'jetpack-ai-client:use-suggestion' );
|
|
@@ -108,7 +107,7 @@ const debug = debugFactory( 'jetpack-ai-client:use-suggestion' );
|
|
|
108
107
|
* @param {SuggestionErrorCode} errorCode - The error code.
|
|
109
108
|
* @returns {RequestingErrorProps} The error data.
|
|
110
109
|
*/
|
|
111
|
-
function getErrorData( errorCode: SuggestionErrorCode ): RequestingErrorProps {
|
|
110
|
+
export function getErrorData( errorCode: SuggestionErrorCode ): RequestingErrorProps {
|
|
112
111
|
switch ( errorCode ) {
|
|
113
112
|
case ERROR_QUOTA_EXCEEDED:
|
|
114
113
|
return {
|
|
@@ -240,10 +239,15 @@ export default function useAiSuggestions( {
|
|
|
240
239
|
/**
|
|
241
240
|
* Request handler.
|
|
242
241
|
*
|
|
242
|
+
* @param {PromptProp} promptArg - The messages array of the prompt.
|
|
243
|
+
* @param {AskQuestionOptionsArgProps} options - The options for the askQuestion request. Uses the hook's askQuestionOptions by default.
|
|
243
244
|
* @returns {Promise<void>} The promise.
|
|
244
245
|
*/
|
|
245
246
|
const request = useCallback(
|
|
246
|
-
async (
|
|
247
|
+
async (
|
|
248
|
+
promptArg: PromptProp,
|
|
249
|
+
options: AskQuestionOptionsArgProps = { ...askQuestionOptions }
|
|
250
|
+
) => {
|
|
247
251
|
if ( Array.isArray( promptArg ) && promptArg?.length ) {
|
|
248
252
|
promptArg.forEach( ( { role, content: promptContent }, i ) =>
|
|
249
253
|
debug( '(%s/%s) %o\n%s', i + 1, promptArg.length, `[${ role }]`, promptContent )
|
|
@@ -256,7 +260,7 @@ export default function useAiSuggestions( {
|
|
|
256
260
|
setRequestingState( 'requesting' );
|
|
257
261
|
|
|
258
262
|
try {
|
|
259
|
-
eventSourceRef.current = await askQuestion( promptArg,
|
|
263
|
+
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
260
264
|
|
|
261
265
|
if ( ! eventSourceRef?.current ) {
|
|
262
266
|
return;
|
|
@@ -302,7 +306,7 @@ export default function useAiSuggestions( {
|
|
|
302
306
|
|
|
303
307
|
// Trigger the request.
|
|
304
308
|
if ( autoRequest ) {
|
|
305
|
-
request( prompt );
|
|
309
|
+
request( prompt, askQuestionOptions );
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
return () => {
|
|
@@ -6,14 +6,16 @@ import debugFactory from 'debug';
|
|
|
6
6
|
/*
|
|
7
7
|
* Types & constants
|
|
8
8
|
*/
|
|
9
|
+
import { getErrorData } from '../hooks/use-ai-suggestions';
|
|
9
10
|
import {
|
|
10
11
|
ERROR_MODERATION,
|
|
11
12
|
ERROR_NETWORK,
|
|
12
13
|
ERROR_QUOTA_EXCEEDED,
|
|
14
|
+
ERROR_RESPONSE,
|
|
13
15
|
ERROR_SERVICE_UNAVAILABLE,
|
|
14
16
|
ERROR_UNCLEAR_PROMPT,
|
|
15
17
|
} from '../types';
|
|
16
|
-
import type { PromptMessagesProp, PromptProp } from '../types';
|
|
18
|
+
import type { PromptMessagesProp, PromptProp, SuggestionErrorCode } from '../types';
|
|
17
19
|
|
|
18
20
|
type SuggestionsEventSourceConstructorArgs = {
|
|
19
21
|
url?: string;
|
|
@@ -150,6 +152,9 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
150
152
|
if ( response.ok ) {
|
|
151
153
|
return;
|
|
152
154
|
}
|
|
155
|
+
|
|
156
|
+
let errorCode: SuggestionErrorCode;
|
|
157
|
+
|
|
153
158
|
if (
|
|
154
159
|
response.status >= 400 &&
|
|
155
160
|
response.status <= 500 &&
|
|
@@ -163,6 +168,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
163
168
|
* service unavailable
|
|
164
169
|
*/
|
|
165
170
|
if ( response.status === 503 ) {
|
|
171
|
+
errorCode = ERROR_SERVICE_UNAVAILABLE;
|
|
166
172
|
this.dispatchEvent( new CustomEvent( ERROR_SERVICE_UNAVAILABLE ) );
|
|
167
173
|
}
|
|
168
174
|
|
|
@@ -171,6 +177,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
171
177
|
* you exceeded your current quota please check your plan and billing details
|
|
172
178
|
*/
|
|
173
179
|
if ( response.status === 429 ) {
|
|
180
|
+
errorCode = ERROR_QUOTA_EXCEEDED;
|
|
174
181
|
this.dispatchEvent( new CustomEvent( ERROR_QUOTA_EXCEEDED ) );
|
|
175
182
|
}
|
|
176
183
|
|
|
@@ -179,9 +186,17 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
179
186
|
* request flagged by moderation system
|
|
180
187
|
*/
|
|
181
188
|
if ( response.status === 422 ) {
|
|
189
|
+
errorCode = ERROR_MODERATION;
|
|
182
190
|
this.dispatchEvent( new CustomEvent( ERROR_MODERATION ) );
|
|
183
191
|
}
|
|
184
192
|
|
|
193
|
+
// Always dispatch a global ERROR_RESPONSE event
|
|
194
|
+
this.dispatchEvent(
|
|
195
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
196
|
+
detail: getErrorData( errorCode ),
|
|
197
|
+
} )
|
|
198
|
+
);
|
|
199
|
+
|
|
185
200
|
throw new Error();
|
|
186
201
|
},
|
|
187
202
|
|
|
@@ -204,6 +219,11 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
204
219
|
if ( replacedMessage.startsWith( 'JETPACK_AI_ERROR' ) ) {
|
|
205
220
|
// The unclear prompt marker was found, so we dispatch an error event
|
|
206
221
|
this.dispatchEvent( new CustomEvent( ERROR_UNCLEAR_PROMPT ) );
|
|
222
|
+
this.dispatchEvent(
|
|
223
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
224
|
+
detail: getErrorData( ERROR_UNCLEAR_PROMPT ),
|
|
225
|
+
} )
|
|
226
|
+
);
|
|
207
227
|
} else if ( 'JETPACK_AI_ERROR'.startsWith( replacedMessage ) ) {
|
|
208
228
|
// Partial unclear prompt marker was found, so we wait for more data and print a debug message without dispatching an event
|
|
209
229
|
debug( this.fullMessage );
|
|
@@ -276,6 +296,11 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
276
296
|
processConnectionError( response ) {
|
|
277
297
|
debug( 'Connection error: %o', response );
|
|
278
298
|
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
|
|
299
|
+
this.dispatchEvent(
|
|
300
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
301
|
+
detail: getErrorData( ERROR_NETWORK ),
|
|
302
|
+
} )
|
|
303
|
+
);
|
|
279
304
|
}
|
|
280
305
|
|
|
281
306
|
processErrorEvent( e ) {
|
|
@@ -283,5 +308,10 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
283
308
|
|
|
284
309
|
// Dispatch a generic network error event
|
|
285
310
|
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
|
|
311
|
+
this.dispatchEvent(
|
|
312
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
313
|
+
detail: getErrorData( ERROR_NETWORK ),
|
|
314
|
+
} )
|
|
315
|
+
);
|
|
286
316
|
}
|
|
287
317
|
}
|
package/src/types.ts
CHANGED
|
@@ -3,13 +3,15 @@ export const ERROR_QUOTA_EXCEEDED = 'error_quota_exceeded' as const;
|
|
|
3
3
|
export const ERROR_MODERATION = 'error_moderation' as const;
|
|
4
4
|
export const ERROR_NETWORK = 'error_network' as const;
|
|
5
5
|
export const ERROR_UNCLEAR_PROMPT = 'error_unclear_prompt' as const;
|
|
6
|
+
export const ERROR_RESPONSE = 'error_response' as const;
|
|
6
7
|
|
|
7
8
|
export type SuggestionErrorCode =
|
|
8
9
|
| typeof ERROR_SERVICE_UNAVAILABLE
|
|
9
10
|
| typeof ERROR_QUOTA_EXCEEDED
|
|
10
11
|
| typeof ERROR_MODERATION
|
|
11
12
|
| typeof ERROR_NETWORK
|
|
12
|
-
| typeof ERROR_UNCLEAR_PROMPT
|
|
13
|
+
| typeof ERROR_UNCLEAR_PROMPT
|
|
14
|
+
| typeof ERROR_RESPONSE;
|
|
13
15
|
|
|
14
16
|
/*
|
|
15
17
|
* Prompt types
|
|
@@ -22,3 +24,29 @@ export type PromptItemProps = {
|
|
|
22
24
|
export type PromptMessagesProp = Array< PromptItemProps >;
|
|
23
25
|
|
|
24
26
|
export type PromptProp = PromptMessagesProp | string;
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Data Flow types
|
|
30
|
+
*/
|
|
31
|
+
export type { UseAiContextOptions } from './data-flow/use-ai-context';
|
|
32
|
+
export type { RequestingErrorProps } from './hooks/use-ai-suggestions';
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Requests types
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const REQUESTING_STATE_INIT = 'init' as const;
|
|
39
|
+
const REQUESTING_STATE_REQUESTING = 'requesting' as const;
|
|
40
|
+
const REQUESTING_STATE_SUGGESTING = 'suggesting' as const;
|
|
41
|
+
const REQUESTING_STATE_DONE = 'done' as const;
|
|
42
|
+
const REQUESTING_STATE_ERROR = 'error' as const;
|
|
43
|
+
|
|
44
|
+
export const REQUESTING_STATES = [
|
|
45
|
+
REQUESTING_STATE_INIT,
|
|
46
|
+
REQUESTING_STATE_REQUESTING,
|
|
47
|
+
REQUESTING_STATE_SUGGESTING,
|
|
48
|
+
REQUESTING_STATE_DONE,
|
|
49
|
+
REQUESTING_STATE_ERROR,
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
export type RequestingStateProp = ( typeof REQUESTING_STATES )[ number ];
|