@automattic/jetpack-ai-client 0.13.1 → 0.14.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 +10 -0
- package/build/ask-question/sync.d.ts +30 -0
- package/build/ask-question/sync.js +66 -0
- package/build/components/ai-control/extension-ai-control.d.ts +2 -2
- package/build/components/ai-control/extension-ai-control.js +2 -2
- package/build/components/message/index.d.ts +8 -3
- package/build/components/message/index.js +9 -4
- package/build/hooks/use-image-generator/index.d.ts +9 -0
- package/build/hooks/use-image-generator/index.js +122 -2
- package/build/libs/index.d.ts +1 -0
- package/build/libs/markdown/index.d.ts +2 -1
- package/build/types.d.ts +1 -0
- package/package.json +14 -14
- package/src/ask-question/sync.ts +91 -0
- package/src/components/ai-control/extension-ai-control.tsx +12 -4
- package/src/components/message/index.tsx +31 -6
- package/src/hooks/use-image-generator/index.ts +158 -2
- package/src/libs/index.ts +2 -0
- package/src/libs/markdown/index.ts +3 -1
- package/src/types.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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.14.0] - 2024-05-20
|
|
9
|
+
### Added
|
|
10
|
+
- AI Client: Expose HTML render rules type. [#37386]
|
|
11
|
+
- AI Featured Image: Support Stable Diffusion image generation. [#37413]
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- AI Client: Change default behavior of Message components [#37365]
|
|
15
|
+
- Updated package dependencies. [#37379] [#37380]
|
|
16
|
+
|
|
8
17
|
## [0.13.1] - 2024-05-13
|
|
9
18
|
### Added
|
|
10
19
|
- AI Client: Add className to AI Control component. [#37322]
|
|
@@ -314,6 +323,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
314
323
|
- Updated package dependencies. [#31659]
|
|
315
324
|
- Updated package dependencies. [#31785]
|
|
316
325
|
|
|
326
|
+
[0.14.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.13.1...v0.14.0
|
|
317
327
|
[0.13.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.13.0...v0.13.1
|
|
318
328
|
[0.13.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.4...v0.13.0
|
|
319
329
|
[0.12.4]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.3...v0.12.4
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AskQuestionOptionsArgProps } from './index.js';
|
|
2
|
+
import type { PromptProp } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The response data from the AI assistant when doing a sync, not-streamed question.
|
|
5
|
+
*/
|
|
6
|
+
export type ResponseData = {
|
|
7
|
+
choices: Array<{
|
|
8
|
+
message: {
|
|
9
|
+
content: string;
|
|
10
|
+
};
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A function that asks a question without streaming.
|
|
15
|
+
*
|
|
16
|
+
* @param {PromptProp} question - The question to ask. It can be a simple string or an array of PromptMessageItemProps objects.
|
|
17
|
+
* @param {AskQuestionOptionsArgProps} options - An optional object for additional configuration: postId, feature, model.
|
|
18
|
+
* @returns {Promise<ResponseData>} - A promise that resolves to an instance of the ResponseData
|
|
19
|
+
* @example
|
|
20
|
+
* const question = "What is the meaning of life?";
|
|
21
|
+
* const options = {
|
|
22
|
+
* feature: 'ai-featured-image',
|
|
23
|
+
* model: 'gpt-4-turbo'
|
|
24
|
+
* }
|
|
25
|
+
* askQuestionSync( question, options ).then( responseData => {
|
|
26
|
+
* // access the choices array on the response data
|
|
27
|
+
* const content = responseData.choices[ 0 ].message.content;
|
|
28
|
+
* } );
|
|
29
|
+
*/
|
|
30
|
+
export default function askQuestionSync(question: PromptProp, { postId, feature, model }?: AskQuestionOptionsArgProps): Promise<ResponseData>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import debugFactory from 'debug';
|
|
5
|
+
/*
|
|
6
|
+
* Types & constants
|
|
7
|
+
*/
|
|
8
|
+
import requestJwt from '../jwt/index.js';
|
|
9
|
+
const debug = debugFactory('jetpack-ai-client:ask-question-sync');
|
|
10
|
+
/**
|
|
11
|
+
* A function that asks a question without streaming.
|
|
12
|
+
*
|
|
13
|
+
* @param {PromptProp} question - The question to ask. It can be a simple string or an array of PromptMessageItemProps objects.
|
|
14
|
+
* @param {AskQuestionOptionsArgProps} options - An optional object for additional configuration: postId, feature, model.
|
|
15
|
+
* @returns {Promise<ResponseData>} - A promise that resolves to an instance of the ResponseData
|
|
16
|
+
* @example
|
|
17
|
+
* const question = "What is the meaning of life?";
|
|
18
|
+
* const options = {
|
|
19
|
+
* feature: 'ai-featured-image',
|
|
20
|
+
* model: 'gpt-4-turbo'
|
|
21
|
+
* }
|
|
22
|
+
* askQuestionSync( question, options ).then( responseData => {
|
|
23
|
+
* // access the choices array on the response data
|
|
24
|
+
* const content = responseData.choices[ 0 ].message.content;
|
|
25
|
+
* } );
|
|
26
|
+
*/
|
|
27
|
+
export default async function askQuestionSync(question, { postId = null, feature, model } = {}) {
|
|
28
|
+
debug('Asking question with no streaming: %o. options: %o', question, {
|
|
29
|
+
postId,
|
|
30
|
+
feature,
|
|
31
|
+
model,
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* The URL to the AI assistant query endpoint.
|
|
35
|
+
*/
|
|
36
|
+
const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-query';
|
|
37
|
+
let token = null;
|
|
38
|
+
try {
|
|
39
|
+
token = (await requestJwt()).token;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
debug('Error getting token: %o', error);
|
|
43
|
+
return Promise.reject(error);
|
|
44
|
+
}
|
|
45
|
+
const body = {
|
|
46
|
+
question: question,
|
|
47
|
+
stream: false,
|
|
48
|
+
postId,
|
|
49
|
+
feature,
|
|
50
|
+
model,
|
|
51
|
+
};
|
|
52
|
+
const headers = {
|
|
53
|
+
Authorization: `Bearer ${token}`,
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
};
|
|
56
|
+
const data = await fetch(URL, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers,
|
|
59
|
+
body: JSON.stringify(body),
|
|
60
|
+
}).then(response => response.json());
|
|
61
|
+
if (data?.data?.status && data?.data?.status > 200) {
|
|
62
|
+
debug('Error generating prompt: %o', data);
|
|
63
|
+
return Promise.reject(data);
|
|
64
|
+
}
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
@@ -3,7 +3,7 @@ import './style.scss';
|
|
|
3
3
|
/**
|
|
4
4
|
* Types
|
|
5
5
|
*/
|
|
6
|
-
import type { RequestingStateProp } from '../../types.js';
|
|
6
|
+
import type { RequestingErrorProps, RequestingStateProp } from '../../types.js';
|
|
7
7
|
import type { ReactElement, MouseEvent } from 'react';
|
|
8
8
|
type ExtensionAIControlProps = {
|
|
9
9
|
className?: string;
|
|
@@ -14,7 +14,7 @@ type ExtensionAIControlProps = {
|
|
|
14
14
|
isTransparent?: boolean;
|
|
15
15
|
state?: RequestingStateProp;
|
|
16
16
|
showGuideLine?: boolean;
|
|
17
|
-
error?:
|
|
17
|
+
error?: RequestingErrorProps;
|
|
18
18
|
requestsRemaining?: number;
|
|
19
19
|
showUpgradeMessage?: boolean;
|
|
20
20
|
wrapperRef?: React.MutableRefObject<HTMLDivElement | null>;
|
|
@@ -75,8 +75,8 @@ export function ExtensionAIControl({ className, disabled = false, value = '', pl
|
|
|
75
75
|
});
|
|
76
76
|
const actions = (_jsx(_Fragment, { children: loading ? (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: stopHandler, variant: "secondary", label: __('Stop request', 'jetpack-ai-client'), children: showButtonLabels ? __('Stop', 'jetpack-ai-client') : _jsx(Icon, { icon: closeSmall }) })) : (_jsxs(_Fragment, { children: [value?.length > 0 && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: sendHandler, variant: "primary", disabled: !value?.length || disabled, label: __('Send request', 'jetpack-ai-client'), children: showButtonLabels ? (__('Generate', 'jetpack-ai-client')) : (_jsx(Icon, { icon: arrowUp })) }) })), value?.length <= 0 && state === 'done' && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: _jsxs(ButtonGroup, { children: [_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Undo', 'jetpack-ai-client'), onClick: undoHandler, tooltipPosition: "top", children: _jsx(Icon, { icon: undo }) }), _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Close', 'jetpack-ai-client'), onClick: closeHandler, variant: "tertiary", children: __('Close', 'jetpack-ai-client') })] }) }))] })) }));
|
|
77
77
|
let message = null;
|
|
78
|
-
if (error) {
|
|
79
|
-
message = _jsx(ErrorMessage, { error: error, onTryAgainClick: tryAgainHandler });
|
|
78
|
+
if (error?.message) {
|
|
79
|
+
message = (_jsx(ErrorMessage, { error: error.message, code: error.code, onTryAgainClick: tryAgainHandler, onUpgradeClick: upgradeHandler }));
|
|
80
80
|
}
|
|
81
81
|
else if (showUpgradeMessage) {
|
|
82
82
|
message = (_jsx(UpgradeMessage, { requestsRemaining: requestsRemaining, onUpgradeClick: upgradeHandler }));
|
|
@@ -5,6 +5,7 @@ import './style.scss';
|
|
|
5
5
|
/**
|
|
6
6
|
* Types
|
|
7
7
|
*/
|
|
8
|
+
import type { SuggestionErrorCode } from '../../types.js';
|
|
8
9
|
import type React from 'react';
|
|
9
10
|
export declare const MESSAGE_SEVERITY_WARNING = "warning";
|
|
10
11
|
export declare const MESSAGE_SEVERITY_ERROR = "error";
|
|
@@ -19,13 +20,17 @@ export type MessageProps = {
|
|
|
19
20
|
onSidebarIconClick?: () => void;
|
|
20
21
|
children: React.ReactNode;
|
|
21
22
|
};
|
|
23
|
+
export type OnUpgradeClick = (event?: React.MouseEvent<HTMLButtonElement>) => void;
|
|
22
24
|
export type UpgradeMessageProps = {
|
|
23
25
|
requestsRemaining: number;
|
|
24
|
-
|
|
26
|
+
severity?: MessageSeverityProp;
|
|
27
|
+
onUpgradeClick: OnUpgradeClick;
|
|
25
28
|
};
|
|
26
29
|
export type ErrorMessageProps = {
|
|
27
30
|
error?: string;
|
|
31
|
+
code?: SuggestionErrorCode;
|
|
28
32
|
onTryAgainClick: () => void;
|
|
33
|
+
onUpgradeClick: OnUpgradeClick;
|
|
29
34
|
};
|
|
30
35
|
/**
|
|
31
36
|
* React component to render a block message.
|
|
@@ -46,12 +51,12 @@ export declare function GuidelineMessage(): React.ReactElement;
|
|
|
46
51
|
* @param {number} requestsRemaining - Number of requests remaining.
|
|
47
52
|
* @returns {React.ReactElement } - Message component.
|
|
48
53
|
*/
|
|
49
|
-
export declare function UpgradeMessage({ requestsRemaining, onUpgradeClick, }: UpgradeMessageProps): React.ReactElement;
|
|
54
|
+
export declare function UpgradeMessage({ requestsRemaining, severity, onUpgradeClick, }: UpgradeMessageProps): React.ReactElement;
|
|
50
55
|
/**
|
|
51
56
|
* React component to render an error message
|
|
52
57
|
*
|
|
53
58
|
* @param {number} requestsRemaining - Number of requests remaining.
|
|
54
59
|
* @returns {React.ReactElement } - Message component.
|
|
55
60
|
*/
|
|
56
|
-
export declare function ErrorMessage({ error, onTryAgainClick }: ErrorMessageProps): React.ReactElement;
|
|
61
|
+
export declare function ErrorMessage({ error, code, onTryAgainClick, onUpgradeClick, }: ErrorMessageProps): React.ReactElement;
|
|
57
62
|
export {};
|
|
@@ -11,6 +11,7 @@ import classNames from 'classnames';
|
|
|
11
11
|
*/
|
|
12
12
|
import './style.scss';
|
|
13
13
|
import errorExclamation from '../../icons/error-exclamation.js';
|
|
14
|
+
import { ERROR_QUOTA_EXCEEDED } from '../../types.js';
|
|
14
15
|
export const MESSAGE_SEVERITY_WARNING = 'warning';
|
|
15
16
|
export const MESSAGE_SEVERITY_ERROR = 'error';
|
|
16
17
|
export const MESSAGE_SEVERITY_SUCCESS = 'success';
|
|
@@ -50,8 +51,12 @@ export function GuidelineMessage() {
|
|
|
50
51
|
* @param {number} requestsRemaining - Number of requests remaining.
|
|
51
52
|
* @returns {React.ReactElement } - Message component.
|
|
52
53
|
*/
|
|
53
|
-
export function UpgradeMessage({ requestsRemaining, onUpgradeClick, }) {
|
|
54
|
-
|
|
54
|
+
export function UpgradeMessage({ requestsRemaining, severity, onUpgradeClick, }) {
|
|
55
|
+
let messageSeverity = severity;
|
|
56
|
+
if (messageSeverity == null) {
|
|
57
|
+
messageSeverity = requestsRemaining > 0 ? MESSAGE_SEVERITY_INFO : MESSAGE_SEVERITY_WARNING;
|
|
58
|
+
}
|
|
59
|
+
return (_jsxs(Message, { severity: messageSeverity, children: [_jsx("span", { children: sprintf(
|
|
55
60
|
// translators: %1$d: number of requests remaining
|
|
56
61
|
__('You have %1$d free requests remaining.', 'jetpack-ai-client'), requestsRemaining) }), _jsx(Button, { variant: "link", onClick: onUpgradeClick, children: __('Upgrade now', 'jetpack-ai-client') })] }));
|
|
57
62
|
}
|
|
@@ -61,9 +66,9 @@ export function UpgradeMessage({ requestsRemaining, onUpgradeClick, }) {
|
|
|
61
66
|
* @param {number} requestsRemaining - Number of requests remaining.
|
|
62
67
|
* @returns {React.ReactElement } - Message component.
|
|
63
68
|
*/
|
|
64
|
-
export function ErrorMessage({ error, onTryAgainClick }) {
|
|
69
|
+
export function ErrorMessage({ error, code, onTryAgainClick, onUpgradeClick, }) {
|
|
65
70
|
const errorMessage = error || __('Something went wrong', 'jetpack-ai-client');
|
|
66
71
|
return (_jsxs(Message, { severity: MESSAGE_SEVERITY_ERROR, children: [_jsx("span", { children: sprintf(
|
|
67
72
|
// translators: %1$d: A dynamic error message
|
|
68
|
-
__('Error: %1$s', 'jetpack-ai-client'), errorMessage) }), _jsx(Button, { variant: "link", onClick: onTryAgainClick, children: __('Try
|
|
73
|
+
__('Error: %1$s', 'jetpack-ai-client'), errorMessage) }), code === ERROR_QUOTA_EXCEEDED ? (_jsx(Button, { variant: "link", onClick: onUpgradeClick, children: __('Upgrade now', 'jetpack-ai-client') })) : (_jsx(Button, { variant: "link", onClick: onTryAgainClick, children: __('Try again', 'jetpack-ai-client') }))] }));
|
|
69
74
|
}
|
|
@@ -9,5 +9,14 @@ declare const useImageGenerator: () => {
|
|
|
9
9
|
[key: string]: string;
|
|
10
10
|
}[];
|
|
11
11
|
}>;
|
|
12
|
+
generateImageWithStableDiffusion: ({ feature, postContent, userPrompt, }: {
|
|
13
|
+
feature: string;
|
|
14
|
+
postContent: string;
|
|
15
|
+
userPrompt?: string;
|
|
16
|
+
}) => Promise<{
|
|
17
|
+
data: {
|
|
18
|
+
[key: string]: string;
|
|
19
|
+
}[];
|
|
20
|
+
}>;
|
|
12
21
|
};
|
|
13
22
|
export default useImageGenerator;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
4
5
|
import debugFactory from 'debug';
|
|
5
6
|
/**
|
|
6
7
|
* Internal dependencies
|
|
7
8
|
*/
|
|
9
|
+
import askQuestionSync from '../../ask-question/sync.js';
|
|
8
10
|
import requestJwt from '../../jwt/index.js';
|
|
9
11
|
const debug = debugFactory('ai-client:use-image-generator');
|
|
10
12
|
/**
|
|
@@ -27,7 +29,7 @@ const truncateContent = (content, currentPromptLength) => {
|
|
|
27
29
|
* @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated.
|
|
28
30
|
* @returns {string} the prompt string
|
|
29
31
|
*/
|
|
30
|
-
const
|
|
32
|
+
const getDalleImageGenerationPrompt = (postContent, userPrompt) => {
|
|
31
33
|
/**
|
|
32
34
|
* If the user provide some custom prompt for the image generation,
|
|
33
35
|
* we will use it, add the post content as additional context and
|
|
@@ -73,7 +75,124 @@ This is the post content:
|
|
|
73
75
|
// truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
|
|
74
76
|
return imageGenerationPrompt + truncateContent(postContent, imageGenerationPrompt.length);
|
|
75
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Create the Stable Diffusion pre-processing prompt based on the provided context.
|
|
80
|
+
* @param {string} postContent - the content of the post.
|
|
81
|
+
* @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated.
|
|
82
|
+
* @returns {string} the prompt string to be fed to the AI Assistant model.
|
|
83
|
+
*/
|
|
84
|
+
const getStableDiffusionPreProcessingPrompt = (postContent, userPrompt) => {
|
|
85
|
+
/**
|
|
86
|
+
* If the user provide some custom prompt for the image generation,
|
|
87
|
+
* we will use it and add the post content as additional context.
|
|
88
|
+
*/
|
|
89
|
+
if (userPrompt) {
|
|
90
|
+
const preProcessingPrompt = `I need a Stable Diffusion prompt to generate a featured image for a blog post based on this user-provided image description:
|
|
91
|
+
|
|
92
|
+
${userPrompt.length > 1000 ? userPrompt.substring(0, 1000) : userPrompt}
|
|
93
|
+
|
|
94
|
+
The image should be a photo. Make sure you highlight the main suject of the image description, and include brief details about the light and style of the image.
|
|
95
|
+
Include a request to use high resolution and produce a highly detailed image, with sharp focus.
|
|
96
|
+
Return just the prompt, without comments.
|
|
97
|
+
|
|
98
|
+
For additional context, this is the post content:
|
|
99
|
+
|
|
100
|
+
`;
|
|
101
|
+
// truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
|
|
102
|
+
return preProcessingPrompt + truncateContent(postContent, preProcessingPrompt.length);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* When the user does not provide a custom prompt, we will use the
|
|
106
|
+
* standard one, based solely on the post content.
|
|
107
|
+
*/
|
|
108
|
+
const preProcessingPrompt = `I need a Stable Diffusion prompt to generate a featured image for a blog post with the following content.
|
|
109
|
+
The image should be a photo. Make sure you highlight the main suject of the content, and include brief details about the light and style of the image.
|
|
110
|
+
Include a request to use high resolution and produce a highly detailed image, with sharp focus.
|
|
111
|
+
Return just the prompt, without comments. The content is:
|
|
112
|
+
|
|
113
|
+
`;
|
|
114
|
+
// truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
|
|
115
|
+
return preProcessingPrompt + truncateContent(postContent, preProcessingPrompt.length);
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Uses the Jetpack AI query endpoint to produce a prompt for the stable diffusion model.
|
|
119
|
+
* @param {string} postContent - the content of the post.
|
|
120
|
+
* @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated
|
|
121
|
+
* @param {string} feature - the feature to be used for the image generation.
|
|
122
|
+
* @returns {string} the prompt string to be used on stable diffusion image generation.
|
|
123
|
+
*/
|
|
124
|
+
const getStableDiffusionImageGenerationPrompt = async (postContent, userPrompt, feature) => {
|
|
125
|
+
const prompt = getStableDiffusionPreProcessingPrompt(postContent, userPrompt);
|
|
126
|
+
/**
|
|
127
|
+
* Request the prompt on the AI Assistant endpoint
|
|
128
|
+
*/
|
|
129
|
+
const data = await askQuestionSync(prompt, { feature });
|
|
130
|
+
return data.choices?.[0]?.message?.content;
|
|
131
|
+
};
|
|
76
132
|
const useImageGenerator = () => {
|
|
133
|
+
const generateImageWithStableDiffusion = async function ({ feature, postContent, userPrompt, }) {
|
|
134
|
+
let token = null;
|
|
135
|
+
try {
|
|
136
|
+
token = await requestJwt();
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
debug('Error getting token: %o', error);
|
|
140
|
+
return Promise.reject(error);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
debug('Generating image with Stable Diffusion');
|
|
144
|
+
const prompt = await getStableDiffusionImageGenerationPrompt(postContent, userPrompt, feature);
|
|
145
|
+
const data = {
|
|
146
|
+
prompt,
|
|
147
|
+
style: 'photographic',
|
|
148
|
+
token: token.token,
|
|
149
|
+
width: 1024,
|
|
150
|
+
height: 768,
|
|
151
|
+
};
|
|
152
|
+
const response = await fetch(`https://public-api.wordpress.com/wpcom/v2/sites/${token.blogId}/ai-image`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(data),
|
|
158
|
+
});
|
|
159
|
+
if (!response?.ok) {
|
|
160
|
+
debug('Error generating image: %o', response);
|
|
161
|
+
return Promise.reject({
|
|
162
|
+
data: {
|
|
163
|
+
status: response.status,
|
|
164
|
+
},
|
|
165
|
+
message: __('Error generating image. Please try again later.', 'jetpack-ai-client'),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const blob = await response.blob();
|
|
169
|
+
/**
|
|
170
|
+
* Convert the blob to base64 to keep the same format as the Dalle API.
|
|
171
|
+
*/
|
|
172
|
+
const base64 = await new Promise((resolve, reject) => {
|
|
173
|
+
const reader = new FileReader();
|
|
174
|
+
reader.onloadend = () => {
|
|
175
|
+
const base64data = reader.result;
|
|
176
|
+
return resolve(base64data.replace(/^data:image\/(png|jpg);base64,/, ''));
|
|
177
|
+
};
|
|
178
|
+
reader.onerror = reject;
|
|
179
|
+
reader.readAsDataURL(blob);
|
|
180
|
+
});
|
|
181
|
+
// Return the Dalle API format
|
|
182
|
+
return {
|
|
183
|
+
data: [
|
|
184
|
+
{
|
|
185
|
+
b64_json: base64,
|
|
186
|
+
revised_prompt: prompt,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
debug('Error generating image: %o', error);
|
|
193
|
+
return Promise.reject(error);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
77
196
|
const generateImage = async function ({ feature, postContent, responseFormat = 'url', userPrompt, }) {
|
|
78
197
|
let token = '';
|
|
79
198
|
try {
|
|
@@ -85,7 +204,7 @@ const useImageGenerator = () => {
|
|
|
85
204
|
}
|
|
86
205
|
try {
|
|
87
206
|
debug('Generating image');
|
|
88
|
-
const imageGenerationPrompt =
|
|
207
|
+
const imageGenerationPrompt = getDalleImageGenerationPrompt(postContent, userPrompt);
|
|
89
208
|
const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
|
|
90
209
|
const body = {
|
|
91
210
|
prompt: imageGenerationPrompt,
|
|
@@ -115,6 +234,7 @@ const useImageGenerator = () => {
|
|
|
115
234
|
};
|
|
116
235
|
return {
|
|
117
236
|
generateImage,
|
|
237
|
+
generateImageWithStableDiffusion,
|
|
118
238
|
};
|
|
119
239
|
};
|
|
120
240
|
export default useImageGenerator;
|
package/build/libs/index.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ import MarkdownToHTML from './markdown-to-html.js';
|
|
|
7
7
|
* Types
|
|
8
8
|
*/
|
|
9
9
|
import type { Fix as HTMLFix } from './markdown-to-html.js';
|
|
10
|
+
export type RenderHTMLRules = 'all' | Array<HTMLFix>;
|
|
10
11
|
declare const renderHTMLFromMarkdown: ({ content, rules, }: {
|
|
11
12
|
content: string;
|
|
12
|
-
rules?:
|
|
13
|
+
rules?: RenderHTMLRules;
|
|
13
14
|
}) => string;
|
|
14
15
|
declare const renderMarkdownFromHTML: ({ content }: {
|
|
15
16
|
content: string;
|
package/build/types.d.ts
CHANGED
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.14.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": {
|
|
@@ -42,24 +42,24 @@
|
|
|
42
42
|
"main": "./build/index.js",
|
|
43
43
|
"types": "./build/index.d.ts",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@automattic/jetpack-base-styles": "^0.6.
|
|
46
|
-
"@automattic/jetpack-connection": "^0.33.
|
|
47
|
-
"@automattic/jetpack-shared-extension-utils": "^0.14.
|
|
45
|
+
"@automattic/jetpack-base-styles": "^0.6.25",
|
|
46
|
+
"@automattic/jetpack-connection": "^0.33.11",
|
|
47
|
+
"@automattic/jetpack-shared-extension-utils": "^0.14.13",
|
|
48
48
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
49
49
|
"@types/react": "18.3.1",
|
|
50
|
-
"@wordpress/api-fetch": "6.
|
|
51
|
-
"@wordpress/block-editor": "12.
|
|
52
|
-
"@wordpress/components": "27.
|
|
53
|
-
"@wordpress/compose": "6.
|
|
54
|
-
"@wordpress/data": "9.
|
|
55
|
-
"@wordpress/element": "5.
|
|
56
|
-
"@wordpress/i18n": "4.
|
|
57
|
-
"@wordpress/icons": "9.
|
|
50
|
+
"@wordpress/api-fetch": "6.54.0",
|
|
51
|
+
"@wordpress/block-editor": "12.25.0",
|
|
52
|
+
"@wordpress/components": "27.5.0",
|
|
53
|
+
"@wordpress/compose": "6.34.0",
|
|
54
|
+
"@wordpress/data": "9.27.0",
|
|
55
|
+
"@wordpress/element": "5.34.0",
|
|
56
|
+
"@wordpress/i18n": "4.57.0",
|
|
57
|
+
"@wordpress/icons": "9.48.0",
|
|
58
58
|
"classnames": "2.3.2",
|
|
59
59
|
"debug": "4.3.4",
|
|
60
60
|
"markdown-it": "14.0.0",
|
|
61
|
-
"react": "18.
|
|
62
|
-
"react-dom": "18.
|
|
61
|
+
"react": "18.3.1",
|
|
62
|
+
"react-dom": "18.3.1",
|
|
63
63
|
"turndown": "7.1.2"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import debugFactory from 'debug';
|
|
5
|
+
/*
|
|
6
|
+
* Types & constants
|
|
7
|
+
*/
|
|
8
|
+
import requestJwt from '../jwt/index.js';
|
|
9
|
+
import { AskQuestionOptionsArgProps } from './index.js';
|
|
10
|
+
import type { PromptProp } from '../types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The response data from the AI assistant when doing a sync, not-streamed question.
|
|
14
|
+
*/
|
|
15
|
+
export type ResponseData = {
|
|
16
|
+
choices: Array< {
|
|
17
|
+
message: {
|
|
18
|
+
content: string;
|
|
19
|
+
};
|
|
20
|
+
} >;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const debug = debugFactory( 'jetpack-ai-client:ask-question-sync' );
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A function that asks a question without streaming.
|
|
27
|
+
*
|
|
28
|
+
* @param {PromptProp} question - The question to ask. It can be a simple string or an array of PromptMessageItemProps objects.
|
|
29
|
+
* @param {AskQuestionOptionsArgProps} options - An optional object for additional configuration: postId, feature, model.
|
|
30
|
+
* @returns {Promise<ResponseData>} - A promise that resolves to an instance of the ResponseData
|
|
31
|
+
* @example
|
|
32
|
+
* const question = "What is the meaning of life?";
|
|
33
|
+
* const options = {
|
|
34
|
+
* feature: 'ai-featured-image',
|
|
35
|
+
* model: 'gpt-4-turbo'
|
|
36
|
+
* }
|
|
37
|
+
* askQuestionSync( question, options ).then( responseData => {
|
|
38
|
+
* // access the choices array on the response data
|
|
39
|
+
* const content = responseData.choices[ 0 ].message.content;
|
|
40
|
+
* } );
|
|
41
|
+
*/
|
|
42
|
+
export default async function askQuestionSync(
|
|
43
|
+
question: PromptProp,
|
|
44
|
+
{ postId = null, feature, model }: AskQuestionOptionsArgProps = {}
|
|
45
|
+
): Promise< ResponseData > {
|
|
46
|
+
debug( 'Asking question with no streaming: %o. options: %o', question, {
|
|
47
|
+
postId,
|
|
48
|
+
feature,
|
|
49
|
+
model,
|
|
50
|
+
} );
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The URL to the AI assistant query endpoint.
|
|
54
|
+
*/
|
|
55
|
+
const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-query';
|
|
56
|
+
|
|
57
|
+
let token = null;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
token = ( await requestJwt() ).token;
|
|
61
|
+
} catch ( error ) {
|
|
62
|
+
debug( 'Error getting token: %o', error );
|
|
63
|
+
return Promise.reject( error );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const body = {
|
|
67
|
+
question: question,
|
|
68
|
+
stream: false,
|
|
69
|
+
postId,
|
|
70
|
+
feature,
|
|
71
|
+
model,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const headers = {
|
|
75
|
+
Authorization: `Bearer ${ token }`,
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const data = await fetch( URL, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers,
|
|
82
|
+
body: JSON.stringify( body ),
|
|
83
|
+
} ).then( response => response.json() );
|
|
84
|
+
|
|
85
|
+
if ( data?.data?.status && data?.data?.status > 200 ) {
|
|
86
|
+
debug( 'Error generating prompt: %o', data );
|
|
87
|
+
return Promise.reject( data );
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return data as ResponseData;
|
|
91
|
+
}
|
|
@@ -16,7 +16,7 @@ import './style.scss';
|
|
|
16
16
|
/**
|
|
17
17
|
* Types
|
|
18
18
|
*/
|
|
19
|
-
import type { RequestingStateProp } from '../../types.js';
|
|
19
|
+
import type { RequestingErrorProps, RequestingStateProp } from '../../types.js';
|
|
20
20
|
import type { ReactElement, MouseEvent } from 'react';
|
|
21
21
|
|
|
22
22
|
type ExtensionAIControlProps = {
|
|
@@ -28,7 +28,7 @@ type ExtensionAIControlProps = {
|
|
|
28
28
|
isTransparent?: boolean;
|
|
29
29
|
state?: RequestingStateProp;
|
|
30
30
|
showGuideLine?: boolean;
|
|
31
|
-
error?:
|
|
31
|
+
error?: RequestingErrorProps;
|
|
32
32
|
requestsRemaining?: number;
|
|
33
33
|
showUpgradeMessage?: boolean;
|
|
34
34
|
wrapperRef?: React.MutableRefObject< HTMLDivElement | null >;
|
|
@@ -202,8 +202,16 @@ export function ExtensionAIControl(
|
|
|
202
202
|
);
|
|
203
203
|
|
|
204
204
|
let message = null;
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
|
|
206
|
+
if ( error?.message ) {
|
|
207
|
+
message = (
|
|
208
|
+
<ErrorMessage
|
|
209
|
+
error={ error.message }
|
|
210
|
+
code={ error.code }
|
|
211
|
+
onTryAgainClick={ tryAgainHandler }
|
|
212
|
+
onUpgradeClick={ upgradeHandler }
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
207
215
|
} else if ( showUpgradeMessage ) {
|
|
208
216
|
message = (
|
|
209
217
|
<UpgradeMessage requestsRemaining={ requestsRemaining } onUpgradeClick={ upgradeHandler } />
|
|
@@ -10,9 +10,11 @@ import classNames from 'classnames';
|
|
|
10
10
|
*/
|
|
11
11
|
import './style.scss';
|
|
12
12
|
import errorExclamation from '../../icons/error-exclamation.js';
|
|
13
|
+
import { ERROR_QUOTA_EXCEEDED } from '../../types.js';
|
|
13
14
|
/**
|
|
14
15
|
* Types
|
|
15
16
|
*/
|
|
17
|
+
import type { SuggestionErrorCode } from '../../types.js';
|
|
16
18
|
import type React from 'react';
|
|
17
19
|
|
|
18
20
|
export const MESSAGE_SEVERITY_WARNING = 'warning';
|
|
@@ -37,14 +39,19 @@ export type MessageProps = {
|
|
|
37
39
|
children: React.ReactNode;
|
|
38
40
|
};
|
|
39
41
|
|
|
42
|
+
export type OnUpgradeClick = ( event?: React.MouseEvent< HTMLButtonElement > ) => void;
|
|
43
|
+
|
|
40
44
|
export type UpgradeMessageProps = {
|
|
41
45
|
requestsRemaining: number;
|
|
42
|
-
|
|
46
|
+
severity?: MessageSeverityProp;
|
|
47
|
+
onUpgradeClick: OnUpgradeClick;
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
export type ErrorMessageProps = {
|
|
46
51
|
error?: string;
|
|
52
|
+
code?: SuggestionErrorCode;
|
|
47
53
|
onTryAgainClick: () => void;
|
|
54
|
+
onUpgradeClick: OnUpgradeClick;
|
|
48
55
|
};
|
|
49
56
|
|
|
50
57
|
const messageIconsMap = {
|
|
@@ -113,10 +120,17 @@ export function GuidelineMessage(): React.ReactElement {
|
|
|
113
120
|
*/
|
|
114
121
|
export function UpgradeMessage( {
|
|
115
122
|
requestsRemaining,
|
|
123
|
+
severity,
|
|
116
124
|
onUpgradeClick,
|
|
117
125
|
}: UpgradeMessageProps ): React.ReactElement {
|
|
126
|
+
let messageSeverity = severity;
|
|
127
|
+
|
|
128
|
+
if ( messageSeverity == null ) {
|
|
129
|
+
messageSeverity = requestsRemaining > 0 ? MESSAGE_SEVERITY_INFO : MESSAGE_SEVERITY_WARNING;
|
|
130
|
+
}
|
|
131
|
+
|
|
118
132
|
return (
|
|
119
|
-
<Message severity={
|
|
133
|
+
<Message severity={ messageSeverity }>
|
|
120
134
|
<span>
|
|
121
135
|
{ sprintf(
|
|
122
136
|
// translators: %1$d: number of requests remaining
|
|
@@ -137,7 +151,12 @@ export function UpgradeMessage( {
|
|
|
137
151
|
* @param {number} requestsRemaining - Number of requests remaining.
|
|
138
152
|
* @returns {React.ReactElement } - Message component.
|
|
139
153
|
*/
|
|
140
|
-
export function ErrorMessage( {
|
|
154
|
+
export function ErrorMessage( {
|
|
155
|
+
error,
|
|
156
|
+
code,
|
|
157
|
+
onTryAgainClick,
|
|
158
|
+
onUpgradeClick,
|
|
159
|
+
}: ErrorMessageProps ): React.ReactElement {
|
|
141
160
|
const errorMessage = error || __( 'Something went wrong', 'jetpack-ai-client' );
|
|
142
161
|
|
|
143
162
|
return (
|
|
@@ -149,9 +168,15 @@ export function ErrorMessage( { error, onTryAgainClick }: ErrorMessageProps ): R
|
|
|
149
168
|
errorMessage
|
|
150
169
|
) }
|
|
151
170
|
</span>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
171
|
+
{ code === ERROR_QUOTA_EXCEEDED ? (
|
|
172
|
+
<Button variant="link" onClick={ onUpgradeClick }>
|
|
173
|
+
{ __( 'Upgrade now', 'jetpack-ai-client' ) }
|
|
174
|
+
</Button>
|
|
175
|
+
) : (
|
|
176
|
+
<Button variant="link" onClick={ onTryAgainClick }>
|
|
177
|
+
{ __( 'Try again', 'jetpack-ai-client' ) }
|
|
178
|
+
</Button>
|
|
179
|
+
) }
|
|
155
180
|
</Message>
|
|
156
181
|
);
|
|
157
182
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
4
5
|
import debugFactory from 'debug';
|
|
5
6
|
/**
|
|
6
7
|
* Internal dependencies
|
|
7
8
|
*/
|
|
9
|
+
import askQuestionSync from '../../ask-question/sync.js';
|
|
8
10
|
import requestJwt from '../../jwt/index.js';
|
|
9
11
|
|
|
10
12
|
const debug = debugFactory( 'ai-client:use-image-generator' );
|
|
@@ -30,7 +32,7 @@ const truncateContent = ( content: string, currentPromptLength: number ): string
|
|
|
30
32
|
* @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated.
|
|
31
33
|
* @returns {string} the prompt string
|
|
32
34
|
*/
|
|
33
|
-
const
|
|
35
|
+
const getDalleImageGenerationPrompt = ( postContent: string, userPrompt?: string ): string => {
|
|
34
36
|
/**
|
|
35
37
|
* If the user provide some custom prompt for the image generation,
|
|
36
38
|
* we will use it, add the post content as additional context and
|
|
@@ -78,7 +80,160 @@ This is the post content:
|
|
|
78
80
|
return imageGenerationPrompt + truncateContent( postContent, imageGenerationPrompt.length );
|
|
79
81
|
};
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Create the Stable Diffusion pre-processing prompt based on the provided context.
|
|
85
|
+
* @param {string} postContent - the content of the post.
|
|
86
|
+
* @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated.
|
|
87
|
+
* @returns {string} the prompt string to be fed to the AI Assistant model.
|
|
88
|
+
*/
|
|
89
|
+
const getStableDiffusionPreProcessingPrompt = (
|
|
90
|
+
postContent: string,
|
|
91
|
+
userPrompt?: string
|
|
92
|
+
): string => {
|
|
93
|
+
/**
|
|
94
|
+
* If the user provide some custom prompt for the image generation,
|
|
95
|
+
* we will use it and add the post content as additional context.
|
|
96
|
+
*/
|
|
97
|
+
if ( userPrompt ) {
|
|
98
|
+
const preProcessingPrompt = `I need a Stable Diffusion prompt to generate a featured image for a blog post based on this user-provided image description:
|
|
99
|
+
|
|
100
|
+
${ userPrompt.length > 1000 ? userPrompt.substring( 0, 1000 ) : userPrompt }
|
|
101
|
+
|
|
102
|
+
The image should be a photo. Make sure you highlight the main suject of the image description, and include brief details about the light and style of the image.
|
|
103
|
+
Include a request to use high resolution and produce a highly detailed image, with sharp focus.
|
|
104
|
+
Return just the prompt, without comments.
|
|
105
|
+
|
|
106
|
+
For additional context, this is the post content:
|
|
107
|
+
|
|
108
|
+
`;
|
|
109
|
+
// truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
|
|
110
|
+
return preProcessingPrompt + truncateContent( postContent, preProcessingPrompt.length );
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* When the user does not provide a custom prompt, we will use the
|
|
115
|
+
* standard one, based solely on the post content.
|
|
116
|
+
*/
|
|
117
|
+
const preProcessingPrompt = `I need a Stable Diffusion prompt to generate a featured image for a blog post with the following content.
|
|
118
|
+
The image should be a photo. Make sure you highlight the main suject of the content, and include brief details about the light and style of the image.
|
|
119
|
+
Include a request to use high resolution and produce a highly detailed image, with sharp focus.
|
|
120
|
+
Return just the prompt, without comments. The content is:
|
|
121
|
+
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
// truncating the content so the whole prompt is not longer than 4000 characters, the model limit.
|
|
125
|
+
return preProcessingPrompt + truncateContent( postContent, preProcessingPrompt.length );
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Uses the Jetpack AI query endpoint to produce a prompt for the stable diffusion model.
|
|
130
|
+
* @param {string} postContent - the content of the post.
|
|
131
|
+
* @param {string} userPrompt - the user prompt for the image generation, if provided. Max length is 1000 characters, will be truncated
|
|
132
|
+
* @param {string} feature - the feature to be used for the image generation.
|
|
133
|
+
* @returns {string} the prompt string to be used on stable diffusion image generation.
|
|
134
|
+
*/
|
|
135
|
+
const getStableDiffusionImageGenerationPrompt = async (
|
|
136
|
+
postContent: string,
|
|
137
|
+
userPrompt?: string,
|
|
138
|
+
feature?: string
|
|
139
|
+
): Promise< string > => {
|
|
140
|
+
const prompt = getStableDiffusionPreProcessingPrompt( postContent, userPrompt );
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Request the prompt on the AI Assistant endpoint
|
|
144
|
+
*/
|
|
145
|
+
const data = await askQuestionSync( prompt, { feature } );
|
|
146
|
+
|
|
147
|
+
return data.choices?.[ 0 ]?.message?.content;
|
|
148
|
+
};
|
|
149
|
+
|
|
81
150
|
const useImageGenerator = () => {
|
|
151
|
+
const generateImageWithStableDiffusion = async function ( {
|
|
152
|
+
feature,
|
|
153
|
+
postContent,
|
|
154
|
+
userPrompt,
|
|
155
|
+
}: {
|
|
156
|
+
feature: string;
|
|
157
|
+
postContent: string;
|
|
158
|
+
userPrompt?: string;
|
|
159
|
+
} ): Promise< { data: Array< { [ key: string ]: string } > } > {
|
|
160
|
+
let token = null;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
token = await requestJwt();
|
|
164
|
+
} catch ( error ) {
|
|
165
|
+
debug( 'Error getting token: %o', error );
|
|
166
|
+
return Promise.reject( error );
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
debug( 'Generating image with Stable Diffusion' );
|
|
171
|
+
|
|
172
|
+
const prompt = await getStableDiffusionImageGenerationPrompt(
|
|
173
|
+
postContent,
|
|
174
|
+
userPrompt,
|
|
175
|
+
feature
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const data = {
|
|
179
|
+
prompt,
|
|
180
|
+
style: 'photographic',
|
|
181
|
+
token: token.token,
|
|
182
|
+
width: 1024,
|
|
183
|
+
height: 768,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const response = await fetch(
|
|
187
|
+
`https://public-api.wordpress.com/wpcom/v2/sites/${ token.blogId }/ai-image`,
|
|
188
|
+
{
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers: {
|
|
191
|
+
'Content-Type': 'application/json',
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify( data ),
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if ( ! response?.ok ) {
|
|
198
|
+
debug( 'Error generating image: %o', response );
|
|
199
|
+
return Promise.reject( {
|
|
200
|
+
data: {
|
|
201
|
+
status: response.status,
|
|
202
|
+
},
|
|
203
|
+
message: __( 'Error generating image. Please try again later.', 'jetpack-ai-client' ),
|
|
204
|
+
} );
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const blob = await response.blob();
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Convert the blob to base64 to keep the same format as the Dalle API.
|
|
211
|
+
*/
|
|
212
|
+
const base64 = await new Promise( ( resolve, reject ) => {
|
|
213
|
+
const reader = new FileReader();
|
|
214
|
+
reader.onloadend = () => {
|
|
215
|
+
const base64data = reader.result as string;
|
|
216
|
+
return resolve( base64data.replace( /^data:image\/(png|jpg);base64,/, '' ) );
|
|
217
|
+
};
|
|
218
|
+
reader.onerror = reject;
|
|
219
|
+
reader.readAsDataURL( blob );
|
|
220
|
+
} );
|
|
221
|
+
|
|
222
|
+
// Return the Dalle API format
|
|
223
|
+
return {
|
|
224
|
+
data: [
|
|
225
|
+
{
|
|
226
|
+
b64_json: base64 as string,
|
|
227
|
+
revised_prompt: prompt,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
} catch ( error ) {
|
|
232
|
+
debug( 'Error generating image: %o', error );
|
|
233
|
+
return Promise.reject( error );
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
82
237
|
const generateImage = async function ( {
|
|
83
238
|
feature,
|
|
84
239
|
postContent,
|
|
@@ -102,7 +257,7 @@ const useImageGenerator = () => {
|
|
|
102
257
|
try {
|
|
103
258
|
debug( 'Generating image' );
|
|
104
259
|
|
|
105
|
-
const imageGenerationPrompt =
|
|
260
|
+
const imageGenerationPrompt = getDalleImageGenerationPrompt( postContent, userPrompt );
|
|
106
261
|
|
|
107
262
|
const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-image';
|
|
108
263
|
|
|
@@ -138,6 +293,7 @@ const useImageGenerator = () => {
|
|
|
138
293
|
|
|
139
294
|
return {
|
|
140
295
|
generateImage,
|
|
296
|
+
generateImageWithStableDiffusion,
|
|
141
297
|
};
|
|
142
298
|
};
|
|
143
299
|
|
package/src/libs/index.ts
CHANGED
|
@@ -11,12 +11,14 @@ import type { Fix as HTMLFix } from './markdown-to-html.js';
|
|
|
11
11
|
const defaultMarkdownConverter = new MarkdownToHTML();
|
|
12
12
|
const defaultHTMLConverter = new HTMLToMarkdown();
|
|
13
13
|
|
|
14
|
+
export type RenderHTMLRules = 'all' | Array< HTMLFix >;
|
|
15
|
+
|
|
14
16
|
const renderHTMLFromMarkdown = ( {
|
|
15
17
|
content,
|
|
16
18
|
rules = 'all',
|
|
17
19
|
}: {
|
|
18
20
|
content: string;
|
|
19
|
-
rules?:
|
|
21
|
+
rules?: RenderHTMLRules;
|
|
20
22
|
} ) => {
|
|
21
23
|
return defaultMarkdownConverter.render( { content, rules } );
|
|
22
24
|
};
|
package/src/types.ts
CHANGED