@automattic/jetpack-ai-client 0.25.6 → 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.
Files changed (227) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/build/components/ai-icon/index.d.ts +10 -0
  3. package/build/components/ai-icon/index.js +16 -0
  4. package/build/components/ai-image/components/ai-image-modal.d.ts +57 -0
  5. package/build/components/ai-image/components/ai-image-modal.js +92 -0
  6. package/build/components/ai-image/components/carrousel.d.ts +28 -0
  7. package/build/components/ai-image/components/carrousel.js +69 -0
  8. package/build/components/ai-image/components/usage-counter.d.ts +16 -0
  9. package/build/components/ai-image/components/usage-counter.js +29 -0
  10. package/build/components/ai-image/featured-image.d.ts +17 -0
  11. package/build/components/ai-image/featured-image.js +278 -0
  12. package/build/components/ai-image/general-purpose-image.d.ts +23 -0
  13. package/build/components/ai-image/general-purpose-image.js +184 -0
  14. package/build/components/ai-image/hooks/use-ai-image.d.ts +48 -0
  15. package/build/components/ai-image/hooks/use-ai-image.js +219 -0
  16. package/build/components/ai-image/hooks/use-site-type.d.ts +6 -0
  17. package/build/components/ai-image/hooks/use-site-type.js +23 -0
  18. package/build/components/ai-image/index.d.ts +4 -0
  19. package/build/components/ai-image/index.js +4 -0
  20. package/build/components/ai-image/types.d.ts +16 -0
  21. package/build/components/ai-image/types.js +6 -0
  22. package/build/{ai-client/src/components → components}/index.d.ts +4 -0
  23. package/build/{ai-client/src/components → components}/index.js +4 -0
  24. package/build/components/modal/index.d.ts +18 -0
  25. package/build/components/modal/index.js +23 -0
  26. package/build/components/quota-exceeded-message/index.d.ts +13 -0
  27. package/build/components/quota-exceeded-message/index.js +152 -0
  28. package/build/components/quota-exceeded-message/light-nudge.d.ts +11 -0
  29. package/build/components/quota-exceeded-message/light-nudge.js +8 -0
  30. package/build/{ai-client/src/constants.d.ts → constants.d.ts} +3 -0
  31. package/build/{ai-client/src/constants.js → constants.js} +4 -0
  32. package/build/hooks/use-ai-checkout/index.d.ts +13 -0
  33. package/build/hooks/use-ai-checkout/index.js +41 -0
  34. package/build/hooks/use-ai-feature/index.d.ts +33 -0
  35. package/build/hooks/use-ai-feature/index.js +37 -0
  36. package/build/hooks/use-post-content.d.ts +5 -0
  37. package/build/hooks/use-post-content.js +20 -0
  38. package/build/hooks/use-save-to-media-library.d.ts +12 -0
  39. package/build/hooks/use-save-to-media-library.js +74 -0
  40. package/build/{ai-client/src/index.d.ts → index.d.ts} +3 -0
  41. package/build/{ai-client/src/index.js → index.js} +3 -0
  42. package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-screen.js +1 -1
  43. package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-fair-usage-notice-message.js +1 -1
  44. package/package.json +20 -14
  45. package/src/components/ai-icon/index.tsx +39 -0
  46. package/src/components/ai-image/components/ai-image-modal.scss +88 -0
  47. package/src/components/ai-image/components/ai-image-modal.tsx +240 -0
  48. package/src/components/ai-image/components/carrousel.scss +163 -0
  49. package/src/components/ai-image/components/carrousel.tsx +217 -0
  50. package/src/components/ai-image/components/usage-counter.scss +19 -0
  51. package/src/components/ai-image/components/usage-counter.tsx +54 -0
  52. package/src/components/ai-image/featured-image.tsx +439 -0
  53. package/src/components/ai-image/general-purpose-image.tsx +303 -0
  54. package/src/components/ai-image/hooks/use-ai-image.ts +339 -0
  55. package/src/components/ai-image/hooks/use-site-type.ts +26 -0
  56. package/src/components/ai-image/index.ts +10 -0
  57. package/src/components/ai-image/style.scss +95 -0
  58. package/src/components/ai-image/types.ts +19 -0
  59. package/src/components/index.ts +12 -0
  60. package/src/components/modal/index.tsx +70 -0
  61. package/src/components/modal/style.scss +45 -0
  62. package/src/components/quota-exceeded-message/index.tsx +319 -0
  63. package/src/components/quota-exceeded-message/light-nudge.tsx +38 -0
  64. package/src/components/quota-exceeded-message/style.scss +35 -0
  65. package/src/constants.ts +5 -0
  66. package/src/hooks/use-ai-checkout/index.ts +65 -0
  67. package/src/hooks/use-ai-feature/Readme.md +20 -0
  68. package/src/hooks/use-ai-feature/index.ts +62 -0
  69. package/src/hooks/use-post-content.ts +27 -0
  70. package/src/hooks/use-save-to-media-library.ts +100 -0
  71. package/src/index.ts +3 -0
  72. package/src/logo-generator/components/upgrade-screen.tsx +1 -1
  73. package/src/logo-generator/hooks/use-fair-usage-notice-message.tsx +1 -1
  74. package/build/components/tools/jp-redirect/index.d.ts +0 -20
  75. package/build/components/tools/jp-redirect/index.js +0 -50
  76. package/build/components/tools/jp-redirect/types.d.ts +0 -39
  77. package/build/components/tools/jp-redirect/types.js +0 -1
  78. /package/build/{ai-client/src/api-fetch → api-fetch}/index.d.ts +0 -0
  79. /package/build/{ai-client/src/api-fetch → api-fetch}/index.js +0 -0
  80. /package/build/{ai-client/src/ask-question → ask-question}/index.d.ts +0 -0
  81. /package/build/{ai-client/src/ask-question → ask-question}/index.js +0 -0
  82. /package/build/{ai-client/src/ask-question → ask-question}/sync.d.ts +0 -0
  83. /package/build/{ai-client/src/ask-question → ask-question}/sync.js +0 -0
  84. /package/build/{ai-client/src/audio-transcription → audio-transcription}/index.d.ts +0 -0
  85. /package/build/{ai-client/src/audio-transcription → audio-transcription}/index.js +0 -0
  86. /package/build/{ai-client/src/components → components}/ai-control/ai-control.d.ts +0 -0
  87. /package/build/{ai-client/src/components → components}/ai-control/ai-control.js +0 -0
  88. /package/build/{ai-client/src/components → components}/ai-control/block-ai-control.d.ts +0 -0
  89. /package/build/{ai-client/src/components → components}/ai-control/block-ai-control.js +0 -0
  90. /package/build/{ai-client/src/components → components}/ai-control/extension-ai-control.d.ts +0 -0
  91. /package/build/{ai-client/src/components → components}/ai-control/extension-ai-control.js +0 -0
  92. /package/build/{ai-client/src/components → components}/ai-control/index.d.ts +0 -0
  93. /package/build/{ai-client/src/components → components}/ai-control/index.js +0 -0
  94. /package/build/{ai-client/src/components → components}/ai-feedback/index.d.ts +0 -0
  95. /package/build/{ai-client/src/components → components}/ai-feedback/index.js +0 -0
  96. /package/build/{ai-client/src/components → components}/ai-modal-footer/index.d.ts +0 -0
  97. /package/build/{ai-client/src/components → components}/ai-modal-footer/index.js +0 -0
  98. /package/build/{ai-client/src/components → components}/ai-status-indicator/index.d.ts +0 -0
  99. /package/build/{ai-client/src/components → components}/ai-status-indicator/index.js +0 -0
  100. /package/build/{ai-client/src/components → components}/audio-duration-display/index.d.ts +0 -0
  101. /package/build/{ai-client/src/components → components}/audio-duration-display/index.js +0 -0
  102. /package/build/{ai-client/src/components → components}/audio-duration-display/lib/media.d.ts +0 -0
  103. /package/build/{ai-client/src/components → components}/audio-duration-display/lib/media.js +0 -0
  104. /package/build/{ai-client/src/components → components}/message/index.d.ts +0 -0
  105. /package/build/{ai-client/src/components → components}/message/index.js +0 -0
  106. /package/build/{ai-client/src/data-flow → data-flow}/context.d.ts +0 -0
  107. /package/build/{ai-client/src/data-flow → data-flow}/context.js +0 -0
  108. /package/build/{ai-client/src/data-flow → data-flow}/index.d.ts +0 -0
  109. /package/build/{ai-client/src/data-flow → data-flow}/index.js +0 -0
  110. /package/build/{ai-client/src/data-flow → data-flow}/use-ai-context.d.ts +0 -0
  111. /package/build/{ai-client/src/data-flow → data-flow}/use-ai-context.js +0 -0
  112. /package/build/{ai-client/src/data-flow → data-flow}/with-ai-assistant-data.d.ts +0 -0
  113. /package/build/{ai-client/src/data-flow → data-flow}/with-ai-assistant-data.js +0 -0
  114. /package/build/{ai-client/src/hooks → hooks}/use-ai-suggestions/index.d.ts +0 -0
  115. /package/build/{ai-client/src/hooks → hooks}/use-ai-suggestions/index.js +0 -0
  116. /package/build/{ai-client/src/hooks → hooks}/use-audio-transcription/index.d.ts +0 -0
  117. /package/build/{ai-client/src/hooks → hooks}/use-audio-transcription/index.js +0 -0
  118. /package/build/{ai-client/src/hooks → hooks}/use-audio-validation/index.d.ts +0 -0
  119. /package/build/{ai-client/src/hooks → hooks}/use-audio-validation/index.js +0 -0
  120. /package/build/{ai-client/src/hooks → hooks}/use-image-generator/constants.d.ts +0 -0
  121. /package/build/{ai-client/src/hooks → hooks}/use-image-generator/constants.js +0 -0
  122. /package/build/{ai-client/src/hooks → hooks}/use-image-generator/index.d.ts +0 -0
  123. /package/build/{ai-client/src/hooks → hooks}/use-image-generator/index.js +0 -0
  124. /package/build/{ai-client/src/hooks → hooks}/use-media-recording/index.d.ts +0 -0
  125. /package/build/{ai-client/src/hooks → hooks}/use-media-recording/index.js +0 -0
  126. /package/build/{ai-client/src/hooks → hooks}/use-save-to-media-library/index.d.ts +0 -0
  127. /package/build/{ai-client/src/hooks → hooks}/use-save-to-media-library/index.js +0 -0
  128. /package/build/{ai-client/src/hooks → hooks}/use-transcription-post-processing/index.d.ts +0 -0
  129. /package/build/{ai-client/src/hooks → hooks}/use-transcription-post-processing/index.js +0 -0
  130. /package/build/{ai-client/src/icons → icons}/ai-assistant.d.ts +0 -0
  131. /package/build/{ai-client/src/icons → icons}/ai-assistant.js +0 -0
  132. /package/build/{ai-client/src/icons → icons}/error-exclamation.d.ts +0 -0
  133. /package/build/{ai-client/src/icons → icons}/error-exclamation.js +0 -0
  134. /package/build/{ai-client/src/icons → icons}/index.d.ts +0 -0
  135. /package/build/{ai-client/src/icons → icons}/index.js +0 -0
  136. /package/build/{ai-client/src/icons → icons}/mic.d.ts +0 -0
  137. /package/build/{ai-client/src/icons → icons}/mic.js +0 -0
  138. /package/build/{ai-client/src/icons → icons}/origami-plane.d.ts +0 -0
  139. /package/build/{ai-client/src/icons → icons}/origami-plane.js +0 -0
  140. /package/build/{ai-client/src/icons → icons}/player-pause.d.ts +0 -0
  141. /package/build/{ai-client/src/icons → icons}/player-pause.js +0 -0
  142. /package/build/{ai-client/src/icons → icons}/player-play.d.ts +0 -0
  143. /package/build/{ai-client/src/icons → icons}/player-play.js +0 -0
  144. /package/build/{ai-client/src/icons → icons}/player-stop.d.ts +0 -0
  145. /package/build/{ai-client/src/icons → icons}/player-stop.js +0 -0
  146. /package/build/{ai-client/src/icons → icons}/speak-tone.d.ts +0 -0
  147. /package/build/{ai-client/src/icons → icons}/speak-tone.js +0 -0
  148. /package/build/{ai-client/src/jwt → jwt}/index.d.ts +0 -0
  149. /package/build/{ai-client/src/jwt → jwt}/index.js +0 -0
  150. /package/build/{ai-client/src/libs → libs}/index.d.ts +0 -0
  151. /package/build/{ai-client/src/libs → libs}/index.js +0 -0
  152. /package/build/{ai-client/src/libs → libs}/map-action-to-human-text.d.ts +0 -0
  153. /package/build/{ai-client/src/libs → libs}/map-action-to-human-text.js +0 -0
  154. /package/build/{ai-client/src/libs → libs}/markdown/html-to-markdown.d.ts +0 -0
  155. /package/build/{ai-client/src/libs → libs}/markdown/html-to-markdown.js +0 -0
  156. /package/build/{ai-client/src/libs → libs}/markdown/index.d.ts +0 -0
  157. /package/build/{ai-client/src/libs → libs}/markdown/index.js +0 -0
  158. /package/build/{ai-client/src/libs → libs}/markdown/markdown-to-html.d.ts +0 -0
  159. /package/build/{ai-client/src/libs → libs}/markdown/markdown-to-html.js +0 -0
  160. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/ai.d.ts +0 -0
  161. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/ai.js +0 -0
  162. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/check.d.ts +0 -0
  163. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/check.js +0 -0
  164. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/logo.d.ts +0 -0
  165. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/logo.js +0 -0
  166. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/media.d.ts +0 -0
  167. /package/build/{ai-client/src/logo-generator → logo-generator}/assets/icons/media.js +0 -0
  168. /package/build/{ai-client/src/logo-generator → logo-generator}/components/fair-usage-notice.d.ts +0 -0
  169. /package/build/{ai-client/src/logo-generator → logo-generator}/components/fair-usage-notice.js +0 -0
  170. /package/build/{ai-client/src/logo-generator → logo-generator}/components/feature-fetch-failure-screen.d.ts +0 -0
  171. /package/build/{ai-client/src/logo-generator → logo-generator}/components/feature-fetch-failure-screen.js +0 -0
  172. /package/build/{ai-client/src/logo-generator → logo-generator}/components/first-load-screen.d.ts +0 -0
  173. /package/build/{ai-client/src/logo-generator → logo-generator}/components/first-load-screen.js +0 -0
  174. /package/build/{ai-client/src/logo-generator → logo-generator}/components/generator-modal.d.ts +0 -0
  175. /package/build/{ai-client/src/logo-generator → logo-generator}/components/generator-modal.js +0 -0
  176. /package/build/{ai-client/src/logo-generator → logo-generator}/components/history-carousel.d.ts +0 -0
  177. /package/build/{ai-client/src/logo-generator → logo-generator}/components/history-carousel.js +0 -0
  178. /package/build/{ai-client/src/logo-generator → logo-generator}/components/image-loader.d.ts +0 -0
  179. /package/build/{ai-client/src/logo-generator → logo-generator}/components/image-loader.js +0 -0
  180. /package/build/{ai-client/src/logo-generator → logo-generator}/components/logo-presenter.d.ts +0 -0
  181. /package/build/{ai-client/src/logo-generator → logo-generator}/components/logo-presenter.js +0 -0
  182. /package/build/{ai-client/src/logo-generator → logo-generator}/components/prompt.d.ts +0 -0
  183. /package/build/{ai-client/src/logo-generator → logo-generator}/components/prompt.js +0 -0
  184. /package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-nudge.d.ts +0 -0
  185. /package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-nudge.js +0 -0
  186. /package/build/{ai-client/src/logo-generator → logo-generator}/components/upgrade-screen.d.ts +0 -0
  187. /package/build/{ai-client/src/logo-generator → logo-generator}/components/visit-site-banner.d.ts +0 -0
  188. /package/build/{ai-client/src/logo-generator → logo-generator}/components/visit-site-banner.js +0 -0
  189. /package/build/{ai-client/src/logo-generator → logo-generator}/constants.d.ts +0 -0
  190. /package/build/{ai-client/src/logo-generator → logo-generator}/constants.js +0 -0
  191. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-checkout.d.ts +0 -0
  192. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-checkout.js +0 -0
  193. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-fair-usage-notice-message.d.ts +0 -0
  194. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-logo-generator.d.ts +0 -0
  195. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-logo-generator.js +0 -0
  196. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-request-errors.d.ts +0 -0
  197. /package/build/{ai-client/src/logo-generator → logo-generator}/hooks/use-request-errors.js +0 -0
  198. /package/build/{ai-client/src/logo-generator → logo-generator}/index.d.ts +0 -0
  199. /package/build/{ai-client/src/logo-generator → logo-generator}/index.js +0 -0
  200. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/logo-storage.d.ts +0 -0
  201. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/logo-storage.js +0 -0
  202. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/media-exists.d.ts +0 -0
  203. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/media-exists.js +0 -0
  204. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/set-site-logo.d.ts +0 -0
  205. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/set-site-logo.js +0 -0
  206. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/wpcom-limited-request.d.ts +0 -0
  207. /package/build/{ai-client/src/logo-generator → logo-generator}/lib/wpcom-limited-request.js +0 -0
  208. /package/build/{ai-client/src/logo-generator → logo-generator}/store/actions.d.ts +0 -0
  209. /package/build/{ai-client/src/logo-generator → logo-generator}/store/actions.js +0 -0
  210. /package/build/{ai-client/src/logo-generator → logo-generator}/store/constants.d.ts +0 -0
  211. /package/build/{ai-client/src/logo-generator → logo-generator}/store/constants.js +0 -0
  212. /package/build/{ai-client/src/logo-generator → logo-generator}/store/index.d.ts +0 -0
  213. /package/build/{ai-client/src/logo-generator → logo-generator}/store/index.js +0 -0
  214. /package/build/{ai-client/src/logo-generator → logo-generator}/store/initial-state.d.ts +0 -0
  215. /package/build/{ai-client/src/logo-generator → logo-generator}/store/initial-state.js +0 -0
  216. /package/build/{ai-client/src/logo-generator → logo-generator}/store/reducer.d.ts +0 -0
  217. /package/build/{ai-client/src/logo-generator → logo-generator}/store/reducer.js +0 -0
  218. /package/build/{ai-client/src/logo-generator → logo-generator}/store/selectors.d.ts +0 -0
  219. /package/build/{ai-client/src/logo-generator → logo-generator}/store/selectors.js +0 -0
  220. /package/build/{ai-client/src/logo-generator → logo-generator}/store/types.d.ts +0 -0
  221. /package/build/{ai-client/src/logo-generator → logo-generator}/store/types.js +0 -0
  222. /package/build/{ai-client/src/logo-generator → logo-generator}/types.d.ts +0 -0
  223. /package/build/{ai-client/src/logo-generator → logo-generator}/types.js +0 -0
  224. /package/build/{ai-client/src/suggestions-event-source → suggestions-event-source}/index.d.ts +0 -0
  225. /package/build/{ai-client/src/suggestions-event-source → suggestions-event-source}/index.js +0 -0
  226. /package/build/{ai-client/src/types.d.ts → types.d.ts} +0 -0
  227. /package/build/{ai-client/src/types.js → types.js} +0 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import {
5
+ useAnalytics,
6
+ PLAN_TYPE_UNLIMITED,
7
+ usePlanType,
8
+ } from '@automattic/jetpack-shared-extension-utils';
9
+ import { Button } from '@wordpress/components';
10
+ import { useCallback, useState } from '@wordpress/element';
11
+ import { __, sprintf } from '@wordpress/i18n';
12
+ import debugFactory from 'debug';
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import './style.scss';
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 {
24
+ IMAGE_GENERATION_MODEL_STABLE_DIFFUSION,
25
+ IMAGE_GENERATION_MODEL_DALL_E_3,
26
+ GENERAL_IMAGE_FEATURE_NAME,
27
+ } from './types.js';
28
+
29
+ /**
30
+ * The type for the callback function that is called when the user selects an image.
31
+ */
32
+ type SetImageCallbackProps = {
33
+ id: number;
34
+ url: string;
35
+ };
36
+
37
+ type GeneralPurposeImageProps = {
38
+ placement: string;
39
+ onClose?: () => void;
40
+ onSetImage?: ( image: SetImageCallbackProps ) => void;
41
+ };
42
+
43
+ const debug = debugFactory( 'jetpack-ai:general-purpose-image' );
44
+
45
+ /**
46
+ * GeneralPurposeImage component
47
+ * @param {GeneralPurposeImageProps} props - The component properties.
48
+ * @return {React.ReactElement} - rendered component.
49
+ */
50
+ export default function GeneralPurposeImage( {
51
+ placement,
52
+ onClose = () => {},
53
+ onSetImage = () => {},
54
+ }: GeneralPurposeImageProps ) {
55
+ const [ isFeaturedImageModalVisible, setIsFeaturedImageModalVisible ] = useState( true );
56
+ const siteType = useSiteType();
57
+ const postContent = usePostContent();
58
+ const { saveToMediaLibrary } = useSaveToMediaLibrary();
59
+ const { tracks } = useAnalytics();
60
+ const { recordEvent } = tracks;
61
+ const [ prompt, setPrompt ] = useState( '' );
62
+
63
+ // Get feature data
64
+ const { requireUpgrade, requestsCount, requestsLimit, currentTier, costs } = useAiFeature();
65
+ const planType = usePlanType( currentTier );
66
+ const generalImageCost = costs?.[ GENERAL_IMAGE_FEATURE_NAME ]?.activeModel ?? 10;
67
+ const generalImageActiveModel =
68
+ generalImageCost === costs?.[ GENERAL_IMAGE_FEATURE_NAME ]?.stableDiffusion
69
+ ? IMAGE_GENERATION_MODEL_STABLE_DIFFUSION
70
+ : IMAGE_GENERATION_MODEL_DALL_E_3;
71
+ const isUnlimited = planType === PLAN_TYPE_UNLIMITED;
72
+ const requestsBalance = requestsLimit - requestsCount;
73
+ const notEnoughRequests = requestsBalance < generalImageCost;
74
+
75
+ const {
76
+ current,
77
+ setCurrent,
78
+ processImageGeneration,
79
+ handlePreviousImage,
80
+ handleNextImage,
81
+ currentImage,
82
+ currentPointer,
83
+ images,
84
+ pointer,
85
+ imageStyles,
86
+ guessStyle,
87
+ } = useAiImage( {
88
+ cost: generalImageCost,
89
+ autoStart: false,
90
+ type: 'general-image-generation',
91
+ feature: GENERAL_IMAGE_FEATURE_NAME,
92
+ } );
93
+
94
+ const hasPrompt = prompt.length >= 3;
95
+ const disableInput = notEnoughRequests || currentPointer?.generating || requireUpgrade;
96
+ const disableAction = disableInput || ! hasPrompt;
97
+
98
+ const handleModalClose = useCallback( () => {
99
+ setIsFeaturedImageModalVisible( false );
100
+ onClose?.();
101
+ }, [ onClose ] );
102
+
103
+ const handleGenerate = useCallback(
104
+ async ( { userPrompt, style }: { userPrompt?: string; style?: string } ) => {
105
+ debug( 'handleGenerate', userPrompt, style );
106
+
107
+ // track the generate image event
108
+ recordEvent( 'jetpack_ai_general_image_generation_generate_image', {
109
+ placement,
110
+ model: generalImageActiveModel,
111
+ site_type: siteType,
112
+ style,
113
+ } );
114
+ processImageGeneration( { userPrompt, postContent, notEnoughRequests, style } ).catch(
115
+ error => {
116
+ recordEvent( 'jetpack_ai_general_image_generation_error', {
117
+ placement,
118
+ error: error?.message,
119
+ model: generalImageActiveModel,
120
+ site_type: siteType,
121
+ style,
122
+ } );
123
+ }
124
+ );
125
+ },
126
+ [
127
+ recordEvent,
128
+ placement,
129
+ generalImageActiveModel,
130
+ siteType,
131
+ processImageGeneration,
132
+ postContent,
133
+ notEnoughRequests,
134
+ ]
135
+ );
136
+
137
+ const handleRegenerate = useCallback(
138
+ ( { userPrompt, style }: { userPrompt?: string; style?: string } ) => {
139
+ debug( 'handleRegenerate', userPrompt );
140
+ // track the regenerate image event
141
+ recordEvent( 'jetpack_ai_general_image_generation_generate_another_image', {
142
+ placement,
143
+ model: generalImageActiveModel,
144
+ site_type: siteType,
145
+ style,
146
+ } );
147
+
148
+ setCurrent( crrt => crrt + 1 );
149
+ processImageGeneration( { userPrompt, postContent, notEnoughRequests, style } ).catch(
150
+ error => {
151
+ recordEvent( 'jetpack_ai_general_image_generation_error', {
152
+ placement,
153
+ error: error?.message,
154
+ model: generalImageActiveModel,
155
+ site_type: siteType,
156
+ } );
157
+ }
158
+ );
159
+ },
160
+ [
161
+ recordEvent,
162
+ placement,
163
+ generalImageActiveModel,
164
+ siteType,
165
+ processImageGeneration,
166
+ postContent,
167
+ notEnoughRequests,
168
+ setCurrent,
169
+ ]
170
+ );
171
+
172
+ const handleTryAgain = useCallback(
173
+ ( { userPrompt, style }: { userPrompt?: string; style?: string } ) => {
174
+ debug( 'handleTryAgain', userPrompt );
175
+ // track the try again event
176
+ recordEvent( 'jetpack_ai_general_image_generation_try_again', {
177
+ placement,
178
+ model: generalImageActiveModel,
179
+ site_type: siteType,
180
+ style,
181
+ } );
182
+
183
+ processImageGeneration( { userPrompt, postContent, notEnoughRequests, style } ).catch(
184
+ error => {
185
+ recordEvent( 'jetpack_ai_general_image_generation_error', {
186
+ placement,
187
+ error: error?.message,
188
+ model: generalImageActiveModel,
189
+ site_type: siteType,
190
+ } );
191
+ }
192
+ );
193
+ },
194
+ [
195
+ recordEvent,
196
+ placement,
197
+ generalImageActiveModel,
198
+ siteType,
199
+ processImageGeneration,
200
+ postContent,
201
+ notEnoughRequests,
202
+ ]
203
+ );
204
+
205
+ const handleAccept = useCallback( () => {
206
+ // track the accept/use image event
207
+ recordEvent( 'jetpack_ai_general_image_generation_use_image', {
208
+ placement,
209
+ model: generalImageActiveModel,
210
+ site_type: siteType,
211
+ } );
212
+
213
+ const setImage = image => {
214
+ onSetImage?.( { id: image.id, url: image.url } );
215
+ handleModalClose();
216
+ };
217
+
218
+ // If the image is already in the media library, use it directly, if it failed for some reason
219
+ // save it to the media library and then use it.
220
+ if ( currentImage?.libraryId ) {
221
+ setImage( {
222
+ id: currentImage?.libraryId,
223
+ url: currentImage?.libraryUrl,
224
+ } );
225
+ } else {
226
+ saveToMediaLibrary( currentImage?.image ).then( image => {
227
+ setImage( image );
228
+ } );
229
+ }
230
+ }, [
231
+ recordEvent,
232
+ placement,
233
+ generalImageActiveModel,
234
+ siteType,
235
+ currentImage?.libraryId,
236
+ currentImage?.libraryUrl,
237
+ currentImage?.image,
238
+ onSetImage,
239
+ handleModalClose,
240
+ saveToMediaLibrary,
241
+ ] );
242
+
243
+ const generateAgainText = __( 'Generate another image', 'jetpack-ai-client' );
244
+ const generateText = __( 'Generate', 'jetpack-ai-client' );
245
+
246
+ const upgradeDescription = notEnoughRequests
247
+ ? sprintf(
248
+ // Translators: %d is the cost of generating a featured image.
249
+ __(
250
+ "Image generation costs %d requests per image. You don't have enough requests to generate another image.",
251
+ 'jetpack-ai-client'
252
+ ),
253
+ generalImageCost
254
+ )
255
+ : null;
256
+
257
+ const acceptButton = (
258
+ <Button
259
+ onClick={ handleAccept }
260
+ variant="primary"
261
+ disabled={ ! currentImage?.image || currentImage?.generating }
262
+ >
263
+ { __( 'Insert image', 'jetpack-ai-client' ) }
264
+ </Button>
265
+ );
266
+
267
+ return (
268
+ <AiImageModal
269
+ postContent={ true }
270
+ images={ images }
271
+ currentIndex={ current }
272
+ title={ __( 'Generate an image with AI', 'jetpack-ai-client' ) }
273
+ cost={ generalImageCost }
274
+ open={ isFeaturedImageModalVisible }
275
+ placement={ placement }
276
+ onClose={ handleModalClose }
277
+ onTryAgain={ handleTryAgain }
278
+ onGenerate={ pointer?.current > 0 ? handleRegenerate : handleGenerate }
279
+ generating={ currentPointer?.generating }
280
+ notEnoughRequests={ notEnoughRequests }
281
+ requireUpgrade={ requireUpgrade }
282
+ upgradeDescription={ upgradeDescription }
283
+ currentLimit={ requestsLimit }
284
+ currentUsage={ requestsCount }
285
+ isUnlimited={ isUnlimited }
286
+ hasError={ Boolean( currentPointer?.error ) }
287
+ handlePreviousImage={ handlePreviousImage }
288
+ handleNextImage={ handleNextImage }
289
+ acceptButton={ acceptButton }
290
+ generateButtonLabel={ pointer?.current > 0 ? generateAgainText : generateText }
291
+ instructionsPlaceholder={ __(
292
+ "Describe the image you'd like to create and select a style.",
293
+ 'jetpack-ai-client'
294
+ ) }
295
+ imageStyles={ imageStyles }
296
+ onGuessStyle={ guessStyle }
297
+ prompt={ prompt }
298
+ setPrompt={ setPrompt }
299
+ inputDisabled={ disableInput }
300
+ actionDisabled={ disableAction }
301
+ />
302
+ );
303
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useDispatch, useSelect } from '@wordpress/data';
5
+ import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
6
+ import { __ } from '@wordpress/i18n';
7
+ import { cleanForSlug } from '@wordpress/url';
8
+ import React from 'react';
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import askQuestionSync from '../../../ask-question/sync.js';
13
+ import useAiFeature from '../../../hooks/use-ai-feature/index.js';
14
+ import { ImageStyleObject, ImageStyle } from '../../../hooks/use-image-generator/constants.js';
15
+ import useImageGenerator from '../../../hooks/use-image-generator/index.js';
16
+ import useSaveToMediaLibrary from '../../../hooks/use-save-to-media-library.js';
17
+ /**
18
+ * Types
19
+ */
20
+ import {
21
+ CoreSelectors,
22
+ FEATURED_IMAGE_FEATURE_NAME,
23
+ GENERAL_IMAGE_FEATURE_NAME,
24
+ } from '../types.js';
25
+ import type { RoleType } from '../../../types.js';
26
+ import type { CarrouselImageData, CarrouselImages } from '../components/carrousel.js';
27
+ import type { FeatureControl } from '@automattic/jetpack-shared-extension-utils/store/wordpress-com/types';
28
+
29
+ type ImageFeatureControl = FeatureControl & {
30
+ styles: Array< ImageStyleObject > | [];
31
+ };
32
+
33
+ type AiImageType = 'featured-image-generation' | 'general-image-generation';
34
+ type AiImageFeature = typeof FEATURED_IMAGE_FEATURE_NAME | typeof GENERAL_IMAGE_FEATURE_NAME;
35
+ export type ImageResponse = {
36
+ image?: string;
37
+ libraryId?: string;
38
+ libraryUrl?: string;
39
+ revisedPrompt?: string;
40
+ };
41
+
42
+ type ProcessImageGenerationProps = {
43
+ userPrompt?: string | null;
44
+ postContent?: string | null;
45
+ notEnoughRequests: boolean;
46
+ style?: string;
47
+ };
48
+
49
+ type GuessStyleFunction = (
50
+ prompt: string,
51
+ requestType: string,
52
+ content: string
53
+ ) => Promise< ImageStyle | null >;
54
+
55
+ type UseAiImageProps = {
56
+ feature: AiImageFeature;
57
+ type: AiImageType;
58
+ cost: number;
59
+ autoStart?: boolean;
60
+ previousMediaId?: number;
61
+ };
62
+
63
+ type UseAiImageReturn = {
64
+ current: number;
65
+ setCurrent: ( value: number ) => void;
66
+ processImageGeneration: ( props: ProcessImageGenerationProps ) => Promise< ImageResponse >;
67
+ handlePreviousImage: () => void;
68
+ handleNextImage: () => void;
69
+ currentImage: CarrouselImageData;
70
+ currentPointer: CarrouselImageData;
71
+ images: CarrouselImages;
72
+ pointer: React.RefObject< number >;
73
+ imageStyles: Array< ImageStyleObject >;
74
+ guessStyle: GuessStyleFunction;
75
+ };
76
+
77
+ /**
78
+ * Hook to get properties for AiImage
79
+ *
80
+ * @param {UseAiImageProps} props - The component properties.
81
+ * @return {UseAiImageReturn} - Object containing properties for AiImage.
82
+ */
83
+ export default function useAiImage( {
84
+ feature,
85
+ type,
86
+ cost,
87
+ autoStart = true,
88
+ previousMediaId,
89
+ }: UseAiImageProps ) {
90
+ const { generateImageWithParameters } = useImageGenerator();
91
+ const { increaseRequestsCount, featuresControl } = useAiFeature();
92
+ const { saveToMediaLibrary } = useSaveToMediaLibrary();
93
+ const { createNotice } = useDispatch( 'core/notices' );
94
+
95
+ /* Images Control */
96
+ // pointer keeps track of request/generation iteration
97
+ const pointer = useRef( 0 );
98
+ // and current keeps track of what is the image exposed at the moment
99
+ // TODO: should current be any relevant here? It's just modal/carrousel logic after all
100
+ const [ current, setCurrent ] = useState( 0 );
101
+ const [ images, setImages ] = useState< CarrouselImages >( [ { generating: autoStart } ] );
102
+
103
+ // map feature-to-control prop, if this goes over 2 options, make a hook for it
104
+ const featureControl = feature === FEATURED_IMAGE_FEATURE_NAME ? 'featured-image' : 'image';
105
+ const imageFeatureControl = featuresControl?.[ featureControl ] as ImageFeatureControl;
106
+ const imageStyles: Array< ImageStyleObject > = imageFeatureControl?.styles;
107
+
108
+ /* Merge the image data with the new data. */
109
+ const updateImages = useCallback( ( data: CarrouselImageData, index ) => {
110
+ setImages( currentImages => {
111
+ const newImages = [ ...currentImages ];
112
+ newImages[ index ] = {
113
+ ...newImages[ index ],
114
+ ...data,
115
+ };
116
+ return newImages;
117
+ } );
118
+ }, [] );
119
+
120
+ // the selec/useEffect combo...
121
+ const loadedMedia = useSelect(
122
+ ( select: ( store ) => CoreSelectors ) => select( 'core' )?.getMedia?.( previousMediaId ),
123
+ [ previousMediaId ]
124
+ );
125
+ useEffect( () => {
126
+ if ( loadedMedia ) {
127
+ updateImages(
128
+ {
129
+ image: loadedMedia.source_url,
130
+ libraryId: loadedMedia.id,
131
+ libraryUrl: loadedMedia.source_url,
132
+ generating: false,
133
+ },
134
+ pointer.current
135
+ );
136
+ }
137
+ }, [ loadedMedia, updateImages ] );
138
+
139
+ /*
140
+ * Function to show a snackbar notice on the editor.
141
+ */
142
+ const showSnackbarNotice = useCallback(
143
+ ( message: string ) => {
144
+ createNotice( 'success', message, {
145
+ type: 'snackbar',
146
+ isDismissible: true,
147
+ } );
148
+ },
149
+ [ createNotice ]
150
+ );
151
+
152
+ /*
153
+ * Function to update the requests count after a featured image generation.
154
+ */
155
+ const updateRequestsCount = useCallback( () => {
156
+ increaseRequestsCount( cost );
157
+ }, [ increaseRequestsCount, cost ] );
158
+
159
+ /*
160
+ * Function to suggest a name for the image based on the user prompt.
161
+ */
162
+ const getImageNameSuggestion = useCallback( ( userPrompt: string ) => {
163
+ if ( ! userPrompt ) {
164
+ return 'image.png';
165
+ }
166
+
167
+ const truncatedPrompt = userPrompt.split( ' ' ).slice( 0, 10 ).join( ' ' );
168
+ return cleanForSlug( truncatedPrompt ) + '.png';
169
+ }, [] );
170
+
171
+ /*
172
+ * Function to generate a new image with the current value of the post content.
173
+ */
174
+ const processImageGeneration = useCallback(
175
+ ( {
176
+ userPrompt,
177
+ postContent,
178
+ notEnoughRequests,
179
+ style = null,
180
+ }: ProcessImageGenerationProps ) => {
181
+ return new Promise< ImageResponse >( ( resolve, reject ) => {
182
+ if ( previousMediaId && pointer.current === 0 ) {
183
+ pointer.current++;
184
+ }
185
+ updateImages( { generating: true, error: null }, pointer.current );
186
+
187
+ // Ensure the site has enough requests to generate the image.
188
+ if ( notEnoughRequests ) {
189
+ updateImages(
190
+ {
191
+ generating: false,
192
+ error: new Error(
193
+ __(
194
+ "You don't have enough requests to generate another image.",
195
+ 'jetpack-ai-client'
196
+ )
197
+ ),
198
+ },
199
+ pointer.current
200
+ );
201
+ resolve( {} );
202
+ return;
203
+ }
204
+
205
+ /**
206
+ * Make a generic call to backend and let it decide the model.
207
+ */
208
+ const generateImagePromise = generateImageWithParameters( {
209
+ feature,
210
+ size: '1792x1024', // the size, when the generation happens with DALL-E-3
211
+ responseFormat: 'b64_json', // the response format, when the generation happens with DALL-E-3
212
+ messages: [
213
+ {
214
+ role: 'jetpack-ai',
215
+ context: {
216
+ type,
217
+ request: userPrompt ? userPrompt : null,
218
+ content: postContent,
219
+ style,
220
+ },
221
+ },
222
+ ],
223
+ style: style || '',
224
+ } );
225
+
226
+ const name = getImageNameSuggestion( userPrompt );
227
+
228
+ generateImagePromise
229
+ .then( result => {
230
+ if ( result.data.length > 0 ) {
231
+ const image = 'data:image/png;base64,' + result.data[ 0 ].b64_json;
232
+ const prompt = userPrompt || null;
233
+ const revisedPrompt = result.data[ 0 ].revised_prompt || null;
234
+ updateImages( { image, prompt, revisedPrompt }, pointer.current );
235
+ updateRequestsCount();
236
+ saveToMediaLibrary( image, name )
237
+ .then( savedImage => {
238
+ showSnackbarNotice( __( 'Image saved to media library.', 'jetpack-ai-client' ) );
239
+ updateImages(
240
+ { libraryId: savedImage?.id, libraryUrl: savedImage?.url, generating: false },
241
+ pointer.current
242
+ );
243
+ pointer.current += 1;
244
+ resolve( {
245
+ image,
246
+ libraryId: savedImage?.id,
247
+ libraryUrl: savedImage?.url,
248
+ revisedPrompt,
249
+ } );
250
+ } )
251
+ .catch( () => {
252
+ updateImages( { generating: false }, pointer.current );
253
+ pointer.current += 1;
254
+ resolve( { image } );
255
+ } );
256
+ }
257
+ } )
258
+ .catch( e => {
259
+ updateImages( { generating: false, error: e }, pointer.current );
260
+ reject( e );
261
+ } );
262
+ } );
263
+ },
264
+ [
265
+ updateImages,
266
+ generateImageWithParameters,
267
+ feature,
268
+ type,
269
+ updateRequestsCount,
270
+ saveToMediaLibrary,
271
+ showSnackbarNotice,
272
+ getImageNameSuggestion,
273
+ previousMediaId,
274
+ ]
275
+ );
276
+
277
+ const handlePreviousImage = useCallback( () => {
278
+ setCurrent( Math.max( current - 1, 0 ) );
279
+ }, [ current ] );
280
+
281
+ const handleNextImage = useCallback( () => {
282
+ setCurrent( Math.min( current + 1, images.length - 1 ) );
283
+ }, [ current, images.length ] );
284
+
285
+ const guessStyle = useCallback(
286
+ async function (
287
+ prompt: string,
288
+ requestType: string = '',
289
+ content: string = ''
290
+ ): Promise< ImageStyle | null > {
291
+ if ( ! imageStyles || ! imageStyles.length ) {
292
+ return null;
293
+ }
294
+
295
+ const messages = [
296
+ {
297
+ role: 'jetpack-ai' as RoleType,
298
+ context: {
299
+ type: requestType || 'general-image-guess-style',
300
+ request: prompt,
301
+ content,
302
+ },
303
+ },
304
+ ];
305
+
306
+ try {
307
+ const style = await askQuestionSync( messages, { feature: 'jetpack-ai-image-generator' } );
308
+
309
+ if ( ! style ) {
310
+ return null;
311
+ }
312
+ const styleObject = imageStyles.find( ( { value } ) => value === style );
313
+
314
+ if ( ! styleObject ) {
315
+ return null;
316
+ }
317
+
318
+ return styleObject.value;
319
+ } catch ( error ) {
320
+ Promise.reject( error );
321
+ }
322
+ },
323
+ [ imageStyles ]
324
+ );
325
+
326
+ return {
327
+ current,
328
+ setCurrent,
329
+ processImageGeneration,
330
+ handlePreviousImage,
331
+ handleNextImage,
332
+ currentImage: images[ current ],
333
+ currentPointer: images[ pointer.current ],
334
+ images,
335
+ pointer,
336
+ imageStyles,
337
+ guessStyle,
338
+ };
339
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isAtomicSite, isSimpleSite } from '@automattic/jetpack-shared-extension-utils';
5
+ import { useState } from '@wordpress/element';
6
+
7
+ /**
8
+ * Hook to get the type of site.
9
+ *
10
+ * @return {string} - The type of site.
11
+ */
12
+ export default function useSiteType() {
13
+ const getSiteType = () => {
14
+ if ( isAtomicSite() ) {
15
+ return 'atomic';
16
+ }
17
+ if ( isSimpleSite() ) {
18
+ return 'simple';
19
+ }
20
+ return 'jetpack';
21
+ };
22
+
23
+ const [ siteType ] = useState( getSiteType() );
24
+
25
+ return siteType;
26
+ }
@@ -0,0 +1,10 @@
1
+ import FeaturedImage from './featured-image.js';
2
+ import GeneralPurposeImage from './general-purpose-image.js';
3
+ import { PLACEMENT_MEDIA_SOURCE_DROPDOWN, PLACEMENT_BLOCK_PLACEHOLDER_BUTTON } from './types.js';
4
+
5
+ export {
6
+ FeaturedImage,
7
+ PLACEMENT_MEDIA_SOURCE_DROPDOWN,
8
+ PLACEMENT_BLOCK_PLACEHOLDER_BUTTON,
9
+ GeneralPurposeImage,
10
+ };