@automattic/jetpack-ai-client 0.14.6 → 0.15.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build/ask-question/sync.d.ts +2 -8
  3. package/build/ask-question/sync.js +20 -19
  4. package/build/hooks/use-image-generator/index.js +1 -1
  5. package/build/hooks/use-save-to-media-library/index.d.ts +12 -0
  6. package/build/hooks/use-save-to-media-library/index.js +74 -0
  7. package/build/index.d.ts +2 -0
  8. package/build/index.js +5 -0
  9. package/build/libs/index.d.ts +1 -1
  10. package/build/libs/index.js +1 -1
  11. package/build/libs/markdown/index.d.ts +2 -2
  12. package/build/libs/markdown/index.js +2 -2
  13. package/build/libs/markdown/markdown-to-html.d.ts +8 -1
  14. package/build/libs/markdown/markdown-to-html.js +10 -1
  15. package/build/logo-generator/assets/icons/ai.d.ts +6 -0
  16. package/build/logo-generator/assets/icons/ai.js +8 -0
  17. package/build/logo-generator/assets/icons/check.d.ts +6 -0
  18. package/build/logo-generator/assets/icons/check.js +8 -0
  19. package/build/logo-generator/assets/icons/logo.d.ts +6 -0
  20. package/build/logo-generator/assets/icons/logo.js +8 -0
  21. package/build/logo-generator/assets/icons/media.d.ts +6 -0
  22. package/build/logo-generator/assets/icons/media.js +8 -0
  23. package/build/logo-generator/components/feature-fetch-failure-screen.d.ts +8 -0
  24. package/build/logo-generator/components/feature-fetch-failure-screen.js +10 -0
  25. package/build/logo-generator/components/first-load-screen.d.ts +5 -0
  26. package/build/logo-generator/components/first-load-screen.js +16 -0
  27. package/build/logo-generator/components/generator-modal.d.ts +7 -0
  28. package/build/logo-generator/components/generator-modal.js +184 -0
  29. package/build/logo-generator/components/history-carousel.d.ts +6 -0
  30. package/build/logo-generator/components/history-carousel.js +36 -0
  31. package/build/logo-generator/components/image-loader.d.ts +7 -0
  32. package/build/logo-generator/components/image-loader.js +12 -0
  33. package/build/logo-generator/components/logo-presenter.d.ts +4 -0
  34. package/build/logo-generator/components/logo-presenter.js +106 -0
  35. package/build/logo-generator/components/prompt.d.ts +5 -0
  36. package/build/logo-generator/components/prompt.js +96 -0
  37. package/build/logo-generator/components/upgrade-nudge.d.ts +2 -0
  38. package/build/logo-generator/components/upgrade-nudge.js +30 -0
  39. package/build/logo-generator/components/upgrade-screen.d.ts +9 -0
  40. package/build/logo-generator/components/upgrade-screen.js +24 -0
  41. package/build/logo-generator/components/visit-site-banner.d.ts +10 -0
  42. package/build/logo-generator/components/visit-site-banner.js +16 -0
  43. package/build/logo-generator/constants.d.ts +16 -0
  44. package/build/logo-generator/constants.js +19 -0
  45. package/build/logo-generator/hooks/use-checkout.d.ts +4 -0
  46. package/build/logo-generator/hooks/use-checkout.js +26 -0
  47. package/build/logo-generator/hooks/use-logo-generator.d.ts +46 -0
  48. package/build/logo-generator/hooks/use-logo-generator.js +286 -0
  49. package/build/logo-generator/hooks/use-request-errors.d.ts +16 -0
  50. package/build/logo-generator/hooks/use-request-errors.js +46 -0
  51. package/build/logo-generator/index.d.ts +1 -0
  52. package/build/logo-generator/index.js +1 -0
  53. package/build/logo-generator/lib/logo-storage.d.ts +58 -0
  54. package/build/logo-generator/lib/logo-storage.js +123 -0
  55. package/build/logo-generator/lib/media-exists.d.ts +12 -0
  56. package/build/logo-generator/lib/media-exists.js +33 -0
  57. package/build/logo-generator/lib/set-site-logo.d.ts +13 -0
  58. package/build/logo-generator/lib/set-site-logo.js +26 -0
  59. package/build/logo-generator/lib/wpcom-limited-request.d.ts +7 -0
  60. package/build/logo-generator/lib/wpcom-limited-request.js +33 -0
  61. package/build/logo-generator/store/actions.d.ts +105 -0
  62. package/build/logo-generator/store/actions.js +193 -0
  63. package/build/logo-generator/store/constants.d.ts +44 -0
  64. package/build/logo-generator/store/constants.js +44 -0
  65. package/build/logo-generator/store/index.d.ts +1 -0
  66. package/build/logo-generator/store/index.js +19 -0
  67. package/build/logo-generator/store/initial-state.d.ts +3 -0
  68. package/build/logo-generator/store/initial-state.js +40 -0
  69. package/build/logo-generator/store/reducer.d.ts +347 -0
  70. package/build/logo-generator/store/reducer.js +293 -0
  71. package/build/logo-generator/store/selectors.d.ts +119 -0
  72. package/build/logo-generator/store/selectors.js +173 -0
  73. package/build/logo-generator/store/types.d.ts +164 -0
  74. package/build/logo-generator/store/types.js +1 -0
  75. package/build/logo-generator/types.d.ts +82 -0
  76. package/build/logo-generator/types.js +1 -0
  77. package/build/types.d.ts +6 -0
  78. package/package.json +5 -3
  79. package/src/ask-question/sync.ts +22 -27
  80. package/src/hooks/use-image-generator/index.ts +1 -1
  81. package/src/hooks/use-save-to-media-library/index.ts +95 -0
  82. package/src/index.ts +6 -0
  83. package/src/libs/index.ts +1 -0
  84. package/src/libs/markdown/index.ts +2 -2
  85. package/src/libs/markdown/markdown-to-html.ts +20 -3
  86. package/src/logo-generator/assets/icons/ai.tsx +21 -0
  87. package/src/logo-generator/assets/icons/check.tsx +23 -0
  88. package/src/logo-generator/assets/icons/icons.scss +5 -0
  89. package/src/logo-generator/assets/icons/logo.tsx +23 -0
  90. package/src/logo-generator/assets/icons/media.tsx +24 -0
  91. package/src/logo-generator/assets/images/jetpack-logo.svg +4 -0
  92. package/src/logo-generator/assets/images/loader.gif +0 -0
  93. package/src/logo-generator/assets/index.d.ts +3 -0
  94. package/src/logo-generator/components/feature-fetch-failure-screen.tsx +35 -0
  95. package/src/logo-generator/components/first-load-screen.scss +12 -0
  96. package/src/logo-generator/components/first-load-screen.tsx +32 -0
  97. package/src/logo-generator/components/generator-modal.scss +92 -0
  98. package/src/logo-generator/components/generator-modal.tsx +291 -0
  99. package/src/logo-generator/components/history-carousel.scss +36 -0
  100. package/src/logo-generator/components/history-carousel.tsx +57 -0
  101. package/src/logo-generator/components/image-loader.tsx +22 -0
  102. package/src/logo-generator/components/logo-presenter.scss +116 -0
  103. package/src/logo-generator/components/logo-presenter.tsx +234 -0
  104. package/src/logo-generator/components/prompt.scss +102 -0
  105. package/src/logo-generator/components/prompt.tsx +211 -0
  106. package/src/logo-generator/components/upgrade-nudge.scss +43 -0
  107. package/src/logo-generator/components/upgrade-nudge.tsx +58 -0
  108. package/src/logo-generator/components/upgrade-screen.tsx +67 -0
  109. package/src/logo-generator/components/visit-site-banner.scss +29 -0
  110. package/src/logo-generator/components/visit-site-banner.tsx +50 -0
  111. package/src/logo-generator/constants.ts +22 -0
  112. package/src/logo-generator/hooks/use-checkout.ts +37 -0
  113. package/src/logo-generator/hooks/use-logo-generator.ts +389 -0
  114. package/src/logo-generator/hooks/use-request-errors.ts +70 -0
  115. package/src/logo-generator/index.ts +1 -0
  116. package/src/logo-generator/lib/logo-storage.ts +166 -0
  117. package/src/logo-generator/lib/media-exists.ts +42 -0
  118. package/src/logo-generator/lib/set-site-logo.ts +32 -0
  119. package/src/logo-generator/lib/wpcom-limited-request.ts +41 -0
  120. package/src/logo-generator/store/actions.ts +251 -0
  121. package/src/logo-generator/store/constants.ts +49 -0
  122. package/src/logo-generator/store/index.ts +25 -0
  123. package/src/logo-generator/store/initial-state.ts +43 -0
  124. package/src/logo-generator/store/reducer.ts +387 -0
  125. package/src/logo-generator/store/selectors.ts +201 -0
  126. package/src/logo-generator/store/types.ts +207 -0
  127. package/src/logo-generator/types.ts +97 -0
  128. package/src/types.ts +8 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
