@automattic/jetpack-ai-client 0.11.0 → 0.12.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 CHANGED
@@ -5,6 +5,20 @@ 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.12.1] - 2024-04-15
9
+ ### Added
10
+ - AI Client: Add callbacks, initial requesting state and change error handling. [#36869]
11
+
12
+ ## [0.12.0] - 2024-04-08
13
+ ### Added
14
+ - Add error rejection in image generation. [#36709]
15
+
16
+ ### Changed
17
+ - Updated package dependencies. [#36756] [#36760] [#36761]
18
+
19
+ ### Fixed
20
+ - AI Featured Image: handle posts longer than the limit of Dall-e generation prompt. [#36703]
21
+
8
22
  ## [0.11.0] - 2024-04-01
9
23
  ### Added
10
24
  - AI Client: include prompt to generate featured image based on post content. [#36591]
@@ -272,6 +286,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
272
286
  - Updated package dependencies. [#31659]
273
287
  - Updated package dependencies. [#31785]
274
288
 
289
+ [0.12.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.0...v0.12.1
290
+ [0.12.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.11.0...v0.12.0
275
291
  [0.11.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.10.1...v0.11.0
276
292
  [0.10.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.10.0...v0.10.1
277
293
  [0.10.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.9.0...v0.10.0
@@ -17,9 +17,12 @@ type useAiSuggestionsOptions = {
17
17
  * AskQuestion options.
18
18
  */
19
19
  askQuestionOptions?: AskQuestionOptionsArgProps;
20
+ initialRequestingState?: RequestingStateProp;
20
21
  onSuggestion?: (suggestion: string) => void;
21
22
  onDone?: (content: string) => void;
23
+ onStop?: () => void;
22
24
  onError?: (error: RequestingErrorProps) => void;
25
+ onAllErrors?: (error: RequestingErrorProps) => void;
23
26
  };
24
27
  type useAiSuggestionsProps = {
25
28
  suggestion: string;
@@ -29,6 +32,7 @@ type useAiSuggestionsProps = {
29
32
  request: (prompt: PromptProp, options?: AskQuestionOptionsArgProps) => Promise<void>;
30
33
  reset: () => void;
31
34
  stopSuggestion: () => void;
35
+ handleErrorQuotaExceededError: () => void;
32
36
  };
33
37
  /**
34
38
  * Get the error data for a given error code.
@@ -44,5 +48,5 @@ export declare function getErrorData(errorCode: SuggestionErrorCode): Requesting
44
48
  * @param {useAiSuggestionsOptions} options - The options for the hook.
45
49
  * @returns {useAiSuggestionsProps} The props for the hook.
46
50
  */
47
- export default function useAiSuggestions({ prompt, autoRequest, askQuestionOptions, onSuggestion, onDone, onError, }?: useAiSuggestionsOptions): useAiSuggestionsProps;
51
+ export default function useAiSuggestions({ prompt, autoRequest, askQuestionOptions, initialRequestingState, onSuggestion, onDone, onStop, onError, onAllErrors, }?: useAiSuggestionsOptions): useAiSuggestionsProps;
48
52
  export {};
@@ -3,13 +3,11 @@
3
3
  */
4
4
  import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
5
5
  import { __ } from '@wordpress/i18n';
6
- import debugFactory from 'debug';
7
6
  /**
8
7
  * Internal dependencies
9
8
  */
10
9
  import askQuestion from '../../ask-question/index.js';
11
- import { ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, } from '../../types.js';
12
- const debug = debugFactory('jetpack-ai-client:use-suggestion');
10
+ import { ERROR_CONTEXT_TOO_LARGE, ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, ERROR_RESPONSE, } from '../../types.js';
13
11
  /**
14
12
  * Get the error data for a given error code.
15
13
  *
@@ -42,6 +40,12 @@ export function getErrorData(errorCode) {
42
40
  message: __('This request has been flagged by our moderation system. Please try to rephrase it and try again.', 'jetpack-ai-client'),
43
41
  severity: 'info',
44
42
  };
43
+ case ERROR_CONTEXT_TOO_LARGE:
44
+ return {
45
+ code: ERROR_CONTEXT_TOO_LARGE,
46
+ message: __('The content is too large to be processed all at once. Please try to shorten it or divide it into smaller parts.', 'jetpack-ai-client'),
47
+ severity: 'info',
48
+ };
45
49
  case ERROR_NETWORK:
46
50
  default:
47
51
  return {
@@ -58,8 +62,8 @@ export function getErrorData(errorCode) {
58
62
  * @param {useAiSuggestionsOptions} options - The options for the hook.
59
63
  * @returns {useAiSuggestionsProps} The props for the hook.
60
64
  */
61
- export default function useAiSuggestions({ prompt, autoRequest = false, askQuestionOptions = {}, onSuggestion, onDone, onError, } = {}) {
62
- const [requestingState, setRequestingState] = useState('init');
65
+ export default function useAiSuggestions({ prompt, autoRequest = false, askQuestionOptions = {}, initialRequestingState = 'init', onSuggestion, onDone, onStop, onError, onAllErrors, } = {}) {
66
+ const [requestingState, setRequestingState] = useState(initialRequestingState);
63
67
  const [suggestion, setSuggestion] = useState('');
64
68
  const [error, setError] = useState();
65
69
  // Store the event source in a ref, so we can handle it if needed.
@@ -81,9 +85,13 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
81
85
  * @returns {void}
82
86
  */
83
87
  const handleDone = useCallback((event) => {
88
+ closeEventSource();
84
89
  onDone?.(event?.detail);
85
90
  setRequestingState('done');
86
91
  }, [onDone]);
92
+ const handleAnyError = useCallback((event) => {
93
+ onAllErrors?.(event?.detail);
94
+ }, [onAllErrors]);
87
95
  const handleError = useCallback((errorCode) => {
88
96
  eventSourceRef?.current?.close();
89
97
  setRequestingState('error');
@@ -103,35 +111,26 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
103
111
  * @returns {Promise<void>} The promise.
104
112
  */
105
113
  const request = useCallback(async (promptArg, options = { ...askQuestionOptions }) => {
106
- if (Array.isArray(promptArg) && promptArg?.length) {
107
- promptArg.forEach(({ role, content: promptContent }, i) => debug('(%s/%s) %o\n%s', i + 1, promptArg.length, `[${role}]`, promptContent));
108
- }
109
- else {
110
- debug('%o', promptArg);
111
- }
114
+ // Clear any error.
115
+ setError(undefined);
112
116
  // Set the request status.
113
117
  setRequestingState('requesting');
114
- try {
115
- eventSourceRef.current = await askQuestion(promptArg, options);
116
- if (!eventSourceRef?.current) {
117
- return;
118
- }
119
- // Alias
120
- const eventSource = eventSourceRef.current;
121
- // Set the request status.
122
- setRequestingState('suggesting');
123
- eventSource.addEventListener('suggestion', handleSuggestion);
124
- eventSource.addEventListener(ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError);
125
- eventSource.addEventListener(ERROR_UNCLEAR_PROMPT, handleUnclearPromptError);
126
- eventSource.addEventListener(ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError);
127
- eventSource.addEventListener(ERROR_MODERATION, handleModerationError);
128
- eventSource.addEventListener(ERROR_NETWORK, handleNetworkError);
129
- eventSource.addEventListener('done', handleDone);
130
- }
131
- catch (e) {
132
- // eslint-disable-next-line no-console
133
- console.error(e);
118
+ eventSourceRef.current = await askQuestion(promptArg, options);
119
+ if (!eventSourceRef?.current) {
120
+ return;
134
121
  }
122
+ // Alias
123
+ const eventSource = eventSourceRef.current;
124
+ // Set the request status.
125
+ setRequestingState('suggesting');
126
+ eventSource.addEventListener('suggestion', handleSuggestion);
127
+ eventSource.addEventListener(ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError);
128
+ eventSource.addEventListener(ERROR_UNCLEAR_PROMPT, handleUnclearPromptError);
129
+ eventSource.addEventListener(ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError);
130
+ eventSource.addEventListener(ERROR_MODERATION, handleModerationError);
131
+ eventSource.addEventListener(ERROR_NETWORK, handleNetworkError);
132
+ eventSource.addEventListener(ERROR_RESPONSE, handleAnyError);
133
+ eventSource.addEventListener('done', handleDone);
135
134
  }, [
136
135
  handleDone,
137
136
  handleErrorQuotaExceededError,
@@ -152,11 +151,11 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
152
151
  setError(undefined);
153
152
  }, []);
154
153
  /**
155
- * Stop suggestion handler.
154
+ * Close the event source connection.
156
155
  *
157
156
  * @returns {void}
158
157
  */
159
- const stopSuggestion = useCallback(() => {
158
+ const closeEventSource = useCallback(() => {
160
159
  if (!eventSourceRef?.current) {
161
160
  return;
162
161
  }
@@ -172,8 +171,6 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
172
171
  eventSource.removeEventListener(ERROR_MODERATION, handleModerationError);
173
172
  eventSource.removeEventListener(ERROR_NETWORK, handleNetworkError);
174
173
  eventSource.removeEventListener('done', handleDone);
175
- // Set requesting state to done since the suggestion stopped.
176
- setRequestingState('done');
177
174
  }, [
178
175
  eventSourceRef,
179
176
  handleSuggestion,
@@ -184,6 +181,16 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
184
181
  handleNetworkError,
185
182
  handleDone,
186
183
  ]);
184
+ /**
185
+ * Stop suggestion handler.
186
+ *
187
+ * @returns {void}
188
+ */
189
+ const stopSuggestion = useCallback(() => {
190
+ closeEventSource();
191
+ onStop?.();
192
+ setRequestingState('done');
193
+ }, [onStop]);
187
194
  // Request suggestions automatically when ready.
188
195
  useEffect(() => {
189
196
  // Check if there is a prompt to request.
@@ -208,6 +215,8 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
208
215
  request,
209
216
  stopSuggestion,
210
217
  reset,
218
+ // Error handlers
219
+ handleErrorQuotaExceededError,
211
220
  // SuggestionsEventSource
212
221
  eventSource: eventSourceRef.current,
213
222
  };
@@ -32,7 +32,7 @@ Do not add text to the image.
32
32
 
33
33
  This is the post content:
34
34
 
35
- ` + postContent;
35
+ ` + (postContent.length > 3000 ? postContent.substring(0, 3000) + ` [...]` : postContent); // truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
36
36
  const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
37
37
  const body = {
38
38
  prompt: imageGenerationPrompt,
@@ -49,10 +49,15 @@ This is the post content:
49
49
  headers,
50
50
  body: JSON.stringify(body),
51
51
  }).then(response => response.json());
52
+ if (data?.data?.status && data?.data?.status > 200) {
53
+ debug('Error generating image: %o', data);
54
+ return Promise.reject(data);
55
+ }
52
56
  return data;
53
57
  }
54
58
  catch (error) {
55
- return;
59
+ debug('Error generating image: %o', error);
60
+ return Promise.reject(error);
56
61
  }
57
62
  };
58
63
  return {
@@ -39,7 +39,6 @@ export default function useTranscriptionPostProcessing({ feature, onReady, onErr
39
39
  onError?.(errorData.message);
40
40
  }, [setPostProcessingError, onError]);
41
41
  const { request, stopSuggestion } = useAiSuggestions({
42
- autoRequest: false,
43
42
  onSuggestion: handleOnSuggestion,
44
43
  onDone: handleOnDone,
45
44
  onError: handleOnError,
package/build/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { default as requestJwt } from './jwt/index.js';
2
2
  export { default as SuggestionsEventSource } from './suggestions-event-source/index.js';
3
3
  export { default as askQuestion } from './ask-question/index.js';
4
4
  export { default as transcribeAudio } from './audio-transcription/index.js';
5
- export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
5
+ export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
6
6
  export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
7
7
  export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
8
8
  export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
package/build/index.js CHANGED
@@ -8,7 +8,7 @@ export { default as transcribeAudio } from './audio-transcription/index.js';
8
8
  /*
9
9
  * Hooks
10
10
  */
11
- export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
11
+ export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
12
12
  export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
13
13
  export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
14
14
  export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
@@ -49,7 +49,6 @@ export default class SuggestionsEventSource extends EventTarget {
49
49
  checkForUnclearPrompt(): void;
50
50
  close(): void;
51
51
  processEvent(e: EventSourceMessage): void;
52
- processConnectionError(response: any): void;
53
52
  processErrorEvent(e: any): void;
54
53
  }
55
54
  export {};
@@ -128,7 +128,9 @@ export default class SuggestionsEventSource extends EventTarget {
128
128
  if (response.status >= 400 &&
129
129
  response.status <= 500 &&
130
130
  ![413, 422, 429].includes(response.status)) {
131
- this.processConnectionError(response);
131
+ debug('Connection error: %o', response);
132
+ errorCode = ERROR_NETWORK;
133
+ this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: response }));
132
134
  }
133
135
  /*
134
136
  * error code 503
@@ -264,13 +266,6 @@ export default class SuggestionsEventSource extends EventTarget {
264
266
  this.dispatchEvent(new CustomEvent('functionCallChunk', { detail: this.fullFunctionCall }));
265
267
  }
266
268
  }
267
- processConnectionError(response) {
268
- debug('Connection error: %o', response);
269
- this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: response }));
270
- this.dispatchEvent(new CustomEvent(ERROR_RESPONSE, {
271
- detail: getErrorData(ERROR_NETWORK),
272
- }));
273
- }
274
269
  processErrorEvent(e) {
275
270
  debug('onerror: %o', e);
276
271
  // Dispatch a generic network error event
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.11.0",
4
+ "version": "0.12.1",
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": {
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "type": "module",
25
25
  "devDependencies": {
26
- "@storybook/addon-actions": "8.0.4",
27
- "@storybook/blocks": "8.0.4",
28
- "@storybook/react": "8.0.4",
26
+ "@storybook/addon-actions": "8.0.6",
27
+ "@storybook/blocks": "8.0.6",
28
+ "@storybook/react": "8.0.6",
29
29
  "jest": "^29.6.2",
30
30
  "jest-environment-jsdom": "29.7.0",
31
31
  "typescript": "5.0.4"
@@ -39,19 +39,19 @@
39
39
  "main": "./build/index.js",
40
40
  "types": "./build/index.d.ts",
41
41
  "dependencies": {
42
- "@automattic/jetpack-base-styles": "^0.6.20",
43
- "@automattic/jetpack-connection": "^0.33.5",
44
- "@automattic/jetpack-shared-extension-utils": "^0.14.8",
42
+ "@automattic/jetpack-base-styles": "^0.6.22",
43
+ "@automattic/jetpack-connection": "^0.33.8",
44
+ "@automattic/jetpack-shared-extension-utils": "^0.14.10",
45
45
  "@microsoft/fetch-event-source": "2.0.1",
46
- "@types/react": "18.2.61",
47
- "@wordpress/api-fetch": "6.51.0",
48
- "@wordpress/block-editor": "12.22.0",
49
- "@wordpress/components": "27.2.0",
50
- "@wordpress/compose": "6.31.0",
51
- "@wordpress/data": "9.24.0",
52
- "@wordpress/element": "5.31.0",
53
- "@wordpress/i18n": "4.54.0",
54
- "@wordpress/icons": "9.45.0",
46
+ "@types/react": "18.2.74",
47
+ "@wordpress/api-fetch": "6.52.0",
48
+ "@wordpress/block-editor": "12.23.0",
49
+ "@wordpress/components": "27.3.0",
50
+ "@wordpress/compose": "6.32.0",
51
+ "@wordpress/data": "9.25.0",
52
+ "@wordpress/element": "5.32.0",
53
+ "@wordpress/i18n": "4.55.0",
54
+ "@wordpress/icons": "9.46.0",
55
55
  "classnames": "2.3.2",
56
56
  "debug": "4.3.4",
57
57
  "react": "18.2.0",
@@ -3,17 +3,18 @@
3
3
  */
4
4
  import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
5
5
  import { __ } from '@wordpress/i18n';
6
- import debugFactory from 'debug';
7
6
  /**
8
7
  * Internal dependencies
9
8
  */
10
9
  import askQuestion from '../../ask-question/index.js';
11
10
  import {
11
+ ERROR_CONTEXT_TOO_LARGE,
12
12
  ERROR_MODERATION,
13
13
  ERROR_NETWORK,
14
14
  ERROR_QUOTA_EXCEEDED,
15
15
  ERROR_SERVICE_UNAVAILABLE,
16
16
  ERROR_UNCLEAR_PROMPT,
17
+ ERROR_RESPONSE,
17
18
  } from '../../types.js';
18
19
  /**
19
20
  * Types & constants
@@ -56,6 +57,11 @@ type useAiSuggestionsOptions = {
56
57
  */
57
58
  askQuestionOptions?: AskQuestionOptionsArgProps;
58
59
 
60
+ /*
61
+ * Initial requesting state.
62
+ */
63
+ initialRequestingState?: RequestingStateProp;
64
+
59
65
  /*
60
66
  * onSuggestion callback.
61
67
  */
@@ -66,10 +72,20 @@ type useAiSuggestionsOptions = {
66
72
  */
67
73
  onDone?: ( content: string ) => void;
68
74
 
75
+ /*
76
+ * onStop callback.
77
+ */
78
+ onStop?: () => void;
79
+
69
80
  /*
70
81
  * onError callback.
71
82
  */
72
83
  onError?: ( error: RequestingErrorProps ) => void;
84
+
85
+ /*
86
+ * Error callback common for all errors.
87
+ */
88
+ onAllErrors?: ( error: RequestingErrorProps ) => void;
73
89
  };
74
90
 
75
91
  type useAiSuggestionsProps = {
@@ -107,9 +123,12 @@ type useAiSuggestionsProps = {
107
123
  * The handler to stop a suggestion.
108
124
  */
109
125
  stopSuggestion: () => void;
110
- };
111
126
 
112
- const debug = debugFactory( 'jetpack-ai-client:use-suggestion' );
127
+ /*
128
+ * The handler to handle the quota exceeded error.
129
+ */
130
+ handleErrorQuotaExceededError: () => void;
131
+ };
113
132
 
114
133
  /**
115
134
  * Get the error data for a given error code.
@@ -149,6 +168,15 @@ export function getErrorData( errorCode: SuggestionErrorCode ): RequestingErrorP
149
168
  ),
150
169
  severity: 'info',
151
170
  };
171
+ case ERROR_CONTEXT_TOO_LARGE:
172
+ return {
173
+ code: ERROR_CONTEXT_TOO_LARGE,
174
+ message: __(
175
+ 'The content is too large to be processed all at once. Please try to shorten it or divide it into smaller parts.',
176
+ 'jetpack-ai-client'
177
+ ),
178
+ severity: 'info',
179
+ };
152
180
  case ERROR_NETWORK:
153
181
  default:
154
182
  return {
@@ -173,11 +201,15 @@ export default function useAiSuggestions( {
173
201
  prompt,
174
202
  autoRequest = false,
175
203
  askQuestionOptions = {},
204
+ initialRequestingState = 'init',
176
205
  onSuggestion,
177
206
  onDone,
207
+ onStop,
178
208
  onError,
209
+ onAllErrors,
179
210
  }: useAiSuggestionsOptions = {} ): useAiSuggestionsProps {
180
- const [ requestingState, setRequestingState ] = useState< RequestingStateProp >( 'init' );
211
+ const [ requestingState, setRequestingState ] =
212
+ useState< RequestingStateProp >( initialRequestingState );
181
213
  const [ suggestion, setSuggestion ] = useState< string >( '' );
182
214
  const [ error, setError ] = useState< RequestingErrorProps >();
183
215
 
@@ -206,12 +238,20 @@ export default function useAiSuggestions( {
206
238
  */
207
239
  const handleDone = useCallback(
208
240
  ( event: CustomEvent ) => {
241
+ closeEventSource();
209
242
  onDone?.( event?.detail );
210
243
  setRequestingState( 'done' );
211
244
  },
212
245
  [ onDone ]
213
246
  );
214
247
 
248
+ const handleAnyError = useCallback(
249
+ ( event: CustomEvent ) => {
250
+ onAllErrors?.( event?.detail );
251
+ },
252
+ [ onAllErrors ]
253
+ );
254
+
215
255
  const handleError = useCallback(
216
256
  ( errorCode: SuggestionErrorCode ) => {
217
257
  eventSourceRef?.current?.close();
@@ -250,43 +290,34 @@ export default function useAiSuggestions( {
250
290
  promptArg: PromptProp,
251
291
  options: AskQuestionOptionsArgProps = { ...askQuestionOptions }
252
292
  ) => {
253
- if ( Array.isArray( promptArg ) && promptArg?.length ) {
254
- promptArg.forEach( ( { role, content: promptContent }, i ) =>
255
- debug( '(%s/%s) %o\n%s', i + 1, promptArg.length, `[${ role }]`, promptContent )
256
- );
257
- } else {
258
- debug( '%o', promptArg );
259
- }
293
+ // Clear any error.
294
+ setError( undefined );
260
295
 
261
296
  // Set the request status.
262
297
  setRequestingState( 'requesting' );
263
298
 
264
- try {
265
- eventSourceRef.current = await askQuestion( promptArg, options );
299
+ eventSourceRef.current = await askQuestion( promptArg, options );
266
300
 
267
- if ( ! eventSourceRef?.current ) {
268
- return;
269
- }
301
+ if ( ! eventSourceRef?.current ) {
302
+ return;
303
+ }
270
304
 
271
- // Alias
272
- const eventSource = eventSourceRef.current;
305
+ // Alias
306
+ const eventSource = eventSourceRef.current;
273
307
 
274
- // Set the request status.
275
- setRequestingState( 'suggesting' );
308
+ // Set the request status.
309
+ setRequestingState( 'suggesting' );
276
310
 
277
- eventSource.addEventListener( 'suggestion', handleSuggestion );
311
+ eventSource.addEventListener( 'suggestion', handleSuggestion );
278
312
 
279
- eventSource.addEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
280
- eventSource.addEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
281
- eventSource.addEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
282
- eventSource.addEventListener( ERROR_MODERATION, handleModerationError );
283
- eventSource.addEventListener( ERROR_NETWORK, handleNetworkError );
313
+ eventSource.addEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
314
+ eventSource.addEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
315
+ eventSource.addEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
316
+ eventSource.addEventListener( ERROR_MODERATION, handleModerationError );
317
+ eventSource.addEventListener( ERROR_NETWORK, handleNetworkError );
318
+ eventSource.addEventListener( ERROR_RESPONSE, handleAnyError );
284
319
 
285
- eventSource.addEventListener( 'done', handleDone );
286
- } catch ( e ) {
287
- // eslint-disable-next-line no-console
288
- console.error( e );
289
- }
320
+ eventSource.addEventListener( 'done', handleDone );
290
321
  },
291
322
  [
292
323
  handleDone,
@@ -311,11 +342,11 @@ export default function useAiSuggestions( {
311
342
  }, [] );
312
343
 
313
344
  /**
314
- * Stop suggestion handler.
345
+ * Close the event source connection.
315
346
  *
316
347
  * @returns {void}
317
348
  */
318
- const stopSuggestion = useCallback( () => {
349
+ const closeEventSource = useCallback( () => {
319
350
  if ( ! eventSourceRef?.current ) {
320
351
  return;
321
352
  }
@@ -336,9 +367,6 @@ export default function useAiSuggestions( {
336
367
  eventSource.removeEventListener( ERROR_NETWORK, handleNetworkError );
337
368
 
338
369
  eventSource.removeEventListener( 'done', handleDone );
339
-
340
- // Set requesting state to done since the suggestion stopped.
341
- setRequestingState( 'done' );
342
370
  }, [
343
371
  eventSourceRef,
344
372
  handleSuggestion,
@@ -350,6 +378,17 @@ export default function useAiSuggestions( {
350
378
  handleDone,
351
379
  ] );
352
380
 
381
+ /**
382
+ * Stop suggestion handler.
383
+ *
384
+ * @returns {void}
385
+ */
386
+ const stopSuggestion = useCallback( () => {
387
+ closeEventSource();
388
+ onStop?.();
389
+ setRequestingState( 'done' );
390
+ }, [ onStop ] );
391
+
353
392
  // Request suggestions automatically when ready.
354
393
  useEffect( () => {
355
394
  // Check if there is a prompt to request.
@@ -379,6 +418,9 @@ export default function useAiSuggestions( {
379
418
  stopSuggestion,
380
419
  reset,
381
420
 
421
+ // Error handlers
422
+ handleErrorQuotaExceededError,
423
+
382
424
  // SuggestionsEventSource
383
425
  eventSource: eventSourceRef.current,
384
426
  };
@@ -45,7 +45,7 @@ Do not add text to the image.
45
45
 
46
46
  This is the post content:
47
47
 
48
- ` + postContent;
48
+ ` + ( postContent.length > 3000 ? postContent.substring( 0, 3000 ) + ` [...]` : postContent ); // truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
49
49
 
50
50
  const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
51
51
 
@@ -67,9 +67,15 @@ This is the post content:
67
67
  body: JSON.stringify( body ),
68
68
  } ).then( response => response.json() );
69
69
 
70
+ if ( data?.data?.status && data?.data?.status > 200 ) {
71
+ debug( 'Error generating image: %o', data );
72
+ return Promise.reject( data );
73
+ }
74
+
70
75
  return data as { data: { [ key: string ]: string }[] };
71
76
  } catch ( error ) {
72
- return;
77
+ debug( 'Error generating image: %o', error );
78
+ return Promise.reject( error );
73
79
  }
74
80
  };
75
81
 
@@ -83,7 +83,6 @@ export default function useTranscriptionPostProcessing( {
83
83
  );
84
84
 
85
85
  const { request, stopSuggestion } = useAiSuggestions( {
86
- autoRequest: false,
87
86
  onSuggestion: handleOnSuggestion,
88
87
  onDone: handleOnDone,
89
88
  onError: handleOnError,
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export { default as transcribeAudio } from './audio-transcription/index.js';
9
9
  /*
10
10
  * Hooks
11
11
  */
12
- export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
12
+ export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
13
13
  export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
14
14
  export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
15
15
  export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
@@ -197,7 +197,9 @@ export default class SuggestionsEventSource extends EventTarget {
197
197
  response.status <= 500 &&
198
198
  ! [ 413, 422, 429 ].includes( response.status )
199
199
  ) {
200
- this.processConnectionError( response );
200
+ debug( 'Connection error: %o', response );
201
+ errorCode = ERROR_NETWORK;
202
+ this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
201
203
  }
202
204
 
203
205
  /*
@@ -358,16 +360,6 @@ export default class SuggestionsEventSource extends EventTarget {
358
360
  }
359
361
  }
360
362
 
361
- processConnectionError( response ) {
362
- debug( 'Connection error: %o', response );
363
- this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
364
- this.dispatchEvent(
365
- new CustomEvent( ERROR_RESPONSE, {
366
- detail: getErrorData( ERROR_NETWORK ),
367
- } )
368
- );
369
- }
370
-
371
363
  processErrorEvent( e ) {
372
364
  debug( 'onerror: %o', e );
373
365