@automattic/jetpack-ai-client 0.18.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,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.20.0] - 2024-09-30
9
+ ### Added
10
+ - AI Client: add support for showStyleSelector on logo generator and use-image-generator [#39530]
11
+
12
+ ## [0.19.0] - 2024-09-23
13
+ ### Changed
14
+ - AI Client: Don't send a default style to jetpack-ai-image endpoint, default is handled in backend and we need to not send it until we're ready for it to be a user option. [#39494]
15
+ - Jetpack AI: Point upgrade links and buttons to checkout instead of product interstitial. [#39469]
16
+ - Logo generator: Get selection from the prompt's document rather than the global `window`. [#39364]
17
+
8
18
  ## [0.18.1] - 2024-09-10
9
19
  ### Changed
10
20
  - Updated package dependencies. [#39302]
@@ -409,6 +419,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
409
419
  - Updated package dependencies. [#31659]
410
420
  - Updated package dependencies. [#31785]
411
421
 
422
+ [0.20.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.19.0...v0.20.0
423
+ [0.19.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.18.1...v0.19.0
412
424
  [0.18.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.18.0...v0.18.1
413
425
  [0.18.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.17.0...v0.18.0
414
426
  [0.17.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.4...v0.17.0
@@ -0,0 +1,39 @@
1
+ export declare const IMAGE_STYLE_ENHANCE = "enhance";
2
+ export declare const IMAGE_STYLE_ANIME = "anime";
3
+ export declare const IMAGE_STYLE_PHOTOGRAPHIC = "photographic";
4
+ export declare const IMAGE_STYLE_DIGITAL_ART = "digital-art";
5
+ export declare const IMAGE_STYLE_COMICBOOK = "comicbook";
6
+ export declare const IMAGE_STYLE_FANTASY_ART = "fantasy-art";
7
+ export declare const IMAGE_STYLE_ANALOG_FILM = "analog-film";
8
+ export declare const IMAGE_STYLE_NEONPUNK = "neonpunk";
9
+ export declare const IMAGE_STYLE_ISOMETRIC = "isometric";
10
+ export declare const IMAGE_STYLE_LOWPOLY = "lowpoly";
11
+ export declare const IMAGE_STYLE_ORIGAMI = "origami";
12
+ export declare const IMAGE_STYLE_LINE_ART = "line-art";
13
+ export declare const IMAGE_STYLE_CRAFT_CLAY = "craft-clay";
14
+ export declare const IMAGE_STYLE_CINEMATIC = "cinematic";
15
+ export declare const IMAGE_STYLE_3D_MODEL = "3d-model";
16
+ export declare const IMAGE_STYLE_PIXEL_ART = "pixel-art";
17
+ export declare const IMAGE_STYLE_TEXTURE = "texture";
18
+ export declare const IMAGE_STYLE_MONTY_PYTHON = "monty-python";
19
+ export declare const IMAGE_STYLE_LABELS: {
20
+ enhance: string;
21
+ anime: string;
22
+ photographic: string;
23
+ "digital-art": string;
24
+ comicbook: string;
25
+ "fantasy-art": string;
26
+ "analog-film": string;
27
+ neonpunk: string;
28
+ isometric: string;
29
+ lowpoly: string;
30
+ origami: string;
31
+ "line-art": string;
32
+ "craft-clay": string;
33
+ cinematic: string;
34
+ "3d-model": string;
35
+ "pixel-art": string;
36
+ texture: string;
37
+ "monty-python": string;
38
+ };
39
+ export type ImageStyle = keyof typeof IMAGE_STYLE_LABELS;
@@ -0,0 +1,40 @@
1
+ import { __ } from '@wordpress/i18n';
2
+ // Styles
3
+ export const IMAGE_STYLE_ENHANCE = 'enhance';
4
+ export const IMAGE_STYLE_ANIME = 'anime';
5
+ export const IMAGE_STYLE_PHOTOGRAPHIC = 'photographic';
6
+ export const IMAGE_STYLE_DIGITAL_ART = 'digital-art';
7
+ export const IMAGE_STYLE_COMICBOOK = 'comicbook';
8
+ export const IMAGE_STYLE_FANTASY_ART = 'fantasy-art';
9
+ export const IMAGE_STYLE_ANALOG_FILM = 'analog-film';
10
+ export const IMAGE_STYLE_NEONPUNK = 'neonpunk';
11
+ export const IMAGE_STYLE_ISOMETRIC = 'isometric';
12
+ export const IMAGE_STYLE_LOWPOLY = 'lowpoly';
13
+ export const IMAGE_STYLE_ORIGAMI = 'origami';
14
+ export const IMAGE_STYLE_LINE_ART = 'line-art';
15
+ export const IMAGE_STYLE_CRAFT_CLAY = 'craft-clay';
16
+ export const IMAGE_STYLE_CINEMATIC = 'cinematic';
17
+ export const IMAGE_STYLE_3D_MODEL = '3d-model';
18
+ export const IMAGE_STYLE_PIXEL_ART = 'pixel-art';
19
+ export const IMAGE_STYLE_TEXTURE = 'texture';
20
+ export const IMAGE_STYLE_MONTY_PYTHON = 'monty-python';
21
+ export const IMAGE_STYLE_LABELS = {
22
+ [IMAGE_STYLE_ENHANCE]: __('Enhance', 'jetpack-ai-client'),
23
+ [IMAGE_STYLE_ANIME]: __('Anime', 'jetpack-ai-client'),
24
+ [IMAGE_STYLE_PHOTOGRAPHIC]: __('Photographic', 'jetpack-ai-client'),
25
+ [IMAGE_STYLE_DIGITAL_ART]: __('Digital Art', 'jetpack-ai-client'),
26
+ [IMAGE_STYLE_COMICBOOK]: __('Comicbook', 'jetpack-ai-client'),
27
+ [IMAGE_STYLE_FANTASY_ART]: __('Fantasy Art', 'jetpack-ai-client'),
28
+ [IMAGE_STYLE_ANALOG_FILM]: __('Analog Film', 'jetpack-ai-client'),
29
+ [IMAGE_STYLE_NEONPUNK]: __('Neon Punk', 'jetpack-ai-client'),
30
+ [IMAGE_STYLE_ISOMETRIC]: __('Isometric', 'jetpack-ai-client'),
31
+ [IMAGE_STYLE_LOWPOLY]: __('Low Poly', 'jetpack-ai-client'),
32
+ [IMAGE_STYLE_ORIGAMI]: __('Origami', 'jetpack-ai-client'),
33
+ [IMAGE_STYLE_LINE_ART]: __('Line Art', 'jetpack-ai-client'),
34
+ [IMAGE_STYLE_CRAFT_CLAY]: __('Craft Clay', 'jetpack-ai-client'),
35
+ [IMAGE_STYLE_CINEMATIC]: __('Cinematic', 'jetpack-ai-client'),
36
+ [IMAGE_STYLE_3D_MODEL]: __('3D Model', 'jetpack-ai-client'),
37
+ [IMAGE_STYLE_PIXEL_ART]: __('Pixel Art', 'jetpack-ai-client'),
38
+ [IMAGE_STYLE_TEXTURE]: __('Texture', 'jetpack-ai-client'),
39
+ [IMAGE_STYLE_MONTY_PYTHON]: __('Monty Python', 'jetpack-ai-client'),
40
+ };
@@ -19,5 +19,7 @@ declare const useImageGenerator: () => {
19
19
  userPrompt?: string;
20
20
  }) => Promise<ImageGenerationResponse>;
21
21
  generateImageWithParameters: (parameters: object) => Promise<ImageGenerationResponse>;
22
+ getImageStyles: () => object;
22
23
  };
23
24
  export default useImageGenerator;
25
+ export * from './constants.js';
@@ -7,6 +7,7 @@ import debugFactory from 'debug';
7
7
  */
8
8
  import askQuestionSync from '../../ask-question/sync.js';
9
9
  import requestJwt from '../../jwt/index.js';
10
+ import { IMAGE_STYLE_LABELS } from './constants.js';
10
11
  const debug = debugFactory('ai-client:use-image-generator');
11
12
  /**
12
13
  * Cut the post content on a given lenght so the total length of the prompt is not longer than 4000 characters.
@@ -168,7 +169,6 @@ const useImageGenerator = () => {
168
169
  prompt,
169
170
  feature,
170
171
  model: 'stable-diffusion',
171
- style: 'photographic',
172
172
  };
173
173
  const data = await executeImageGeneration(parameters);
174
174
  return data;
@@ -196,10 +196,20 @@ const useImageGenerator = () => {
196
196
  return Promise.reject(error);
197
197
  }
198
198
  };
199
+ /**
200
+ * Get available styles.
201
+ *
202
+ * @return {object} with the styles {key:label} for the image generation.
203
+ */
204
+ const getImageStyles = function () {
205
+ return IMAGE_STYLE_LABELS;
206
+ };
199
207
  return {
200
208
  generateImage,
201
209
  generateImageWithStableDiffusion,
202
210
  generateImageWithParameters: executeImageGeneration,
211
+ getImageStyles,
203
212
  };
204
213
  };
205
214
  export default useImageGenerator;
215
+ export * from './constants.js';
@@ -29,7 +29,7 @@ import { UpgradeScreen } from './upgrade-screen.js';
29
29
  import { VisitSiteBanner } from './visit-site-banner.js';
30
30
  import './generator-modal.scss';
31
31
  const debug = debugFactory('jetpack-ai-calypso:generator-modal');
32
- export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDetails, context, placement, }) => {
32
+ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDetails, context, placement, showStyleSelector, }) => {
33
33
  const { tracks } = useAnalytics();
34
34
  const { recordEvent: recordTracksEvent } = tracks;
35
35
  const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory, setIsLoadingHistory } = useDispatch(STORE_NAME);
@@ -181,7 +181,7 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDet
181
181
  body = (_jsx(UpgradeScreen, { onCancel: closeModal, upgradeURL: upgradeURL, reason: needsFeature ? 'feature' : 'requests' }));
182
182
  }
183
183
  else {
184
- body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {}), _jsx("div", { className: "jetpack-ai-logo-generator__accept-actions", children: _jsx(Button, { variant: "primary", onClick: closeModal, children: __('Close', 'jetpack-ai-client') }) })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsxs(Button, { variant: "link", className: "jetpack-ai-logo-generator__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: [_jsx("span", { children: __('Provide feedback', 'jetpack-ai-client') }), _jsx(Icon, { icon: external, className: "icon" })] }) })] }))] }));
184
+ body = (_jsxs(_Fragment, { children: [!logoAccepted && (_jsx(Prompt, { initialPrompt: initialPrompt, showStyleSelector: showStyleSelector })), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {}), _jsx("div", { className: "jetpack-ai-logo-generator__accept-actions", children: _jsx(Button, { variant: "primary", onClick: closeModal, children: __('Close', 'jetpack-ai-client') }) })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsxs(Button, { variant: "link", className: "jetpack-ai-logo-generator__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: [_jsx("span", { children: __('Provide feedback', 'jetpack-ai-client') }), _jsx(Icon, { icon: external, className: "icon" })] }) })] }))] }));
185
185
  }
186
186
  return (_jsx(_Fragment, { children: isOpen && (_jsx(Modal, { className: "jetpack-ai-logo-generator-modal", onRequestClose: closeModal, shouldCloseOnClickOutside: false, shouldCloseOnEsc: false, title: __('Jetpack AI Logo Generator', 'jetpack-ai-client'), children: _jsx("div", { className: clsx('jetpack-ai-logo-generator-modal__body', {
187
187
  'notice-modal': needsFeature || needsMoreRequests || featureFetchError || firstLogoPromptFetchError,
@@ -1,5 +1,7 @@
1
- /// <reference types="react" resolution-mode="require"/>
2
1
  import './prompt.scss';
3
- export declare const Prompt: React.FC<{
2
+ type PromptProps = {
4
3
  initialPrompt?: string;
5
- }>;
4
+ showStyleSelector?: boolean;
5
+ };
6
+ export declare const Prompt: ({ initialPrompt, showStyleSelector }: PromptProps) => import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  * External dependencies
4
4
  */
5
5
  import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
6
- import { Button, Tooltip } from '@wordpress/components';
6
+ import { Button, Tooltip, SelectControl } from '@wordpress/components';
7
7
  import { __, sprintf } from '@wordpress/i18n';
8
8
  import { Icon, info } from '@wordpress/icons';
9
9
  import debugFactory from 'debug';
@@ -11,8 +11,9 @@ import { useCallback, useEffect, useState, useRef } from 'react';
11
11
  /**
12
12
  * Internal dependencies
13
13
  */
14
+ import { IMAGE_STYLE_MONTY_PYTHON, IMAGE_STYLE_LINE_ART, } from '../../hooks/use-image-generator/constants.js';
14
15
  import AiIcon from '../assets/icons/ai.js';
15
- import { EVENT_GENERATE, MINIMUM_PROMPT_LENGTH, EVENT_UPGRADE, EVENT_PLACEMENT_INPUT_FOOTER, } from '../constants.js';
16
+ import { EVENT_GENERATE, MINIMUM_PROMPT_LENGTH, EVENT_UPGRADE, EVENT_PLACEMENT_INPUT_FOOTER, EVENT_SWITCH_STYLE, } from '../constants.js';
16
17
  import { useCheckout } from '../hooks/use-checkout.js';
17
18
  import useLogoGenerator from '../hooks/use-logo-generator.js';
18
19
  import useRequestErrors from '../hooks/use-request-errors.js';
@@ -20,7 +21,7 @@ import { FairUsageNotice } from './fair-usage-notice.js';
20
21
  import { UpgradeNudge } from './upgrade-nudge.js';
21
22
  import './prompt.scss';
22
23
  const debug = debugFactory('jetpack-ai-calypso:prompt-box');
23
- export const Prompt = ({ initialPrompt = '' }) => {
24
+ export const Prompt = ({ initialPrompt = '', showStyleSelector = false }) => {
24
25
  const { tracks } = useAnalytics();
25
26
  const { recordEvent: recordTracksEvent } = tracks;
26
27
  const [prompt, setPrompt] = useState(initialPrompt);
@@ -28,7 +29,8 @@ export const Prompt = ({ initialPrompt = '' }) => {
28
29
  const { enhancePromptFetchError, logoFetchError } = useRequestErrors();
29
30
  const { nextTierCheckoutURL: checkoutUrl, hasNextTier } = useCheckout();
30
31
  const hasPrompt = prompt?.length >= MINIMUM_PROMPT_LENGTH;
31
- const { generateLogo, enhancePrompt, setIsEnhancingPrompt, isBusy, isEnhancingPrompt, site, getAiAssistantFeature, requireUpgrade, context, tierPlansEnabled, } = useLogoGenerator();
32
+ const [style, setStyle] = useState(showStyleSelector ? IMAGE_STYLE_LINE_ART : null);
33
+ const { generateLogo, enhancePrompt, setIsEnhancingPrompt, isBusy, isEnhancingPrompt, site, getAiAssistantFeature, requireUpgrade, context, tierPlansEnabled, getImageStyles, } = useLogoGenerator();
32
34
  const enhancingLabel = __('Enhancing…', 'jetpack-ai-client');
33
35
  const enhanceLabel = __('Enhance prompt', 'jetpack-ai-client');
34
36
  const enhanceButtonLabel = isEnhancingPrompt ? enhancingLabel : enhanceLabel;
@@ -66,15 +68,16 @@ export const Prompt = ({ initialPrompt = '' }) => {
66
68
  }
67
69
  }, [prompt]);
68
70
  const onGenerate = useCallback(async () => {
69
- recordTracksEvent(EVENT_GENERATE, { context, tool: 'image' });
70
- generateLogo({ prompt });
71
- }, [context, generateLogo, prompt]);
71
+ // shouldn't tool be "logo-generator" to be more specific?
72
+ recordTracksEvent(EVENT_GENERATE, { context, tool: 'image', style });
73
+ generateLogo({ prompt, style });
74
+ }, [context, generateLogo, prompt, style]);
72
75
  const onPromptInput = (event) => {
73
76
  setPrompt(event.target.textContent || '');
74
77
  };
75
78
  const onPromptPaste = (event) => {
76
79
  event.preventDefault();
77
- const selection = window.getSelection();
80
+ const selection = event.currentTarget.ownerDocument.getSelection();
78
81
  if (!selection || !selection.rangeCount) {
79
82
  return;
80
83
  }
@@ -89,7 +92,21 @@ export const Prompt = ({ initialPrompt = '' }) => {
89
92
  const onUpgradeClick = () => {
90
93
  recordTracksEvent(EVENT_UPGRADE, { context, placement: EVENT_PLACEMENT_INPUT_FOOTER });
91
94
  };
92
- return (_jsxs("div", { className: "jetpack-ai-logo-generator__prompt", children: [_jsxs("div", { className: "jetpack-ai-logo-generator__prompt-header", children: [_jsx("div", { className: "jetpack-ai-logo-generator__prompt-label", children: __('Describe your site:', 'jetpack-ai-client') }), _jsx("div", { className: "jetpack-ai-logo-generator__prompt-actions", children: _jsxs(Button, { variant: "link", disabled: isBusy || requireUpgrade || !hasPrompt, onClick: onEnhance, children: [_jsx(AiIcon, {}), _jsx("span", { children: enhanceButtonLabel })] }) })] }), _jsxs("div", { className: "jetpack-ai-logo-generator__prompt-query", children: [_jsx("div", { ref: inputRef, contentEditable: !isBusy && !requireUpgrade,
95
+ const updateStyle = useCallback((imageStyle) => {
96
+ debug('change style', imageStyle);
97
+ setStyle(imageStyle);
98
+ recordTracksEvent(EVENT_SWITCH_STYLE, { context, style: imageStyle });
99
+ }, [context, setStyle, recordTracksEvent]);
100
+ const imageStyles = getImageStyles();
101
+ const availableStyles = Object.keys(imageStyles).filter((styleKey) => styleKey !== IMAGE_STYLE_MONTY_PYTHON);
102
+ return (_jsxs("div", { className: "jetpack-ai-logo-generator__prompt", children: [_jsxs("div", { className: "jetpack-ai-logo-generator__prompt-header", children: [_jsx("div", { className: "jetpack-ai-logo-generator__prompt-label", children: __('Describe your site:', 'jetpack-ai-client') }), _jsx("div", { className: "jetpack-ai-logo-generator__prompt-actions", children: _jsxs(Button, { variant: "link", disabled: isBusy || requireUpgrade || !hasPrompt, onClick: onEnhance, children: [_jsx(AiIcon, {}), enhanceButtonLabel] }) }), showStyleSelector && availableStyles && (_jsx(SelectControl
103
+ // label={ __( 'Style', 'jetpack-ai-client' ) }
104
+ , {
105
+ // label={ __( 'Style', 'jetpack-ai-client' ) }
106
+ __nextHasNoMarginBottom: true, value: style, options: availableStyles.map(imageStyle => ({
107
+ label: imageStyles[imageStyle],
108
+ value: imageStyle,
109
+ })), onChange: updateStyle }))] }), _jsxs("div", { className: "jetpack-ai-logo-generator__prompt-query", children: [_jsx("div", { ref: inputRef, contentEditable: !isBusy && !requireUpgrade,
93
110
  // The content editable div is expected to be updated by the enhance prompt, so warnings are suppressed
94
111
  suppressContentEditableWarning: true, className: "prompt-query__input", onInput: onPromptInput, onPaste: onPromptPaste, "data-placeholder": __('Describe your site or simply ask for a logo specifying some details about it', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", className: "jetpack-ai-logo-generator__prompt-submit", onClick: onGenerate, disabled: isBusy || requireUpgrade || !hasPrompt, children: __('Generate', 'jetpack-ai-client') })] }), _jsxs("div", { className: "jetpack-ai-logo-generator__prompt-footer", children: [!isUnlimited && !requireUpgrade && (_jsxs("div", { className: "jetpack-ai-logo-generator__prompt-requests", children: [_jsx("div", { children: sprintf(
95
112
  // translators: %u is the number of requests
@@ -8,6 +8,7 @@ export declare const EVENT_USE = "jetpack_ai_logo_generator_use";
8
8
  export declare const EVENT_NAVIGATE = "jetpack_ai_logo_generator_navigate";
9
9
  export declare const EVENT_FEEDBACK = "jetpack_ai_logo_generator_feedback";
10
10
  export declare const EVENT_UPGRADE = "jetpack_ai_upgrade_button";
11
+ export declare const EVENT_SWITCH_STYLE = "jetpack_ai_logo_generator_switch_style";
11
12
  export declare const EVENT_PLACEMENT_QUICK_LINKS = "quick_links";
12
13
  export declare const EVENT_PLACEMENT_INPUT_FOOTER = "input_footer";
13
14
  export declare const EVENT_PLACEMENT_FREE_USER_SCREEN = "free_user_screen";
@@ -9,6 +9,7 @@ export const EVENT_USE = 'jetpack_ai_logo_generator_use';
9
9
  export const EVENT_NAVIGATE = 'jetpack_ai_logo_generator_navigate';
10
10
  export const EVENT_FEEDBACK = 'jetpack_ai_logo_generator_feedback';
11
11
  export const EVENT_UPGRADE = 'jetpack_ai_upgrade_button';
12
+ export const EVENT_SWITCH_STYLE = 'jetpack_ai_logo_generator_switch_style';
12
13
  // Event placement constants
13
14
  export const EVENT_PLACEMENT_QUICK_LINKS = 'quick_links';
14
15
  export const EVENT_PLACEMENT_INPUT_FOOTER = 'input_footer';
@@ -17,18 +17,27 @@ export const useCheckout = () => {
17
17
  tierPlansEnabled: selectors.getAiAssistantFeature().tierPlansEnabled,
18
18
  };
19
19
  }, []);
20
+ const isJetpackSite = !isAtomicSite() && !isSimpleSite();
21
+ const redirectSource = isJetpackSite
22
+ ? 'jetpack-ai-upgrade-url-for-jetpack-sites'
23
+ : 'jetpack-ai-yearly-tier-upgrade-nudge';
20
24
  /**
21
- * Use the Jetpack redirect URL to open the checkout page
25
+ * Determine the post-checkout URL for non-Jetpack sites
22
26
  */
23
- const wpcomCheckoutUrl = new URL(`https://jetpack.com/redirect/`);
24
- wpcomCheckoutUrl.searchParams.set('source', 'jetpack-ai-yearly-tier-upgrade-nudge');
25
- wpcomCheckoutUrl.searchParams.set('site', getSiteFragment());
26
- wpcomCheckoutUrl.searchParams.set('path', tierPlansEnabled ? `jetpack_ai_yearly:-q-${nextTier?.limit}` : 'jetpack_ai_yearly');
27
+ const siteFragment = getSiteFragment();
28
+ const wpcomRedirectToURL = `https://wordpress.com/home/${siteFragment}`;
27
29
  /**
28
- * Open the product interstitial page
30
+ * Use the Jetpack redirect URL to open the checkout page
29
31
  */
30
- const jetpackCheckoutUrl = `${window?.Jetpack_Editor_Initial_State?.adminUrl}admin.php?redirect_to_referrer=1&page=my-jetpack#/add-jetpack-ai`;
31
- const nextTierCheckoutURL = isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl.toString() : jetpackCheckoutUrl;
32
+ const checkoutUrl = new URL(`https://jetpack.com/redirect/`);
33
+ checkoutUrl.searchParams.set('source', redirectSource);
34
+ checkoutUrl.searchParams.set('site', siteFragment);
35
+ checkoutUrl.searchParams.set('path', tierPlansEnabled ? `jetpack_ai_yearly:-q-${nextTier?.limit}` : 'jetpack_ai_yearly');
36
+ // For Jetpack sites, the redirect_to parameter is handled by the Jetpack redirect source
37
+ if (!isJetpackSite) {
38
+ checkoutUrl.searchParams.set('query', `redirect_to=${encodeURIComponent(wpcomRedirectToURL)}`);
39
+ }
40
+ const nextTierCheckoutURL = checkoutUrl.toString();
32
41
  debug('Next tier checkout URL: ', nextTierCheckoutURL);
33
42
  return {
34
43
  nextTierCheckoutURL,
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Types
3
3
  */
4
+ import type { ImageStyle } from '../../hooks/use-image-generator/constants.js';
4
5
  import type { Logo, SaveLogo } from '../store/types.js';
5
6
  declare const useLogoGenerator: () => {
6
7
  logos: Logo[];
@@ -15,8 +16,9 @@ declare const useLogoGenerator: () => {
15
16
  generateFirstPrompt: () => Promise<string>;
16
17
  saveLogo: SaveLogo;
17
18
  applyLogo: () => Promise<void>;
18
- generateImage: ({ prompt, }: {
19
+ generateImage: ({ prompt, style, }: {
19
20
  prompt: string;
21
+ style?: ImageStyle | null;
20
22
  }) => Promise<{
21
23
  data: Array<{
22
24
  url: string;
@@ -26,8 +28,9 @@ declare const useLogoGenerator: () => {
26
28
  prompt: string;
27
29
  }) => Promise<string>;
28
30
  storeLogo: (logo: Logo) => void;
29
- generateLogo: ({ prompt }: {
31
+ generateLogo: ({ prompt, style, }: {
30
32
  prompt: string;
33
+ style?: ImageStyle | null;
31
34
  }) => Promise<void>;
32
35
  setIsEnhancingPrompt: any;
33
36
  setIsRequestingImage: any;
@@ -45,5 +48,6 @@ declare const useLogoGenerator: () => {
45
48
  tierPlansEnabled: boolean;
46
49
  isLoadingHistory: boolean;
47
50
  setIsLoadingHistory: any;
51
+ getImageStyles: () => object;
48
52
  };
49
53
  export default useLogoGenerator;
@@ -37,7 +37,7 @@ const useLogoGenerator = () => {
37
37
  };
38
38
  }, []);
39
39
  const { setFirstLogoPromptFetchError, setEnhancePromptFetchError, setLogoFetchError, setSaveToLibraryError, setLogoUpdateError, } = useRequestErrors();
40
- const { generateImageWithParameters } = useImageGenerator();
40
+ const { generateImageWithParameters, getImageStyles } = useImageGenerator();
41
41
  const { saveToMediaLibrary } = useSaveToMediaLibrary();
42
42
  const { ID = null, name = null, description = null } = siteDetails || {};
43
43
  const siteId = ID ? String(ID) : null;
@@ -127,7 +127,7 @@ For example: user's prompt: A logo for an ice cream shop. Returned prompt: A log
127
127
  throw error;
128
128
  }
129
129
  };
130
- const generateImage = useCallback(async function ({ prompt, }) {
130
+ const generateImage = useCallback(async function ({ prompt, style = null, }) {
131
131
  setLogoFetchError(null);
132
132
  try {
133
133
  const tokenData = await requestJwt();
@@ -148,6 +148,7 @@ User request:${prompt}`;
148
148
  prompt: imageGenerationPrompt,
149
149
  feature: 'jetpack-ai-logo-generator',
150
150
  response_format: 'b64_json',
151
+ style: style || '', // backend expects an empty string if no style is provided
151
152
  };
152
153
  const data = await generateImageWithParameters(body);
153
154
  return data;
@@ -212,7 +213,7 @@ User request:${prompt}`;
212
213
  addLogoToHistory(logo);
213
214
  stashLogo({ ...logo, siteId: String(siteId) });
214
215
  }, [siteId, addLogoToHistory, stashLogo]);
215
- const generateLogo = useCallback(async function ({ prompt }) {
216
+ const generateLogo = useCallback(async function ({ prompt, style, }) {
216
217
  debug('Generating logo for site');
217
218
  setIsRequestingImage(true);
218
219
  try {
@@ -222,7 +223,7 @@ User request:${prompt}`;
222
223
  increaseAiAssistantRequestsCount(logoGenerationCost);
223
224
  let image;
224
225
  try {
225
- image = await generateImage({ prompt });
226
+ image = await generateImage({ prompt, style });
226
227
  if (!image || !image.data.length) {
227
228
  throw new Error('No image returned');
228
229
  }
@@ -286,6 +287,7 @@ User request:${prompt}`;
286
287
  tierPlansEnabled,
287
288
  isLoadingHistory,
288
289
  setIsLoadingHistory,
290
+ getImageStyles,
289
291
  };
290
292
  };
291
293
  export default useLogoGenerator;
@@ -17,6 +17,7 @@ export interface GeneratorModalProps {
17
17
  onReload: () => void;
18
18
  context: string;
19
19
  placement: string;
20
+ showStyleSelector?: boolean;
20
21
  }
21
22
  export interface LogoPresenterProps {
22
23
  logo?: Logo;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.18.1",
4
+ "version": "0.20.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,8 +44,8 @@
44
44
  "types": "./build/index.d.ts",
45
45
  "dependencies": {
46
46
  "@automattic/jetpack-base-styles": "^0.6.32",
47
- "@automattic/jetpack-connection": "^0.35.7",
48
- "@automattic/jetpack-shared-extension-utils": "^0.15.9",
47
+ "@automattic/jetpack-connection": "^0.35.10",
48
+ "@automattic/jetpack-shared-extension-utils": "^0.15.11",
49
49
  "@microsoft/fetch-event-source": "2.0.1",
50
50
  "@types/react": "18.3.3",
51
51
  "@types/wordpress__block-editor": "11.5.15",
@@ -0,0 +1,44 @@
1
+ import { __ } from '@wordpress/i18n';
2
+
3
+ // Styles
4
+ export const IMAGE_STYLE_ENHANCE = 'enhance';
5
+ export const IMAGE_STYLE_ANIME = 'anime';
6
+ export const IMAGE_STYLE_PHOTOGRAPHIC = 'photographic';
7
+ export const IMAGE_STYLE_DIGITAL_ART = 'digital-art';
8
+ export const IMAGE_STYLE_COMICBOOK = 'comicbook';
9
+ export const IMAGE_STYLE_FANTASY_ART = 'fantasy-art';
10
+ export const IMAGE_STYLE_ANALOG_FILM = 'analog-film';
11
+ export const IMAGE_STYLE_NEONPUNK = 'neonpunk';
12
+ export const IMAGE_STYLE_ISOMETRIC = 'isometric';
13
+ export const IMAGE_STYLE_LOWPOLY = 'lowpoly';
14
+ export const IMAGE_STYLE_ORIGAMI = 'origami';
15
+ export const IMAGE_STYLE_LINE_ART = 'line-art';
16
+ export const IMAGE_STYLE_CRAFT_CLAY = 'craft-clay';
17
+ export const IMAGE_STYLE_CINEMATIC = 'cinematic';
18
+ export const IMAGE_STYLE_3D_MODEL = '3d-model';
19
+ export const IMAGE_STYLE_PIXEL_ART = 'pixel-art';
20
+ export const IMAGE_STYLE_TEXTURE = 'texture';
21
+ export const IMAGE_STYLE_MONTY_PYTHON = 'monty-python';
22
+
23
+ export const IMAGE_STYLE_LABELS = {
24
+ [ IMAGE_STYLE_ENHANCE ]: __( 'Enhance', 'jetpack-ai-client' ),
25
+ [ IMAGE_STYLE_ANIME ]: __( 'Anime', 'jetpack-ai-client' ),
26
+ [ IMAGE_STYLE_PHOTOGRAPHIC ]: __( 'Photographic', 'jetpack-ai-client' ),
27
+ [ IMAGE_STYLE_DIGITAL_ART ]: __( 'Digital Art', 'jetpack-ai-client' ),
28
+ [ IMAGE_STYLE_COMICBOOK ]: __( 'Comicbook', 'jetpack-ai-client' ),
29
+ [ IMAGE_STYLE_FANTASY_ART ]: __( 'Fantasy Art', 'jetpack-ai-client' ),
30
+ [ IMAGE_STYLE_ANALOG_FILM ]: __( 'Analog Film', 'jetpack-ai-client' ),
31
+ [ IMAGE_STYLE_NEONPUNK ]: __( 'Neon Punk', 'jetpack-ai-client' ),
32
+ [ IMAGE_STYLE_ISOMETRIC ]: __( 'Isometric', 'jetpack-ai-client' ),
33
+ [ IMAGE_STYLE_LOWPOLY ]: __( 'Low Poly', 'jetpack-ai-client' ),
34
+ [ IMAGE_STYLE_ORIGAMI ]: __( 'Origami', 'jetpack-ai-client' ),
35
+ [ IMAGE_STYLE_LINE_ART ]: __( 'Line Art', 'jetpack-ai-client' ),
36
+ [ IMAGE_STYLE_CRAFT_CLAY ]: __( 'Craft Clay', 'jetpack-ai-client' ),
37
+ [ IMAGE_STYLE_CINEMATIC ]: __( 'Cinematic', 'jetpack-ai-client' ),
38
+ [ IMAGE_STYLE_3D_MODEL ]: __( '3D Model', 'jetpack-ai-client' ),
39
+ [ IMAGE_STYLE_PIXEL_ART ]: __( 'Pixel Art', 'jetpack-ai-client' ),
40
+ [ IMAGE_STYLE_TEXTURE ]: __( 'Texture', 'jetpack-ai-client' ),
41
+ [ IMAGE_STYLE_MONTY_PYTHON ]: __( 'Monty Python', 'jetpack-ai-client' ),
42
+ };
43
+
44
+ export type ImageStyle = keyof typeof IMAGE_STYLE_LABELS;
@@ -7,6 +7,7 @@ import debugFactory from 'debug';
7
7
  */
8
8
  import askQuestionSync from '../../ask-question/sync.js';
9
9
  import requestJwt from '../../jwt/index.js';
10
+ import { IMAGE_STYLE_LABELS } from './constants.js';
10
11
 
11
12
  const debug = debugFactory( 'ai-client:use-image-generator' );
12
13
 
@@ -214,7 +215,6 @@ const useImageGenerator = () => {
214
215
  prompt,
215
216
  feature,
216
217
  model: 'stable-diffusion',
217
- style: 'photographic',
218
218
  };
219
219
 
220
220
  const data: ImageGenerationResponse = await executeImageGeneration( parameters );
@@ -256,11 +256,22 @@ const useImageGenerator = () => {
256
256
  }
257
257
  };
258
258
 
259
+ /**
260
+ * Get available styles.
261
+ *
262
+ * @return {object} with the styles {key:label} for the image generation.
263
+ */
264
+ const getImageStyles = function (): object {
265
+ return IMAGE_STYLE_LABELS;
266
+ };
267
+
259
268
  return {
260
269
  generateImage,
261
270
  generateImageWithStableDiffusion,
262
271
  generateImageWithParameters: executeImageGeneration,
272
+ getImageStyles,
263
273
  };
264
274
  };
265
275
 
266
276
  export default useImageGenerator;
277
+ export * from './constants.js';
@@ -26,7 +26,9 @@
26
26
 
27
27
  &.is-link {
28
28
  text-decoration: none;
29
- color: var(--color-link, #3858e9);
29
+ &:not(:disabled) {
30
+ color: var(--color-link, #3858e9);
31
+ }
30
32
  }
31
33
  }
32
34
  }
@@ -49,6 +49,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
49
49
  siteDetails,
50
50
  context,
51
51
  placement,
52
+ showStyleSelector,
52
53
  } ) => {
53
54
  const { tracks } = useAnalytics();
54
55
  const { recordEvent: recordTracksEvent } = tracks;
@@ -242,7 +243,9 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
242
243
  } else {
243
244
  body = (
244
245
  <>
245
- { ! logoAccepted && <Prompt initialPrompt={ initialPrompt } /> }
246
+ { ! logoAccepted && (
247
+ <Prompt initialPrompt={ initialPrompt } showStyleSelector={ showStyleSelector } />
248
+ ) }
246
249
 
247
250
  <LogoPresenter
248
251
  logo={ selectedLogo }
@@ -9,12 +9,12 @@
9
9
 
10
10
  .jetpack-ai-logo-generator__prompt-header {
11
11
  display: flex;
12
- justify-content: space-between;
13
- align-items: flex-start;
14
- align-self: stretch;
12
+ gap: 16px;
13
+ align-items: center;
15
14
 
16
15
  .jetpack-ai-logo-generator__prompt-label {
17
16
  font-weight: 500;
17
+ flex-grow: 1;
18
18
  }
19
19
 
20
20
  .jetpack-ai-logo-generator__prompt-actions {
@@ -24,6 +24,8 @@
24
24
 
25
25
  .jetpack-ai-logo-generator-icon {
26
26
  margin-right: 4px;
27
+ // the svg icon with this class has no fill, only paths
28
+ stroke: currentColor;
27
29
  }
28
30
  }
29
31
  }
@@ -2,7 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
- import { Button, Tooltip } from '@wordpress/components';
5
+ import { Button, Tooltip, SelectControl } from '@wordpress/components';
6
6
  import { __, sprintf } from '@wordpress/i18n';
7
7
  import { Icon, info } from '@wordpress/icons';
8
8
  import debugFactory from 'debug';
@@ -10,12 +10,17 @@ import { useCallback, useEffect, useState, useRef } from 'react';
10
10
  /**
11
11
  * Internal dependencies
12
12
  */
13
+ import {
14
+ IMAGE_STYLE_MONTY_PYTHON,
15
+ IMAGE_STYLE_LINE_ART,
16
+ } from '../../hooks/use-image-generator/constants.js';
13
17
  import AiIcon from '../assets/icons/ai.js';
14
18
  import {
15
19
  EVENT_GENERATE,
16
20
  MINIMUM_PROMPT_LENGTH,
17
21
  EVENT_UPGRADE,
18
22
  EVENT_PLACEMENT_INPUT_FOOTER,
23
+ EVENT_SWITCH_STYLE,
19
24
  } from '../constants.js';
20
25
  import { useCheckout } from '../hooks/use-checkout.js';
21
26
  import useLogoGenerator from '../hooks/use-logo-generator.js';
@@ -23,10 +28,19 @@ import useRequestErrors from '../hooks/use-request-errors.js';
23
28
  import { FairUsageNotice } from './fair-usage-notice.js';
24
29
  import { UpgradeNudge } from './upgrade-nudge.js';
25
30
  import './prompt.scss';
31
+ /**
32
+ * Types
33
+ */
34
+ import type { ImageStyle } from '../../hooks/use-image-generator/constants.js';
26
35
 
27
36
  const debug = debugFactory( 'jetpack-ai-calypso:prompt-box' );
28
37
 
29
- export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt = '' } ) => {
38
+ type PromptProps = {
39
+ initialPrompt?: string;
40
+ showStyleSelector?: boolean;
41
+ };
42
+
43
+ export const Prompt = ( { initialPrompt = '', showStyleSelector = false }: PromptProps ) => {
30
44
  const { tracks } = useAnalytics();
31
45
  const { recordEvent: recordTracksEvent } = tracks;
32
46
  const [ prompt, setPrompt ] = useState< string >( initialPrompt );
@@ -34,6 +48,9 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
34
48
  const { enhancePromptFetchError, logoFetchError } = useRequestErrors();
35
49
  const { nextTierCheckoutURL: checkoutUrl, hasNextTier } = useCheckout();
36
50
  const hasPrompt = prompt?.length >= MINIMUM_PROMPT_LENGTH;
51
+ const [ style, setStyle ] = useState< ImageStyle >(
52
+ showStyleSelector ? IMAGE_STYLE_LINE_ART : null
53
+ );
37
54
 
38
55
  const {
39
56
  generateLogo,
@@ -46,6 +63,7 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
46
63
  requireUpgrade,
47
64
  context,
48
65
  tierPlansEnabled,
66
+ getImageStyles,
49
67
  } = useLogoGenerator();
50
68
 
51
69
  const enhancingLabel = __( 'Enhancing…', 'jetpack-ai-client' );
@@ -91,9 +109,10 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
91
109
  }, [ prompt ] );
92
110
 
93
111
  const onGenerate = useCallback( async () => {
94
- recordTracksEvent( EVENT_GENERATE, { context, tool: 'image' } );
95
- generateLogo( { prompt } );
96
- }, [ context, generateLogo, prompt ] );
112
+ // shouldn't tool be "logo-generator" to be more specific?
113
+ recordTracksEvent( EVENT_GENERATE, { context, tool: 'image', style } );
114
+ generateLogo( { prompt, style } );
115
+ }, [ context, generateLogo, prompt, style ] );
97
116
 
98
117
  const onPromptInput = ( event: React.ChangeEvent< HTMLInputElement > ) => {
99
118
  setPrompt( event.target.textContent || '' );
@@ -102,7 +121,7 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
102
121
  const onPromptPaste = ( event: React.ClipboardEvent< HTMLInputElement > ) => {
103
122
  event.preventDefault();
104
123
 
105
- const selection = window.getSelection();
124
+ const selection = event.currentTarget.ownerDocument.getSelection();
106
125
  if ( ! selection || ! selection.rangeCount ) {
107
126
  return;
108
127
  }
@@ -122,6 +141,20 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
122
141
  recordTracksEvent( EVENT_UPGRADE, { context, placement: EVENT_PLACEMENT_INPUT_FOOTER } );
123
142
  };
124
143
 
144
+ const updateStyle = useCallback(
145
+ ( imageStyle: ImageStyle ) => {
146
+ debug( 'change style', imageStyle );
147
+ setStyle( imageStyle );
148
+ recordTracksEvent( EVENT_SWITCH_STYLE, { context, style: imageStyle } );
149
+ },
150
+ [ context, setStyle, recordTracksEvent ]
151
+ );
152
+
153
+ const imageStyles = getImageStyles();
154
+ const availableStyles = Object.keys( imageStyles ).filter(
155
+ ( styleKey: ImageStyle ) => styleKey !== IMAGE_STYLE_MONTY_PYTHON
156
+ );
157
+
125
158
  return (
126
159
  <div className="jetpack-ai-logo-generator__prompt">
127
160
  <div className="jetpack-ai-logo-generator__prompt-header">
@@ -135,9 +168,21 @@ export const Prompt: React.FC< { initialPrompt?: string } > = ( { initialPrompt
135
168
  onClick={ onEnhance }
136
169
  >
137
170
  <AiIcon />
138
- <span>{ enhanceButtonLabel }</span>
171
+ { enhanceButtonLabel }
139
172
  </Button>
140
173
  </div>
174
+ { showStyleSelector && availableStyles && (
175
+ <SelectControl
176
+ // label={ __( 'Style', 'jetpack-ai-client' ) }
177
+ __nextHasNoMarginBottom
178
+ value={ style }
179
+ options={ availableStyles.map( imageStyle => ( {
180
+ label: imageStyles[ imageStyle ],
181
+ value: imageStyle,
182
+ } ) ) }
183
+ onChange={ updateStyle }
184
+ />
185
+ ) }
141
186
  </div>
142
187
  <div className="jetpack-ai-logo-generator__prompt-query">
143
188
  <div
@@ -10,6 +10,7 @@ export const EVENT_USE = 'jetpack_ai_logo_generator_use';
10
10
  export const EVENT_NAVIGATE = 'jetpack_ai_logo_generator_navigate';
11
11
  export const EVENT_FEEDBACK = 'jetpack_ai_logo_generator_feedback';
12
12
  export const EVENT_UPGRADE = 'jetpack_ai_upgrade_button';
13
+ export const EVENT_SWITCH_STYLE = 'jetpack_ai_logo_generator_switch_style';
13
14
 
14
15
  // Event placement constants
15
16
  export const EVENT_PLACEMENT_QUICK_LINKS = 'quick_links';
@@ -28,25 +28,37 @@ export const useCheckout = () => {
28
28
  };
29
29
  }, [] );
30
30
 
31
+ const isJetpackSite = ! isAtomicSite() && ! isSimpleSite();
32
+ const redirectSource = isJetpackSite
33
+ ? 'jetpack-ai-upgrade-url-for-jetpack-sites'
34
+ : 'jetpack-ai-yearly-tier-upgrade-nudge';
35
+
31
36
  /**
32
- * Use the Jetpack redirect URL to open the checkout page
37
+ * Determine the post-checkout URL for non-Jetpack sites
33
38
  */
34
- const wpcomCheckoutUrl = new URL( `https://jetpack.com/redirect/` );
35
- wpcomCheckoutUrl.searchParams.set( 'source', 'jetpack-ai-yearly-tier-upgrade-nudge' );
36
- wpcomCheckoutUrl.searchParams.set( 'site', getSiteFragment() as string );
39
+ const siteFragment = getSiteFragment() as string;
40
+ const wpcomRedirectToURL = `https://wordpress.com/home/${ siteFragment }`;
37
41
 
38
- wpcomCheckoutUrl.searchParams.set(
42
+ /**
43
+ * Use the Jetpack redirect URL to open the checkout page
44
+ */
45
+ const checkoutUrl = new URL( `https://jetpack.com/redirect/` );
46
+ checkoutUrl.searchParams.set( 'source', redirectSource );
47
+ checkoutUrl.searchParams.set( 'site', siteFragment );
48
+ checkoutUrl.searchParams.set(
39
49
  'path',
40
50
  tierPlansEnabled ? `jetpack_ai_yearly:-q-${ nextTier?.limit }` : 'jetpack_ai_yearly'
41
51
  );
42
52
 
43
- /**
44
- * Open the product interstitial page
45
- */
46
- const jetpackCheckoutUrl = `${ window?.Jetpack_Editor_Initial_State?.adminUrl }admin.php?redirect_to_referrer=1&page=my-jetpack#/add-jetpack-ai`;
53
+ // For Jetpack sites, the redirect_to parameter is handled by the Jetpack redirect source
54
+ if ( ! isJetpackSite ) {
55
+ checkoutUrl.searchParams.set(
56
+ 'query',
57
+ `redirect_to=${ encodeURIComponent( wpcomRedirectToURL ) }`
58
+ );
59
+ }
47
60
 
48
- const nextTierCheckoutURL =
49
- isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl.toString() : jetpackCheckoutUrl;
61
+ const nextTierCheckoutURL = checkoutUrl.toString();
50
62
 
51
63
  debug( 'Next tier checkout URL: ', nextTierCheckoutURL );
52
64
 
@@ -17,6 +17,7 @@ import useRequestErrors from './use-request-errors.js';
17
17
  /**
18
18
  * Types
19
19
  */
20
+ import type { ImageStyle } from '../../hooks/use-image-generator/constants.js';
20
21
  import type { Logo, Selectors, SaveLogo } from '../store/types.js';
21
22
 
22
23
  const debug = debugFactory( 'jetpack-ai-calypso:use-logo-generator' );
@@ -78,7 +79,7 @@ const useLogoGenerator = () => {
78
79
  setLogoUpdateError,
79
80
  } = useRequestErrors();
80
81
 
81
- const { generateImageWithParameters } = useImageGenerator();
82
+ const { generateImageWithParameters, getImageStyles } = useImageGenerator();
82
83
  const { saveToMediaLibrary } = useSaveToMediaLibrary();
83
84
 
84
85
  const { ID = null, name = null, description = null } = siteDetails || {};
@@ -193,8 +194,10 @@ For example: user's prompt: A logo for an ice cream shop. Returned prompt: A log
193
194
 
194
195
  const generateImage = useCallback( async function ( {
195
196
  prompt,
197
+ style = null,
196
198
  }: {
197
199
  prompt: string;
200
+ style?: ImageStyle | null;
198
201
  } ): Promise< { data: Array< { url: string } > } > {
199
202
  setLogoFetchError( null );
200
203
 
@@ -221,6 +224,7 @@ User request:${ prompt }`;
221
224
  prompt: imageGenerationPrompt,
222
225
  feature: 'jetpack-ai-logo-generator',
223
226
  response_format: 'b64_json',
227
+ style: style || '', // backend expects an empty string if no style is provided
224
228
  };
225
229
 
226
230
  const data = await generateImageWithParameters( body );
@@ -309,7 +313,13 @@ User request:${ prompt }`;
309
313
  );
310
314
 
311
315
  const generateLogo = useCallback(
312
- async function ( { prompt }: { prompt: string } ): Promise< void > {
316
+ async function ( {
317
+ prompt,
318
+ style,
319
+ }: {
320
+ prompt: string;
321
+ style?: ImageStyle | null;
322
+ } ): Promise< void > {
313
323
  debug( 'Generating logo for site' );
314
324
 
315
325
  setIsRequestingImage( true );
@@ -324,7 +334,7 @@ User request:${ prompt }`;
324
334
  let image;
325
335
 
326
336
  try {
327
- image = await generateImage( { prompt } );
337
+ image = await generateImage( { prompt, style } );
328
338
 
329
339
  if ( ! image || ! image.data.length ) {
330
340
  throw new Error( 'No image returned' );
@@ -391,6 +401,7 @@ User request:${ prompt }`;
391
401
  tierPlansEnabled,
392
402
  isLoadingHistory,
393
403
  setIsLoadingHistory,
404
+ getImageStyles,
394
405
  };
395
406
  };
396
407
 
@@ -19,6 +19,7 @@ export interface GeneratorModalProps {
19
19
  onReload: () => void;
20
20
  context: string;
21
21
  placement: string;
22
+ showStyleSelector?: boolean;
22
23
  }
23
24
 
24
25
  export interface LogoPresenterProps {