@automattic/jetpack-ai-client 0.1.0 → 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 +39 -0
- package/README.md +18 -96
- package/index.ts +28 -1
- package/package.json +17 -4
- package/src/ask-question/Readme.md +1 -1
- package/src/ask-question/index.ts +21 -7
- package/src/components/ai-control/Readme.md +29 -0
- package/src/components/ai-control/index.tsx +165 -0
- package/src/components/ai-control/stories/index.stories.tsx +68 -0
- package/src/components/ai-control/style.scss +130 -0
- 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 +115 -0
- package/src/data-flow/context.tsx +76 -0
- package/src/data-flow/index.ts +3 -0
- package/src/data-flow/use-ai-context.ts +88 -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 +358 -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 +113 -15
- package/src/types.ts +48 -0
- package/src/index.js +0 -26
|
@@ -6,19 +6,34 @@ import debugFactory from 'debug';
|
|
|
6
6
|
/*
|
|
7
7
|
* Types & constants
|
|
8
8
|
*/
|
|
9
|
-
import
|
|
9
|
+
import { getErrorData } from '../hooks/use-ai-suggestions';
|
|
10
|
+
import {
|
|
11
|
+
ERROR_MODERATION,
|
|
12
|
+
ERROR_NETWORK,
|
|
13
|
+
ERROR_QUOTA_EXCEEDED,
|
|
14
|
+
ERROR_RESPONSE,
|
|
15
|
+
ERROR_SERVICE_UNAVAILABLE,
|
|
16
|
+
ERROR_UNCLEAR_PROMPT,
|
|
17
|
+
} from '../types';
|
|
18
|
+
import type { PromptMessagesProp, PromptProp, SuggestionErrorCode } from '../types';
|
|
10
19
|
|
|
11
20
|
type SuggestionsEventSourceConstructorArgs = {
|
|
12
21
|
url?: string;
|
|
13
|
-
question:
|
|
22
|
+
question: PromptProp;
|
|
14
23
|
token: string;
|
|
15
24
|
options?: {
|
|
16
25
|
postId?: number;
|
|
17
26
|
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
18
27
|
fromCache?: boolean;
|
|
28
|
+
functions?: Array< object >;
|
|
19
29
|
};
|
|
20
30
|
};
|
|
21
31
|
|
|
32
|
+
type FunctionCallProps = {
|
|
33
|
+
name?: string;
|
|
34
|
+
arguments?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
22
37
|
const debug = debugFactory( 'jetpack-ai-client:suggestions-event-source' );
|
|
23
38
|
|
|
24
39
|
/**
|
|
@@ -41,12 +56,17 @@ const debug = debugFactory( 'jetpack-ai-client:suggestions-event-source' );
|
|
|
41
56
|
*/
|
|
42
57
|
export default class SuggestionsEventSource extends EventTarget {
|
|
43
58
|
fullMessage: string;
|
|
59
|
+
fullFunctionCall: FunctionCallProps;
|
|
44
60
|
isPromptClear: boolean;
|
|
45
61
|
controller: AbortController;
|
|
46
62
|
|
|
47
63
|
constructor( data: SuggestionsEventSourceConstructorArgs ) {
|
|
48
64
|
super();
|
|
49
65
|
this.fullMessage = '';
|
|
66
|
+
this.fullFunctionCall = {
|
|
67
|
+
name: '',
|
|
68
|
+
arguments: '',
|
|
69
|
+
};
|
|
50
70
|
this.isPromptClear = false;
|
|
51
71
|
|
|
52
72
|
// The AbortController is used to close the fetchEventSource connection
|
|
@@ -61,7 +81,18 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
61
81
|
token,
|
|
62
82
|
options = {},
|
|
63
83
|
}: SuggestionsEventSourceConstructorArgs ) {
|
|
64
|
-
const bodyData
|
|
84
|
+
const bodyData: {
|
|
85
|
+
post_id?: number;
|
|
86
|
+
messages?: PromptMessagesProp;
|
|
87
|
+
question?: PromptProp;
|
|
88
|
+
feature?: string;
|
|
89
|
+
functions?: Array< object >;
|
|
90
|
+
} = {};
|
|
91
|
+
|
|
92
|
+
// Populate body data with post id
|
|
93
|
+
if ( options?.postId ) {
|
|
94
|
+
bodyData.post_id = options.postId;
|
|
95
|
+
}
|
|
65
96
|
|
|
66
97
|
// If the url is not provided, we use the default one
|
|
67
98
|
if ( ! url ) {
|
|
@@ -76,7 +107,7 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
76
107
|
debug( 'URL not provided, using default: %o', url );
|
|
77
108
|
}
|
|
78
109
|
|
|
79
|
-
// question can be a string or an array of
|
|
110
|
+
// question can be a string or an array of PromptMessagesProp
|
|
80
111
|
if ( Array.isArray( question ) ) {
|
|
81
112
|
bodyData.messages = question;
|
|
82
113
|
} else {
|
|
@@ -89,6 +120,12 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
89
120
|
bodyData.feature = options.feature;
|
|
90
121
|
}
|
|
91
122
|
|
|
123
|
+
// Propagate the functions option
|
|
124
|
+
if ( options?.functions?.length ) {
|
|
125
|
+
debug( 'Functions: %o', options.functions );
|
|
126
|
+
bodyData.functions = options.functions;
|
|
127
|
+
}
|
|
128
|
+
|
|
92
129
|
await fetchEventSource( url, {
|
|
93
130
|
openWhenHidden: true,
|
|
94
131
|
method: 'POST',
|
|
@@ -115,6 +152,9 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
115
152
|
if ( response.ok ) {
|
|
116
153
|
return;
|
|
117
154
|
}
|
|
155
|
+
|
|
156
|
+
let errorCode: SuggestionErrorCode;
|
|
157
|
+
|
|
118
158
|
if (
|
|
119
159
|
response.status >= 400 &&
|
|
120
160
|
response.status <= 500 &&
|
|
@@ -128,7 +168,8 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
128
168
|
* service unavailable
|
|
129
169
|
*/
|
|
130
170
|
if ( response.status === 503 ) {
|
|
131
|
-
|
|
171
|
+
errorCode = ERROR_SERVICE_UNAVAILABLE;
|
|
172
|
+
this.dispatchEvent( new CustomEvent( ERROR_SERVICE_UNAVAILABLE ) );
|
|
132
173
|
}
|
|
133
174
|
|
|
134
175
|
/*
|
|
@@ -136,7 +177,8 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
136
177
|
* you exceeded your current quota please check your plan and billing details
|
|
137
178
|
*/
|
|
138
179
|
if ( response.status === 429 ) {
|
|
139
|
-
|
|
180
|
+
errorCode = ERROR_QUOTA_EXCEEDED;
|
|
181
|
+
this.dispatchEvent( new CustomEvent( ERROR_QUOTA_EXCEEDED ) );
|
|
140
182
|
}
|
|
141
183
|
|
|
142
184
|
/*
|
|
@@ -144,9 +186,17 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
144
186
|
* request flagged by moderation system
|
|
145
187
|
*/
|
|
146
188
|
if ( response.status === 422 ) {
|
|
147
|
-
|
|
189
|
+
errorCode = ERROR_MODERATION;
|
|
190
|
+
this.dispatchEvent( new CustomEvent( ERROR_MODERATION ) );
|
|
148
191
|
}
|
|
149
192
|
|
|
193
|
+
// Always dispatch a global ERROR_RESPONSE event
|
|
194
|
+
this.dispatchEvent(
|
|
195
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
196
|
+
detail: getErrorData( errorCode ),
|
|
197
|
+
} )
|
|
198
|
+
);
|
|
199
|
+
|
|
150
200
|
throw new Error();
|
|
151
201
|
},
|
|
152
202
|
|
|
@@ -168,7 +218,12 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
168
218
|
const replacedMessage = this.fullMessage.replace( /__|(\*\*)/g, '' );
|
|
169
219
|
if ( replacedMessage.startsWith( 'JETPACK_AI_ERROR' ) ) {
|
|
170
220
|
// The unclear prompt marker was found, so we dispatch an error event
|
|
171
|
-
this.dispatchEvent( new CustomEvent(
|
|
221
|
+
this.dispatchEvent( new CustomEvent( ERROR_UNCLEAR_PROMPT ) );
|
|
222
|
+
this.dispatchEvent(
|
|
223
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
224
|
+
detail: getErrorData( ERROR_UNCLEAR_PROMPT ),
|
|
225
|
+
} )
|
|
226
|
+
);
|
|
172
227
|
} else if ( 'JETPACK_AI_ERROR'.startsWith( replacedMessage ) ) {
|
|
173
228
|
// Partial unclear prompt marker was found, so we wait for more data and print a debug message without dispatching an event
|
|
174
229
|
debug( this.fullMessage );
|
|
@@ -184,14 +239,31 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
184
239
|
|
|
185
240
|
processEvent( e: EventSourceMessage ) {
|
|
186
241
|
if ( e.data === '[DONE]' ) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
242
|
+
if ( this.fullMessage.length ) {
|
|
243
|
+
// Dispatch an event with the full content
|
|
244
|
+
this.dispatchEvent( new CustomEvent( 'done', { detail: this.fullMessage } ) );
|
|
245
|
+
debug( 'Done: %o', this.fullMessage );
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if ( this.fullFunctionCall.name.length ) {
|
|
250
|
+
this.dispatchEvent( new CustomEvent( 'function_done', { detail: this.fullFunctionCall } ) );
|
|
251
|
+
debug( 'Done: %o', this.fullFunctionCall );
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let data;
|
|
257
|
+
try {
|
|
258
|
+
data = JSON.parse( e.data );
|
|
259
|
+
} catch ( err ) {
|
|
260
|
+
debug( 'Error parsing JSON', e, err );
|
|
190
261
|
return;
|
|
191
262
|
}
|
|
263
|
+
const { delta } = data?.choices?.[ 0 ] ?? { delta: { content: null, function_call: null } };
|
|
264
|
+
const chunk = delta.content;
|
|
265
|
+
const functionCallChunk = delta.function_call;
|
|
192
266
|
|
|
193
|
-
const data = JSON.parse( e.data );
|
|
194
|
-
const chunk = data.choices[ 0 ].delta.content;
|
|
195
267
|
if ( chunk ) {
|
|
196
268
|
this.fullMessage += chunk;
|
|
197
269
|
this.checkForUnclearPrompt();
|
|
@@ -200,20 +272,46 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
200
272
|
// Dispatch an event with the chunk
|
|
201
273
|
this.dispatchEvent( new CustomEvent( 'chunk', { detail: chunk } ) );
|
|
202
274
|
// Dispatch an event with the full message
|
|
275
|
+
debug( 'suggestion: %o', this.fullMessage );
|
|
203
276
|
this.dispatchEvent( new CustomEvent( 'suggestion', { detail: this.fullMessage } ) );
|
|
204
277
|
}
|
|
205
278
|
}
|
|
279
|
+
|
|
280
|
+
if ( functionCallChunk ) {
|
|
281
|
+
if ( functionCallChunk.name != null ) {
|
|
282
|
+
this.fullFunctionCall.name += functionCallChunk.name;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if ( functionCallChunk.arguments != null ) {
|
|
286
|
+
this.fullFunctionCall.arguments += functionCallChunk.arguments;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Dispatch an event with the function call
|
|
290
|
+
this.dispatchEvent(
|
|
291
|
+
new CustomEvent( 'functionCallChunk', { detail: this.fullFunctionCall } )
|
|
292
|
+
);
|
|
293
|
+
}
|
|
206
294
|
}
|
|
207
295
|
|
|
208
296
|
processConnectionError( response ) {
|
|
209
297
|
debug( 'Connection error: %o', response );
|
|
210
|
-
this.dispatchEvent( new CustomEvent(
|
|
298
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
|
|
299
|
+
this.dispatchEvent(
|
|
300
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
301
|
+
detail: getErrorData( ERROR_NETWORK ),
|
|
302
|
+
} )
|
|
303
|
+
);
|
|
211
304
|
}
|
|
212
305
|
|
|
213
306
|
processErrorEvent( e ) {
|
|
214
307
|
debug( 'onerror: %o', e );
|
|
215
308
|
|
|
216
309
|
// Dispatch a generic network error event
|
|
217
|
-
this.dispatchEvent( new CustomEvent(
|
|
310
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
|
|
311
|
+
this.dispatchEvent(
|
|
312
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
313
|
+
detail: getErrorData( ERROR_NETWORK ),
|
|
314
|
+
} )
|
|
315
|
+
);
|
|
218
316
|
}
|
|
219
317
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
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
|
+
export const ERROR_RESPONSE = 'error_response' as const;
|
|
7
|
+
|
|
8
|
+
export type SuggestionErrorCode =
|
|
9
|
+
| typeof ERROR_SERVICE_UNAVAILABLE
|
|
10
|
+
| typeof ERROR_QUOTA_EXCEEDED
|
|
11
|
+
| typeof ERROR_MODERATION
|
|
12
|
+
| typeof ERROR_NETWORK
|
|
13
|
+
| typeof ERROR_UNCLEAR_PROMPT
|
|
14
|
+
| typeof ERROR_RESPONSE;
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Prompt types
|
|
18
|
+
*/
|
|
1
19
|
export type PromptItemProps = {
|
|
2
20
|
role: 'system' | 'user' | 'assistant';
|
|
3
21
|
content: string;
|
|
4
22
|
};
|
|
23
|
+
|
|
24
|
+
export type PromptMessagesProp = Array< PromptItemProps >;
|
|
25
|
+
|
|
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 ];
|
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
|
-
}
|