@automattic/jetpack-ai-client 0.12.3 → 0.13.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,18 @@ 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.13.0] - 2024-05-06
9
+ ### Added
10
+ - AI Client: Add wrapper ref to AI Control. [#37145]
11
+ - AI Featured Image: Support custom user prompt on the image generation. [#37086]
12
+
13
+ ### Changed
14
+ - Updated package dependencies. [#37147] [#37148] [#37160]
15
+
16
+ ## [0.12.4] - 2024-04-29
17
+ ### Added
18
+ - AI Client: Export ExtensionAIControl. [#37087]
19
+
8
20
  ## [0.12.3] - 2024-04-25
9
21
  ### Changed
10
22
  - AI Client: Separate AIControl UI from block logic. [#36967]
@@ -294,6 +306,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
294
306
  - Updated package dependencies. [#31659]
295
307
  - Updated package dependencies. [#31785]
296
308
 
309
+ [0.13.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.4...v0.13.0
310
+ [0.12.4]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.3...v0.12.4
297
311
  [0.12.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.2...v0.12.3
298
312
  [0.12.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.1...v0.12.2
299
313
  [0.12.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.0...v0.12.1
@@ -17,6 +17,7 @@ type AIControlProps = {
17
17
  actions?: ReactElement;
18
18
  message?: ReactElement;
19
19
  promptUserInputRef?: React.MutableRefObject<HTMLInputElement>;
20
+ wrapperRef?: React.MutableRefObject<HTMLDivElement | null>;
20
21
  };
21
22
  /**
22
23
  * Base AIControl component. Contains the main structure of the control component and slots for banner, error, actions and message.
@@ -24,5 +25,5 @@ type AIControlProps = {
24
25
  * @param {AIControlProps} props - Component props
25
26
  * @returns {ReactElement} Rendered component
26
27
  */
27
- export default function AIControl({ disabled, value, placeholder, isTransparent, state, onChange, banner, error, actions, message, promptUserInputRef, }: AIControlProps): ReactElement;
28
+ export default function AIControl({ disabled, value, placeholder, isTransparent, state, onChange, banner, error, actions, message, promptUserInputRef, wrapperRef, }: AIControlProps): ReactElement;
28
29
  export {};
@@ -15,8 +15,8 @@ import './style.scss';
15
15
  * @param {AIControlProps} props - Component props
16
16
  * @returns {ReactElement} Rendered component
17
17
  */
18
- export default function AIControl({ disabled = false, value = '', placeholder = '', isTransparent = false, state = 'init', onChange, banner = null, error = null, actions = null, message = null, promptUserInputRef = null, }) {
19
- return (_jsxs("div", { className: "jetpack-components-ai-control__container-wrapper", children: [error, _jsxs("div", { className: "jetpack-components-ai-control__container", children: [banner, _jsxs("div", { className: classNames('jetpack-components-ai-control__wrapper', {
18
+ export default function AIControl({ disabled = false, value = '', placeholder = '', isTransparent = false, state = 'init', onChange, banner = null, error = null, actions = null, message = null, promptUserInputRef = null, wrapperRef = null, }) {
19
+ return (_jsxs("div", { className: "jetpack-components-ai-control__container-wrapper", ref: wrapperRef, children: [error, _jsxs("div", { className: "jetpack-components-ai-control__container", children: [banner, _jsxs("div", { className: classNames('jetpack-components-ai-control__wrapper', {
20
20
  'is-transparent': isTransparent,
21
21
  }), children: [_jsx(AiStatusIndicator, { state: state }), _jsx("div", { className: "jetpack-components-ai-control__input-wrapper", children: _jsx(PlainText, { value: value, onChange: onChange, placeholder: placeholder, className: "jetpack-components-ai-control__input", disabled: disabled, ref: promptUserInputRef }) }), actions] }), message] })] }));
22
22
  }
@@ -16,6 +16,7 @@ type ExtensionAIControlProps = {
16
16
  error?: string;
17
17
  requestsRemaining?: number;
18
18
  showUpgradeMessage?: boolean;
19
+ wrapperRef?: React.MutableRefObject<HTMLDivElement | null>;
19
20
  onChange?: (newValue: string) => void;
20
21
  onSend?: (currentValue: string) => void;
21
22
  onStop?: () => void;
@@ -30,6 +31,6 @@ type ExtensionAIControlProps = {
30
31
  * @param {React.MutableRefObject} ref - Ref to the component
31
32
  * @returns {ReactElement} Rendered component
32
33
  */
33
- export declare function ExtensionAIControl({ disabled, value, placeholder, showButtonLabels, isTransparent, state, showGuideLine, error, requestsRemaining, showUpgradeMessage, onChange, onSend, onStop, onClose, onUndo, onUpgrade, }: ExtensionAIControlProps, ref: React.MutableRefObject<HTMLInputElement>): ReactElement;
34
+ export declare function ExtensionAIControl({ disabled, value, placeholder, showButtonLabels, isTransparent, state, showGuideLine, error, requestsRemaining, showUpgradeMessage, wrapperRef, onChange, onSend, onStop, onClose, onUndo, onUpgrade, }: ExtensionAIControlProps, ref: React.MutableRefObject<HTMLInputElement>): ReactElement;
34
35
  declare const _default: React.ForwardRefExoticComponent<ExtensionAIControlProps & React.RefAttributes<HTMLInputElement>>;
35
36
  export default _default;
@@ -21,7 +21,7 @@ import './style.scss';
21
21
  * @param {React.MutableRefObject} ref - Ref to the component
22
22
  * @returns {ReactElement} Rendered component
23
23
  */
24
- export function ExtensionAIControl({ disabled = false, value = '', placeholder = '', showButtonLabels = true, isTransparent = false, state = 'init', showGuideLine = false, error, requestsRemaining, showUpgradeMessage = false, onChange, onSend, onStop, onClose, onUndo, onUpgrade, }, ref) {
24
+ export function ExtensionAIControl({ disabled = false, value = '', placeholder = '', showButtonLabels = true, isTransparent = false, state = 'init', showGuideLine = false, error, requestsRemaining, showUpgradeMessage = false, wrapperRef, onChange, onSend, onStop, onClose, onUndo, onUpgrade, }, ref) {
25
25
  const loading = state === 'requesting' || state === 'suggesting';
26
26
  const [editRequest, setEditRequest] = useState(false);
27
27
  const [lastValue, setLastValue] = useState(value || null);
@@ -81,6 +81,6 @@ export function ExtensionAIControl({ disabled = false, value = '', placeholder =
81
81
  else if (showGuideLine) {
82
82
  message = _jsx(GuidelineMessage, {});
83
83
  }
84
- return (_jsx(AIControl, { disabled: disabled || loading, value: value, placeholder: placeholder, isTransparent: isTransparent, state: state, onChange: changeHandler, actions: actions, message: message, promptUserInputRef: promptUserInputRef }));
84
+ return (_jsx(AIControl, { disabled: disabled || loading, value: value, placeholder: placeholder, isTransparent: isTransparent, state: state, onChange: changeHandler, actions: actions, message: message, promptUserInputRef: promptUserInputRef, wrapperRef: wrapperRef }));
85
85
  }
86
86
  export default forwardRef(ExtensionAIControl);
@@ -1,4 +1,4 @@
1
- export { AIControl, BlockAIControl } from './ai-control/index.js';
1
+ export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
4
4
  export { GuidelineMessage, UpgradeMessage, ErrorMessage, default as FooterMessage, } from './message/index.js';
@@ -1,4 +1,4 @@
1
- export { AIControl, BlockAIControl } from './ai-control/index.js';
1
+ export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
4
4
  export { GuidelineMessage, UpgradeMessage, ErrorMessage, default as FooterMessage, } from './message/index.js';
@@ -1,8 +1,9 @@
1
1
  declare const useImageGenerator: () => {
2
- generateImage: ({ feature, postContent, responseFormat, }: {
2
+ generateImage: ({ feature, postContent, responseFormat, userPrompt, }: {
3
3
  feature: string;
4
4
  postContent: string;
5
5
  responseFormat?: 'url' | 'b64_json';
6
+ userPrompt?: string;
6
7
  }) => Promise<{
7
8
  data: {
8
9
  [key: string]: string;
@@ -7,20 +7,57 @@ import debugFactory from 'debug';
7
7
  */
8
8
  import requestJwt from '../../jwt/index.js';
9
9
  const debug = debugFactory('ai-client:use-image-generator');
10
- const useImageGenerator = () => {
11
- const generateImage = async function ({ feature, postContent, responseFormat = 'url', }) {
12
- let token = '';
13
- try {
14
- token = (await requestJwt()).token;
15
- }
16
- catch (error) {
17
- debug('Error getting token: %o', error);
18
- return Promise.reject(error);
19
- }
20
- try {
21
- debug('Generating image');
22
- // TODO: fine tune the prompt as we move forward
23
- const imageGenerationPrompt = `I need a cover image for a blog post.
10
+ /**
11
+ * Cut the post content on a given lenght so the total length of the prompt is not longer than 4000 characters.
12
+ * @param {string} content - the content to be truncated
13
+ * @param {number} currentPromptLength - the length of the prompt already in use
14
+ * @returns {string} a truncated version of the content respecting the prompt length limit
15
+ */
16
+ const truncateContent = (content, currentPromptLength) => {
17
+ const maxLength = 4000;
18
+ const remainingLength = maxLength - currentPromptLength;
19
+ // 6 is the length of the ellipsis and the space before it
20
+ return content.length > remainingLength
21
+ ? content.substring(0, remainingLength - 6) + ` [...]`
22
+ : content;
23
+ };
24
+ /**
25
+ * Create the prompt string based on the provided context.
26
+ * @param {string} postContent - the content of the post
27
+ * @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated.
28
+ * @returns {string} the prompt string
29
+ */
30
+ const getImageGenerationPrompt = (postContent, userPrompt) => {
31
+ /**
32
+ * If the user provide some custom prompt for the image generation,
33
+ * we will use it, add the post content as additional context and
34
+ * provide some guardrails for the generation.
35
+ */
36
+ if (userPrompt) {
37
+ const imageGenerationPrompt = `I need a cover image for a blog post based on this user prompt:
38
+
39
+ ${userPrompt.length > 1000 ? userPrompt.substring(0, 1000) : userPrompt}
40
+
41
+ Before creating the image, identify the main topic of the user prompt and relate it to the post content.
42
+ Do not represent the whole content in one image, keep it simple and just represent one single idea.
43
+ Do not add details, detailed explanations or highlights from the content, just represent the main idea as if it was a photograph.
44
+ Do not use collages or compositions with multiple elements or scenes. Stick to one single scene. Do not compose unrealistic scenes.
45
+ If the content describes facts, objects or concepts from the real world, represent them on a realistic style and do not make unreal compositions.
46
+ If the content is more abstract, use a more abstract style to represent the main idea.
47
+ Make sure the light and the style are visually appealing.
48
+ Do not add text to the image.
49
+
50
+ For additional context, this is the post content:
51
+
52
+ `;
53
+ // truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
54
+ return imageGenerationPrompt + truncateContent(postContent, imageGenerationPrompt.length);
55
+ }
56
+ /**
57
+ * When the user does not provide a custom prompt, we will use the
58
+ * standard one, based solely on the post content.
59
+ */
60
+ const imageGenerationPrompt = `I need a cover image for a blog post.
24
61
  Before creating the image, identify the main topic of the content and only represent it.
25
62
  Do not represent the whole content in one image, keep it simple and just represent one single idea.
26
63
  Do not add details, detailed explanations or highlights from the content, just represent the main idea as if it was a photograph.
@@ -32,7 +69,23 @@ Do not add text to the image.
32
69
 
33
70
  This is the post content:
34
71
 
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.
72
+ `;
73
+ // truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
74
+ return imageGenerationPrompt + truncateContent(postContent, imageGenerationPrompt.length);
75
+ };
76
+ const useImageGenerator = () => {
77
+ const generateImage = async function ({ feature, postContent, responseFormat = 'url', userPrompt, }) {
78
+ let token = '';
79
+ try {
80
+ token = (await requestJwt()).token;
81
+ }
82
+ catch (error) {
83
+ debug('Error getting token: %o', error);
84
+ return Promise.reject(error);
85
+ }
86
+ try {
87
+ debug('Generating image');
88
+ const imageGenerationPrompt = getImageGenerationPrompt(postContent, userPrompt);
36
89
  const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
37
90
  const body = {
38
91
  prompt: imageGenerationPrompt,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.12.3",
4
+ "version": "0.13.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": {
@@ -23,11 +23,11 @@
23
23
  },
24
24
  "type": "module",
25
25
  "devDependencies": {
26
- "@storybook/addon-actions": "8.0.6",
27
- "@storybook/blocks": "8.0.6",
28
- "@storybook/preview-api": "8.0.6",
29
- "@storybook/react": "8.0.6",
30
- "@types/markdown-it": "14.0.0",
26
+ "@storybook/addon-actions": "8.0.9",
27
+ "@storybook/blocks": "8.0.9",
28
+ "@storybook/preview-api": "8.0.9",
29
+ "@storybook/react": "8.0.9",
30
+ "@types/markdown-it": "14.0.1",
31
31
  "@types/turndown": "5.0.4",
32
32
  "jest": "^29.6.2",
33
33
  "jest-environment-jsdom": "29.7.0",
@@ -42,19 +42,19 @@
42
42
  "main": "./build/index.js",
43
43
  "types": "./build/index.d.ts",
44
44
  "dependencies": {
45
- "@automattic/jetpack-base-styles": "^0.6.23",
46
- "@automattic/jetpack-connection": "^0.33.8",
47
- "@automattic/jetpack-shared-extension-utils": "^0.14.10",
45
+ "@automattic/jetpack-base-styles": "^0.6.24",
46
+ "@automattic/jetpack-connection": "^0.33.9",
47
+ "@automattic/jetpack-shared-extension-utils": "^0.14.11",
48
48
  "@microsoft/fetch-event-source": "2.0.1",
49
- "@types/react": "18.2.74",
50
- "@wordpress/api-fetch": "6.52.0",
51
- "@wordpress/block-editor": "12.23.0",
52
- "@wordpress/components": "27.3.0",
53
- "@wordpress/compose": "6.32.0",
54
- "@wordpress/data": "9.25.0",
55
- "@wordpress/element": "5.32.0",
56
- "@wordpress/i18n": "4.55.0",
57
- "@wordpress/icons": "9.46.0",
49
+ "@types/react": "18.3.1",
50
+ "@wordpress/api-fetch": "6.53.0",
51
+ "@wordpress/block-editor": "12.24.0",
52
+ "@wordpress/components": "27.4.0",
53
+ "@wordpress/compose": "6.33.0",
54
+ "@wordpress/data": "9.26.0",
55
+ "@wordpress/element": "5.33.0",
56
+ "@wordpress/i18n": "4.56.0",
57
+ "@wordpress/icons": "9.47.0",
58
58
  "classnames": "2.3.2",
59
59
  "debug": "4.3.4",
60
60
  "markdown-it": "14.0.0",
@@ -27,6 +27,7 @@ type AIControlProps = {
27
27
  actions?: ReactElement;
28
28
  message?: ReactElement;
29
29
  promptUserInputRef?: React.MutableRefObject< HTMLInputElement >;
30
+ wrapperRef?: React.MutableRefObject< HTMLDivElement | null >;
30
31
  };
31
32
 
32
33
  /**
@@ -47,9 +48,10 @@ export default function AIControl( {
47
48
  actions = null,
48
49
  message = null,
49
50
  promptUserInputRef = null,
51
+ wrapperRef = null,
50
52
  }: AIControlProps ): ReactElement {
51
53
  return (
52
- <div className="jetpack-components-ai-control__container-wrapper">
54
+ <div className="jetpack-components-ai-control__container-wrapper" ref={ wrapperRef }>
53
55
  { error }
54
56
  <div className="jetpack-components-ai-control__container">
55
57
  { banner }
@@ -30,6 +30,7 @@ type ExtensionAIControlProps = {
30
30
  error?: string;
31
31
  requestsRemaining?: number;
32
32
  showUpgradeMessage?: boolean;
33
+ wrapperRef?: React.MutableRefObject< HTMLDivElement | null >;
33
34
  onChange?: ( newValue: string ) => void;
34
35
  onSend?: ( currentValue: string ) => void;
35
36
  onStop?: () => void;
@@ -57,6 +58,7 @@ export function ExtensionAIControl(
57
58
  error,
58
59
  requestsRemaining,
59
60
  showUpgradeMessage = false,
61
+ wrapperRef,
60
62
  onChange,
61
63
  onSend,
62
64
  onStop,
@@ -210,6 +212,7 @@ export function ExtensionAIControl(
210
212
  actions={ actions }
211
213
  message={ message }
212
214
  promptUserInputRef={ promptUserInputRef }
215
+ wrapperRef={ wrapperRef }
213
216
  />
214
217
  );
215
218
  }
@@ -1,4 +1,4 @@
1
- export { AIControl, BlockAIControl } from './ai-control/index.js';
1
+ export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
4
4
  export {
@@ -9,15 +9,86 @@ import requestJwt from '../../jwt/index.js';
9
9
 
10
10
  const debug = debugFactory( 'ai-client:use-image-generator' );
11
11
 
12
+ /**
13
+ * Cut the post content on a given lenght so the total length of the prompt is not longer than 4000 characters.
14
+ * @param {string} content - the content to be truncated
15
+ * @param {number} currentPromptLength - the length of the prompt already in use
16
+ * @returns {string} a truncated version of the content respecting the prompt length limit
17
+ */
18
+ const truncateContent = ( content: string, currentPromptLength: number ): string => {
19
+ const maxLength = 4000;
20
+ const remainingLength = maxLength - currentPromptLength;
21
+ // 6 is the length of the ellipsis and the space before it
22
+ return content.length > remainingLength
23
+ ? content.substring( 0, remainingLength - 6 ) + ` [...]`
24
+ : content;
25
+ };
26
+
27
+ /**
28
+ * Create the prompt string based on the provided context.
29
+ * @param {string} postContent - the content of the post
30
+ * @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated.
31
+ * @returns {string} the prompt string
32
+ */
33
+ const getImageGenerationPrompt = ( postContent: string, userPrompt?: string ): string => {
34
+ /**
35
+ * If the user provide some custom prompt for the image generation,
36
+ * we will use it, add the post content as additional context and
37
+ * provide some guardrails for the generation.
38
+ */
39
+ if ( userPrompt ) {
40
+ const imageGenerationPrompt = `I need a cover image for a blog post based on this user prompt:
41
+
42
+ ${ userPrompt.length > 1000 ? userPrompt.substring( 0, 1000 ) : userPrompt }
43
+
44
+ Before creating the image, identify the main topic of the user prompt and relate it to the post content.
45
+ Do not represent the whole content in one image, keep it simple and just represent one single idea.
46
+ Do not add details, detailed explanations or highlights from the content, just represent the main idea as if it was a photograph.
47
+ Do not use collages or compositions with multiple elements or scenes. Stick to one single scene. Do not compose unrealistic scenes.
48
+ If the content describes facts, objects or concepts from the real world, represent them on a realistic style and do not make unreal compositions.
49
+ If the content is more abstract, use a more abstract style to represent the main idea.
50
+ Make sure the light and the style are visually appealing.
51
+ Do not add text to the image.
52
+
53
+ For additional context, this is the post content:
54
+
55
+ `;
56
+ // truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
57
+ return imageGenerationPrompt + truncateContent( postContent, imageGenerationPrompt.length );
58
+ }
59
+
60
+ /**
61
+ * When the user does not provide a custom prompt, we will use the
62
+ * standard one, based solely on the post content.
63
+ */
64
+ const imageGenerationPrompt = `I need a cover image for a blog post.
65
+ Before creating the image, identify the main topic of the content and only represent it.
66
+ Do not represent the whole content in one image, keep it simple and just represent one single idea.
67
+ Do not add details, detailed explanations or highlights from the content, just represent the main idea as if it was a photograph.
68
+ Do not use collages or compositions with multiple elements or scenes. Stick to one single scene. Do not compose unrealistic scenes.
69
+ If the content describes facts, objects or concepts from the real world, represent them on a realistic style and do not make unreal compositions.
70
+ If the content is more abstract, use a more abstract style to represent the main idea.
71
+ Make sure the light and the style are visually appealing.
72
+ Do not add text to the image.
73
+
74
+ This is the post content:
75
+
76
+ `;
77
+ // truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
78
+ return imageGenerationPrompt + truncateContent( postContent, imageGenerationPrompt.length );
79
+ };
80
+
12
81
  const useImageGenerator = () => {
13
82
  const generateImage = async function ( {
14
83
  feature,
15
84
  postContent,
16
85
  responseFormat = 'url',
86
+ userPrompt,
17
87
  }: {
18
88
  feature: string;
19
89
  postContent: string;
20
90
  responseFormat?: 'url' | 'b64_json';
91
+ userPrompt?: string;
21
92
  } ): Promise< { data: Array< { [ key: string ]: string } > } > {
22
93
  let token = '';
23
94
 
@@ -31,21 +102,7 @@ const useImageGenerator = () => {
31
102
  try {
32
103
  debug( 'Generating image' );
33
104
 
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.
105
+ const imageGenerationPrompt = getImageGenerationPrompt( postContent, userPrompt );
49
106
 
50
107
  const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
51
108