+ import { Button } from '@wordpress/components';
6
+ import { __ } from '@wordpress/i18n';
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { EVENT_PLACEMENT_FREE_USER_SCREEN, EVENT_UPGRADE } from '../constants.js';
11
+ import useLogoGenerator from '../hooks/use-logo-generator.js';
12
+ /**
13
+ * Types
14
+ */
15
+ import type React from 'react';
16
+
17
+ export const UpgradeScreen: React.FC< {
18
+ onCancel: () => void;
19
+ upgradeURL: string;
20
+ reason: 'feature' | 'requests';
21
+ } > = ( { onCancel, upgradeURL, reason } ) => {
22
+ const { tracks } = useAnalytics();
23
+ const { recordEvent: recordTracksEvent } = tracks;
24
+ const upgradeMessageFeature = __(
25
+ 'Upgrade your Jetpack AI for access to exclusive features, including logo generation. This upgrade will also increase the amount of requests you can use in all AI-powered features.',
26
+ 'jetpack-ai-client'
27
+ );
28
+
29
+ const upgradeMessageRequests = __(
30
+ 'Not enough requests left to generate a logo. Upgrade your Jetpack AI to increase the amount of requests you can use in all AI-powered features.',
31
+ 'jetpack-ai-client'
32
+ );
33
+
34
+ const { context } = useLogoGenerator();
35
+
36
+ const handleUpgradeClick = () => {
37
+ recordTracksEvent( EVENT_UPGRADE, { context, placement: EVENT_PLACEMENT_FREE_USER_SCREEN } );
38
+ onCancel();
39
+ };
40
+
41
+ return (
42
+ <div className="jetpack-ai-logo-generator-modal__notice-message-wrapper">
43
+ <div className="jetpack-ai-logo-generator-modal__notice-message">
44
+ <span className="jetpack-ai-logo-generator-modal__loading-message">
45
+ { reason === 'feature' ? upgradeMessageFeature : upgradeMessageRequests }
46
+ </span>
47
+ &nbsp;
48
+ <Button variant="link" href="https://jetpack.com/ai/" target="_blank">
49
+ { __( 'Learn more', 'jetpack-ai-client' ) }
50
+ </Button>
51
+ </div>
52
+ <div className="jetpack-ai-logo-generator-modal__notice-actions">
53
+ <Button variant="tertiary" onClick={ onCancel }>
54
+ { __( 'Cancel', 'jetpack-ai-client' ) }
55
+ </Button>
56
+ <Button
57
+ variant="primary"
58
+ href={ upgradeURL }
59
+ target="_blank"
60
+ onClick={ handleUpgradeClick }
61
+ >
62
+ { __( 'Upgrade', 'jetpack-ai-client' ) }
63
+ </Button>
64
+ </div>
65
+ </div>
66
+ );
67
+ };
@@ -0,0 +1,29 @@
1
+ @import '@automattic/jetpack-base-styles/root-variables';
2
+
3
+ .jetpack-ai-logo-generator-modal-visit-site-banner {
4
+ border-radius: 4px;
5
+ background: var(--studio-gray-0, #f6f7f7);
6
+ padding: 16px 20px;
7
+ display: flex;
8
+ gap: 16px;
9
+
10
+ @media (max-width: 700px) {
11
+ flex-direction: column;
12
+ }
13
+ }
14
+
15
+ .jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo {
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ }
20
+
21
+ .jetpack-ai-logo-generator-modal-visit-site-banner__content {
22
+ font-size: var(--font-body-small, 14px);
23
+ display: flex;
24
+ flex-direction: column;
25
+
26
+ @media (max-width: 700px) {
27
+ gap: 8px;
28
+ }
29
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { Button, Icon } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { external } from '@wordpress/icons';
7
+ import clsx from 'clsx';
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import jetpackLogo from '../assets/images/jetpack-logo.svg';
12
+ import './visit-site-banner.scss';
13
+ /**
14
+ * Types
15
+ */
16
+ import type React from 'react';
17
+
18
+ export const VisitSiteBanner: React.FC< {
19
+ className?: string;
20
+ siteURL?: string;
21
+ onVisitBlankTarget: () => void;
22
+ } > = ( { className = null, siteURL = '#', onVisitBlankTarget } ) => {
23
+ return (
24
+ <div className={ clsx( 'jetpack-ai-logo-generator-modal-visit-site-banner', className ) }>
25
+ <div className="jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo">
26
+ <img src={ jetpackLogo } alt="Jetpack" />
27
+ </div>
28
+ <div className="jetpack-ai-logo-generator-modal-visit-site-banner__content">
29
+ <strong>
30
+ { __(
31
+ 'Do you want to know all the amazing things you can do with Jetpack AI?',
32
+ 'jetpack-ai-client'
33
+ ) }
34
+ </strong>
35
+ <span>
36
+ { __(
37
+ 'Generate and tweak content, create forms, get feedback and much more.',
38
+ 'jetpack-ai-client'
39
+ ) }
40
+ </span>
41
+ <div>
42
+ <Button variant="link" href={ siteURL } target="_blank" onClick={ onVisitBlankTarget }>
43
+ { __( 'Visit website', 'jetpack-ai-client' ) }
44
+ <Icon icon={ external } size={ 20 } />
45
+ </Button>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ );
50
+ };
@@ -0,0 +1,22 @@
1
+ export const JWT_TOKEN_ID = 'jetpack-ai-jwt';
2
+ export const JWT_TOKEN_EXPIRATION_TIME = 2 * 60 * 1000; // 2 minutes
3
+
4
+ // Tracks event names
5
+ export const EVENT_MODAL_OPEN = 'jetpack_ai_logo_generator_modal_open';
6
+ export const EVENT_MODAL_CLOSE = 'jetpack_ai_logo_generator_modal_close';
7
+ export const EVENT_GENERATE = 'jetpack_ai_logo_generator_generate';
8
+ export const EVENT_SAVE = 'jetpack_ai_logo_generator_save';
9
+ export const EVENT_USE = 'jetpack_ai_logo_generator_use';
10
+ export const EVENT_NAVIGATE = 'jetpack_ai_logo_generator_navigate';
11
+ export const EVENT_FEEDBACK = 'jetpack_ai_logo_generator_feedback';
12
+ export const EVENT_UPGRADE = 'jetpack_ai_upgrade_button';
13
+
14
+ // Event placement constants
15
+ export const EVENT_PLACEMENT_QUICK_LINKS = 'quick_links';
16
+ export const EVENT_PLACEMENT_INPUT_FOOTER = 'input_footer';
17
+ export const EVENT_PLACEMENT_FREE_USER_SCREEN = 'free_user_screen';
18
+ export const EVENT_PLACEMENT_UPGRADE_PROMPT = 'upgrade_prompt';
19
+
20
+ // Feature constants
21
+ export const MINIMUM_PROMPT_LENGTH = 3;
22
+ export const DEFAULT_LOGO_COST = 10;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import debugFactory from 'debug';
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { STORE_NAME } from '../store/index.js';
10
+ /**
11
+ * Types
12
+ */
13
+ import type { Selectors } from '../store/types.js';
14
+
15
+ const debug = debugFactory( 'ai-client:logo-generator:use-checkout' );
16
+
17
+ export const useCheckout = () => {
18
+ const { nextTier, siteDetails } = useSelect( select => {
19
+ const selectors: Selectors = select( STORE_NAME );
20
+ return {
21
+ nextTier: selectors.getAiAssistantFeature().nextTier,
22
+ siteDetails: selectors.getSiteDetails(),
23
+ };
24
+ }, [] );
25
+
26
+ const upgradeURL = new URL(
27
+ `${ location.origin }/checkout/${ siteDetails?.domain }/${ nextTier?.slug }`
28
+ );
29
+ upgradeURL.searchParams.set( 'redirect_to', location.href );
30
+
31
+ debug( 'Next tier checkout URL: ', upgradeURL.toString() );
32
+
33
+ return {
34
+ nextTierCheckoutURL: upgradeURL.toString(),
35
+ hasNextTier: !! nextTier,
36
+ };
37
+ };
@@ -0,0 +1,389 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useDispatch, useSelect } from '@wordpress/data';
5
+ import debugFactory from 'debug';
6
+ import { useCallback } from 'react';
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import useImageGenerator from '../../hooks/use-image-generator/index.js';
11
+ import useSaveToMediaLibrary from '../../hooks/use-save-to-media-library/index.js';
12
+ import requestJwt from '../../jwt/index.js';
13
+ import { stashLogo } from '../lib/logo-storage.js';
14
+ import { setSiteLogo } from '../lib/set-site-logo.js';
15
+ import { STORE_NAME } from '../store/index.js';
16
+ import useRequestErrors from './use-request-errors.js';
17
+ /**
18
+ * Types
19
+ */
20
+ import type { Logo, Selectors, SaveLogo } from '../store/types.js';
21
+
22
+ const debug = debugFactory( 'jetpack-ai-calypso:use-logo-generator' );
23
+
24
+ const useLogoGenerator = () => {
25
+ const {
26
+ setSelectedLogoIndex,
27
+ setIsSavingLogoToLibrary,
28
+ setIsApplyingLogo,
29
+ setIsRequestingImage,
30
+ setIsEnhancingPrompt,
31
+ increaseAiAssistantRequestsCount,
32
+ addLogoToHistory,
33
+ setContext,
34
+ } = useDispatch( STORE_NAME );
35
+
36
+ const {
37
+ logos,
38
+ selectedLogoIndex,
39
+ selectedLogo,
40
+ siteDetails,
41
+ isSavingLogoToLibrary,
42
+ isApplyingLogo,
43
+ isEnhancingPrompt,
44
+ isBusy,
45
+ isRequestingImage,
46
+ getAiAssistantFeature,
47
+ requireUpgrade,
48
+ context,
49
+ } = useSelect( select => {
50
+ const selectors: Selectors = select( STORE_NAME );
51
+
52
+ return {
53
+ logos: selectors.getLogos(),
54
+ selectedLogoIndex: selectors.getSelectedLogoIndex(),
55
+ selectedLogo: selectors.getSelectedLogo(),
56
+ siteDetails: selectors.getSiteDetails(),
57
+ isSavingLogoToLibrary: selectors.getIsSavingLogoToLibrary(),
58
+ isApplyingLogo: selectors.getIsApplyingLogo(),
59
+ isRequestingImage: selectors.getIsRequestingImage(),
60
+ isEnhancingPrompt: selectors.getIsEnhancingPrompt(),
61
+ isBusy: selectors.getIsBusy(),
62
+ getAiAssistantFeature: selectors.getAiAssistantFeature,
63
+ requireUpgrade: selectors.getRequireUpgrade(),
64
+ context: selectors.getContext(),
65
+ };
66
+ }, [] );
67
+
68
+ const {
69
+ setFirstLogoPromptFetchError,
70
+ setEnhancePromptFetchError,
71
+ setLogoFetchError,
72
+ setSaveToLibraryError,
73
+ setLogoUpdateError,
74
+ } = useRequestErrors();
75
+
76
+ const { generateImageWithParameters } = useImageGenerator();
77
+ const { saveToMediaLibrary } = useSaveToMediaLibrary();
78
+
79
+ const { ID = null, name = null, description = null } = siteDetails || {};
80
+ const siteId = ID ? String( ID ) : null;
81
+
82
+ const aiAssistantFeatureData = getAiAssistantFeature( siteId );
83
+ const logoGenerationCost = aiAssistantFeatureData?.costs?.[ 'jetpack-ai-logo-generator' ]?.logo;
84
+
85
+ const generateFirstPrompt = useCallback(
86
+ async function (): Promise< string > {
87
+ setFirstLogoPromptFetchError( null );
88
+ increaseAiAssistantRequestsCount();
89
+
90
+ try {
91
+ const tokenData = await requestJwt();
92
+
93
+ if ( ! tokenData || ! tokenData.token ) {
94
+ throw new Error( 'No token provided' );
95
+ }
96
+
97
+ debug( 'Generating first prompt for site' );
98
+
99
+ const firstPromptGenerationPrompt = `Generate a simple and short prompt asking for a logo based on the site's name and description, keeping the same language.
100
+ Example for a site named "The minimalist fashion blog", described as "Daily inspiration for all things fashion": A logo for a minimalist fashion site focused on daily sartorial inspiration with a clean and modern aesthetic that is sleek and sophisticated.
101
+ Another example, now for a site called "El observatorio de aves", described as "Un sitio dedicado a nuestros compañeros y compañeras entusiastas de la observación de aves.": Un logo para un sitio web dedicado a la observación de aves, capturando la esencia de la naturaleza y la pasión por la avifauna en un diseño elegante y representativo, reflejando una estética natural y apasionada por la vida silvestre.
102
+
103
+ Site name: ${ name }
104
+ Site description: ${ description }`;
105
+
106
+ const body = {
107
+ question: firstPromptGenerationPrompt,
108
+ feature: 'jetpack-ai-logo-generator',
109
+ stream: false,
110
+ };
111
+
112
+ const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-query';
113
+ const headers = {
114
+ Authorization: `Bearer ${ tokenData.token }`,
115
+ 'Content-Type': 'application/json',
116
+ };
117
+
118
+ const data = await fetch( URL, {
119
+ method: 'POST',
120
+ headers,
121
+ body: JSON.stringify( body ),
122
+ } ).then( response => response.json() );
123
+
124
+ return data?.choices?.[ 0 ]?.message?.content;
125
+ } catch ( error ) {
126
+ increaseAiAssistantRequestsCount( -1 );
127
+ setFirstLogoPromptFetchError( error );
128
+ throw error;
129
+ }
130
+ },
131
+ [ setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount ]
132
+ );
133
+
134
+ const enhancePrompt = async function ( { prompt }: { prompt: string } ): Promise< string > {
135
+ setEnhancePromptFetchError( null );
136
+ increaseAiAssistantRequestsCount();
137
+
138
+ try {
139
+ const tokenData = await requestJwt();
140
+
141
+ if ( ! tokenData || ! tokenData.token ) {
142
+ throw new Error( 'No token provided' );
143
+ }
144
+
145
+ debug( 'Enhancing prompt', prompt );
146
+
147
+ const systemMessage = `Enhance the prompt you receive.
148
+ The prompt is meant for generating a logo. Return the same prompt enhanced, and make each enhancement wrapped in brackets.
149
+ Do not add any mention to text, letters, typography or the name of the site in the prompt.
150
+ For example: user's prompt: A logo for an ice cream shop. Returned prompt: A logo for an ice cream shop [that is pink] [and vibrant].`;
151
+
152
+ const messages = [
153
+ {
154
+ role: 'system',
155
+ content: systemMessage,
156
+ },
157
+ {
158
+ role: 'user',
159
+ content: prompt,
160
+ },
161
+ ];
162
+
163
+ const body = {
164
+ messages,
165
+ feature: 'jetpack-ai-logo-generator',
166
+ stream: false,
167
+ };
168
+
169
+ const URL = 'https://public-api.wordpress.com/wpcom/v2/jetpack-ai-query';
170
+ const headers = {
171
+ Authorization: `Bearer ${ tokenData.token }`,
172
+ 'Content-Type': 'application/json',
173
+ };
174
+
175
+ const data = await fetch( URL, {
176
+ method: 'POST',
177
+ headers,
178
+ body: JSON.stringify( body ),
179
+ } ).then( response => response.json() );
180
+
181
+ return data?.choices?.[ 0 ]?.message?.content;
182
+ } catch ( error ) {
183
+ increaseAiAssistantRequestsCount( -1 );
184
+ setEnhancePromptFetchError( error );
185
+ throw error;
186
+ }
187
+ };
188
+
189
+ const generateImage = useCallback( async function ( {
190
+ prompt,
191
+ }: {
192
+ prompt: string;
193
+ } ): Promise< { data: Array< { url: string } > } > {
194
+ setLogoFetchError( null );
195
+
196
+ try {
197
+ const tokenData = await requestJwt();
198
+
199
+ if ( ! tokenData || ! tokenData.token ) {
200
+ throw new Error( 'No token provided' );
201
+ }
202
+
203
+ debug( 'Generating image with prompt', prompt );
204
+
205
+ const imageGenerationPrompt = `I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS-IS:
206
+ Create a single text-free iconic vector logo that symbolically represents the user request, using abstract or symbolic imagery.
207
+ The design should be modern, with either a vivid color scheme full of gradients or a color scheme that's monochromatic. Use any of those styles based on the user request mood.
208
+ Ensure the logo is set against a clean solid background.
209
+ Ensure the logo works in small sizes.
210
+ The imagery in the logo should subtly hint at the mood of the user request but DO NOT use any text, letters, or the name of the site on the imagery.
211
+ The image should contain a single icon, without variations, color palettes or different versions.
212
+
213
+ User request:${ prompt }`;
214
+
215
+ const body = {
216
+ prompt: imageGenerationPrompt,
217
+ feature: 'jetpack-ai-logo-generator',
218
+ response_format: 'b64_json',
219
+ };
220
+
221
+ const data = await generateImageWithParameters( body );
222
+
223
+ return data as { data: { url: string }[] };
224
+ } catch ( error ) {
225
+ setLogoFetchError( error );
226
+ throw error;
227
+ }
228
+ }, [] );
229
+
230
+ const saveLogo = useCallback< SaveLogo >(
231
+ async logo => {
232
+ setSaveToLibraryError( null );
233
+
234
+ try {
235
+ debug( 'Saving logo for site' );
236
+
237
+ // If the logo is already saved, return its mediaId and mediaURL.
238
+ if ( logo.mediaId ) {
239
+ return { mediaId: logo.mediaId, mediaURL: logo.url };
240
+ }
241
+
242
+ const savedLogo = {
243
+ mediaId: 0,
244
+ mediaURL: '',
245
+ };
246
+
247
+ setIsSavingLogoToLibrary( true );
248
+
249
+ const { id: mediaId, url: mediaURL } = await saveToMediaLibrary(
250
+ logo.url,
251
+ 'site-logo.png'
252
+ );
253
+
254
+ savedLogo.mediaId = parseInt( mediaId );
255
+ savedLogo.mediaURL = mediaURL;
256
+
257
+ return savedLogo;
258
+ } catch ( error ) {
259
+ setSaveToLibraryError( error );
260
+ throw error;
261
+ } finally {
262
+ setIsSavingLogoToLibrary( false );
263
+ }
264
+ },
265
+ [ setIsSavingLogoToLibrary, setSaveToLibraryError ]
266
+ );
267
+
268
+ const applyLogo = useCallback( async () => {
269
+ setLogoUpdateError( null );
270
+
271
+ try {
272
+ if ( ! siteId || ! selectedLogo ) {
273
+ throw new Error( 'Missing siteId or logo' );
274
+ }
275
+
276
+ debug( 'Applying logo for site', siteId );
277
+
278
+ setIsApplyingLogo( true );
279
+
280
+ const { mediaId } = selectedLogo;
281
+
282
+ if ( ! mediaId ) {
283
+ throw new Error( 'Missing mediaId' );
284
+ }
285
+
286
+ await setSiteLogo( {
287
+ siteId: siteId,
288
+ imageId: String( mediaId ),
289
+ } );
290
+ } catch ( error ) {
291
+ setLogoUpdateError( error );
292
+ throw error;
293
+ } finally {
294
+ setIsApplyingLogo( false );
295
+ }
296
+ }, [ selectedLogo, setIsApplyingLogo, setLogoUpdateError, siteId ] );
297
+
298
+ const storeLogo = useCallback(
299
+ ( logo: Logo ) => {
300
+ addLogoToHistory( logo );
301
+ stashLogo( { ...logo, siteId: String( siteId ) } );
302
+ },
303
+ [ siteId, addLogoToHistory, stashLogo ]
304
+ );
305
+
306
+ const generateLogo = useCallback(
307
+ async function ( { prompt }: { prompt: string } ): Promise< void > {
308
+ debug( 'Generating logo for site' );
309
+
310
+ setIsRequestingImage( true );
311
+
312
+ try {
313
+ if ( ! logoGenerationCost ) {
314
+ throw new Error( 'Missing cost information' );
315
+ }
316
+
317
+ increaseAiAssistantRequestsCount( logoGenerationCost );
318
+
319
+ let image;
320
+
321
+ try {
322
+ image = await generateImage( { prompt } );
323
+
324
+ if ( ! image || ! image.data.length ) {
325
+ throw new Error( 'No image returned' );
326
+ }
327
+ } catch ( error ) {
328
+ increaseAiAssistantRequestsCount( -logoGenerationCost );
329
+ throw error;
330
+ }
331
+
332
+ // response_format=url returns object with url, otherwise b64_json
333
+ const logo: Logo = {
334
+ url: 'data:image/png;base64,' + image.data[ 0 ].b64_json,
335
+ description: prompt,
336
+ };
337
+
338
+ try {
339
+ const savedLogo = await saveLogo( logo );
340
+ storeLogo( {
341
+ url: savedLogo.mediaURL,
342
+ description: prompt,
343
+ mediaId: savedLogo.mediaId,
344
+ } );
345
+ } catch ( error ) {
346
+ storeLogo( logo );
347
+ throw error;
348
+ }
349
+ } finally {
350
+ setIsRequestingImage( false );
351
+ }
352
+ },
353
+ [ logoGenerationCost, increaseAiAssistantRequestsCount, saveLogo, storeLogo, generateImage ]
354
+ );
355
+
356
+ return {
357
+ logos,
358
+ selectedLogoIndex,
359
+ selectedLogo,
360
+ setSelectedLogoIndex,
361
+ site: {
362
+ id: siteId,
363
+ name,
364
+ description,
365
+ },
366
+ generateFirstPrompt,
367
+ saveLogo,
368
+ applyLogo,
369
+ generateImage,
370
+ enhancePrompt,
371
+ storeLogo,
372
+ generateLogo,
373
+ setIsEnhancingPrompt,
374
+ setIsRequestingImage,
375
+ setIsSavingLogoToLibrary,
376
+ setIsApplyingLogo,
377
+ setContext,
378
+ isEnhancingPrompt,
379
+ isRequestingImage,
380
+ isSavingLogoToLibrary,
381
+ isApplyingLogo,
382
+ isBusy,
383
+ getAiAssistantFeature,
384
+ requireUpgrade,
385
+ context,
386
+ };
387
+ };
388
+
389
+ export default useLogoGenerator;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useDispatch, useSelect } from '@wordpress/data';
5
+ /**
6
+ * Internal dependencies
7
+ */
8
+ import { STORE_NAME } from '../store/index.js';
9
+ /**
10
+ * Types
11
+ */
12
+ import type { Selectors } from '../store/types.js';
13
+
14
+ const useRequestErrors = () => {
15
+ const {
16
+ setFeatureFetchError,
17
+ setFirstLogoPromptFetchError,
18
+ setEnhancePromptFetchError,
19
+ setLogoFetchError,
20
+ setSaveToLibraryError,
21
+ setLogoUpdateError,
22
+ } = useDispatch( STORE_NAME );
23
+
24
+ const {
25
+ featureFetchError,
26
+ firstLogoPromptFetchError,
27
+ enhancePromptFetchError,
28
+ logoFetchError,
29
+ saveToLibraryError,
30
+ logoUpdateError,
31
+ } = useSelect( select => {
32
+ const selectors: Selectors = select( STORE_NAME );
33
+
34
+ return {
35
+ featureFetchError: selectors.getFeatureFetchError(),
36
+ firstLogoPromptFetchError: selectors.getFirstLogoPromptFetchError(),
37
+ enhancePromptFetchError: selectors.getEnhancePromptFetchError(),
38
+ logoFetchError: selectors.getLogoFetchError(),
39
+ saveToLibraryError: selectors.getSaveToLibraryError(),
40
+ logoUpdateError: selectors.getLogoUpdateError(),
41
+ };
42
+ }, [] );
43
+
44
+ const clearErrors = () => {
45
+ setFeatureFetchError( null );
46
+ setFirstLogoPromptFetchError( null );
47
+ setEnhancePromptFetchError( null );
48
+ setLogoFetchError( null );
49
+ setSaveToLibraryError( null );
50
+ setLogoUpdateError( null );
51
+ };
52
+
53
+ return {
54
+ setFeatureFetchError,
55
+ setFirstLogoPromptFetchError,
56
+ setEnhancePromptFetchError,
57
+ setLogoFetchError,
58
+ setSaveToLibraryError,
59
+ setLogoUpdateError,
60
+ clearErrors,
61
+ featureFetchError,
62
+ firstLogoPromptFetchError,
63
+ enhancePromptFetchError,
64
+ logoFetchError,
65
+ saveToLibraryError,
66
+ logoUpdateError,
67
+ };
68
+ };
69
+
70
+ export default useRequestErrors;
@@ -0,0 +1 @@
1
+ export * from './components/generator-modal.js';