@automattic/jetpack-ai-client 0.26.3 → 0.27.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 +11 -0
- package/build/chrome-ai/factory.js +19 -11
- package/build/chrome-ai/suggestions.d.ts +4 -1
- package/build/chrome-ai/suggestions.js +45 -5
- package/build/components/ai-image/components/ai-image-modal.d.ts +0 -1
- package/build/components/ai-image/featured-image.js +11 -11
- package/build/components/ai-image/general-purpose-image.js +23 -8
- package/build/hooks/use-post-content.d.ts +4 -4
- package/build/hooks/use-post-content.js +16 -6
- package/build/libs/index.d.ts +2 -0
- package/build/libs/index.js +2 -0
- package/build/libs/open-block-sidebar.d.ts +6 -0
- package/build/libs/open-block-sidebar.js +20 -0
- package/build/libs/show-ai-assistant-section.d.ts +1 -0
- package/build/libs/show-ai-assistant-section.js +21 -0
- package/build/types.d.ts +16 -0
- package/package.json +19 -19
- package/src/chrome-ai/factory.ts +26 -13
- package/src/chrome-ai/suggestions.ts +59 -5
- package/src/components/ai-image/components/ai-image-modal.tsx +0 -1
- package/src/components/ai-image/featured-image.tsx +10 -11
- package/src/components/ai-image/general-purpose-image.tsx +44 -36
- package/src/hooks/use-post-content.ts +21 -8
- package/src/libs/index.ts +4 -0
- package/src/libs/open-block-sidebar.ts +27 -0
- package/src/libs/show-ai-assistant-section.ts +28 -0
- package/src/types.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.27.0] - 2025-03-03
|
|
9
|
+
### Added
|
|
10
|
+
- AI Client: Move openBlockSidebar utility function. [#42016]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- AI Assistant: Add experimental functionality to test Chrome's built-in AI API with the AI excerpt. [#41922]
|
|
14
|
+
- AI Client: Move showAiAssistantSection function to AI Client. [#42158]
|
|
15
|
+
- AI Client: Refactor usePostContent hook to expose isEditedPostEmpty. [#42149]
|
|
16
|
+
- Update package dependencies. [#42163]
|
|
17
|
+
|
|
8
18
|
## [0.26.3] - 2025-02-24
|
|
9
19
|
### Changed
|
|
10
20
|
- Update package dependencies. [#41955]
|
|
@@ -532,6 +542,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
532
542
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
533
543
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
534
544
|
|
|
545
|
+
[0.27.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.3...v0.27.0
|
|
535
546
|
[0.26.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.2...v0.26.3
|
|
536
547
|
[0.26.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.1...v0.26.2
|
|
537
548
|
[0.26.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.0...v0.26.1
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { getJetpackExtensionAvailability } from '@automattic/jetpack-shared-extension-utils';
|
|
2
|
-
import { PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
3
|
-
//PROMPT_TYPE_SUMMARIZE,
|
|
4
|
-
} from '../constants.js';
|
|
2
|
+
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.js';
|
|
5
3
|
import ChromeAISuggestionsEventSource from './suggestions.js';
|
|
6
4
|
/**
|
|
7
5
|
* Check for the feature flag.
|
|
@@ -26,6 +24,8 @@ export default async function ChromeAIFactory(promptArg) {
|
|
|
26
24
|
language: '',
|
|
27
25
|
};
|
|
28
26
|
let promptType = '';
|
|
27
|
+
let tone = null;
|
|
28
|
+
let wordCount = null;
|
|
29
29
|
if (Array.isArray(promptArg)) {
|
|
30
30
|
for (let i = 0; i < promptArg.length; i++) {
|
|
31
31
|
const prompt = promptArg[i];
|
|
@@ -45,6 +45,12 @@ export default async function ChromeAIFactory(promptArg) {
|
|
|
45
45
|
if (promptContext.content) {
|
|
46
46
|
context.content = promptContext.content;
|
|
47
47
|
}
|
|
48
|
+
if (promptContext.tone) {
|
|
49
|
+
tone = promptContext.tone;
|
|
50
|
+
}
|
|
51
|
+
if (promptContext.words) {
|
|
52
|
+
wordCount = promptContext.words;
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
if (promptType.startsWith('ai-assistant-change-language')) {
|
|
@@ -84,16 +90,18 @@ export default async function ChromeAIFactory(promptArg) {
|
|
|
84
90
|
});
|
|
85
91
|
return chromeAI;
|
|
86
92
|
}
|
|
87
|
-
// TODO
|
|
88
|
-
if (promptType.startsWith('ai-
|
|
89
|
-
|
|
93
|
+
// TODO: consider also using ChromeAI for ai-assistant-summarize
|
|
94
|
+
if (promptType.startsWith('ai-content-lens')) {
|
|
95
|
+
const summaryOpts = {
|
|
96
|
+
tone: tone,
|
|
97
|
+
wordCount: wordCount,
|
|
98
|
+
};
|
|
99
|
+
// TODO: detect if the content is in English and fallback if it's not
|
|
90
100
|
return new ChromeAISuggestionsEventSource({
|
|
91
|
-
content:
|
|
101
|
+
content: context.content,
|
|
92
102
|
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
93
|
-
options:
|
|
94
|
-
}
|
|
95
|
-
*/
|
|
96
|
-
return false;
|
|
103
|
+
options: summaryOpts,
|
|
104
|
+
});
|
|
97
105
|
}
|
|
98
106
|
return false;
|
|
99
107
|
}
|
|
@@ -8,6 +8,8 @@ type ChromeAISuggestionsEventSourceConstructorArgs = {
|
|
|
8
8
|
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
9
9
|
sourceLanguage?: string;
|
|
10
10
|
targetLanguage?: string;
|
|
11
|
+
tone?: string;
|
|
12
|
+
wordCount?: number;
|
|
11
13
|
functions?: Array<object>;
|
|
12
14
|
model?: AiModelTypeProp;
|
|
13
15
|
};
|
|
@@ -30,6 +32,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
30
32
|
processEvent(e: EventSourceMessage): void;
|
|
31
33
|
processErrorEvent(e: any): void;
|
|
32
34
|
translate(text: string, target: string, source?: string): Promise<void>;
|
|
33
|
-
|
|
35
|
+
private getSummarizerOptions;
|
|
36
|
+
summarize(text: string, tone?: string, wordCount?: number): Promise<void>;
|
|
34
37
|
}
|
|
35
38
|
export {};
|
|
@@ -24,7 +24,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
24
24
|
this.translate(content, options.targetLanguage, options.sourceLanguage);
|
|
25
25
|
}
|
|
26
26
|
if (promptType === PROMPT_TYPE_SUMMARIZE) {
|
|
27
|
-
this.summarize(content);
|
|
27
|
+
this.summarize(content, options.tone, options.wordCount);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
async initEventSource() { }
|
|
@@ -39,7 +39,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
39
39
|
this.processErrorEvent(err);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
if (e.event === 'translation') {
|
|
42
|
+
if (e.event === 'translation' || e.event === 'summary') {
|
|
43
43
|
this.dispatchEvent(new CustomEvent('suggestion', { detail: data.message }));
|
|
44
44
|
}
|
|
45
45
|
if (data.complete) {
|
|
@@ -80,8 +80,48 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
80
80
|
this.processErrorEvent(error);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// Helper function to format summarizer options
|
|
84
|
+
getSummarizerOptions(tone, wordCount) {
|
|
85
|
+
let sharedContext = `The summary you write should contain approximately ${wordCount ?? 50} words long. Strive for precision in word count without compromising clarity and significance`;
|
|
86
|
+
if (tone) {
|
|
87
|
+
sharedContext += `\n - Write with a ${tone} tone.\n`;
|
|
88
|
+
}
|
|
89
|
+
const options = {
|
|
90
|
+
sharedContext: sharedContext,
|
|
91
|
+
type: 'teaser',
|
|
92
|
+
format: 'plain-text',
|
|
93
|
+
length: 'medium',
|
|
94
|
+
};
|
|
95
|
+
return options;
|
|
96
|
+
}
|
|
97
|
+
// use the Chrome AI summarizer
|
|
98
|
+
async summarize(text, tone, wordCount) {
|
|
99
|
+
if (!('ai' in self) || !('summarizer' in self.ai)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const available = (await self.ai.summarizer.capabilities()).available;
|
|
103
|
+
if (available === 'no') {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const options = this.getSummarizerOptions(tone, wordCount);
|
|
107
|
+
const summarizer = await self.ai.summarizer.create(options);
|
|
108
|
+
if (available === 'after-download') {
|
|
109
|
+
await summarizer.ready;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const context = `Write with a ${tone} tone.`;
|
|
113
|
+
const summary = await summarizer.summarize(text, { context: context });
|
|
114
|
+
this.processEvent({
|
|
115
|
+
id: '',
|
|
116
|
+
event: 'summary',
|
|
117
|
+
data: JSON.stringify({
|
|
118
|
+
message: summary,
|
|
119
|
+
complete: true,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.processErrorEvent(error);
|
|
125
|
+
}
|
|
86
126
|
}
|
|
87
127
|
}
|
|
@@ -30,7 +30,7 @@ const debug = debugFactory('jetpack-ai-client:featured-image');
|
|
|
30
30
|
export default function FeaturedImage({ busy, disabled, placement, onClose = () => { }, }) {
|
|
31
31
|
const [isFeaturedImageModalVisible, setIsFeaturedImageModalVisible] = useState(placement === PLACEMENT_MEDIA_SOURCE_DROPDOWN);
|
|
32
32
|
const siteType = useSiteType();
|
|
33
|
-
const
|
|
33
|
+
const { getPostContent, isEditedPostEmpty } = usePostContent();
|
|
34
34
|
const { postTitle, postFeaturedMediaId, isEditorPanelOpened } = useSelect(select => {
|
|
35
35
|
return {
|
|
36
36
|
postTitle: select(editorStore).getEditedPostAttribute('title'),
|
|
@@ -80,9 +80,9 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
80
80
|
* Handle the guess style for the image. It is reworked here to include the post content.
|
|
81
81
|
*/
|
|
82
82
|
const handleGuessStyle = useCallback(userPrompt => {
|
|
83
|
-
const content = postTitle + '\n\n' +
|
|
83
|
+
const content = postTitle + '\n\n' + getPostContent();
|
|
84
84
|
return guessStyle(userPrompt, 'featured-image-guess-style', content);
|
|
85
|
-
}, [
|
|
85
|
+
}, [postTitle, getPostContent, guessStyle]);
|
|
86
86
|
const handleGenerate = useCallback(({ userPrompt, style, }) => {
|
|
87
87
|
// track the generate image event
|
|
88
88
|
recordEvent('jetpack_ai_featured_image_generation_generate_image', {
|
|
@@ -95,7 +95,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
95
95
|
setIsFeaturedImageModalVisible(true);
|
|
96
96
|
return processImageGeneration({
|
|
97
97
|
userPrompt,
|
|
98
|
-
postContent: postTitle + '\n\n' +
|
|
98
|
+
postContent: postTitle + '\n\n' + getPostContent(),
|
|
99
99
|
notEnoughRequests,
|
|
100
100
|
style,
|
|
101
101
|
}).catch(error => {
|
|
@@ -113,7 +113,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
113
113
|
featuredImageActiveModel,
|
|
114
114
|
siteType,
|
|
115
115
|
processImageGeneration,
|
|
116
|
-
|
|
116
|
+
getPostContent,
|
|
117
117
|
notEnoughRequests,
|
|
118
118
|
postTitle,
|
|
119
119
|
]);
|
|
@@ -138,7 +138,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
138
138
|
setCurrent(() => images.length);
|
|
139
139
|
processImageGeneration({
|
|
140
140
|
userPrompt,
|
|
141
|
-
postContent: postTitle + '\n\n' +
|
|
141
|
+
postContent: postTitle + '\n\n' + getPostContent(),
|
|
142
142
|
notEnoughRequests,
|
|
143
143
|
style,
|
|
144
144
|
}).catch(error => {
|
|
@@ -159,7 +159,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
159
159
|
setCurrent,
|
|
160
160
|
processImageGeneration,
|
|
161
161
|
postTitle,
|
|
162
|
-
|
|
162
|
+
getPostContent,
|
|
163
163
|
notEnoughRequests,
|
|
164
164
|
images,
|
|
165
165
|
]);
|
|
@@ -173,7 +173,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
173
173
|
});
|
|
174
174
|
processImageGeneration({
|
|
175
175
|
userPrompt,
|
|
176
|
-
postContent: postTitle + '\n\n' +
|
|
176
|
+
postContent: postTitle + '\n\n' + getPostContent(),
|
|
177
177
|
notEnoughRequests,
|
|
178
178
|
style,
|
|
179
179
|
}).catch(error => {
|
|
@@ -191,7 +191,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
191
191
|
featuredImageActiveModel,
|
|
192
192
|
siteType,
|
|
193
193
|
processImageGeneration,
|
|
194
|
-
|
|
194
|
+
getPostContent,
|
|
195
195
|
notEnoughRequests,
|
|
196
196
|
postTitle,
|
|
197
197
|
]);
|
|
@@ -261,7 +261,7 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
261
261
|
]);
|
|
262
262
|
const generateAgainText = __('Generate another image', 'jetpack-ai-client');
|
|
263
263
|
const generateText = __('Generate', 'jetpack-ai-client');
|
|
264
|
-
const hasContent =
|
|
264
|
+
const hasContent = !isEditedPostEmpty() || postTitle.trim?.() ? true : false;
|
|
265
265
|
const hasPrompt = hasContent ? prompt.length >= 0 : prompt.length >= 3;
|
|
266
266
|
const disableInput = notEnoughRequests || currentPointer?.generating || requireUpgrade;
|
|
267
267
|
const disableAction = disableInput || (!hasContent && !hasPrompt);
|
|
@@ -274,5 +274,5 @@ export default function FeaturedImage({ busy, disabled, placement, onClose = ()
|
|
|
274
274
|
currentImage?.generating ||
|
|
275
275
|
currentImage?.libraryId === postFeaturedMediaId, children: __('Set as featured image', 'jetpack-ai-client') }));
|
|
276
276
|
return (_jsxs(_Fragment, { children: [(placement === PLACEMENT_JETPACK_SIDEBAR ||
|
|
277
|
-
placement === PLACEMENT_DOCUMENT_SETTINGS) && (_jsxs(_Fragment, { children: [_jsx("p", { className: "jetpack-ai-assistant__help-text", children: __('Based on your post content.', 'jetpack-ai-client') }), _jsx(Button, { onClick: handleModalOpen, isBusy: busy, disabled: disabled || notEnoughRequests, variant: "secondary", __next40pxDefaultSize: true, children: __('Generate image', 'jetpack-ai-client') })] })), _jsx(AiImageModal, {
|
|
277
|
+
placement === PLACEMENT_DOCUMENT_SETTINGS) && (_jsxs(_Fragment, { children: [_jsx("p", { className: "jetpack-ai-assistant__help-text", children: __('Based on your post content.', 'jetpack-ai-client') }), _jsx(Button, { onClick: handleModalOpen, isBusy: busy, disabled: disabled || notEnoughRequests, variant: "secondary", __next40pxDefaultSize: true, children: __('Generate image', 'jetpack-ai-client') })] })), _jsx(AiImageModal, { autoStart: hasContent && !postFeaturedMediaId, autoStartAction: handleFirstGenerate, images: images, currentIndex: current, title: __('Generate a featured image with AI', 'jetpack-ai-client'), cost: featuredImageCost, open: isFeaturedImageModalVisible, placement: placement, onClose: handleModalClose, onTryAgain: handleTryAgain, onGenerate: pointer?.current > 0 || postFeaturedMediaId ? handleRegenerate : handleGenerate, generating: currentPointer?.generating, notEnoughRequests: notEnoughRequests, requireUpgrade: requireUpgrade, upgradeDescription: upgradeDescription, currentLimit: requestsLimit, currentUsage: requestsCount, isUnlimited: isUnlimited, hasError: Boolean(currentPointer?.error), handlePreviousImage: handlePreviousImage, handleNextImage: handleNextImage, acceptButton: acceptButton, generateButtonLabel: pointer?.current > 0 ? generateAgainText : generateText, instructionsPlaceholder: __("Describe the featured image you'd like to create and select a style.", 'jetpack-ai-client'), imageStyles: imageStyles, onGuessStyle: handleGuessStyle, prompt: prompt, setPrompt: setPrompt, initialStyle: requestStyle, inputDisabled: disableInput, actionDisabled: disableAction })] }));
|
|
278
278
|
}
|
|
@@ -27,7 +27,7 @@ const debug = debugFactory('jetpack-ai:general-purpose-image');
|
|
|
27
27
|
export default function GeneralPurposeImage({ placement, onClose = () => { }, onSetImage = () => { }, }) {
|
|
28
28
|
const [isFeaturedImageModalVisible, setIsFeaturedImageModalVisible] = useState(true);
|
|
29
29
|
const siteType = useSiteType();
|
|
30
|
-
const
|
|
30
|
+
const { getPostContent } = usePostContent();
|
|
31
31
|
const { saveToMediaLibrary } = useSaveToMediaLibrary();
|
|
32
32
|
const { tracks } = useAnalytics();
|
|
33
33
|
const { recordEvent } = tracks;
|
|
@@ -64,7 +64,12 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
64
64
|
site_type: siteType,
|
|
65
65
|
style,
|
|
66
66
|
});
|
|
67
|
-
processImageGeneration({
|
|
67
|
+
processImageGeneration({
|
|
68
|
+
userPrompt,
|
|
69
|
+
postContent: getPostContent(),
|
|
70
|
+
notEnoughRequests,
|
|
71
|
+
style,
|
|
72
|
+
}).catch(error => {
|
|
68
73
|
recordEvent('jetpack_ai_general_image_generation_error', {
|
|
69
74
|
placement,
|
|
70
75
|
error: error?.message,
|
|
@@ -79,7 +84,7 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
79
84
|
generalImageActiveModel,
|
|
80
85
|
siteType,
|
|
81
86
|
processImageGeneration,
|
|
82
|
-
|
|
87
|
+
getPostContent,
|
|
83
88
|
notEnoughRequests,
|
|
84
89
|
]);
|
|
85
90
|
const handleRegenerate = useCallback(({ userPrompt, style }) => {
|
|
@@ -92,7 +97,12 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
92
97
|
style,
|
|
93
98
|
});
|
|
94
99
|
setCurrent(crrt => crrt + 1);
|
|
95
|
-
processImageGeneration({
|
|
100
|
+
processImageGeneration({
|
|
101
|
+
userPrompt,
|
|
102
|
+
postContent: getPostContent(),
|
|
103
|
+
notEnoughRequests,
|
|
104
|
+
style,
|
|
105
|
+
}).catch(error => {
|
|
96
106
|
recordEvent('jetpack_ai_general_image_generation_error', {
|
|
97
107
|
placement,
|
|
98
108
|
error: error?.message,
|
|
@@ -106,7 +116,7 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
106
116
|
generalImageActiveModel,
|
|
107
117
|
siteType,
|
|
108
118
|
processImageGeneration,
|
|
109
|
-
|
|
119
|
+
getPostContent,
|
|
110
120
|
notEnoughRequests,
|
|
111
121
|
setCurrent,
|
|
112
122
|
]);
|
|
@@ -119,7 +129,12 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
119
129
|
site_type: siteType,
|
|
120
130
|
style,
|
|
121
131
|
});
|
|
122
|
-
processImageGeneration({
|
|
132
|
+
processImageGeneration({
|
|
133
|
+
userPrompt,
|
|
134
|
+
postContent: getPostContent(),
|
|
135
|
+
notEnoughRequests,
|
|
136
|
+
style,
|
|
137
|
+
}).catch(error => {
|
|
123
138
|
recordEvent('jetpack_ai_general_image_generation_error', {
|
|
124
139
|
placement,
|
|
125
140
|
error: error?.message,
|
|
@@ -133,7 +148,7 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
133
148
|
generalImageActiveModel,
|
|
134
149
|
siteType,
|
|
135
150
|
processImageGeneration,
|
|
136
|
-
|
|
151
|
+
getPostContent,
|
|
137
152
|
notEnoughRequests,
|
|
138
153
|
]);
|
|
139
154
|
const handleAccept = useCallback(() => {
|
|
@@ -180,5 +195,5 @@ export default function GeneralPurposeImage({ placement, onClose = () => { }, on
|
|
|
180
195
|
__("Image generation costs %d requests per image. You don't have enough requests to generate another image.", 'jetpack-ai-client'), generalImageCost)
|
|
181
196
|
: null;
|
|
182
197
|
const acceptButton = (_jsx(Button, { onClick: handleAccept, variant: "primary", disabled: !currentImage?.image || currentImage?.generating, children: __('Insert image', 'jetpack-ai-client') }));
|
|
183
|
-
return (_jsx(AiImageModal, {
|
|
198
|
+
return (_jsx(AiImageModal, { images: images, currentIndex: current, title: __('Generate an image with AI', 'jetpack-ai-client'), cost: generalImageCost, open: isFeaturedImageModalVisible, placement: placement, onClose: handleModalClose, onTryAgain: handleTryAgain, onGenerate: pointer?.current > 0 ? handleRegenerate : handleGenerate, generating: currentPointer?.generating, notEnoughRequests: notEnoughRequests, requireUpgrade: requireUpgrade, upgradeDescription: upgradeDescription, currentLimit: requestsLimit, currentUsage: requestsCount, isUnlimited: isUnlimited, hasError: Boolean(currentPointer?.error), handlePreviousImage: handlePreviousImage, handleNextImage: handleNextImage, acceptButton: acceptButton, generateButtonLabel: pointer?.current > 0 ? generateAgainText : generateText, instructionsPlaceholder: __("Describe the image you'd like to create and select a style.", 'jetpack-ai-client'), imageStyles: imageStyles, onGuessStyle: guessStyle, prompt: prompt, setPrompt: setPrompt, inputDisabled: disableInput, actionDisabled: disableAction }));
|
|
184
199
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
declare const usePostContent: () => {
|
|
2
|
+
getPostContent: () => string;
|
|
3
|
+
isEditedPostEmpty: () => boolean;
|
|
4
|
+
};
|
|
5
5
|
export default usePostContent;
|
|
@@ -3,18 +3,28 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { serialize } from '@wordpress/blocks';
|
|
5
5
|
import { useSelect } from '@wordpress/data';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
import { renderMarkdownFromHTML } from '../libs/markdown/index.js';
|
|
6
|
+
import { store as editorStore } from '@wordpress/editor';
|
|
7
|
+
import { useCallback } from '@wordpress/element';
|
|
10
8
|
/**
|
|
11
9
|
* Internal dependencies
|
|
12
10
|
*/
|
|
11
|
+
import { renderMarkdownFromHTML } from '../libs/markdown/index.js';
|
|
13
12
|
/*
|
|
14
13
|
* Simple helper to get the post content as markdown
|
|
15
14
|
*/
|
|
16
15
|
const usePostContent = () => {
|
|
17
|
-
const
|
|
18
|
-
|
|
16
|
+
const { getBlocks, isEditedPostEmpty } = useSelect(select => {
|
|
17
|
+
const blockEditorSelect = select('core/block-editor');
|
|
18
|
+
const coreEditorSelect = select(editorStore);
|
|
19
|
+
return {
|
|
20
|
+
getBlocks: blockEditorSelect.getBlocks,
|
|
21
|
+
isEditedPostEmpty: coreEditorSelect.isEditedPostEmpty,
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
const getPostContent = useCallback(() => {
|
|
25
|
+
const blocks = getBlocks();
|
|
26
|
+
return blocks?.length ? renderMarkdownFromHTML({ content: serialize(blocks) }) : '';
|
|
27
|
+
}, [getBlocks]);
|
|
28
|
+
return { getPostContent, isEditedPostEmpty };
|
|
19
29
|
};
|
|
20
30
|
export default usePostContent;
|
package/build/libs/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML, fixes, } from './markdown/index.js';
|
|
2
2
|
export type { RenderHTMLRules } from './markdown/index.js';
|
|
3
3
|
export { mapActionToHumanText } from './map-action-to-human-text.js';
|
|
4
|
+
export { openBlockSidebar } from './open-block-sidebar.js';
|
|
5
|
+
export { showAiAssistantSection } from './show-ai-assistant-section.js';
|
package/build/libs/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML, fixes, } from './markdown/index.js';
|
|
2
2
|
export { mapActionToHumanText } from './map-action-to-human-text.js';
|
|
3
|
+
export { openBlockSidebar } from './open-block-sidebar.js';
|
|
4
|
+
export { showAiAssistantSection } from './show-ai-assistant-section.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
5
|
+
import { dispatch } from '@wordpress/data';
|
|
6
|
+
/**
|
|
7
|
+
* Open the block sidebar for the given client ID.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} clientId - The client ID of the block to open the sidebar for.
|
|
10
|
+
*/
|
|
11
|
+
export function openBlockSidebar(clientId) {
|
|
12
|
+
if (!clientId) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const { selectBlock } = dispatch(blockEditorStore);
|
|
16
|
+
const { enableComplementaryArea } = dispatch('core/interface');
|
|
17
|
+
selectBlock(clientId);
|
|
18
|
+
// This only works for the post editor, as the SEO Assistant is only available there
|
|
19
|
+
enableComplementaryArea('core/edit-post', 'edit-post/block');
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const showAiAssistantSection: () => Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { dispatch } from '@wordpress/data';
|
|
5
|
+
export const showAiAssistantSection = async () => {
|
|
6
|
+
const { clearSelectedBlock } = dispatch('core/block-editor');
|
|
7
|
+
const { enableComplementaryArea } = dispatch('core/interface');
|
|
8
|
+
// Clear any block selection, because selected blocks have precedence on settings sidebar
|
|
9
|
+
clearSelectedBlock();
|
|
10
|
+
await enableComplementaryArea('core/edit-post', 'jetpack-sidebar/jetpack');
|
|
11
|
+
const panel = document.querySelector('.jetpack-ai-assistant-panel');
|
|
12
|
+
const isAlreadyOpen = panel?.classList.contains('is-opened');
|
|
13
|
+
const button = panel?.querySelector('h2 button');
|
|
14
|
+
if (isAlreadyOpen) {
|
|
15
|
+
// Close it before opening it to ensure the content is scrolled to view
|
|
16
|
+
button?.click();
|
|
17
|
+
}
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
button?.click();
|
|
20
|
+
}, 50);
|
|
21
|
+
};
|
package/build/types.d.ts
CHANGED
|
@@ -74,6 +74,22 @@ declare global {
|
|
|
74
74
|
}[]>;
|
|
75
75
|
}>;
|
|
76
76
|
};
|
|
77
|
+
summarizer?: {
|
|
78
|
+
capabilities: () => Promise<{
|
|
79
|
+
available: 'no' | 'yes' | 'after-download';
|
|
80
|
+
}>;
|
|
81
|
+
create: (options: {
|
|
82
|
+
sharedContext?: string;
|
|
83
|
+
type?: string;
|
|
84
|
+
format?: string;
|
|
85
|
+
length?: string;
|
|
86
|
+
}) => Promise<{
|
|
87
|
+
ready: Promise<void>;
|
|
88
|
+
summarize: (text: string, summarizeOptions?: {
|
|
89
|
+
context?: string;
|
|
90
|
+
}) => Promise<string>;
|
|
91
|
+
}>;
|
|
92
|
+
};
|
|
77
93
|
};
|
|
78
94
|
}
|
|
79
95
|
}
|
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.27.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": {
|
|
@@ -44,28 +44,28 @@
|
|
|
44
44
|
"main": "./build/index.js",
|
|
45
45
|
"types": "./build/index.d.ts",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@automattic/jetpack-base-styles": "^0.6.
|
|
48
|
-
"@automattic/jetpack-components": "^0.
|
|
49
|
-
"@automattic/jetpack-connection": "^0.
|
|
50
|
-
"@automattic/jetpack-shared-extension-utils": "^0.17.
|
|
47
|
+
"@automattic/jetpack-base-styles": "^0.6.44",
|
|
48
|
+
"@automattic/jetpack-components": "^0.68.0",
|
|
49
|
+
"@automattic/jetpack-connection": "^0.38.0",
|
|
50
|
+
"@automattic/jetpack-shared-extension-utils": "^0.17.4",
|
|
51
51
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
52
52
|
"@types/jest": "29.5.14",
|
|
53
53
|
"@types/react": "18.3.18",
|
|
54
54
|
"@types/wordpress__block-editor": "11.5.16",
|
|
55
|
-
"@wordpress/api-fetch": "7.
|
|
56
|
-
"@wordpress/base-styles": "5.
|
|
57
|
-
"@wordpress/blob": "4.
|
|
58
|
-
"@wordpress/blocks": "14.
|
|
59
|
-
"@wordpress/block-editor": "14.
|
|
60
|
-
"@wordpress/components": "29.
|
|
61
|
-
"@wordpress/compose": "7.
|
|
62
|
-
"@wordpress/data": "10.
|
|
63
|
-
"@wordpress/editor": "14.
|
|
64
|
-
"@wordpress/element": "6.
|
|
65
|
-
"@wordpress/i18n": "5.
|
|
66
|
-
"@wordpress/icons": "10.
|
|
67
|
-
"@wordpress/primitives": "4.
|
|
68
|
-
"@wordpress/url": "4.
|
|
55
|
+
"@wordpress/api-fetch": "7.19.0",
|
|
56
|
+
"@wordpress/base-styles": "5.19.0",
|
|
57
|
+
"@wordpress/blob": "4.19.0",
|
|
58
|
+
"@wordpress/blocks": "14.8.0",
|
|
59
|
+
"@wordpress/block-editor": "14.14.0",
|
|
60
|
+
"@wordpress/components": "29.5.0",
|
|
61
|
+
"@wordpress/compose": "7.19.0",
|
|
62
|
+
"@wordpress/data": "10.19.0",
|
|
63
|
+
"@wordpress/editor": "14.19.0",
|
|
64
|
+
"@wordpress/element": "6.19.0",
|
|
65
|
+
"@wordpress/i18n": "5.19.0",
|
|
66
|
+
"@wordpress/icons": "10.19.0",
|
|
67
|
+
"@wordpress/primitives": "4.19.0",
|
|
68
|
+
"@wordpress/url": "4.19.0",
|
|
69
69
|
"clsx": "2.1.1",
|
|
70
70
|
"debug": "4.4.0",
|
|
71
71
|
"markdown-it": "14.1.0",
|
package/src/chrome-ai/factory.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { getJetpackExtensionAvailability } from '@automattic/jetpack-shared-extension-utils';
|
|
2
|
-
import {
|
|
3
|
-
PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
4
|
-
//PROMPT_TYPE_SUMMARIZE,
|
|
5
|
-
} from '../constants.js';
|
|
2
|
+
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.js';
|
|
6
3
|
import { PromptProp, PromptItemProps } from '../types.js';
|
|
7
4
|
import ChromeAISuggestionsEventSource from './suggestions.js';
|
|
8
5
|
|
|
@@ -19,6 +16,8 @@ interface PromptContext {
|
|
|
19
16
|
type?: string;
|
|
20
17
|
content?: string;
|
|
21
18
|
language?: string;
|
|
19
|
+
tone?: string;
|
|
20
|
+
words?: number;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -36,7 +35,11 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
|
36
35
|
content: '',
|
|
37
36
|
language: '',
|
|
38
37
|
};
|
|
38
|
+
|
|
39
39
|
let promptType = '';
|
|
40
|
+
let tone = null;
|
|
41
|
+
let wordCount = null;
|
|
42
|
+
|
|
40
43
|
if ( Array.isArray( promptArg ) ) {
|
|
41
44
|
for ( let i = 0; i < promptArg.length; i++ ) {
|
|
42
45
|
const prompt: PromptItemProps = promptArg[ i ];
|
|
@@ -61,6 +64,14 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
|
61
64
|
if ( promptContext.content ) {
|
|
62
65
|
context.content = promptContext.content;
|
|
63
66
|
}
|
|
67
|
+
|
|
68
|
+
if ( promptContext.tone ) {
|
|
69
|
+
tone = promptContext.tone;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if ( promptContext.words ) {
|
|
73
|
+
wordCount = promptContext.words;
|
|
74
|
+
}
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
|
|
@@ -112,17 +123,19 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
|
112
123
|
return chromeAI;
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
// TODO
|
|
116
|
-
if ( promptType.startsWith( 'ai-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
// TODO: consider also using ChromeAI for ai-assistant-summarize
|
|
127
|
+
if ( promptType.startsWith( 'ai-content-lens' ) ) {
|
|
128
|
+
const summaryOpts = {
|
|
129
|
+
tone: tone,
|
|
130
|
+
wordCount: wordCount,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// TODO: detect if the content is in English and fallback if it's not
|
|
134
|
+
return new ChromeAISuggestionsEventSource( {
|
|
135
|
+
content: context.content,
|
|
120
136
|
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
121
|
-
options:
|
|
137
|
+
options: summaryOpts,
|
|
122
138
|
} );
|
|
123
|
-
*/
|
|
124
|
-
|
|
125
|
-
return false;
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
return false;
|
|
@@ -15,6 +15,10 @@ type ChromeAISuggestionsEventSourceConstructorArgs = {
|
|
|
15
15
|
sourceLanguage?: string;
|
|
16
16
|
targetLanguage?: string;
|
|
17
17
|
|
|
18
|
+
// summarization
|
|
19
|
+
tone?: string;
|
|
20
|
+
wordCount?: number;
|
|
21
|
+
|
|
18
22
|
// not sure if we need these
|
|
19
23
|
functions?: Array< object >;
|
|
20
24
|
model?: AiModelTypeProp;
|
|
@@ -64,7 +68,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
if ( promptType === PROMPT_TYPE_SUMMARIZE ) {
|
|
67
|
-
this.summarize( content );
|
|
71
|
+
this.summarize( content, options.tone, options.wordCount );
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -83,7 +87,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
83
87
|
return;
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
if ( e.event === 'translation' ) {
|
|
90
|
+
if ( e.event === 'translation' || e.event === 'summary' ) {
|
|
87
91
|
this.dispatchEvent( new CustomEvent( 'suggestion', { detail: data.message } ) );
|
|
88
92
|
}
|
|
89
93
|
|
|
@@ -134,8 +138,58 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
// Helper function to format summarizer options
|
|
142
|
+
private getSummarizerOptions( tone?: string, wordCount?: number ) {
|
|
143
|
+
let sharedContext = `The summary you write should contain approximately ${
|
|
144
|
+
wordCount ?? 50
|
|
145
|
+
} words long. Strive for precision in word count without compromising clarity and significance`;
|
|
146
|
+
|
|
147
|
+
if ( tone ) {
|
|
148
|
+
sharedContext += `\n - Write with a ${ tone } tone.\n`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const options = {
|
|
152
|
+
sharedContext: sharedContext,
|
|
153
|
+
type: 'teaser',
|
|
154
|
+
format: 'plain-text',
|
|
155
|
+
length: 'medium',
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return options;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// use the Chrome AI summarizer
|
|
162
|
+
async summarize( text: string, tone?: string, wordCount?: number ) {
|
|
163
|
+
if ( ! ( 'ai' in self ) || ! ( 'summarizer' in self.ai ) ) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const available = ( await self.ai.summarizer.capabilities() ).available;
|
|
167
|
+
|
|
168
|
+
if ( available === 'no' ) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const options = this.getSummarizerOptions( tone, wordCount );
|
|
173
|
+
|
|
174
|
+
const summarizer = await self.ai.summarizer.create( options );
|
|
175
|
+
|
|
176
|
+
if ( available === 'after-download' ) {
|
|
177
|
+
await summarizer.ready;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const context = `Write with a ${ tone } tone.`;
|
|
182
|
+
const summary = await summarizer.summarize( text, { context: context } );
|
|
183
|
+
this.processEvent( {
|
|
184
|
+
id: '',
|
|
185
|
+
event: 'summary',
|
|
186
|
+
data: JSON.stringify( {
|
|
187
|
+
message: summary,
|
|
188
|
+
complete: true,
|
|
189
|
+
} ),
|
|
190
|
+
} );
|
|
191
|
+
} catch ( error ) {
|
|
192
|
+
this.processErrorEvent( error );
|
|
193
|
+
}
|
|
140
194
|
}
|
|
141
195
|
}
|
|
@@ -57,7 +57,7 @@ export default function FeaturedImage( {
|
|
|
57
57
|
placement === PLACEMENT_MEDIA_SOURCE_DROPDOWN
|
|
58
58
|
);
|
|
59
59
|
const siteType = useSiteType();
|
|
60
|
-
const
|
|
60
|
+
const { getPostContent, isEditedPostEmpty } = usePostContent();
|
|
61
61
|
const { postTitle, postFeaturedMediaId, isEditorPanelOpened } = useSelect( select => {
|
|
62
62
|
return {
|
|
63
63
|
postTitle: select( editorStore ).getEditedPostAttribute( 'title' ),
|
|
@@ -133,10 +133,10 @@ export default function FeaturedImage( {
|
|
|
133
133
|
*/
|
|
134
134
|
const handleGuessStyle = useCallback(
|
|
135
135
|
userPrompt => {
|
|
136
|
-
const content = postTitle + '\n\n' +
|
|
136
|
+
const content = postTitle + '\n\n' + getPostContent();
|
|
137
137
|
return guessStyle( userPrompt, 'featured-image-guess-style', content );
|
|
138
138
|
},
|
|
139
|
-
[
|
|
139
|
+
[ postTitle, getPostContent, guessStyle ]
|
|
140
140
|
);
|
|
141
141
|
|
|
142
142
|
const handleGenerate = useCallback(
|
|
@@ -159,7 +159,7 @@ export default function FeaturedImage( {
|
|
|
159
159
|
setIsFeaturedImageModalVisible( true );
|
|
160
160
|
return processImageGeneration( {
|
|
161
161
|
userPrompt,
|
|
162
|
-
postContent: postTitle + '\n\n' +
|
|
162
|
+
postContent: postTitle + '\n\n' + getPostContent(),
|
|
163
163
|
notEnoughRequests,
|
|
164
164
|
style,
|
|
165
165
|
} ).catch( error => {
|
|
@@ -178,7 +178,7 @@ export default function FeaturedImage( {
|
|
|
178
178
|
featuredImageActiveModel,
|
|
179
179
|
siteType,
|
|
180
180
|
processImageGeneration,
|
|
181
|
-
|
|
181
|
+
getPostContent,
|
|
182
182
|
notEnoughRequests,
|
|
183
183
|
postTitle,
|
|
184
184
|
]
|
|
@@ -209,7 +209,7 @@ export default function FeaturedImage( {
|
|
|
209
209
|
setCurrent( () => images.length );
|
|
210
210
|
processImageGeneration( {
|
|
211
211
|
userPrompt,
|
|
212
|
-
postContent: postTitle + '\n\n' +
|
|
212
|
+
postContent: postTitle + '\n\n' + getPostContent(),
|
|
213
213
|
notEnoughRequests,
|
|
214
214
|
style,
|
|
215
215
|
} ).catch( error => {
|
|
@@ -231,7 +231,7 @@ export default function FeaturedImage( {
|
|
|
231
231
|
setCurrent,
|
|
232
232
|
processImageGeneration,
|
|
233
233
|
postTitle,
|
|
234
|
-
|
|
234
|
+
getPostContent,
|
|
235
235
|
notEnoughRequests,
|
|
236
236
|
images,
|
|
237
237
|
]
|
|
@@ -249,7 +249,7 @@ export default function FeaturedImage( {
|
|
|
249
249
|
|
|
250
250
|
processImageGeneration( {
|
|
251
251
|
userPrompt,
|
|
252
|
-
postContent: postTitle + '\n\n' +
|
|
252
|
+
postContent: postTitle + '\n\n' + getPostContent(),
|
|
253
253
|
notEnoughRequests,
|
|
254
254
|
style,
|
|
255
255
|
} ).catch( error => {
|
|
@@ -268,7 +268,7 @@ export default function FeaturedImage( {
|
|
|
268
268
|
featuredImageActiveModel,
|
|
269
269
|
siteType,
|
|
270
270
|
processImageGeneration,
|
|
271
|
-
|
|
271
|
+
getPostContent,
|
|
272
272
|
notEnoughRequests,
|
|
273
273
|
postTitle,
|
|
274
274
|
]
|
|
@@ -346,7 +346,7 @@ export default function FeaturedImage( {
|
|
|
346
346
|
const generateAgainText = __( 'Generate another image', 'jetpack-ai-client' );
|
|
347
347
|
const generateText = __( 'Generate', 'jetpack-ai-client' );
|
|
348
348
|
|
|
349
|
-
const hasContent =
|
|
349
|
+
const hasContent = ! isEditedPostEmpty() || postTitle.trim?.() ? true : false;
|
|
350
350
|
const hasPrompt = hasContent ? prompt.length >= 0 : prompt.length >= 3;
|
|
351
351
|
const disableInput = notEnoughRequests || currentPointer?.generating || requireUpgrade;
|
|
352
352
|
const disableAction = disableInput || ( ! hasContent && ! hasPrompt );
|
|
@@ -396,7 +396,6 @@ export default function FeaturedImage( {
|
|
|
396
396
|
</>
|
|
397
397
|
) }
|
|
398
398
|
<AiImageModal
|
|
399
|
-
postContent={ hasContent }
|
|
400
399
|
autoStart={ hasContent && ! postFeaturedMediaId }
|
|
401
400
|
autoStartAction={ handleFirstGenerate }
|
|
402
401
|
images={ images }
|
|
@@ -54,7 +54,7 @@ export default function GeneralPurposeImage( {
|
|
|
54
54
|
}: GeneralPurposeImageProps ) {
|
|
55
55
|
const [ isFeaturedImageModalVisible, setIsFeaturedImageModalVisible ] = useState( true );
|
|
56
56
|
const siteType = useSiteType();
|
|
57
|
-
const
|
|
57
|
+
const { getPostContent } = usePostContent();
|
|
58
58
|
const { saveToMediaLibrary } = useSaveToMediaLibrary();
|
|
59
59
|
const { tracks } = useAnalytics();
|
|
60
60
|
const { recordEvent } = tracks;
|
|
@@ -111,17 +111,20 @@ export default function GeneralPurposeImage( {
|
|
|
111
111
|
site_type: siteType,
|
|
112
112
|
style,
|
|
113
113
|
} );
|
|
114
|
-
processImageGeneration( {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
processImageGeneration( {
|
|
115
|
+
userPrompt,
|
|
116
|
+
postContent: getPostContent(),
|
|
117
|
+
notEnoughRequests,
|
|
118
|
+
style,
|
|
119
|
+
} ).catch( error => {
|
|
120
|
+
recordEvent( 'jetpack_ai_general_image_generation_error', {
|
|
121
|
+
placement,
|
|
122
|
+
error: error?.message,
|
|
123
|
+
model: generalImageActiveModel,
|
|
124
|
+
site_type: siteType,
|
|
125
|
+
style,
|
|
126
|
+
} );
|
|
127
|
+
} );
|
|
125
128
|
},
|
|
126
129
|
[
|
|
127
130
|
recordEvent,
|
|
@@ -129,7 +132,7 @@ export default function GeneralPurposeImage( {
|
|
|
129
132
|
generalImageActiveModel,
|
|
130
133
|
siteType,
|
|
131
134
|
processImageGeneration,
|
|
132
|
-
|
|
135
|
+
getPostContent,
|
|
133
136
|
notEnoughRequests,
|
|
134
137
|
]
|
|
135
138
|
);
|
|
@@ -146,16 +149,19 @@ export default function GeneralPurposeImage( {
|
|
|
146
149
|
} );
|
|
147
150
|
|
|
148
151
|
setCurrent( crrt => crrt + 1 );
|
|
149
|
-
processImageGeneration( {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
processImageGeneration( {
|
|
153
|
+
userPrompt,
|
|
154
|
+
postContent: getPostContent(),
|
|
155
|
+
notEnoughRequests,
|
|
156
|
+
style,
|
|
157
|
+
} ).catch( error => {
|
|
158
|
+
recordEvent( 'jetpack_ai_general_image_generation_error', {
|
|
159
|
+
placement,
|
|
160
|
+
error: error?.message,
|
|
161
|
+
model: generalImageActiveModel,
|
|
162
|
+
site_type: siteType,
|
|
163
|
+
} );
|
|
164
|
+
} );
|
|
159
165
|
},
|
|
160
166
|
[
|
|
161
167
|
recordEvent,
|
|
@@ -163,7 +169,7 @@ export default function GeneralPurposeImage( {
|
|
|
163
169
|
generalImageActiveModel,
|
|
164
170
|
siteType,
|
|
165
171
|
processImageGeneration,
|
|
166
|
-
|
|
172
|
+
getPostContent,
|
|
167
173
|
notEnoughRequests,
|
|
168
174
|
setCurrent,
|
|
169
175
|
]
|
|
@@ -180,16 +186,19 @@ export default function GeneralPurposeImage( {
|
|
|
180
186
|
style,
|
|
181
187
|
} );
|
|
182
188
|
|
|
183
|
-
processImageGeneration( {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
processImageGeneration( {
|
|
190
|
+
userPrompt,
|
|
191
|
+
postContent: getPostContent(),
|
|
192
|
+
notEnoughRequests,
|
|
193
|
+
style,
|
|
194
|
+
} ).catch( error => {
|
|
195
|
+
recordEvent( 'jetpack_ai_general_image_generation_error', {
|
|
196
|
+
placement,
|
|
197
|
+
error: error?.message,
|
|
198
|
+
model: generalImageActiveModel,
|
|
199
|
+
site_type: siteType,
|
|
200
|
+
} );
|
|
201
|
+
} );
|
|
193
202
|
},
|
|
194
203
|
[
|
|
195
204
|
recordEvent,
|
|
@@ -197,7 +206,7 @@ export default function GeneralPurposeImage( {
|
|
|
197
206
|
generalImageActiveModel,
|
|
198
207
|
siteType,
|
|
199
208
|
processImageGeneration,
|
|
200
|
-
|
|
209
|
+
getPostContent,
|
|
201
210
|
notEnoughRequests,
|
|
202
211
|
]
|
|
203
212
|
);
|
|
@@ -266,7 +275,6 @@ export default function GeneralPurposeImage( {
|
|
|
266
275
|
|
|
267
276
|
return (
|
|
268
277
|
<AiImageModal
|
|
269
|
-
postContent={ true }
|
|
270
278
|
images={ images }
|
|
271
279
|
currentIndex={ current }
|
|
272
280
|
title={ __( 'Generate an image with AI', 'jetpack-ai-client' ) }
|
|
@@ -3,25 +3,38 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { serialize } from '@wordpress/blocks';
|
|
5
5
|
import { useSelect } from '@wordpress/data';
|
|
6
|
+
import { store as editorStore } from '@wordpress/editor';
|
|
7
|
+
import { useCallback } from '@wordpress/element';
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
9
|
+
* Internal dependencies
|
|
8
10
|
*/
|
|
9
11
|
import { renderMarkdownFromHTML } from '../libs/markdown/index.js';
|
|
10
|
-
import type * as BlockEditorSelectors from '@wordpress/block-editor/store/selectors.js';
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* Types
|
|
13
14
|
*/
|
|
15
|
+
import type * as BlockEditorSelectors from '@wordpress/block-editor/store/selectors.js';
|
|
14
16
|
|
|
15
17
|
/*
|
|
16
18
|
* Simple helper to get the post content as markdown
|
|
17
19
|
*/
|
|
18
20
|
const usePostContent = () => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const { getBlocks, isEditedPostEmpty } = useSelect( select => {
|
|
22
|
+
const blockEditorSelect = select( 'core/block-editor' ) as typeof BlockEditorSelectors;
|
|
23
|
+
const coreEditorSelect = select( editorStore );
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
getBlocks: blockEditorSelect.getBlocks,
|
|
27
|
+
isEditedPostEmpty: coreEditorSelect.isEditedPostEmpty,
|
|
28
|
+
};
|
|
29
|
+
}, [] );
|
|
30
|
+
|
|
31
|
+
const getPostContent = useCallback( () => {
|
|
32
|
+
const blocks = getBlocks();
|
|
33
|
+
|
|
34
|
+
return blocks?.length ? renderMarkdownFromHTML( { content: serialize( blocks ) } ) : '';
|
|
35
|
+
}, [ getBlocks ] );
|
|
23
36
|
|
|
24
|
-
return
|
|
37
|
+
return { getPostContent, isEditedPostEmpty };
|
|
25
38
|
};
|
|
26
39
|
|
|
27
40
|
export default usePostContent;
|
package/src/libs/index.ts
CHANGED
|
@@ -9,3 +9,7 @@ export {
|
|
|
9
9
|
export type { RenderHTMLRules } from './markdown/index.js';
|
|
10
10
|
|
|
11
11
|
export { mapActionToHumanText } from './map-action-to-human-text.js';
|
|
12
|
+
|
|
13
|
+
export { openBlockSidebar } from './open-block-sidebar.js';
|
|
14
|
+
|
|
15
|
+
export { showAiAssistantSection } from './show-ai-assistant-section.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
5
|
+
import { dispatch } from '@wordpress/data';
|
|
6
|
+
|
|
7
|
+
type CoreInterfaceDispatch = {
|
|
8
|
+
enableComplementaryArea: ( area: string, slot: string ) => Promise< void >;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Open the block sidebar for the given client ID.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} clientId - The client ID of the block to open the sidebar for.
|
|
15
|
+
*/
|
|
16
|
+
export function openBlockSidebar( clientId: string ) {
|
|
17
|
+
if ( ! clientId ) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { selectBlock } = dispatch( blockEditorStore );
|
|
22
|
+
const { enableComplementaryArea } = dispatch( 'core/interface' ) as CoreInterfaceDispatch;
|
|
23
|
+
|
|
24
|
+
selectBlock( clientId );
|
|
25
|
+
// This only works for the post editor, as the SEO Assistant is only available there
|
|
26
|
+
enableComplementaryArea( 'core/edit-post', 'edit-post/block' );
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { dispatch } from '@wordpress/data';
|
|
5
|
+
|
|
6
|
+
export const showAiAssistantSection = async () => {
|
|
7
|
+
const { clearSelectedBlock } = dispatch( 'core/block-editor' );
|
|
8
|
+
const { enableComplementaryArea } = dispatch( 'core/interface' ) as {
|
|
9
|
+
enableComplementaryArea: ( area: string, slot: string ) => Promise< void >;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Clear any block selection, because selected blocks have precedence on settings sidebar
|
|
13
|
+
clearSelectedBlock();
|
|
14
|
+
await enableComplementaryArea( 'core/edit-post', 'jetpack-sidebar/jetpack' );
|
|
15
|
+
|
|
16
|
+
const panel = document.querySelector( '.jetpack-ai-assistant-panel' );
|
|
17
|
+
const isAlreadyOpen = panel?.classList.contains( 'is-opened' );
|
|
18
|
+
const button: HTMLElement | null | undefined = panel?.querySelector( 'h2 button' );
|
|
19
|
+
|
|
20
|
+
if ( isAlreadyOpen ) {
|
|
21
|
+
// Close it before opening it to ensure the content is scrolled to view
|
|
22
|
+
button?.click();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setTimeout( () => {
|
|
26
|
+
button?.click();
|
|
27
|
+
}, 50 );
|
|
28
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -158,6 +158,20 @@ declare global {
|
|
|
158
158
|
>;
|
|
159
159
|
} >;
|
|
160
160
|
};
|
|
161
|
+
summarizer?: {
|
|
162
|
+
capabilities: () => Promise< {
|
|
163
|
+
available: 'no' | 'yes' | 'after-download';
|
|
164
|
+
} >;
|
|
165
|
+
create: ( options: {
|
|
166
|
+
sharedContext?: string;
|
|
167
|
+
type?: string;
|
|
168
|
+
format?: string;
|
|
169
|
+
length?: string;
|
|
170
|
+
} ) => Promise< {
|
|
171
|
+
ready: Promise< void >;
|
|
172
|
+
summarize: ( text: string, summarizeOptions?: { context?: string } ) => Promise< string >;
|
|
173
|
+
} >;
|
|
174
|
+
};
|
|
161
175
|
};
|
|
162
176
|
}
|
|
163
177
|
}
|