@automattic/jetpack-ai-client 0.10.1 → 0.12.0

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,25 @@ 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.0] - 2024-04-08
9
+ ### Added
10
+ - Add error rejection in image generation. [#36709]
11
+
12
+ ### Changed
13
+ - Updated package dependencies. [#36756] [#36760] [#36761]
14
+
15
+ ### Fixed
16
+ - AI Featured Image: handle posts longer than the limit of Dall-e generation prompt. [#36703]
17
+
18
+ ## [0.11.0] - 2024-04-01
19
+ ### Added
20
+ - AI Client: include prompt to generate featured image based on post content. [#36591]
21
+ - Support different responses in image hook [#36626]
22
+
23
+ ### Fixed
24
+ - AI Client: fix a bug where quick prompts would not work after getting suggested content [#36651]
25
+ - AI Client: set request content type as JSON on image generation hook and use rectangular images instead of square images. [#36620]
26
+
8
27
  ## [0.10.1] - 2024-03-27
9
28
  ### Changed
10
29
  - Updated package dependencies. [#36539, #36585]
@@ -263,6 +282,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
263
282
  - Updated package dependencies. [#31659]
264
283
  - Updated package dependencies. [#31785]
265
284
 
285
+ [0.12.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.11.0...v0.12.0
286
+ [0.11.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.10.1...v0.11.0
266
287
  [0.10.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.10.0...v0.10.1
267
288
  [0.10.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.9.0...v0.10.0
268
289
  [0.9.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.8.2...v0.9.0
@@ -9,8 +9,8 @@ import { useImperativeHandle, useRef, useEffect, useCallback } from '@wordpress/
9
9
  import { __ } from '@wordpress/i18n';
10
10
  import { Icon, closeSmall, check, arrowUp, trash, reusableBlock } from '@wordpress/icons';
11
11
  import classNames from 'classnames';
12
- import { forwardRef } from 'react';
13
- import React from 'react';
12
+ import debugFactory from 'debug';
13
+ import React, { forwardRef } from 'react';
14
14
  /**
15
15
  * Internal dependencies
16
16
  */
@@ -19,6 +19,7 @@ import AiStatusIndicator from '../ai-status-indicator/index.js';
19
19
  import { GuidelineMessage } from './message.js';
20
20
  // eslint-disable-next-line @typescript-eslint/no-empty-function
21
21
  const noop = () => { };
22
+ const debug = debugFactory('jetpack-ai-client:ai-control');
22
23
  /**
23
24
  * AI Control component.
24
25
  *
@@ -35,10 +36,7 @@ export function AIControl({ disabled = false, value = '', placeholder = '', show
35
36
  if (editRequest) {
36
37
  promptUserInputRef?.current?.focus();
37
38
  }
38
- if (!editRequest && lastValue !== null && value !== lastValue) {
39
- onChange?.(lastValue);
40
- }
41
- }, [editRequest, lastValue, value]);
39
+ }, [editRequest]);
42
40
  const sendRequest = useCallback(() => {
43
41
  setLastValue(value);
44
42
  setEditRequest(false);
@@ -62,6 +60,7 @@ export function AIControl({ disabled = false, value = '', placeholder = '', show
62
60
  onDiscard?.();
63
61
  }, []);
64
62
  const cancelEdit = useCallback(() => {
63
+ debug('cancelEdit, revert to last value', lastValue);
65
64
  onChange(lastValue || '');
66
65
  setEditRequest(false);
67
66
  }, [lastValue]);
@@ -1,10 +1,12 @@
1
1
  declare const useImageGenerator: () => {
2
- generateImage: ({ feature, }: {
2
+ generateImage: ({ feature, postContent, responseFormat, }: {
3
3
  feature: string;
4
+ postContent: string;
5
+ responseFormat?: 'url' | 'b64_json';
4
6
  }) => Promise<{
5
- data: Array<{
6
- url: string;
7
- }>;
7
+ data: {
8
+ [key: string]: string;
9
+ }[];
8
10
  }>;
9
11
  };
10
12
  export default useImageGenerator;
@@ -8,7 +8,7 @@ import debugFactory from 'debug';
8
8
  import requestJwt from '../../jwt/index.js';
9
9
  const debug = debugFactory('ai-client:use-image-generator');
10
10
  const useImageGenerator = () => {
11
- const generateImage = async function ({ feature, }) {
11
+ const generateImage = async function ({ feature, postContent, responseFormat = 'url', }) {
12
12
  let token = '';
13
13
  try {
14
14
  token = (await requestJwt()).token;
@@ -19,26 +19,45 @@ const useImageGenerator = () => {
19
19
  }
20
20
  try {
21
21
  debug('Generating image');
22
- // TODO: Find a proper prompt for the image generation
23
- const imageGenerationPrompt = ``;
22
+ // TODO: fine tune the prompt as we move forward
23
+ const imageGenerationPrompt = `I need a cover image for a blog post.
24
+ Before creating the image, identify the main topic of the content and only represent it.
25
+ Do not represent the whole content in one image, keep it simple and just represent one single idea.
26
+ Do not add details, detailed explanations or highlights from the content, just represent the main idea as if it was a photograph.
27
+ Do not use collages or compositions with multiple elements or scenes. Stick to one single scene. Do not compose unrealistic scenes.
28
+ If the content describes facts, objects or concepts from the real world, represent them on a realistic style and do not make unreal compositions.
29
+ If the content is more abstract, use a more abstract style to represent the main idea.
30
+ Make sure the light and the style are visually appealing.
31
+ Do not add text to the image.
32
+
33
+ This is the post content:
34
+
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.
24
36
  const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
25
37
  const body = {
26
38
  prompt: imageGenerationPrompt,
27
- response_format: 'url',
39
+ response_format: responseFormat,
28
40
  feature,
41
+ size: '1792x1024',
29
42
  };
30
43
  const headers = {
31
44
  Authorization: `Bearer ${token}`,
45
+ 'Content-Type': 'application/json',
32
46
  };
33
47
  const data = await fetch(URL, {
34
48
  method: 'POST',
35
49
  headers,
36
50
  body: JSON.stringify(body),
37
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
+ }
38
56
  return data;
39
57
  }
40
58
  catch (error) {
41
- return;
59
+ debug('Error generating image: %o', error);
60
+ return Promise.reject(error);
42
61
  }
43
62
  };
44
63
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.10.1",
4
+ "version": "0.12.0",
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": {
@@ -18,13 +18,14 @@
18
18
  "build": "pnpm run clean && pnpm run compile-ts",
19
19
  "clean": "rm -rf build/",
20
20
  "compile-ts": "tsc --pretty",
21
- "test": "NODE_OPTIONS=--experimental-vm-modules jest"
21
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
22
+ "watch": "tsc --watch --pretty"
22
23
  },
23
24
  "type": "module",
24
25
  "devDependencies": {
25
- "@storybook/addon-actions": "8.0.4",
26
- "@storybook/blocks": "8.0.4",
27
- "@storybook/react": "8.0.4",
26
+ "@storybook/addon-actions": "8.0.6",
27
+ "@storybook/blocks": "8.0.6",
28
+ "@storybook/react": "8.0.6",
28
29
  "jest": "^29.6.2",
29
30
  "jest-environment-jsdom": "29.7.0",
30
31
  "typescript": "5.0.4"
@@ -38,19 +39,19 @@
38
39
  "main": "./build/index.js",
39
40
  "types": "./build/index.d.ts",
40
41
  "dependencies": {
41
- "@automattic/jetpack-base-styles": "^0.6.20",
42
- "@automattic/jetpack-connection": "^0.33.5",
43
- "@automattic/jetpack-shared-extension-utils": "^0.14.8",
42
+ "@automattic/jetpack-base-styles": "^0.6.21",
43
+ "@automattic/jetpack-connection": "^0.33.7",
44
+ "@automattic/jetpack-shared-extension-utils": "^0.14.9",
44
45
  "@microsoft/fetch-event-source": "2.0.1",
45
- "@types/react": "18.2.61",
46
- "@wordpress/api-fetch": "6.51.0",
47
- "@wordpress/block-editor": "12.22.0",
48
- "@wordpress/components": "27.2.0",
49
- "@wordpress/compose": "6.31.0",
50
- "@wordpress/data": "9.24.0",
51
- "@wordpress/element": "5.31.0",
52
- "@wordpress/i18n": "4.54.0",
53
- "@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",
54
55
  "classnames": "2.3.2",
55
56
  "debug": "4.3.4",
56
57
  "react": "18.2.0",
@@ -8,8 +8,8 @@ import { useImperativeHandle, useRef, useEffect, useCallback } from '@wordpress/
8
8
  import { __ } from '@wordpress/i18n';
9
9
  import { Icon, closeSmall, check, arrowUp, trash, reusableBlock } from '@wordpress/icons';
10
10
  import classNames from 'classnames';
11
- import { forwardRef } from 'react';
12
- import React from 'react';
11
+ import debugFactory from 'debug';
12
+ import React, { forwardRef } from 'react';
13
13
  /**
14
14
  * Internal dependencies
15
15
  */
@@ -45,6 +45,8 @@ type AiControlProps = {
45
45
  // eslint-disable-next-line @typescript-eslint/no-empty-function
46
46
  const noop = () => {};
47
47
 
48
+ const debug = debugFactory( 'jetpack-ai-client:ai-control' );
49
+
48
50
  /**
49
51
  * AI Control component.
50
52
  *
@@ -84,11 +86,7 @@ export function AIControl(
84
86
  if ( editRequest ) {
85
87
  promptUserInputRef?.current?.focus();
86
88
  }
87
-
88
- if ( ! editRequest && lastValue !== null && value !== lastValue ) {
89
- onChange?.( lastValue );
90
- }
91
- }, [ editRequest, lastValue, value ] );
89
+ }, [ editRequest ] );
92
90
 
93
91
  const sendRequest = useCallback( () => {
94
92
  setLastValue( value );
@@ -119,6 +117,7 @@ export function AIControl(
119
117
  }, [] );
120
118
 
121
119
  const cancelEdit = useCallback( () => {
120
+ debug( 'cancelEdit, revert to last value', lastValue );
122
121
  onChange( lastValue || '' );
123
122
  setEditRequest( false );
124
123
  }, [ lastValue ] );
@@ -12,9 +12,13 @@ const debug = debugFactory( 'ai-client:use-image-generator' );
12
12
  const useImageGenerator = () => {
13
13
  const generateImage = async function ( {
14
14
  feature,
15
+ postContent,
16
+ responseFormat = 'url',
15
17
  }: {
16
18
  feature: string;
17
- } ): Promise< { data: Array< { url: string } > } > {
19
+ postContent: string;
20
+ responseFormat?: 'url' | 'b64_json';
21
+ } ): Promise< { data: Array< { [ key: string ]: string } > } > {
18
22
  let token = '';
19
23
 
20
24
  try {
@@ -27,19 +31,34 @@ const useImageGenerator = () => {
27
31
  try {
28
32
  debug( 'Generating image' );
29
33
 
30
- // TODO: Find a proper prompt for the image generation
31
- const imageGenerationPrompt = ``;
34
+ // TODO: fine tune the prompt as we move forward
35
+ const imageGenerationPrompt =
36
+ `I need a cover image for a blog post.
37
+ Before creating the image, identify the main topic of the content and only represent it.
38
+ Do not represent the whole content in one image, keep it simple and just represent one single idea.
39
+ Do not add details, detailed explanations or highlights from the content, just represent the main idea as if it was a photograph.
40
+ Do not use collages or compositions with multiple elements or scenes. Stick to one single scene. Do not compose unrealistic scenes.
41
+ If the content describes facts, objects or concepts from the real world, represent them on a realistic style and do not make unreal compositions.
42
+ If the content is more abstract, use a more abstract style to represent the main idea.
43
+ Make sure the light and the style are visually appealing.
44
+ Do not add text to the image.
45
+
46
+ This is the post content:
47
+
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.
32
49
 
33
50
  const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
34
51
 
35
52
  const body = {
36
53
  prompt: imageGenerationPrompt,
37
- response_format: 'url',
54
+ response_format: responseFormat,
38
55
  feature,
56
+ size: '1792x1024',
39
57
  };
40
58
 
41
59
  const headers = {
42
60
  Authorization: `Bearer ${ token }`,
61
+ 'Content-Type': 'application/json',
43
62
  };
44
63
 
45
64
  const data = await fetch( URL, {
@@ -48,9 +67,15 @@ const useImageGenerator = () => {
48
67
  body: JSON.stringify( body ),
49
68
  } ).then( response => response.json() );
50
69
 
51
- return data as { data: { url: string }[] };
70
+ if ( data?.data?.status && data?.data?.status > 200 ) {
71
+ debug( 'Error generating image: %o', data );
72
+ return Promise.reject( data );
73
+ }
74
+
75
+ return data as { data: { [ key: string ]: string }[] };
52
76
  } catch ( error ) {
53
- return;
77
+ debug( 'Error generating image: %o', error );
78
+ return Promise.reject( error );
54
79
  }
55
80
  };
56
81