@automattic/jetpack-ai-client 0.25.7 → 0.26.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 +8 -0
- package/build/components/ai-icon/index.d.ts +10 -0
- package/build/components/ai-icon/index.js +16 -0
- package/build/components/ai-image/components/ai-image-modal.d.ts +57 -0
- package/build/components/ai-image/components/ai-image-modal.js +92 -0
- package/build/components/ai-image/components/carrousel.d.ts +28 -0
- package/build/components/ai-image/components/carrousel.js +69 -0
- package/build/components/ai-image/components/usage-counter.d.ts +16 -0
- package/build/components/ai-image/components/usage-counter.js +29 -0
- package/build/components/ai-image/featured-image.d.ts +17 -0
- package/build/components/ai-image/featured-image.js +278 -0
- package/build/components/ai-image/general-purpose-image.d.ts +23 -0
- package/build/components/ai-image/general-purpose-image.js +184 -0
- package/build/components/ai-image/hooks/use-ai-image.d.ts +48 -0
- package/build/components/ai-image/hooks/use-ai-image.js +219 -0
- package/build/components/ai-image/hooks/use-site-type.d.ts +6 -0
- package/build/components/ai-image/hooks/use-site-type.js +23 -0
- package/build/components/ai-image/index.d.ts +4 -0
- package/build/components/ai-image/index.js +4 -0
- package/build/components/ai-image/types.d.ts +16 -0
- package/build/components/ai-image/types.js +6 -0
- package/build/{ai-client/src/components → components}/index.d.ts +4 -0
- package/build/{ai-client/src/components → components}/index.js +4 -0
- package/build/components/modal/index.d.ts +18 -0
- package/build/components/modal/index.js +23 -0
- package/build/components/quota-exceeded-message/index.d.ts +13 -0
- package/build/components/quota-exceeded-message/index.js +152 -0
- package/build/components/quota-exceeded-message/light-nudge.d.ts +11 -0
- package/build/components/quota-exceeded-message/light-nudge.js +8 -0
- package/build/{ai-client/src/constants.d.ts → constants.d.ts} +3 -0
- package/build/{ai-client/src/constants.js → constants.js} +4 -0
- package/build/hooks/use-ai-checkout/index.d.ts +13 -0
- package/build/hooks/use-ai-checkout/index.js +41 -0
- package/build/hooks/use-ai-feature/index.d.ts +33 -0
- package/build/hooks/use-ai-feature/index.js +37 -0
- package/build/hooks/use-post-content.d.ts +5 -0
- package/build/hooks/use-post-content.js +20 -0
- package/build/hooks/use-save-to-media-library.d.ts +12 -0
- package/build/hooks/use-save-to-media-library.js +74 -0
- package/build/{ai-client/src/index.d.ts → index.d.ts} +3 -0
- package/build/{ai-client/src/index.js → index.js} +3 -0
- package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-screen.js +1 -1
- package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-fair-usage-notice-message.js +1 -1
- package/package.json +20 -14
- package/src/components/ai-icon/index.tsx +39 -0
- package/src/components/ai-image/components/ai-image-modal.scss +88 -0
- package/src/components/ai-image/components/ai-image-modal.tsx +240 -0
- package/src/components/ai-image/components/carrousel.scss +163 -0
- package/src/components/ai-image/components/carrousel.tsx +217 -0
- package/src/components/ai-image/components/usage-counter.scss +19 -0
- package/src/components/ai-image/components/usage-counter.tsx +54 -0
- package/src/components/ai-image/featured-image.tsx +439 -0
- package/src/components/ai-image/general-purpose-image.tsx +303 -0
- package/src/components/ai-image/hooks/use-ai-image.ts +339 -0
- package/src/components/ai-image/hooks/use-site-type.ts +26 -0
- package/src/components/ai-image/index.ts +10 -0
- package/src/components/ai-image/style.scss +95 -0
- package/src/components/ai-image/types.ts +19 -0
- package/src/components/index.ts +12 -0
- package/src/components/modal/index.tsx +70 -0
- package/src/components/modal/style.scss +45 -0
- package/src/components/quota-exceeded-message/index.tsx +319 -0
- package/src/components/quota-exceeded-message/light-nudge.tsx +38 -0
- package/src/components/quota-exceeded-message/style.scss +35 -0
- package/src/constants.ts +5 -0
- package/src/hooks/use-ai-checkout/index.ts +65 -0
- package/src/hooks/use-ai-feature/Readme.md +20 -0
- package/src/hooks/use-ai-feature/index.ts +62 -0
- package/src/hooks/use-post-content.ts +27 -0
- package/src/hooks/use-save-to-media-library.ts +100 -0
- package/src/index.ts +3 -0
- package/src/logo-generator/components/upgrade-screen.tsx +1 -1
- package/src/logo-generator/hooks/use-fair-usage-notice-message.tsx +1 -1
- package/build/components/tools/jp-redirect/index.d.ts +0 -20
- package/build/components/tools/jp-redirect/index.js +0 -50
- package/build/components/tools/jp-redirect/types.d.ts +0 -39
- package/build/components/tools/jp-redirect/types.js +0 -1
- /package/build/{ai-client/src/api-fetch → api-fetch}/index.d.ts +0 -0
- /package/build/{ai-client/src/api-fetch → api-fetch}/index.js +0 -0
- /package/build/{ai-client/src/ask-question → ask-question}/index.d.ts +0 -0
- /package/build/{ai-client/src/ask-question → ask-question}/index.js +0 -0
- /package/build/{ai-client/src/ask-question → ask-question}/sync.d.ts +0 -0
- /package/build/{ai-client/src/ask-question → ask-question}/sync.js +0 -0
- /package/build/{ai-client/src/audio-transcription → audio-transcription}/index.d.ts +0 -0
- /package/build/{ai-client/src/audio-transcription → audio-transcription}/index.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/ai-control.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/ai-control.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/block-ai-control.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/block-ai-control.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/extension-ai-control.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/extension-ai-control.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/index.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-control/index.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-feedback/index.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-feedback/index.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-modal-footer/index.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-modal-footer/index.js +0 -0
- /package/build/{ai-client/src/components → components}/ai-status-indicator/index.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/ai-status-indicator/index.js +0 -0
- /package/build/{ai-client/src/components → components}/audio-duration-display/index.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/audio-duration-display/index.js +0 -0
- /package/build/{ai-client/src/components → components}/audio-duration-display/lib/media.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/audio-duration-display/lib/media.js +0 -0
- /package/build/{ai-client/src/components → components}/message/index.d.ts +0 -0
- /package/build/{ai-client/src/components → components}/message/index.js +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/context.d.ts +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/context.js +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/index.d.ts +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/index.js +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/use-ai-context.d.ts +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/use-ai-context.js +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/with-ai-assistant-data.d.ts +0 -0
- /package/build/{ai-client/src/data-flow → data-flow}/with-ai-assistant-data.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-ai-suggestions/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-ai-suggestions/index.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-audio-transcription/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-audio-transcription/index.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-audio-validation/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-audio-validation/index.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-image-generator/constants.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-image-generator/constants.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-image-generator/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-image-generator/index.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-media-recording/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-media-recording/index.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-save-to-media-library/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-save-to-media-library/index.js +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-transcription-post-processing/index.d.ts +0 -0
- /package/build/{ai-client/src/hooks → hooks}/use-transcription-post-processing/index.js +0 -0
- /package/build/{ai-client/src/icons → icons}/ai-assistant.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/ai-assistant.js +0 -0
- /package/build/{ai-client/src/icons → icons}/error-exclamation.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/error-exclamation.js +0 -0
- /package/build/{ai-client/src/icons → icons}/index.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/index.js +0 -0
- /package/build/{ai-client/src/icons → icons}/mic.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/mic.js +0 -0
- /package/build/{ai-client/src/icons → icons}/origami-plane.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/origami-plane.js +0 -0
- /package/build/{ai-client/src/icons → icons}/player-pause.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/player-pause.js +0 -0
- /package/build/{ai-client/src/icons → icons}/player-play.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/player-play.js +0 -0
- /package/build/{ai-client/src/icons → icons}/player-stop.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/player-stop.js +0 -0
- /package/build/{ai-client/src/icons → icons}/speak-tone.d.ts +0 -0
- /package/build/{ai-client/src/icons → icons}/speak-tone.js +0 -0
- /package/build/{ai-client/src/jwt → jwt}/index.d.ts +0 -0
- /package/build/{ai-client/src/jwt → jwt}/index.js +0 -0
- /package/build/{ai-client/src/libs → libs}/index.d.ts +0 -0
- /package/build/{ai-client/src/libs → libs}/index.js +0 -0
- /package/build/{ai-client/src/libs → libs}/map-action-to-human-text.d.ts +0 -0
- /package/build/{ai-client/src/libs → libs}/map-action-to-human-text.js +0 -0
- /package/build/{ai-client/src/libs → libs}/markdown/html-to-markdown.d.ts +0 -0
- /package/build/{ai-client/src/libs → libs}/markdown/html-to-markdown.js +0 -0
- /package/build/{ai-client/src/libs → libs}/markdown/index.d.ts +0 -0
- /package/build/{ai-client/src/libs → libs}/markdown/index.js +0 -0
- /package/build/{ai-client/src/libs → libs}/markdown/markdown-to-html.d.ts +0 -0
- /package/build/{ai-client/src/libs → libs}/markdown/markdown-to-html.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/ai.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/ai.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/check.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/check.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/logo.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/logo.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/media.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/media.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/fair-usage-notice.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/fair-usage-notice.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/feature-fetch-failure-screen.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/feature-fetch-failure-screen.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/first-load-screen.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/first-load-screen.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/generator-modal.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/generator-modal.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/history-carousel.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/history-carousel.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/image-loader.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/image-loader.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/logo-presenter.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/logo-presenter.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/prompt.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/prompt.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-nudge.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-nudge.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-screen.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/visit-site-banner.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/components/visit-site-banner.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/constants.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/constants.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-checkout.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-checkout.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-fair-usage-notice-message.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-logo-generator.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-logo-generator.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-request-errors.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-request-errors.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/index.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/index.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/logo-storage.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/logo-storage.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/media-exists.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/media-exists.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/set-site-logo.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/set-site-logo.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/wpcom-limited-request.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/lib/wpcom-limited-request.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/actions.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/actions.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/constants.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/constants.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/index.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/index.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/initial-state.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/initial-state.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/reducer.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/reducer.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/selectors.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/selectors.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/types.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/store/types.js +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/types.d.ts +0 -0
- /package/build/{ai-client/src/logo-generator → logo-generator}/types.js +0 -0
- /package/build/{ai-client/src/suggestions-event-source → suggestions-event-source}/index.d.ts +0 -0
- /package/build/{ai-client/src/suggestions-event-source → suggestions-event-source}/index.js +0 -0
- /package/build/{ai-client/src/types.d.ts → types.d.ts} +0 -0
- /package/build/{ai-client/src/types.js → types.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ 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.26.0] - 2025-02-10
|
|
9
|
+
### Added
|
|
10
|
+
- Add shared components from ai-assistant-plugin [#41078]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Updated package dependencies. [#41491] [#41577]
|
|
14
|
+
|
|
8
15
|
## [0.25.7] - 2025-01-27
|
|
9
16
|
### Changed
|
|
10
17
|
- Internal updates.
|
|
@@ -510,6 +517,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
510
517
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
511
518
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
512
519
|
|
|
520
|
+
[0.26.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.7...v0.26.0
|
|
513
521
|
[0.25.7]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.6...v0.25.7
|
|
514
522
|
[0.25.6]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.5...v0.25.6
|
|
515
523
|
[0.25.5]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.4...v0.25.5
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const AiSVG: import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
/**
|
|
3
|
+
* AiIcon component
|
|
4
|
+
* @param {string} className - The wrapper class name.
|
|
5
|
+
* @return {React.ReactElement} The `AiIcon` component.
|
|
6
|
+
*/
|
|
7
|
+
export default function AiIcon({ className, size }: {
|
|
8
|
+
className?: string;
|
|
9
|
+
size?: number;
|
|
10
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { G, Path, SVG, Rect } from '@wordpress/components';
|
|
6
|
+
import { Icon } from '@wordpress/icons';
|
|
7
|
+
import { Defs } from '@wordpress/primitives';
|
|
8
|
+
export const AiSVG = (_jsxs(SVG, { width: "42", height: "42", viewBox: "0 0 42 42", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsxs(G, { clipPath: "url(#clip0_4479_1006)", children: [_jsx(Path, { d: "M7.87488 0L10.1022 5.64753L15.7498 7.87488L10.1022 10.1022L7.87488 15.7498L5.64753 10.1022L0 7.87488L5.64753 5.64753L7.87488 0Z", fill: "#A7AAAD" }), _jsx(Path, { d: "M31.4998 0L34.4696 7.53004L41.9997 10.4998L34.4696 13.4696L31.4998 20.9997L28.53 13.4696L21 10.4998L28.53 7.53004L31.4998 0Z", fill: "#A7AAAD" }), _jsx(Path, { d: "M18.3748 15.7496L22.0871 25.1621L31.4996 28.8744L22.0871 32.5866L18.3748 41.9992L14.6625 32.5866L5.25 28.8744L14.6625 25.1621L18.3748 15.7496Z", fill: "#A7AAAD" })] }), _jsx(Defs, { children: _jsx("clipPath", { id: "clip0_4479_1006", children: _jsx(Rect, { width: "41.9997", height: "41.9992", fill: "white" }) }) })] }));
|
|
9
|
+
/**
|
|
10
|
+
* AiIcon component
|
|
11
|
+
* @param {string} className - The wrapper class name.
|
|
12
|
+
* @return {React.ReactElement} The `AiIcon` component.
|
|
13
|
+
*/
|
|
14
|
+
export default function AiIcon({ className, size = 42 }) {
|
|
15
|
+
return _jsx(Icon, { icon: AiSVG, width: size, height: size, className: className });
|
|
16
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/// <reference types="react" resolution-mode="require"/>
|
|
2
|
+
/**
|
|
3
|
+
* Internal dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { ImageStyleObject, ImageStyle } from '../../../hooks/use-image-generator/constants.js';
|
|
6
|
+
import { CarrouselImages } from './carrousel.js';
|
|
7
|
+
import './ai-image-modal.scss';
|
|
8
|
+
type AiImageModalProps = {
|
|
9
|
+
title: string;
|
|
10
|
+
cost: number;
|
|
11
|
+
open: boolean;
|
|
12
|
+
placement: string;
|
|
13
|
+
images: CarrouselImages;
|
|
14
|
+
currentIndex: number;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
onTryAgain: ({ userPrompt, style }: {
|
|
17
|
+
userPrompt?: string;
|
|
18
|
+
style?: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
onGenerate: ({ userPrompt, style }: {
|
|
21
|
+
userPrompt?: string;
|
|
22
|
+
style?: string;
|
|
23
|
+
}) => void;
|
|
24
|
+
generating: boolean;
|
|
25
|
+
notEnoughRequests: boolean;
|
|
26
|
+
requireUpgrade: boolean;
|
|
27
|
+
currentLimit: number;
|
|
28
|
+
currentUsage: number;
|
|
29
|
+
isUnlimited: boolean;
|
|
30
|
+
upgradeDescription: string;
|
|
31
|
+
hasError: boolean;
|
|
32
|
+
postContent?: string | boolean | null;
|
|
33
|
+
handlePreviousImage: () => void;
|
|
34
|
+
handleNextImage: () => void;
|
|
35
|
+
acceptButton: React.JSX.Element;
|
|
36
|
+
autoStart?: boolean;
|
|
37
|
+
autoStartAction?: ({ userPrompt, style }: {
|
|
38
|
+
userPrompt?: string;
|
|
39
|
+
style?: string;
|
|
40
|
+
}) => void;
|
|
41
|
+
generateButtonLabel: string;
|
|
42
|
+
instructionsPlaceholder: string;
|
|
43
|
+
imageStyles?: Array<ImageStyleObject>;
|
|
44
|
+
onGuessStyle?: (userPrompt: string) => Promise<ImageStyle>;
|
|
45
|
+
prompt?: string;
|
|
46
|
+
setPrompt?: (userPrompt: string) => void;
|
|
47
|
+
initialStyle?: ImageStyle;
|
|
48
|
+
inputDisabled?: boolean;
|
|
49
|
+
actionDisabled?: boolean;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* AiImageModal component
|
|
53
|
+
* @param {AiImageModalProps} props - The component properties.
|
|
54
|
+
* @return {React.ReactElement} - rendered component.
|
|
55
|
+
*/
|
|
56
|
+
export default function AiImageModal({ title, cost, open, images, currentIndex, onClose, onTryAgain, onGenerate, generating, notEnoughRequests, requireUpgrade, currentLimit, currentUsage, isUnlimited, upgradeDescription, hasError, handlePreviousImage, handleNextImage, acceptButton, autoStart, autoStartAction, instructionsPlaceholder, imageStyles, onGuessStyle, prompt, setPrompt, initialStyle, inputDisabled, actionDisabled, }: AiImageModalProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
|
|
6
|
+
import { SelectControl } from '@wordpress/components';
|
|
7
|
+
import { useCallback, useRef, useState, useEffect } from '@wordpress/element';
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
import debugFactory from 'debug';
|
|
10
|
+
/**
|
|
11
|
+
* Internal dependencies
|
|
12
|
+
*/
|
|
13
|
+
import { IMAGE_STYLE_NONE, IMAGE_STYLE_AUTO, } from '../../../hooks/use-image-generator/constants.js';
|
|
14
|
+
import { AiModalPromptInput } from '../../../logo-generator/index.js';
|
|
15
|
+
import AiModalFooter from '../../ai-modal-footer/index.js';
|
|
16
|
+
import AiAssistantModal from '../../modal/index.js';
|
|
17
|
+
import QuotaExceededMessage from '../../quota-exceeded-message/index.js';
|
|
18
|
+
import Carrousel from './carrousel.js';
|
|
19
|
+
import UsageCounter from './usage-counter.js';
|
|
20
|
+
import './ai-image-modal.scss';
|
|
21
|
+
const FEATURED_IMAGE_UPGRADE_PROMPT_PLACEMENT = 'ai-image-generator';
|
|
22
|
+
const debug = debugFactory('jetpack-ai-client:ai-image-modal');
|
|
23
|
+
/**
|
|
24
|
+
* AiImageModal component
|
|
25
|
+
* @param {AiImageModalProps} props - The component properties.
|
|
26
|
+
* @return {React.ReactElement} - rendered component.
|
|
27
|
+
*/
|
|
28
|
+
export default function AiImageModal({ title, cost, open, images, currentIndex = 0, onClose = null, onTryAgain = null, onGenerate = null, generating = false, notEnoughRequests = false, requireUpgrade = false, currentLimit = null, currentUsage = null, isUnlimited = false, upgradeDescription = null, hasError = false, handlePreviousImage = () => { }, handleNextImage = () => { }, acceptButton = null, autoStart = false, autoStartAction = null, instructionsPlaceholder = null, imageStyles = [], onGuessStyle = null, prompt = '', setPrompt = () => { }, initialStyle = null, inputDisabled = false, actionDisabled = false, }) {
|
|
29
|
+
const { tracks } = useAnalytics();
|
|
30
|
+
const { recordEvent: recordTracksEvent } = tracks;
|
|
31
|
+
const triggeredAutoGeneration = useRef(false);
|
|
32
|
+
const [showStyleSelector, setShowStyleSelector] = useState(false);
|
|
33
|
+
const [style, setStyle] = useState(null);
|
|
34
|
+
const [styles, setStyles] = useState(imageStyles || []);
|
|
35
|
+
const handleTryAgain = useCallback(() => {
|
|
36
|
+
onTryAgain?.({ userPrompt: prompt, style });
|
|
37
|
+
}, [onTryAgain, prompt, style]);
|
|
38
|
+
const handleGenerate = useCallback(async () => {
|
|
39
|
+
if (style === IMAGE_STYLE_AUTO && onGuessStyle) {
|
|
40
|
+
recordTracksEvent('jetpack_ai_general_image_guess_style', {
|
|
41
|
+
context: 'block-editor',
|
|
42
|
+
tool: 'image',
|
|
43
|
+
});
|
|
44
|
+
const guessedStyle = (await onGuessStyle(prompt)) || IMAGE_STYLE_NONE;
|
|
45
|
+
setStyle(guessedStyle);
|
|
46
|
+
debug('guessed style', guessedStyle);
|
|
47
|
+
onGenerate?.({ userPrompt: prompt, style: guessedStyle });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
onGenerate?.({ userPrompt: prompt, style });
|
|
51
|
+
}
|
|
52
|
+
}, [onGenerate, prompt, style, onGuessStyle, recordTracksEvent]);
|
|
53
|
+
const updateStyle = useCallback((imageStyle) => {
|
|
54
|
+
debug('change style', imageStyle);
|
|
55
|
+
setStyle(imageStyle);
|
|
56
|
+
recordTracksEvent('jetpack_ai_image_generator_switch_style', {
|
|
57
|
+
context: 'block-editor',
|
|
58
|
+
style: imageStyle,
|
|
59
|
+
});
|
|
60
|
+
}, [setStyle, recordTracksEvent]);
|
|
61
|
+
// Controllers
|
|
62
|
+
const upgradePromptVisible = (requireUpgrade || notEnoughRequests) && !generating;
|
|
63
|
+
const counterVisible = Boolean(!isUnlimited && cost && currentLimit);
|
|
64
|
+
const generateLabel = __('Generate', 'jetpack-ai-client');
|
|
65
|
+
const tryAgainLabel = __('Try again', 'jetpack-ai-client');
|
|
66
|
+
/**
|
|
67
|
+
* Trigger image generation automatically.
|
|
68
|
+
*/
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (autoStart && open) {
|
|
71
|
+
if (!triggeredAutoGeneration.current) {
|
|
72
|
+
triggeredAutoGeneration.current = true;
|
|
73
|
+
autoStartAction?.({});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}, [autoStart, autoStartAction, open]);
|
|
77
|
+
// initialize styles dropdown
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (imageStyles && imageStyles.length > 0) {
|
|
80
|
+
// Sort styles to have "None" and "Auto" first
|
|
81
|
+
setStyles([
|
|
82
|
+
imageStyles.find(({ value }) => value === IMAGE_STYLE_NONE),
|
|
83
|
+
imageStyles.find(({ value }) => value === IMAGE_STYLE_AUTO),
|
|
84
|
+
...imageStyles.filter(({ value }) => ![IMAGE_STYLE_NONE, IMAGE_STYLE_AUTO].includes(value)),
|
|
85
|
+
].filter(v => v) // simplest way to get rid of empty values
|
|
86
|
+
);
|
|
87
|
+
setShowStyleSelector(true);
|
|
88
|
+
setStyle(initialStyle || IMAGE_STYLE_NONE);
|
|
89
|
+
}
|
|
90
|
+
}, [imageStyles, initialStyle]);
|
|
91
|
+
return (_jsx(_Fragment, { children: open && (_jsxs(AiAssistantModal, { handleClose: onClose, title: title, children: [_jsxs("div", { className: "ai-image-modal__content", children: [showStyleSelector && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 16 }, children: [_jsx("div", { style: { fontWeight: 500, flexGrow: 1 }, children: __('Generate image', 'jetpack-ai-client') }), _jsx("div", { children: _jsx(SelectControl, { __nextHasNoMarginBottom: true, value: style, options: styles, onChange: updateStyle }) })] })), _jsx(AiModalPromptInput, { prompt: prompt, setPrompt: setPrompt, disabled: inputDisabled, actionDisabled: actionDisabled, generateHandler: hasError ? handleTryAgain : handleGenerate, placeholder: instructionsPlaceholder, buttonLabel: hasError ? tryAgainLabel : generateLabel }), upgradePromptVisible && (_jsx(QuotaExceededMessage, { description: upgradeDescription, placement: FEATURED_IMAGE_UPGRADE_PROMPT_PLACEMENT, useLightNudge: true })), _jsx("div", { className: "ai-image-modal__actions", children: _jsx("div", { className: "ai-image-modal__actions-left", children: counterVisible && (_jsx(UsageCounter, { cost: cost, currentLimit: currentLimit, currentUsage: currentUsage })) }) }), _jsx("div", { className: "ai-image-modal__image-canvas", children: _jsx(Carrousel, { images: images, current: currentIndex, handlePreviousImage: handlePreviousImage, handleNextImage: handleNextImage, actions: acceptButton }) })] }), _jsx("div", { className: "ai-image-modal__footer", children: _jsx(AiModalFooter, {}) })] })) }));
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/// <reference types="react" resolution-mode="require"/>
|
|
2
|
+
import './carrousel.scss';
|
|
3
|
+
export type CarrouselImageData = {
|
|
4
|
+
image?: string;
|
|
5
|
+
libraryId?: number | string;
|
|
6
|
+
prompt?: string;
|
|
7
|
+
revisedPrompt?: string;
|
|
8
|
+
libraryUrl?: string;
|
|
9
|
+
generating?: boolean;
|
|
10
|
+
error?: {
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type CarrouselImages = CarrouselImageData[];
|
|
15
|
+
type CarrouselProps = {
|
|
16
|
+
images: CarrouselImages;
|
|
17
|
+
current: number;
|
|
18
|
+
handlePreviousImage: () => void;
|
|
19
|
+
handleNextImage: () => void;
|
|
20
|
+
actions?: React.JSX.Element;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Carrousel component
|
|
24
|
+
* @param {CarrouselProps} props - The component properties.
|
|
25
|
+
* @return {React.ReactElement} - rendered component.
|
|
26
|
+
*/
|
|
27
|
+
export default function Carrousel({ images, current, handlePreviousImage, handleNextImage, actions, }: CarrouselProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { Spinner } from '@wordpress/components';
|
|
6
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
8
|
+
import { Icon, chevronLeft, chevronRight } from '@wordpress/icons';
|
|
9
|
+
import clsx from 'clsx';
|
|
10
|
+
/**
|
|
11
|
+
* Internal dependencies
|
|
12
|
+
*/
|
|
13
|
+
import AiFeedbackThumbs from '../../ai-feedback/index.js';
|
|
14
|
+
import AiIcon from '../../ai-icon/index.js';
|
|
15
|
+
import './carrousel.scss';
|
|
16
|
+
/**
|
|
17
|
+
* BlankImage component
|
|
18
|
+
* @param {BlankImageProps} props - The component properties.
|
|
19
|
+
* @return {React.ReactElement} - rendered component.
|
|
20
|
+
*/
|
|
21
|
+
function BlankImage({ children, isDotted = false, contentClassName = '' }) {
|
|
22
|
+
const blankImage = (_jsx("img", { className: "ai-assistant-image__carrousel-image", src: "data:image/svg+xml;utf8,<svg viewBox='0 0 1 1' width='1024' height='768' xmlns='http://www.w3.org/2000/svg'><path d='M0 0 L1 0 L1 1 L0 1 L0 0 Z' fill='none' /></svg>", alt: "" }));
|
|
23
|
+
return (_jsxs("div", { className: "ai-assistant-image__blank", children: [blankImage, _jsx("div", { className: clsx('ai-assistant-image__blank-content', contentClassName, {
|
|
24
|
+
'is-dotted': isDotted,
|
|
25
|
+
}), children: children })] }));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Carrousel component
|
|
29
|
+
* @param {CarrouselProps} props - The component properties.
|
|
30
|
+
* @return {React.ReactElement} - rendered component.
|
|
31
|
+
*/
|
|
32
|
+
export default function Carrousel({ images, current, handlePreviousImage, handleNextImage, actions = null, }) {
|
|
33
|
+
const [imageFeedbackDisabled, setImageFeedbackDisabled] = useState(false);
|
|
34
|
+
const prevButton = (_jsx("button", { className: "ai-carrousel__prev", onClick: handlePreviousImage, children: _jsx(Icon, { icon: chevronLeft, className: clsx('ai-carrousel__prev-icon', {
|
|
35
|
+
'is-disabled': current === 0,
|
|
36
|
+
}) }) }));
|
|
37
|
+
const nextButton = (_jsx("button", { className: "ai-carrousel__next", onClick: handleNextImage, children: _jsx(Icon, { icon: chevronRight, className: clsx('ai-carrousel__next-icon', {
|
|
38
|
+
'is-disabled': current + 1 === images.length,
|
|
39
|
+
}) }) }));
|
|
40
|
+
const total = images?.filter?.(item => item?.generating || Object.hasOwn(item, 'image') || Object.hasOwn(item, 'libraryId'))?.length;
|
|
41
|
+
const actual = current === 0 && total === 0 ? 0 : current + 1;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const imageData = images[current];
|
|
44
|
+
if (!imageData) {
|
|
45
|
+
setImageFeedbackDisabled(true);
|
|
46
|
+
}
|
|
47
|
+
const { image, generating, error } = imageData || {};
|
|
48
|
+
// disable if there's an empty modal
|
|
49
|
+
if (!image && !generating && !error) {
|
|
50
|
+
return setImageFeedbackDisabled(true);
|
|
51
|
+
}
|
|
52
|
+
// also disable if we're generating or have an error
|
|
53
|
+
if (generating || error) {
|
|
54
|
+
return setImageFeedbackDisabled(true);
|
|
55
|
+
}
|
|
56
|
+
setImageFeedbackDisabled(false);
|
|
57
|
+
}, [current, images]);
|
|
58
|
+
return (_jsxs("div", { className: "ai-assistant-image__carrousel", children: [_jsxs("div", { className: "ai-assistant-image__carrousel-images", children: [images.length > 1 && prevButton, images.map(({ image, generating, error, revisedPrompt, libraryUrl }, index) => (_jsx("div", { className: clsx('ai-assistant-image__carrousel-image-container', {
|
|
59
|
+
'is-current': current === index,
|
|
60
|
+
'is-prev': current > index,
|
|
61
|
+
}), children: generating ? (_jsxs(BlankImage, { contentClassName: "ai-assistant-image__loading", children: [__('Creating image…', 'jetpack-ai-client'), _jsx(Spinner, { style: {
|
|
62
|
+
width: '50px',
|
|
63
|
+
height: '50px',
|
|
64
|
+
} })] })) : (_jsx(_Fragment, { children: error ? (_jsx(BlankImage, { isDotted: true, children: _jsxs("div", { className: "ai-assistant-image__error", children: [__('An error occurred while generating the image. Please, try again!', 'jetpack-ai-client'), error?.message && (_jsx("span", { className: "ai-assistant-image__error-message", children: error?.message }))] }) })) : (_jsx(_Fragment, { children: !generating && !image && !libraryUrl ? (_jsx(BlankImage, { children: _jsx(AiIcon, {}) })) : (_jsx("img", { className: "ai-assistant-image__carrousel-image", src: image || libraryUrl, alt: revisedPrompt })) })) })) }, `image:` + index))), images.length > 1 && nextButton] }), _jsxs("div", { className: "ai-assistant-image__carrousel-footer", children: [_jsxs("div", { className: "ai-assistant-image__carrousel-footer-left", children: [_jsxs("div", { className: "ai-assistant-image__carrousel-counter", children: [prevButton, actual, " / ", total, nextButton] }), _jsx(AiFeedbackThumbs, { disabled: imageFeedbackDisabled, ratedItem: images[current]?.libraryUrl || '', iconSize: 20, options: {
|
|
65
|
+
mediaLibraryId: Number(images[current].libraryId),
|
|
66
|
+
prompt: images[current].prompt,
|
|
67
|
+
revisedPrompt: images[current].revisedPrompt,
|
|
68
|
+
}, feature: "image-generator" })] }), _jsx("div", { className: "ai-assistant-image__carrousel-actions", children: actions })] })] }));
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import './usage-counter.scss';
|
|
5
|
+
type UsageCounterProps = {
|
|
6
|
+
currentLimit: number;
|
|
7
|
+
currentUsage: number;
|
|
8
|
+
cost: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* UsageCounter component
|
|
12
|
+
* @param {UsageCounterProps} props - The component properties.
|
|
13
|
+
* @return {React.ReactElement} - rendered component.
|
|
14
|
+
*/
|
|
15
|
+
export default function UsageCounter({ currentLimit, currentUsage, cost }: UsageCounterProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { createInterpolateElement } from '@wordpress/element';
|
|
6
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import './usage-counter.scss';
|
|
11
|
+
/**
|
|
12
|
+
* UsageCounter component
|
|
13
|
+
* @param {UsageCounterProps} props - The component properties.
|
|
14
|
+
* @return {React.ReactElement} - rendered component.
|
|
15
|
+
*/
|
|
16
|
+
export default function UsageCounter({ currentLimit, currentUsage, cost }) {
|
|
17
|
+
const requestsBalance = currentLimit - currentUsage;
|
|
18
|
+
const requestsNeeded = createInterpolateElement(
|
|
19
|
+
// Translators: %d is the cost of one image.
|
|
20
|
+
sprintf(__('Requests needed: <counter>%d</counter>', 'jetpack-ai-client'), cost), {
|
|
21
|
+
counter: _jsx("span", {}),
|
|
22
|
+
});
|
|
23
|
+
const requestsAvailable = createInterpolateElement(sprintf(
|
|
24
|
+
// Translators: %d is the current requests balance.
|
|
25
|
+
__('Requests available: <counter>%d</counter>', 'jetpack-ai-client'), requestsBalance), {
|
|
26
|
+
counter: requestsBalance < cost ? (_jsx("span", { className: "ai-assistant-featured-image__usage-counter-no-limit" })) : (_jsx("strong", {})),
|
|
27
|
+
});
|
|
28
|
+
return (_jsxs("div", { className: "ai-assistant-featured-image__usage-counter", children: [_jsx("span", { children: requestsNeeded }), _jsx("span", { children: requestsAvailable })] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import './style.scss';
|
|
5
|
+
type FeaturedImageProps = {
|
|
6
|
+
busy: boolean;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
placement: string;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* FeaturedImage component
|
|
13
|
+
* @param {FeaturedImageProps} props - The component properties.
|
|
14
|
+
* @return {React.ReactElement} - rendered component.
|
|
15
|
+
*/
|
|
16
|
+
export default function FeaturedImage({ busy, disabled, placement, onClose, }: FeaturedImageProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { useAnalytics, PLAN_TYPE_UNLIMITED, usePlanType, } from '@automattic/jetpack-shared-extension-utils';
|
|
6
|
+
import { Button } from '@wordpress/components';
|
|
7
|
+
import { useDispatch, useSelect } from '@wordpress/data';
|
|
8
|
+
import { store as editorStore } from '@wordpress/editor';
|
|
9
|
+
import { useCallback, useState } from '@wordpress/element';
|
|
10
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
11
|
+
import debugFactory from 'debug';
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import './style.scss';
|
|
16
|
+
import { PLACEMENT_JETPACK_SIDEBAR, PLACEMENT_DOCUMENT_SETTINGS } from '../../constants.js';
|
|
17
|
+
import useAiFeature from '../../hooks/use-ai-feature/index.js';
|
|
18
|
+
import usePostContent from '../../hooks/use-post-content.js';
|
|
19
|
+
import useSaveToMediaLibrary from '../../hooks/use-save-to-media-library.js';
|
|
20
|
+
import AiImageModal from './components/ai-image-modal.js';
|
|
21
|
+
import useAiImage from './hooks/use-ai-image.js';
|
|
22
|
+
import useSiteType from './hooks/use-site-type.js';
|
|
23
|
+
import { FEATURED_IMAGE_FEATURE_NAME, IMAGE_GENERATION_MODEL_STABLE_DIFFUSION, IMAGE_GENERATION_MODEL_DALL_E_3, PLACEMENT_MEDIA_SOURCE_DROPDOWN, } from './types.js';
|
|
24
|
+
const debug = debugFactory('jetpack-ai-client:featured-image');
|
|
25
|
+
/**
|
|
26
|
+
* FeaturedImage component
|
|
27
|
+
* @param {FeaturedImageProps} props - The component properties.
|
|
28
|
+
* @return {React.ReactElement} - rendered component.
|
|
29
|
+
*/
|
|
30
|
+
export default function FeaturedImage({ busy, disabled, placement, onClose = () => { }, }) {
|
|
31
|
+
const [isFeaturedImageModalVisible, setIsFeaturedImageModalVisible] = useState(placement === PLACEMENT_MEDIA_SOURCE_DROPDOWN);
|
|
32
|
+
const siteType = useSiteType();
|
|
33
|
+
const postContent = usePostContent();
|
|
34
|
+
const { postTitle, postFeaturedMediaId, isEditorPanelOpened } = useSelect(select => {
|
|
35
|
+
return {
|
|
36
|
+
postTitle: select(editorStore).getEditedPostAttribute('title'),
|
|
37
|
+
postFeaturedMediaId: select(editorStore).getEditedPostAttribute('featured_media'),
|
|
38
|
+
isEditorPanelOpened: select(editorStore).isEditorPanelOpened ??
|
|
39
|
+
select('core/edit-post').isEditorPanelOpened,
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
const { saveToMediaLibrary } = useSaveToMediaLibrary();
|
|
43
|
+
const { tracks } = useAnalytics();
|
|
44
|
+
const { recordEvent } = tracks;
|
|
45
|
+
const [requestStyle, setRequestStyle] = useState(null);
|
|
46
|
+
const [prompt, setPrompt] = useState('');
|
|
47
|
+
// Editor actions
|
|
48
|
+
const { enableComplementaryArea } = useDispatch('core/interface');
|
|
49
|
+
const { clearSelectedBlock } = useDispatch('core/block-editor');
|
|
50
|
+
const { toggleEditorPanelOpened: toggleEditorPanelOpenedFromEditPost } = useDispatch('core/edit-post');
|
|
51
|
+
const { editPost, toggleEditorPanelOpened: toggleEditorPanelOpenedFromEditor } = useDispatch(editorStore);
|
|
52
|
+
// Get feature data
|
|
53
|
+
const { requireUpgrade, requestsCount, requestsLimit, currentTier, costs } = useAiFeature();
|
|
54
|
+
const planType = usePlanType(currentTier);
|
|
55
|
+
const featuredImageCost = costs?.[FEATURED_IMAGE_FEATURE_NAME]?.activeModel ?? 10;
|
|
56
|
+
const featuredImageActiveModel = featuredImageCost === costs?.[FEATURED_IMAGE_FEATURE_NAME]?.stableDiffusion
|
|
57
|
+
? IMAGE_GENERATION_MODEL_STABLE_DIFFUSION
|
|
58
|
+
: IMAGE_GENERATION_MODEL_DALL_E_3;
|
|
59
|
+
const isUnlimited = planType === PLAN_TYPE_UNLIMITED;
|
|
60
|
+
const requestsBalance = requestsLimit - requestsCount;
|
|
61
|
+
const notEnoughRequests = requestsBalance < featuredImageCost;
|
|
62
|
+
// Handle deprecation and move of toggle action from edit-post.
|
|
63
|
+
// https://github.com/WordPress/gutenberg/blob/fe4d8cb936df52945c01c1863f7b87b58b7cc69f/packages/edit-post/CHANGELOG.md?plain=1#L19
|
|
64
|
+
const toggleEditorPanelOpened = toggleEditorPanelOpenedFromEditor ?? toggleEditorPanelOpenedFromEditPost;
|
|
65
|
+
const { pointer, current, setCurrent, processImageGeneration, handlePreviousImage, handleNextImage, currentImage, currentPointer, images, imageStyles, guessStyle, } = useAiImage({
|
|
66
|
+
autoStart: false,
|
|
67
|
+
cost: featuredImageCost,
|
|
68
|
+
type: 'featured-image-generation',
|
|
69
|
+
feature: FEATURED_IMAGE_FEATURE_NAME,
|
|
70
|
+
previousMediaId: postFeaturedMediaId,
|
|
71
|
+
});
|
|
72
|
+
const handleModalClose = useCallback(() => {
|
|
73
|
+
setIsFeaturedImageModalVisible(false);
|
|
74
|
+
onClose?.();
|
|
75
|
+
}, [onClose]);
|
|
76
|
+
const handleModalOpen = useCallback(() => {
|
|
77
|
+
setIsFeaturedImageModalVisible(true);
|
|
78
|
+
}, []);
|
|
79
|
+
/**
|
|
80
|
+
* Handle the guess style for the image. It is reworked here to include the post content.
|
|
81
|
+
*/
|
|
82
|
+
const handleGuessStyle = useCallback(userPrompt => {
|
|
83
|
+
const content = postTitle + '\n\n' + postContent;
|
|
84
|
+
return guessStyle(userPrompt, 'featured-image-guess-style', content);
|
|
85
|
+
}, [postContent, postTitle, guessStyle]);
|
|
86
|
+
const handleGenerate = useCallback(({ userPrompt, style, }) => {
|
|
87
|
+
// track the generate image event
|
|
88
|
+
recordEvent('jetpack_ai_featured_image_generation_generate_image', {
|
|
89
|
+
placement,
|
|
90
|
+
model: featuredImageActiveModel,
|
|
91
|
+
site_type: siteType,
|
|
92
|
+
style,
|
|
93
|
+
userPrompt,
|
|
94
|
+
});
|
|
95
|
+
setIsFeaturedImageModalVisible(true);
|
|
96
|
+
return processImageGeneration({
|
|
97
|
+
userPrompt,
|
|
98
|
+
postContent: postTitle + '\n\n' + postContent,
|
|
99
|
+
notEnoughRequests,
|
|
100
|
+
style,
|
|
101
|
+
}).catch(error => {
|
|
102
|
+
recordEvent('jetpack_ai_featured_image_generation_error', {
|
|
103
|
+
placement,
|
|
104
|
+
error: error?.message,
|
|
105
|
+
model: featuredImageActiveModel,
|
|
106
|
+
site_type: siteType,
|
|
107
|
+
style,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}, [
|
|
111
|
+
recordEvent,
|
|
112
|
+
placement,
|
|
113
|
+
featuredImageActiveModel,
|
|
114
|
+
siteType,
|
|
115
|
+
processImageGeneration,
|
|
116
|
+
postContent,
|
|
117
|
+
notEnoughRequests,
|
|
118
|
+
postTitle,
|
|
119
|
+
]);
|
|
120
|
+
const handleFirstGenerate = useCallback(async () => {
|
|
121
|
+
currentPointer.generating = true;
|
|
122
|
+
const guessedStyle = await handleGuessStyle('');
|
|
123
|
+
setRequestStyle(guessedStyle);
|
|
124
|
+
const response = await handleGenerate({ userPrompt: '', style: guessedStyle });
|
|
125
|
+
if (response) {
|
|
126
|
+
debug('handleFirstGenerate', response.revisedPrompt);
|
|
127
|
+
setPrompt(response.revisedPrompt || '');
|
|
128
|
+
}
|
|
129
|
+
}, [currentPointer, handleGenerate, handleGuessStyle]);
|
|
130
|
+
const handleRegenerate = useCallback(({ userPrompt, style }) => {
|
|
131
|
+
// track the regenerate image event
|
|
132
|
+
recordEvent('jetpack_ai_featured_image_generation_generate_another_image', {
|
|
133
|
+
placement,
|
|
134
|
+
model: featuredImageActiveModel,
|
|
135
|
+
site_type: siteType,
|
|
136
|
+
style: style,
|
|
137
|
+
});
|
|
138
|
+
setCurrent(() => images.length);
|
|
139
|
+
processImageGeneration({
|
|
140
|
+
userPrompt,
|
|
141
|
+
postContent: postTitle + '\n\n' + postContent,
|
|
142
|
+
notEnoughRequests,
|
|
143
|
+
style,
|
|
144
|
+
}).catch(error => {
|
|
145
|
+
recordEvent('jetpack_ai_featured_image_generation_error', {
|
|
146
|
+
placement,
|
|
147
|
+
error: error?.message,
|
|
148
|
+
model: featuredImageActiveModel,
|
|
149
|
+
site_type: siteType,
|
|
150
|
+
style,
|
|
151
|
+
userPrompt,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}, [
|
|
155
|
+
recordEvent,
|
|
156
|
+
placement,
|
|
157
|
+
featuredImageActiveModel,
|
|
158
|
+
siteType,
|
|
159
|
+
setCurrent,
|
|
160
|
+
processImageGeneration,
|
|
161
|
+
postTitle,
|
|
162
|
+
postContent,
|
|
163
|
+
notEnoughRequests,
|
|
164
|
+
images,
|
|
165
|
+
]);
|
|
166
|
+
const handleTryAgain = useCallback(({ userPrompt, style }) => {
|
|
167
|
+
// track the try again event
|
|
168
|
+
recordEvent('jetpack_ai_featured_image_generation_try_again', {
|
|
169
|
+
placement,
|
|
170
|
+
model: featuredImageActiveModel,
|
|
171
|
+
site_type: siteType,
|
|
172
|
+
style,
|
|
173
|
+
});
|
|
174
|
+
processImageGeneration({
|
|
175
|
+
userPrompt,
|
|
176
|
+
postContent: postTitle + '\n\n' + postContent,
|
|
177
|
+
notEnoughRequests,
|
|
178
|
+
style,
|
|
179
|
+
}).catch(error => {
|
|
180
|
+
recordEvent('jetpack_ai_featured_image_generation_error', {
|
|
181
|
+
placement,
|
|
182
|
+
error: error?.message,
|
|
183
|
+
model: featuredImageActiveModel,
|
|
184
|
+
site_type: siteType,
|
|
185
|
+
style,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}, [
|
|
189
|
+
recordEvent,
|
|
190
|
+
placement,
|
|
191
|
+
featuredImageActiveModel,
|
|
192
|
+
siteType,
|
|
193
|
+
processImageGeneration,
|
|
194
|
+
postContent,
|
|
195
|
+
notEnoughRequests,
|
|
196
|
+
postTitle,
|
|
197
|
+
]);
|
|
198
|
+
const triggerComplementaryArea = useCallback(() => {
|
|
199
|
+
// clear any block selection, because selected blocks have precedence on settings sidebar
|
|
200
|
+
clearSelectedBlock();
|
|
201
|
+
return enableComplementaryArea('core/edit-post', 'edit-post/document');
|
|
202
|
+
}, [clearSelectedBlock, enableComplementaryArea]);
|
|
203
|
+
const handleAccept = useCallback(() => {
|
|
204
|
+
// track the accept/use image event
|
|
205
|
+
recordEvent('jetpack_ai_featured_image_generation_use_image', {
|
|
206
|
+
placement,
|
|
207
|
+
model: featuredImageActiveModel,
|
|
208
|
+
site_type: siteType,
|
|
209
|
+
});
|
|
210
|
+
const setAsFeaturedImage = image => {
|
|
211
|
+
editPost({ featured_media: image });
|
|
212
|
+
handleModalClose();
|
|
213
|
+
// Open the featured image panel for users to see the new image.
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
const isFeaturedImagePanelOpened = isEditorPanelOpened('featured-image');
|
|
216
|
+
const isPostStatusPanelOpened = isEditorPanelOpened('post-status');
|
|
217
|
+
// open the complementary area and then trigger the featured image panel.
|
|
218
|
+
triggerComplementaryArea().then(() => {
|
|
219
|
+
if (!isFeaturedImagePanelOpened) {
|
|
220
|
+
toggleEditorPanelOpened('featured-image');
|
|
221
|
+
}
|
|
222
|
+
// handle the case where the featured image panel is not present
|
|
223
|
+
if (!isPostStatusPanelOpened) {
|
|
224
|
+
toggleEditorPanelOpened('post-status');
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}, 500);
|
|
228
|
+
};
|
|
229
|
+
// If the image is already in the media library, use it directly, if it failed for some reason
|
|
230
|
+
// save it to the media library and then use it.
|
|
231
|
+
if (currentImage?.libraryId) {
|
|
232
|
+
setAsFeaturedImage(currentImage?.libraryId);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
saveToMediaLibrary(currentImage?.image)
|
|
236
|
+
.then(image => {
|
|
237
|
+
setAsFeaturedImage(image?.id);
|
|
238
|
+
})
|
|
239
|
+
.catch(error => {
|
|
240
|
+
recordEvent('jetpack_ai_featured_image_saving_error', {
|
|
241
|
+
placement,
|
|
242
|
+
error: error?.message,
|
|
243
|
+
model: featuredImageActiveModel,
|
|
244
|
+
site_type: siteType,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}, [
|
|
249
|
+
recordEvent,
|
|
250
|
+
placement,
|
|
251
|
+
featuredImageActiveModel,
|
|
252
|
+
siteType,
|
|
253
|
+
currentImage?.libraryId,
|
|
254
|
+
currentImage?.image,
|
|
255
|
+
editPost,
|
|
256
|
+
handleModalClose,
|
|
257
|
+
isEditorPanelOpened,
|
|
258
|
+
triggerComplementaryArea,
|
|
259
|
+
toggleEditorPanelOpened,
|
|
260
|
+
saveToMediaLibrary,
|
|
261
|
+
]);
|
|
262
|
+
const generateAgainText = __('Generate another image', 'jetpack-ai-client');
|
|
263
|
+
const generateText = __('Generate', 'jetpack-ai-client');
|
|
264
|
+
const hasContent = postContent.trim?.() || postTitle.trim?.() ? true : false;
|
|
265
|
+
const hasPrompt = hasContent ? prompt.length >= 0 : prompt.length >= 3;
|
|
266
|
+
const disableInput = notEnoughRequests || currentPointer?.generating || requireUpgrade;
|
|
267
|
+
const disableAction = disableInput || (!hasContent && !hasPrompt);
|
|
268
|
+
const upgradeDescription = notEnoughRequests
|
|
269
|
+
? sprintf(
|
|
270
|
+
// Translators: %d is the cost of generating a featured image.
|
|
271
|
+
__("Featured image generation costs %d requests per image. You don't have enough requests to generate another image.", 'jetpack-ai-client'), featuredImageCost)
|
|
272
|
+
: null;
|
|
273
|
+
const acceptButton = (_jsx(Button, { onClick: handleAccept, variant: "primary", disabled: !currentImage?.image ||
|
|
274
|
+
currentImage?.generating ||
|
|
275
|
+
currentImage?.libraryId === postFeaturedMediaId, children: __('Set as featured image', 'jetpack-ai-client') }));
|
|
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, { postContent: hasContent, 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
|
+
}
|