@automattic/jetpack-ai-client 0.1.7 → 0.1.9

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 CHANGED
@@ -5,6 +5,23 @@ 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.9] - 2023-09-25
9
+ ### Added
10
+ - Export GuidelineMessage for use in other blocks. [#33180]
11
+
12
+ ## [0.1.8] - 2023-09-19
13
+ ### Added
14
+ - AI Client: Add support for the jetpack-ai role on the prompt messages. [#33052]
15
+ - AI Client: add `model` param to request helpers [#33083]
16
+ - AI Client: Emit specific error for large context error on SuggestionsEventSource [#33157]
17
+ - AI Client: Introduce blockListBlockWithAiDataProvider() function [#33025]
18
+
19
+ ### Changed
20
+ - AI Client: Move showGuideLine to AIControl component props [#33084]
21
+
22
+ ### Fixed
23
+ - AI Client: check media record ref of the useMediaRecording() hook before to remove the listeners [#33013]
24
+
8
25
  ## [0.1.7] - 2023-09-11
9
26
  ### Added
10
27
  - AI Client: add and expose reset() from useAiSuggestions() hook [#32886]
@@ -115,6 +132,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
115
132
  - Updated package dependencies. [#31659]
116
133
  - Updated package dependencies. [#31785]
117
134
 
135
+ [0.1.9]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.8...v0.1.9
136
+ [0.1.8]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.7...v0.1.8
118
137
  [0.1.7]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.6...v0.1.7
119
138
  [0.1.6]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.5...v0.1.6
120
139
  [0.1.5]: https://github.com/Automattic/jetpack-ai-client/compare/v0.1.4...v0.1.5
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.7",
4
+ "version": "0.1.9",
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": {
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@automattic/jetpack-base-styles": "^0.6.9",
36
- "@automattic/jetpack-connection": "^0.29.9",
37
- "@automattic/jetpack-shared-extension-utils": "^0.11.4",
36
+ "@automattic/jetpack-connection": "^0.30.0",
37
+ "@automattic/jetpack-shared-extension-utils": "^0.12.0",
38
38
  "@microsoft/fetch-event-source": "2.0.1",
39
39
  "@wordpress/api-fetch": "6.38.0",
40
40
  "@wordpress/block-editor": "12.9.0",
@@ -22,6 +22,7 @@ function askQuestion(
22
22
  - **postId** (**number**, optional): ID of the post where the question is asked.
23
23
  - **fromCache** (**boolean**, optional): If set to true, the answer will be fetched from the cache. Default value is false.
24
24
  - **feature** (**string**, optional): Allows to use a specific AI assistant feature.
25
+ - **model**( **AiModelTypeProp** optional): Allows to use a specific AI model.
25
26
 
26
27
  ## Returns
27
28
 
@@ -6,7 +6,7 @@ import SuggestionsEventSource from '../suggestions-event-source';
6
6
  /*
7
7
  * Types & constants
8
8
  */
9
- import type { PromptProp } from '../types';
9
+ import type { AiModelTypeProp, PromptProp } from '../types';
10
10
 
11
11
  export type AskQuestionOptionsArgProps = {
12
12
  /*
@@ -24,6 +24,11 @@ export type AskQuestionOptionsArgProps = {
24
24
  */
25
25
  feature?: 'ai-assistant-experimental' | string | undefined;
26
26
 
27
+ /*
28
+ * Allows to use a specific AI model.
29
+ */
30
+ model?: AiModelTypeProp;
31
+
27
32
  /*
28
33
  * Allows the use of function calling. Default value is undefined.
29
34
  */
@@ -57,12 +62,18 @@ const debug = debugFactory( 'jetpack-ai-client:ask-question' );
57
62
  */
58
63
  export default async function askQuestion(
59
64
  question: PromptProp,
60
- { postId = null, fromCache = false, feature, functions }: AskQuestionOptionsArgProps = {}
65
+ { postId = null, fromCache = false, feature, functions, model }: AskQuestionOptionsArgProps = {}
61
66
  ): Promise< SuggestionsEventSource > {
62
- debug( 'Asking question: %o. options: %o', question, { postId, fromCache, feature, functions } );
67
+ debug( 'Asking question: %o. options: %o', question, {
68
+ postId,
69
+ fromCache,
70
+ feature,
71
+ functions,
72
+ model,
73
+ } );
63
74
 
64
75
  return new SuggestionsEventSource( {
65
76
  question,
66
- options: { postId, feature, fromCache, functions },
77
+ options: { postId, feature, fromCache, functions, model },
67
78
  } );
68
79
  }
@@ -36,6 +36,7 @@ const noop = () => {};
36
36
  * @param {boolean} props.isTransparent - Whether the component has low opacity
37
37
  * @param {string} props.state - The request state
38
38
  * @param {boolean} props.showClearButton - Whether to show the clear button when the input has a value
39
+ * @param {boolean} props.showGuideLine - WHether to show the guideline message
39
40
  * @param {Function} props.onChange - Input change handler
40
41
  * @param {Function} props.onSend - Request send handler
41
42
  * @param {Function} props.onStop - Request stop handler
@@ -54,6 +55,7 @@ export function AIControl(
54
55
  isTransparent = false,
55
56
  state = 'init',
56
57
  showClearButton = true,
58
+ showGuideLine = false,
57
59
  onChange = noop,
58
60
  onSend = noop,
59
61
  onStop = noop,
@@ -68,6 +70,7 @@ export function AIControl(
68
70
  isTransparent?: boolean;
69
71
  state?: RequestingStateProp;
70
72
  showClearButton?: boolean;
73
+ showGuideLine?: boolean;
71
74
  onChange?: ( newValue: string ) => void;
72
75
  onSend?: ( currentValue: string ) => void;
73
76
  onStop?: () => void;
@@ -77,7 +80,6 @@ export function AIControl(
77
80
  ) {
78
81
  const promptUserInputRef = useRef( null );
79
82
  const loading = state === 'requesting' || state === 'suggesting';
80
- const showGuideLine = ! ( loading || disabled || value?.length || isTransparent );
81
83
 
82
84
  // Pass the ref to forwardRef.
83
85
  useImperativeHandle( ref, () => promptUserInputRef.current );
@@ -1,3 +1,4 @@
1
1
  export { default as AIControl } from './ai-control';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display';
4
+ export { GuidelineMessage } from './ai-control/message';
@@ -33,6 +33,7 @@ export default withAiAssistantData( MyComponent );
33
33
 
34
34
  * [AI Data Context](#ai-assistant-content)
35
35
  * [withAiDataProvider HOC](#with-ai-data-provider)
36
+ * [blockListBlockWithAiDataProvider](#block-list-block-with-ai-data-provider)
36
37
  * [useAiContext Hook](#use-ai-context)
37
38
 
38
39
  <h2 id="ai-assistant-content">Ai Data Context</h2>
@@ -113,3 +114,39 @@ Optional options object:
113
114
  These callbacks will be invoked with the detail of the corresponding event emitted by SuggestionsEventSource.
114
115
 
115
116
  When called, the hook returns the Ai Data Context.
117
+
118
+
119
+ <h2 id="block-list-block-with-ai-data-provider">blockListBlockWithAiDataProvider Function</h2>
120
+
121
+ The `blockListBlockWithAiDataProvider` function returns a Higher Order Component (HOC) that wraps and provides the AI Assistant Data context to a specified set of blocks. Primarily designed for use with the `editor.BlockListBlock` filter in the WordPress Block Editor, it conditionally applies the data provider only to block types specified in the function options.
122
+
123
+ ### Usage
124
+
125
+ To use the `blockListBlockWithAiDataProvider`, you'll need to specify which blocks should have access to the AI Assistant Data context:
126
+
127
+ ```jsx
128
+ import { blockListBlockWithAiDataProvider } from '@automattic/jetpack-ai-client';
129
+
130
+ // Example usage with WordPress filters
131
+ const enhancedBlockListBlock = blockListBlockWithAiDataProvider( {
132
+ blocks: [ 'core/paragraph', 'core/heading' ]
133
+ } );
134
+
135
+ wp.hooks.addFilter( 'editor.BlockListBlock', 'my-plugin/with-ai-data', enhancedBlockListBlock );
136
+ ```
137
+
138
+ ### Parameters
139
+
140
+ The function accepts an optional options object:
141
+
142
+ #### `options.blocks`
143
+ - Type: `string[]`
144
+ - Default: `['']`
145
+
146
+ An array of block names (e.g., `[ 'core/paragraph', 'core/image' ]`) to which the data provider should be applied. Only the blocks specified in this array can access the AI Assistant Data context.
147
+
148
+ ### Returned Wrapped Component
149
+
150
+ When a block type matches one of the specified names in `options.blocks`, the returned component will be wrapped with the AI Assistant Data context, providing all the available data and functionalities. The original component will be returned without any modifications for other block types.
151
+
152
+ _Before using this function, ensure the AI Assistant Data is available in the higher component hierarchy. The [Ai Data Context](#ai-assistant-content) should typically wrap the top-level component or application._
@@ -1,3 +1,6 @@
1
1
  export { AiDataContext, AiDataContextProvider } from './context';
2
- export { default as withAiDataProvider } from './with-ai-assistant-data';
2
+ export {
3
+ default as withAiDataProvider,
4
+ blockListBlockWithAiDataProvider,
5
+ } from './with-ai-assistant-data';
3
6
  export { default as useAiContext } from './use-ai-context';
@@ -59,3 +59,82 @@ const withAiDataProvider = createHigherOrderComponent( ( WrappedComponent: React
59
59
  }, 'withAiDataProvider' );
60
60
 
61
61
  export default withAiDataProvider;
62
+
63
+ type OptionsProps = {
64
+ /**
65
+ * Array of block names to apply the data provider to.
66
+ */
67
+ blocks: string[] | string;
68
+ };
69
+
70
+ /**
71
+ * Function that returns a High Order Component that provides the
72
+ * AI Assistant Data context to the wrapped component.
73
+ *
74
+ * Ideally though to use with the `editor.BlockListBlock` filter.
75
+ *
76
+ * @see https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#editor-blocklistblock
77
+ * @param {OptionsProps} options - Options
78
+ * @param {string[]} options.blocks - Array of block names to apply the data provider to.
79
+ * @returns {ReactNode} Wrapped component, populated with the AI Assistant Data context.
80
+ */
81
+ export const blockListBlockWithAiDataProvider = ( options: OptionsProps = { blocks: [ '' ] } ) => {
82
+ return createHigherOrderComponent( ( WrappedComponent: ReactNode ) => {
83
+ return props => {
84
+ const blockName = props?.block?.name;
85
+ if ( ! blockName ) {
86
+ return <WrappedComponent { ...props } />;
87
+ }
88
+
89
+ /*
90
+ * Extend only blocks that are specified in the blocks option.
91
+ * `blocks` option accepts a string or an array of strings.
92
+ */
93
+ const blockTypesToExtend = Array.isArray( options.blocks )
94
+ ? options.blocks
95
+ : [ options.blocks ];
96
+
97
+ if ( ! blockTypesToExtend.includes( blockName ) ) {
98
+ return <WrappedComponent { ...props } />;
99
+ }
100
+
101
+ // Connect with the AI Assistant communication layer.
102
+ // @todo: this is a copy of the code above, we should refactor this.
103
+ const {
104
+ suggestion,
105
+ error: requestingError,
106
+ requestingState,
107
+ request: requestSuggestion,
108
+ stopSuggestion,
109
+ eventSource,
110
+ } = useAiSuggestions();
111
+
112
+ // Build the context value to pass to the ai assistant data provider.
113
+ const dataContextValue = useMemo(
114
+ () => ( {
115
+ suggestion,
116
+ requestingError,
117
+ requestingState,
118
+ eventSource,
119
+
120
+ requestSuggestion,
121
+ stopSuggestion,
122
+ } ),
123
+ [
124
+ suggestion,
125
+ requestingError,
126
+ requestingState,
127
+ eventSource,
128
+ requestSuggestion,
129
+ stopSuggestion,
130
+ ]
131
+ );
132
+
133
+ return (
134
+ <AiDataContextProvider value={ dataContextValue }>
135
+ <WrappedComponent { ...props } />
136
+ </AiDataContextProvider>
137
+ );
138
+ };
139
+ }, 'blockListBlockWithAiDataProvider' );
140
+ };
@@ -189,6 +189,15 @@ export default function useMediaRecording( {
189
189
  throw err;
190
190
  } );
191
191
  return () => {
192
+ /*
193
+ * mediaRecordRef is not defined when
194
+ * the getUserMedia API is not supported,
195
+ * or when the user has not granted access
196
+ */
197
+ if ( ! mediaRecordRef?.current ) {
198
+ return;
199
+ }
200
+
192
201
  mediaRecordRef.current.removeEventListener( 'start', onStartListener );
193
202
  mediaRecordRef.current.removeEventListener( 'stop', onStopListener );
194
203
  mediaRecordRef.current.removeEventListener( 'pause', onPauseListener );
@@ -39,6 +39,7 @@ The constructor takes an object with the following properties:
39
39
  - `postId` (optional): The post ID.
40
40
  - `feature` (optional): A string that specifies the AI model to use (default or 'ai-assistant-experimental').
41
41
  - `fromCache` (optional): A boolean to indicate whether to fetch the response from cache.
42
+ - `model` (optional): Allows to use a specific AI model.
42
43
 
43
44
  ### Usage
44
45
 
@@ -12,6 +12,7 @@ import requestJwt from '../jwt';
12
12
  * Types & constants
13
13
  */
14
14
  import {
15
+ ERROR_CONTEXT_TOO_LARGE,
15
16
  ERROR_MODERATION,
16
17
  ERROR_NETWORK,
17
18
  ERROR_QUOTA_EXCEEDED,
@@ -19,7 +20,12 @@ import {
19
20
  ERROR_SERVICE_UNAVAILABLE,
20
21
  ERROR_UNCLEAR_PROMPT,
21
22
  } from '../types';
22
- import type { PromptMessagesProp, PromptProp, SuggestionErrorCode } from '../types';
23
+ import type {
24
+ AiModelTypeProp,
25
+ PromptMessagesProp,
26
+ PromptProp,
27
+ SuggestionErrorCode,
28
+ } from '../types';
23
29
 
24
30
  type SuggestionsEventSourceConstructorArgs = {
25
31
  url?: string;
@@ -30,6 +36,7 @@ type SuggestionsEventSourceConstructorArgs = {
30
36
  feature?: 'ai-assistant-experimental' | string | undefined;
31
37
  fromCache?: boolean;
32
38
  functions?: Array< object >;
39
+ model?: AiModelTypeProp;
33
40
  };
34
41
  };
35
42
 
@@ -47,16 +54,17 @@ const debug = debugFactory( 'jetpack-ai-client:suggestions-event-source' );
47
54
  * It also emits a 'suggestion' event with the full suggestion received so far
48
55
  *
49
56
  * @returns {EventSource} The event source
50
- * @fires suggestion - The full suggestion has been received so far
51
- * @fires message - A message has been received
52
- * @fires chunk - A chunk of data has been received
53
- * @fires done - The stream has been closed. No more data will be received
54
- * @fires error - An error has occurred
55
- * @fires error_network - The EventSource connection to the server returned some error
56
- * @fires error_service_unavailable - The server returned a 503 error
57
- * @fires error_quota_exceeded - The server returned a 429 error
58
- * @fires error_moderation - The server returned a 422 error
59
- * @fires error_unclear_prompt - The server returned a message starting with JETPACK_AI_ERROR
57
+ * @fires SuggestionsEventSource#suggestion - The full suggestion has been received so far
58
+ * @fires SuggestionsEventSource#message - A message has been received
59
+ * @fires SuggestionsEventSource#chunk - A chunk of data has been received
60
+ * @fires SuggestionsEventSource#done - The stream has been closed. No more data will be received
61
+ * @fires SuggestionsEventSource#error - An error has occurred
62
+ * @fires SuggestionsEventSource#error_network - The EventSource connection to the server returned some error
63
+ * @fires SuggestionsEventSource#error_context_too_large - The server returned a 413 error
64
+ * @fires SuggestionsEventSource#error_moderation - The server returned a 422 error
65
+ * @fires SuggestionsEventSource#error_quota_exceeded - The server returned a 429 error
66
+ * @fires SuggestionsEventSource#error_service_unavailable - The server returned a 503 error
67
+ * @fires SuggestionsEventSource#error_unclear_prompt - The server returned a message starting with JETPACK_AI_ERROR
60
68
  */
61
69
  export default class SuggestionsEventSource extends EventTarget {
62
70
  fullMessage: string;
@@ -103,6 +111,7 @@ export default class SuggestionsEventSource extends EventTarget {
103
111
  question?: PromptProp;
104
112
  feature?: string;
105
113
  functions?: Array< object >;
114
+ model?: AiModelTypeProp;
106
115
  } = {};
107
116
 
108
117
  // Populate body data with post id
@@ -142,6 +151,12 @@ export default class SuggestionsEventSource extends EventTarget {
142
151
  bodyData.functions = options.functions;
143
152
  }
144
153
 
154
+ // Model
155
+ if ( options?.model?.length ) {
156
+ debug( 'Model: %o', options.model );
157
+ bodyData.model = options.model;
158
+ }
159
+
145
160
  await fetchEventSource( url, {
146
161
  openWhenHidden: true,
147
162
  method: 'POST',
@@ -174,7 +189,7 @@ export default class SuggestionsEventSource extends EventTarget {
174
189
  if (
175
190
  response.status >= 400 &&
176
191
  response.status <= 500 &&
177
- ! [ 422, 429 ].includes( response.status )
192
+ ! [ 413, 422, 429 ].includes( response.status )
178
193
  ) {
179
194
  this.processConnectionError( response );
180
195
  }
@@ -189,12 +204,12 @@ export default class SuggestionsEventSource extends EventTarget {
189
204
  }
190
205
 
191
206
  /*
192
- * error code 429
193
- * you exceeded your current quota please check your plan and billing details
207
+ * error code 413
208
+ * request context too large
194
209
  */
195
- if ( response.status === 429 ) {
196
- errorCode = ERROR_QUOTA_EXCEEDED;
197
- this.dispatchEvent( new CustomEvent( ERROR_QUOTA_EXCEEDED ) );
210
+ if ( response.status === 413 ) {
211
+ errorCode = ERROR_CONTEXT_TOO_LARGE;
212
+ this.dispatchEvent( new CustomEvent( ERROR_CONTEXT_TOO_LARGE ) );
198
213
  }
199
214
 
200
215
  /*
@@ -206,6 +221,15 @@ export default class SuggestionsEventSource extends EventTarget {
206
221
  this.dispatchEvent( new CustomEvent( ERROR_MODERATION ) );
207
222
  }
208
223
 
224
+ /*
225
+ * error code 429
226
+ * you exceeded your current quota please check your plan and billing details
227
+ */
228
+ if ( response.status === 429 ) {
229
+ errorCode = ERROR_QUOTA_EXCEEDED;
230
+ this.dispatchEvent( new CustomEvent( ERROR_QUOTA_EXCEEDED ) );
231
+ }
232
+
209
233
  // Always dispatch a global ERROR_RESPONSE event
210
234
  this.dispatchEvent(
211
235
  new CustomEvent( ERROR_RESPONSE, {
package/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export const ERROR_SERVICE_UNAVAILABLE = 'error_service_unavailable' as const;
2
2
  export const ERROR_QUOTA_EXCEEDED = 'error_quota_exceeded' as const;
3
3
  export const ERROR_MODERATION = 'error_moderation' as const;
4
+ export const ERROR_CONTEXT_TOO_LARGE = 'error_context_too_large' as const;
4
5
  export const ERROR_NETWORK = 'error_network' as const;
5
6
  export const ERROR_UNCLEAR_PROMPT = 'error_unclear_prompt' as const;
6
7
  export const ERROR_RESPONSE = 'error_response' as const;
@@ -9,6 +10,7 @@ export type SuggestionErrorCode =
9
10
  | typeof ERROR_SERVICE_UNAVAILABLE
10
11
  | typeof ERROR_QUOTA_EXCEEDED
11
12
  | typeof ERROR_MODERATION
13
+ | typeof ERROR_CONTEXT_TOO_LARGE
12
14
  | typeof ERROR_NETWORK
13
15
  | typeof ERROR_UNCLEAR_PROMPT
14
16
  | typeof ERROR_RESPONSE;
@@ -17,7 +19,7 @@ export type SuggestionErrorCode =
17
19
  * Prompt types
18
20
  */
19
21
  export type PromptItemProps = {
20
- role: 'system' | 'user' | 'assistant';
22
+ role: 'system' | 'user' | 'assistant' | 'jetpack-ai';
21
23
  content?: string;
22
24
  context?: object;
23
25
  };
@@ -35,7 +37,6 @@ export type { RequestingErrorProps } from './hooks/use-ai-suggestions';
35
37
  /*
36
38
  * Requests types
37
39
  */
38
-
39
40
  const REQUESTING_STATE_INIT = 'init' as const;
40
41
  const REQUESTING_STATE_REQUESTING = 'requesting' as const;
41
42
  const REQUESTING_STATE_SUGGESTING = 'suggesting' as const;
@@ -51,3 +52,11 @@ export const REQUESTING_STATES = [
51
52
  ] as const;
52
53
 
53
54
  export type RequestingStateProp = ( typeof REQUESTING_STATES )[ number ];
55
+
56
+ /*
57
+ * Model types and constants
58
+ */
59
+ export const AI_MODEL_GPT_3_5_Turbo_16K = 'gpt-3.5-turbo-16k' as const;
60
+ export const AI_MODEL_GPT_4 = 'gpt-4' as const;
61
+
62
+ export type AiModelTypeProp = typeof AI_MODEL_GPT_3_5_Turbo_16K | typeof AI_MODEL_GPT_4;