@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 +16 -0
- package/build/hooks/use-ai-suggestions/index.d.ts +5 -1
- package/build/hooks/use-ai-suggestions/index.js +44 -35
- package/build/hooks/use-image-generator/index.js +7 -2
- package/build/hooks/use-transcription-post-processing/index.js +0 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/suggestions-event-source/index.d.ts +0 -1
- package/build/suggestions-event-source/index.js +3 -8
- package/package.json +16 -16
- package/src/hooks/use-ai-suggestions/index.ts +78 -36
- package/src/hooks/use-image-generator/index.ts +8 -2
- package/src/hooks/use-transcription-post-processing/index.ts +0 -1
- package/src/index.ts +1 -1
- package/src/suggestions-event-source/index.ts +3 -11
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(
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
*
|
|
154
|
+
* Close the event source connection.
|
|
156
155
|
*
|
|
157
156
|
* @returns {void}
|
|
158
157
|
*/
|
|
159
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
27
|
-
"@storybook/blocks": "8.0.
|
|
28
|
-
"@storybook/react": "8.0.
|
|
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.
|
|
43
|
-
"@automattic/jetpack-connection": "^0.33.
|
|
44
|
-
"@automattic/jetpack-shared-extension-utils": "^0.14.
|
|
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.
|
|
47
|
-
"@wordpress/api-fetch": "6.
|
|
48
|
-
"@wordpress/block-editor": "12.
|
|
49
|
-
"@wordpress/components": "27.
|
|
50
|
-
"@wordpress/compose": "6.
|
|
51
|
-
"@wordpress/data": "9.
|
|
52
|
-
"@wordpress/element": "5.
|
|
53
|
-
"@wordpress/i18n": "4.
|
|
54
|
-
"@wordpress/icons": "9.
|
|
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
|
-
|
|
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 ] =
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
265
|
-
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
299
|
+
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
266
300
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
301
|
+
if ( ! eventSourceRef?.current ) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
270
304
|
|
|
271
|
-
|
|
272
|
-
|
|
305
|
+
// Alias
|
|
306
|
+
const eventSource = eventSourceRef.current;
|
|
273
307
|
|
|
274
|
-
|
|
275
|
-
|
|
308
|
+
// Set the request status.
|
|
309
|
+
setRequestingState( 'suggesting' );
|
|
276
310
|
|
|
277
|
-
|
|
311
|
+
eventSource.addEventListener( 'suggestion', handleSuggestion );
|
|
278
312
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
345
|
+
* Close the event source connection.
|
|
315
346
|
*
|
|
316
347
|
* @returns {void}
|
|
317
348
|
*/
|
|
318
|
-
const
|
|
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
|
-
|
|
77
|
+
debug( 'Error generating image: %o', error );
|
|
78
|
+
return Promise.reject( error );
|
|
73
79
|
}
|
|
74
80
|
};
|
|
75
81
|
|
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
|
-
|
|
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
|
|