@automattic/jetpack-ai-client 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/index.ts +23 -1
- package/package.json +13 -3
- package/src/ask-question/Readme.md +1 -1
- package/src/ask-question/index.ts +21 -7
- package/src/components/ai-control/index.tsx +130 -0
- package/src/components/ai-control/stories/index.stories.tsx +42 -0
- package/src/components/ai-control/style.scss +107 -0
- package/src/data-flow/Readme.md +113 -0
- package/src/data-flow/context.tsx +74 -0
- package/src/data-flow/index.ts +3 -0
- package/src/data-flow/use-ai-context.ts +73 -0
- package/src/data-flow/with-ai-assistant-data.tsx +52 -0
- package/src/hooks/use-ai-suggestions/Readme.md +127 -0
- package/src/hooks/use-ai-suggestions/index.ts +354 -0
- package/src/icons/Readme.md +22 -0
- package/src/icons/ai-assistant.tsx +31 -0
- package/src/icons/index.ts +3 -0
- package/src/icons/origami-plane.tsx +20 -0
- package/src/icons/speak-tone.tsx +24 -0
- package/src/icons/stories/index.stories.tsx +46 -0
- package/src/icons/stories/style.module.scss +21 -0
- package/src/suggestions-event-source/index.ts +83 -15
- package/src/types.ts +20 -0
- package/src/index.js +0 -26
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { SVG, Path } from '@wordpress/components';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
const speakTone = (
|
|
8
|
+
<SVG width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
9
|
+
<Path
|
|
10
|
+
fillRule="evenodd"
|
|
11
|
+
clipRule="evenodd"
|
|
12
|
+
d="M12.5 10C12.5 11.3807 11.3807 12.5 10 12.5C8.61929 12.5 7.5 11.3807 7.5 10C7.5 8.61929 8.61929 7.5 10 7.5C11.3807 7.5 12.5 8.61929 12.5 10ZM14 10C14 12.2091 12.2091 14 10 14C7.79086 14 6 12.2091 6 10C6 7.79086 7.79086 6 10 6C12.2091 6 14 7.79086 14 10ZM16.75 21V19C16.75 17.4812 15.5188 16.25 14 16.25L6 16.25C4.48122 16.25 3.25 17.4812 3.25 19V21H4.75L4.75 19C4.75 18.3096 5.30964 17.75 6 17.75L14 17.75C14.6904 17.75 15.25 18.3096 15.25 19V21H16.75Z"
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
/>
|
|
15
|
+
<Path
|
|
16
|
+
fillRule="evenodd"
|
|
17
|
+
clipRule="evenodd"
|
|
18
|
+
d="M19.976 16.3599C21.2507 14.5642 22.0001 12.3695 22.0001 9.99969C22.0001 7.63128 21.2515 5.43769 19.9782 3.64258L18.754 4.50967C19.8537 6.05996 20.5001 7.95434 20.5001 9.99969C20.5001 12.0464 19.8528 13.9419 18.7519 15.4928L19.976 16.3599ZM17.3357 14.4897C18.2357 13.222 18.7648 11.6727 18.7648 9.99969C18.7648 8.32808 18.2365 6.77984 17.3379 5.51279L16.1137 6.37988C16.8387 7.4021 17.2648 8.65114 17.2648 9.99969C17.2648 11.3496 16.8378 12.5998 16.1116 13.6226L17.3357 14.4897Z"
|
|
19
|
+
fill="currentColor"
|
|
20
|
+
/>
|
|
21
|
+
</SVG>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export default speakTone;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Icon } from '@wordpress/components';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import * as allIcons from '../index';
|
|
10
|
+
import styles from './style.module.scss';
|
|
11
|
+
/**
|
|
12
|
+
* Types
|
|
13
|
+
*/
|
|
14
|
+
import type { Meta } from '@storybook/react';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
title: 'JS Packages/AI Client/Icons',
|
|
18
|
+
component: allIcons,
|
|
19
|
+
parameters: {},
|
|
20
|
+
} as Meta< typeof allIcons >;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Icons story components.
|
|
24
|
+
*
|
|
25
|
+
* @returns {object} - story component
|
|
26
|
+
*/
|
|
27
|
+
function IconsStory() {
|
|
28
|
+
return (
|
|
29
|
+
<div className={ styles[ 'icons-container' ] }>
|
|
30
|
+
{ Object.entries( allIcons ).map( ( [ name, icon ] ) => {
|
|
31
|
+
return (
|
|
32
|
+
<div key={ name } className={ styles[ 'icon-container' ] }>
|
|
33
|
+
<Icon icon={ icon } size={ 32 } />
|
|
34
|
+
<div className={ styles[ 'icon-name' ] }>{ name }</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
} ) }
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Template = args => <IconsStory { ...args } />;
|
|
43
|
+
|
|
44
|
+
const DefaultArgs = {};
|
|
45
|
+
export const Default = Template.bind( {} );
|
|
46
|
+
Default.args = DefaultArgs;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.icons-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
margin-bottom: 20px;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.icon-container {
|
|
9
|
+
padding: 10px;
|
|
10
|
+
box-shadow: 0 0 1px inset rgba(0, 0, 0 , 0.5 );
|
|
11
|
+
background-color: white;
|
|
12
|
+
border-radius: 5px;
|
|
13
|
+
margin: 2px;
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 8px;
|
|
16
|
+
align-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.icon-name {
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
}
|
|
@@ -6,19 +6,32 @@ import debugFactory from 'debug';
|
|
|
6
6
|
/*
|
|
7
7
|
* Types & constants
|
|
8
8
|
*/
|
|
9
|
-
import
|
|
9
|
+
import {
|
|
10
|
+
ERROR_MODERATION,
|
|
11
|
+
ERROR_NETWORK,
|
|
12
|
+
ERROR_QUOTA_EXCEEDED,
|
|
13
|
+
ERROR_SERVICE_UNAVAILABLE,
|
|
14
|
+
ERROR_UNCLEAR_PROMPT,
|
|
15
|
+
} from '../types';
|
|
16
|
+
import type { PromptMessagesProp, PromptProp } from '../types';
|
|
10
17
|
|
|
11
18
|
type SuggestionsEventSourceConstructorArgs = {
|
|
12
19
|
url?: string;
|
|
13
|
-
question:
|
|
20
|
+
question: PromptProp;
|
|
14
21
|
token: string;
|
|
15
22
|
options?: {
|
|
16
23
|
postId?: number;
|
|
17
24
|
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
18
25
|
fromCache?: boolean;
|
|
26
|
+
functions?: Array< object >;
|
|
19
27
|
};
|
|
20
28
|
};
|
|
21
29
|
|
|
30
|
+
type FunctionCallProps = {
|
|
31
|
+
name?: string;
|
|
32
|
+
arguments?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
22
35
|
const debug = debugFactory( 'jetpack-ai-client:suggestions-event-source' );
|
|
23
36
|
|
|
24
37
|
/**
|
|
@@ -41,12 +54,17 @@ const debug = debugFactory( 'jetpack-ai-client:suggestions-event-source' );
|
|
|
41
54
|
*/
|
|
42
55
|
export default class SuggestionsEventSource extends EventTarget {
|
|
43
56
|
fullMessage: string;
|
|
57
|
+
fullFunctionCall: FunctionCallProps;
|
|
44
58
|
isPromptClear: boolean;
|
|
45
59
|
controller: AbortController;
|
|
46
60
|
|
|
47
61
|
constructor( data: SuggestionsEventSourceConstructorArgs ) {
|
|
48
62
|
super();
|
|
49
63
|
this.fullMessage = '';
|
|
64
|
+
this.fullFunctionCall = {
|
|
65
|
+
name: '',
|
|
66
|
+
arguments: '',
|
|
67
|
+
};
|
|
50
68
|
this.isPromptClear = false;
|
|
51
69
|
|
|
52
70
|
// The AbortController is used to close the fetchEventSource connection
|
|
@@ -61,7 +79,18 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
61
79
|
token,
|
|
62
80
|
options = {},
|
|
63
81
|
}: SuggestionsEventSourceConstructorArgs ) {
|
|
64
|
-
const bodyData
|
|
82
|
+
const bodyData: {
|
|
83
|
+
post_id?: number;
|
|
84
|
+
messages?: PromptMessagesProp;
|
|
85
|
+
question?: PromptProp;
|
|
86
|
+
feature?: string;
|
|
87
|
+
functions?: Array< object >;
|
|
88
|
+
} = {};
|
|
89
|
+
|
|
90
|
+
// Populate body data with post id
|
|
91
|
+
if ( options?.postId ) {
|
|
92
|
+
bodyData.post_id = options.postId;
|
|
93
|
+
}
|
|
65
94
|
|
|
66
95
|
// If the url is not provided, we use the default one
|
|
67
96
|
if ( ! url ) {
|
|
@@ -76,7 +105,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
76
105
|
debug( 'URL not provided, using default: %o', url );
|
|
77
106
|
}
|
|
78
107
|
|
|
79
|
-
// question can be a string or an array of
|
|
108
|
+
// question can be a string or an array of PromptMessagesProp
|
|
80
109
|
if ( Array.isArray( question ) ) {
|
|
81
110
|
bodyData.messages = question;
|
|
82
111
|
} else {
|
|
@@ -89,6 +118,12 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
89
118
|
bodyData.feature = options.feature;
|
|
90
119
|
}
|
|
91
120
|
|
|
121
|
+
// Propagate the functions option
|
|
122
|
+
if ( options?.functions?.length ) {
|
|
123
|
+
debug( 'Functions: %o', options.functions );
|
|
124
|
+
bodyData.functions = options.functions;
|
|
125
|
+
}
|
|
126
|
+
|
|
92
127
|
await fetchEventSource( url, {
|
|
93
128
|
openWhenHidden: true,
|
|
94
129
|
method: 'POST',
|
|
@@ -128,7 +163,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
128
163
|
* service unavailable
|
|
129
164
|
*/
|
|
130
165
|
if ( response.status === 503 ) {
|
|
131
|
-
this.dispatchEvent( new CustomEvent(
|
|
166
|
+
this.dispatchEvent( new CustomEvent( ERROR_SERVICE_UNAVAILABLE ) );
|
|
132
167
|
}
|
|
133
168
|
|
|
134
169
|
/*
|
|
@@ -136,7 +171,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
136
171
|
* you exceeded your current quota please check your plan and billing details
|
|
137
172
|
*/
|
|
138
173
|
if ( response.status === 429 ) {
|
|
139
|
-
this.dispatchEvent( new CustomEvent(
|
|
174
|
+
this.dispatchEvent( new CustomEvent( ERROR_QUOTA_EXCEEDED ) );
|
|
140
175
|
}
|
|
141
176
|
|
|
142
177
|
/*
|
|
@@ -144,7 +179,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
144
179
|
* request flagged by moderation system
|
|
145
180
|
*/
|
|
146
181
|
if ( response.status === 422 ) {
|
|
147
|
-
this.dispatchEvent( new CustomEvent(
|
|
182
|
+
this.dispatchEvent( new CustomEvent( ERROR_MODERATION ) );
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
throw new Error();
|
|
@@ -168,7 +203,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
168
203
|
const replacedMessage = this.fullMessage.replace( /__|(\*\*)/g, '' );
|
|
169
204
|
if ( replacedMessage.startsWith( 'JETPACK_AI_ERROR' ) ) {
|
|
170
205
|
// The unclear prompt marker was found, so we dispatch an error event
|
|
171
|
-
this.dispatchEvent( new CustomEvent(
|
|
206
|
+
this.dispatchEvent( new CustomEvent( ERROR_UNCLEAR_PROMPT ) );
|
|
172
207
|
} else if ( 'JETPACK_AI_ERROR'.startsWith( replacedMessage ) ) {
|
|
173
208
|
// Partial unclear prompt marker was found, so we wait for more data and print a debug message without dispatching an event
|
|
174
209
|
debug( this.fullMessage );
|
|
@@ -184,14 +219,31 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
184
219
|
|
|
185
220
|
processEvent( e: EventSourceMessage ) {
|
|
186
221
|
if ( e.data === '[DONE]' ) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
222
|
+
if ( this.fullMessage.length ) {
|
|
223
|
+
// Dispatch an event with the full content
|
|
224
|
+
this.dispatchEvent( new CustomEvent( 'done', { detail: this.fullMessage } ) );
|
|
225
|
+
debug( 'Done: %o', this.fullMessage );
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if ( this.fullFunctionCall.name.length ) {
|
|
230
|
+
this.dispatchEvent( new CustomEvent( 'function_done', { detail: this.fullFunctionCall } ) );
|
|
231
|
+
debug( 'Done: %o', this.fullFunctionCall );
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let data;
|
|
237
|
+
try {
|
|
238
|
+
data = JSON.parse( e.data );
|
|
239
|
+
} catch ( err ) {
|
|
240
|
+
debug( 'Error parsing JSON', e, err );
|
|
190
241
|
return;
|
|
191
242
|
}
|
|
243
|
+
const { delta } = data?.choices?.[ 0 ] ?? { delta: { content: null, function_call: null } };
|
|
244
|
+
const chunk = delta.content;
|
|
245
|
+
const functionCallChunk = delta.function_call;
|
|
192
246
|
|
|
193
|
-
const data = JSON.parse( e.data );
|
|
194
|
-
const chunk = data.choices[ 0 ].delta.content;
|
|
195
247
|
if ( chunk ) {
|
|
196
248
|
this.fullMessage += chunk;
|
|
197
249
|
this.checkForUnclearPrompt();
|
|
@@ -200,20 +252,36 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
200
252
|
// Dispatch an event with the chunk
|
|
201
253
|
this.dispatchEvent( new CustomEvent( 'chunk', { detail: chunk } ) );
|
|
202
254
|
// Dispatch an event with the full message
|
|
255
|
+
debug( 'suggestion: %o', this.fullMessage );
|
|
203
256
|
this.dispatchEvent( new CustomEvent( 'suggestion', { detail: this.fullMessage } ) );
|
|
204
257
|
}
|
|
205
258
|
}
|
|
259
|
+
|
|
260
|
+
if ( functionCallChunk ) {
|
|
261
|
+
if ( functionCallChunk.name != null ) {
|
|
262
|
+
this.fullFunctionCall.name += functionCallChunk.name;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if ( functionCallChunk.arguments != null ) {
|
|
266
|
+
this.fullFunctionCall.arguments += functionCallChunk.arguments;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Dispatch an event with the function call
|
|
270
|
+
this.dispatchEvent(
|
|
271
|
+
new CustomEvent( 'functionCallChunk', { detail: this.fullFunctionCall } )
|
|
272
|
+
);
|
|
273
|
+
}
|
|
206
274
|
}
|
|
207
275
|
|
|
208
276
|
processConnectionError( response ) {
|
|
209
277
|
debug( 'Connection error: %o', response );
|
|
210
|
-
this.dispatchEvent( new CustomEvent(
|
|
278
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
|
|
211
279
|
}
|
|
212
280
|
|
|
213
281
|
processErrorEvent( e ) {
|
|
214
282
|
debug( 'onerror: %o', e );
|
|
215
283
|
|
|
216
284
|
// Dispatch a generic network error event
|
|
217
|
-
this.dispatchEvent( new CustomEvent(
|
|
285
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
|
|
218
286
|
}
|
|
219
287
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
|
+
export const ERROR_SERVICE_UNAVAILABLE = 'error_service_unavailable' as const;
|
|
2
|
+
export const ERROR_QUOTA_EXCEEDED = 'error_quota_exceeded' as const;
|
|
3
|
+
export const ERROR_MODERATION = 'error_moderation' as const;
|
|
4
|
+
export const ERROR_NETWORK = 'error_network' as const;
|
|
5
|
+
export const ERROR_UNCLEAR_PROMPT = 'error_unclear_prompt' as const;
|
|
6
|
+
|
|
7
|
+
export type SuggestionErrorCode =
|
|
8
|
+
| typeof ERROR_SERVICE_UNAVAILABLE
|
|
9
|
+
| typeof ERROR_QUOTA_EXCEEDED
|
|
10
|
+
| typeof ERROR_MODERATION
|
|
11
|
+
| typeof ERROR_NETWORK
|
|
12
|
+
| typeof ERROR_UNCLEAR_PROMPT;
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Prompt types
|
|
16
|
+
*/
|
|
1
17
|
export type PromptItemProps = {
|
|
2
18
|
role: 'system' | 'user' | 'assistant';
|
|
3
19
|
content: string;
|
|
4
20
|
};
|
|
21
|
+
|
|
22
|
+
export type PromptMessagesProp = Array< PromptItemProps >;
|
|
23
|
+
|
|
24
|
+
export type PromptProp = PromptMessagesProp | string;
|
package/src/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Fetches images from Jetpack AI
|
|
5
|
-
*
|
|
6
|
-
* It's up to the consumer to catch errors
|
|
7
|
-
*
|
|
8
|
-
* @param {*} prompt - The prompt to send to Jetpack AI
|
|
9
|
-
* @param {*} postId - The post ID where the completion is being requested
|
|
10
|
-
* @returns {Promise<Array<string>>} The images
|
|
11
|
-
*/
|
|
12
|
-
export function requestImages( prompt, postId ) {
|
|
13
|
-
return apiFetch( {
|
|
14
|
-
path: '/wpcom/v2/jetpack-ai/images/generations',
|
|
15
|
-
method: 'POST',
|
|
16
|
-
data: {
|
|
17
|
-
prompt,
|
|
18
|
-
post_id: postId,
|
|
19
|
-
},
|
|
20
|
-
} ).then( res => {
|
|
21
|
-
const images = res.data.map( image => {
|
|
22
|
-
return 'data:image/png;base64,' + image.b64_json;
|
|
23
|
-
} );
|
|
24
|
-
return images;
|
|
25
|
-
} );
|
|
26
|
-
}
|