@automattic/jetpack-ai-client 0.25.7 → 0.26.1

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,88 @@
1
+ .ai-image-modal {
2
+ &__content {
3
+ display: flex;
4
+ margin-top: 32px;
5
+ flex-direction: column;
6
+ justify-content: center;
7
+ gap: 8px;
8
+
9
+ .jetpack-ai-fair-usage-notice {
10
+ width: 100%;
11
+ }
12
+ }
13
+
14
+ &__actions {
15
+ width: 100%;
16
+ display: flex;
17
+ }
18
+
19
+ &__user-prompt {
20
+ width: 100%;
21
+ display: flex;
22
+ justify-content: center;
23
+ flex-direction: row;
24
+ }
25
+
26
+ &__user-prompt-textarea {
27
+ width: 100%;
28
+ display: flex;
29
+ justify-content: center;
30
+
31
+ div {
32
+ width: 100%;
33
+ display: flex;
34
+ }
35
+
36
+ textarea {
37
+ width: 100%;
38
+ outline: none;
39
+ border: 1px solid #ccc;
40
+ border-radius: 6px;
41
+ padding: 12px;
42
+ color: var( --studio-black );
43
+ }
44
+
45
+ textarea:disabled {
46
+ opacity: 0.5;
47
+ }
48
+ }
49
+
50
+ &__actions-left {
51
+ width: 50%;
52
+ display: flex;
53
+ justify-content: flex-start;
54
+ }
55
+
56
+ &__actions-right {
57
+ width: 50%;
58
+ display: flex;
59
+ justify-content: flex-end;
60
+ }
61
+
62
+ &__action-buttons {
63
+ display: flex;
64
+ justify-content: center;
65
+ gap: 12px;
66
+ }
67
+
68
+ &__image-canvas {
69
+ width: 100%;
70
+ margin-top: 24px;
71
+ & > .jetpack-ai-upgrade-banner {
72
+ margin-bottom: 8px;
73
+ }
74
+ }
75
+
76
+ &__footer {
77
+ display: flex;
78
+ justify-content: space-between;
79
+ align-items: center;
80
+ margin-right: -32px;
81
+ margin-left: -32px;
82
+ margin-top: 32px;
83
+ margin-bottom: -32px;
84
+ padding: 8px 32px;
85
+ border-top: solid 1px #dcdcde;
86
+ height: 60px;
87
+ }
88
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
+ import { SelectControl } from '@wordpress/components';
6
+ import { useCallback, useRef, useState, useEffect } from '@wordpress/element';
7
+ import { __ } from '@wordpress/i18n';
8
+ import debugFactory from 'debug';
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import {
13
+ IMAGE_STYLE_NONE,
14
+ IMAGE_STYLE_AUTO,
15
+ ImageStyleObject,
16
+ ImageStyle,
17
+ } from '../../../hooks/use-image-generator/constants.js';
18
+ import { AiModalPromptInput } from '../../../logo-generator/index.js';
19
+ import AiModalFooter from '../../ai-modal-footer/index.js';
20
+ import AiAssistantModal from '../../modal/index.js';
21
+ import QuotaExceededMessage from '../../quota-exceeded-message/index.js';
22
+ import Carrousel, { CarrouselImages } from './carrousel.js';
23
+ import UsageCounter from './usage-counter.js';
24
+ import './ai-image-modal.scss';
25
+
26
+ type AiImageModalProps = {
27
+ title: string;
28
+ cost: number;
29
+ open: boolean;
30
+ placement: string;
31
+ images: CarrouselImages;
32
+ currentIndex: number;
33
+ onClose: () => void;
34
+ onTryAgain: ( { userPrompt, style }: { userPrompt?: string; style?: string } ) => void;
35
+ onGenerate: ( { userPrompt, style }: { userPrompt?: string; style?: string } ) => void;
36
+ generating: boolean;
37
+ notEnoughRequests: boolean;
38
+ requireUpgrade: boolean;
39
+ currentLimit: number;
40
+ currentUsage: number;
41
+ isUnlimited: boolean;
42
+ upgradeDescription: string;
43
+ hasError: boolean;
44
+ postContent?: string | boolean | null;
45
+ handlePreviousImage: () => void;
46
+ handleNextImage: () => void;
47
+ acceptButton: React.JSX.Element;
48
+ autoStart?: boolean;
49
+ autoStartAction?: ( { userPrompt, style }: { userPrompt?: string; style?: string } ) => void;
50
+ generateButtonLabel: string;
51
+ instructionsPlaceholder: string;
52
+ imageStyles?: Array< ImageStyleObject >;
53
+ onGuessStyle?: ( userPrompt: string ) => Promise< ImageStyle >;
54
+ prompt?: string;
55
+ setPrompt?: ( userPrompt: string ) => void;
56
+ initialStyle?: ImageStyle;
57
+ inputDisabled?: boolean;
58
+ actionDisabled?: boolean;
59
+ };
60
+
61
+ const FEATURED_IMAGE_UPGRADE_PROMPT_PLACEMENT = 'ai-image-generator';
62
+
63
+ const debug = debugFactory( 'jetpack-ai-client:ai-image-modal' );
64
+
65
+ /**
66
+ * AiImageModal component
67
+ * @param {AiImageModalProps} props - The component properties.
68
+ * @return {React.ReactElement} - rendered component.
69
+ */
70
+ export default function AiImageModal( {
71
+ title,
72
+ cost,
73
+ open,
74
+ images,
75
+ currentIndex = 0,
76
+ onClose = null,
77
+ onTryAgain = null,
78
+ onGenerate = null,
79
+ generating = false,
80
+ notEnoughRequests = false,
81
+ requireUpgrade = false,
82
+ currentLimit = null,
83
+ currentUsage = null,
84
+ isUnlimited = false,
85
+ upgradeDescription = null,
86
+ hasError = false,
87
+ handlePreviousImage = () => {},
88
+ handleNextImage = () => {},
89
+ acceptButton = null,
90
+ autoStart = false,
91
+ autoStartAction = null,
92
+ instructionsPlaceholder = null,
93
+ imageStyles = [],
94
+ onGuessStyle = null,
95
+ prompt = '',
96
+ setPrompt = () => {},
97
+ initialStyle = null,
98
+ inputDisabled = false,
99
+ actionDisabled = false,
100
+ }: AiImageModalProps ) {
101
+ const { tracks } = useAnalytics();
102
+ const { recordEvent: recordTracksEvent } = tracks;
103
+ const triggeredAutoGeneration = useRef( false );
104
+ const [ showStyleSelector, setShowStyleSelector ] = useState( false );
105
+ const [ style, setStyle ] = useState< ImageStyle >( null );
106
+ const [ styles, setStyles ] = useState< Array< ImageStyleObject > >( imageStyles || [] );
107
+
108
+ const handleTryAgain = useCallback( () => {
109
+ onTryAgain?.( { userPrompt: prompt, style } );
110
+ }, [ onTryAgain, prompt, style ] );
111
+
112
+ const handleGenerate = useCallback( async () => {
113
+ if ( style === IMAGE_STYLE_AUTO && onGuessStyle ) {
114
+ recordTracksEvent( 'jetpack_ai_general_image_guess_style', {
115
+ context: 'block-editor',
116
+ tool: 'image',
117
+ } );
118
+ const guessedStyle = ( await onGuessStyle( prompt ) ) || IMAGE_STYLE_NONE;
119
+ setStyle( guessedStyle );
120
+ debug( 'guessed style', guessedStyle );
121
+ onGenerate?.( { userPrompt: prompt, style: guessedStyle } );
122
+ } else {
123
+ onGenerate?.( { userPrompt: prompt, style } );
124
+ }
125
+ }, [ onGenerate, prompt, style, onGuessStyle, recordTracksEvent ] );
126
+
127
+ const updateStyle = useCallback(
128
+ ( imageStyle: ImageStyle ) => {
129
+ debug( 'change style', imageStyle );
130
+ setStyle( imageStyle );
131
+ recordTracksEvent( 'jetpack_ai_image_generator_switch_style', {
132
+ context: 'block-editor',
133
+ style: imageStyle,
134
+ } );
135
+ },
136
+ [ setStyle, recordTracksEvent ]
137
+ );
138
+
139
+ // Controllers
140
+ const upgradePromptVisible = ( requireUpgrade || notEnoughRequests ) && ! generating;
141
+ const counterVisible = Boolean( ! isUnlimited && cost && currentLimit );
142
+
143
+ const generateLabel = __( 'Generate', 'jetpack-ai-client' );
144
+ const tryAgainLabel = __( 'Try again', 'jetpack-ai-client' );
145
+
146
+ /**
147
+ * Trigger image generation automatically.
148
+ */
149
+ useEffect( () => {
150
+ if ( autoStart && open ) {
151
+ if ( ! triggeredAutoGeneration.current ) {
152
+ triggeredAutoGeneration.current = true;
153
+ autoStartAction?.( {} );
154
+ }
155
+ }
156
+ }, [ autoStart, autoStartAction, open ] );
157
+
158
+ // initialize styles dropdown
159
+ useEffect( () => {
160
+ if ( imageStyles && imageStyles.length > 0 ) {
161
+ // Sort styles to have "None" and "Auto" first
162
+ setStyles(
163
+ [
164
+ imageStyles.find( ( { value } ) => value === IMAGE_STYLE_NONE ),
165
+ imageStyles.find( ( { value } ) => value === IMAGE_STYLE_AUTO ),
166
+ ...imageStyles.filter(
167
+ ( { value } ) => ! [ IMAGE_STYLE_NONE, IMAGE_STYLE_AUTO ].includes( value )
168
+ ),
169
+ ].filter( v => v ) // simplest way to get rid of empty values
170
+ );
171
+ setShowStyleSelector( true );
172
+ setStyle( initialStyle || IMAGE_STYLE_NONE );
173
+ }
174
+ }, [ imageStyles, initialStyle ] );
175
+
176
+ return (
177
+ <>
178
+ { open && (
179
+ <AiAssistantModal handleClose={ onClose } title={ title }>
180
+ <div className="ai-image-modal__content">
181
+ { showStyleSelector && (
182
+ <div style={ { display: 'flex', alignItems: 'center', gap: 16 } }>
183
+ <div style={ { fontWeight: 500, flexGrow: 1 } }>
184
+ { __( 'Generate image', 'jetpack-ai-client' ) }
185
+ </div>
186
+ <div>
187
+ <SelectControl
188
+ __nextHasNoMarginBottom
189
+ value={ style }
190
+ options={ styles }
191
+ onChange={ updateStyle }
192
+ />
193
+ </div>
194
+ </div>
195
+ ) }
196
+ <AiModalPromptInput
197
+ prompt={ prompt }
198
+ setPrompt={ setPrompt }
199
+ disabled={ inputDisabled }
200
+ actionDisabled={ actionDisabled }
201
+ generateHandler={ hasError ? handleTryAgain : handleGenerate }
202
+ placeholder={ instructionsPlaceholder }
203
+ buttonLabel={ hasError ? tryAgainLabel : generateLabel }
204
+ />
205
+ { upgradePromptVisible && (
206
+ <QuotaExceededMessage
207
+ description={ upgradeDescription }
208
+ placement={ FEATURED_IMAGE_UPGRADE_PROMPT_PLACEMENT }
209
+ useLightNudge={ true }
210
+ />
211
+ ) }
212
+ <div className="ai-image-modal__actions">
213
+ <div className="ai-image-modal__actions-left">
214
+ { counterVisible && (
215
+ <UsageCounter
216
+ cost={ cost }
217
+ currentLimit={ currentLimit }
218
+ currentUsage={ currentUsage }
219
+ />
220
+ ) }
221
+ </div>
222
+ </div>
223
+ <div className="ai-image-modal__image-canvas">
224
+ <Carrousel
225
+ images={ images }
226
+ current={ currentIndex }
227
+ handlePreviousImage={ handlePreviousImage }
228
+ handleNextImage={ handleNextImage }
229
+ actions={ acceptButton }
230
+ />
231
+ </div>
232
+ </div>
233
+ <div className="ai-image-modal__footer">
234
+ <AiModalFooter />
235
+ </div>
236
+ </AiAssistantModal>
237
+ ) }
238
+ </>
239
+ );
240
+ }
@@ -0,0 +1,163 @@
1
+ $scale-factor: 0.55;
2
+
3
+ .ai-assistant-image {
4
+ &__blank {
5
+ display: flex;
6
+ justify-content: center;
7
+ position: relative;
8
+ align-items: center;
9
+ width: 100%;
10
+ height: auto;
11
+
12
+ &-content {
13
+ position: absolute;
14
+ width: 100%;
15
+ height: 100%;
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+
20
+ &.is-dotted {
21
+ border: 1px dotted;
22
+ }
23
+ }
24
+ }
25
+
26
+ &__loading {
27
+ flex-direction: column;
28
+ gap: 8px;
29
+ }
30
+
31
+ &__error {
32
+ display: flex;
33
+ flex-direction: column;
34
+ align-items: center;
35
+ justify-content: center;
36
+
37
+ &-message {
38
+ font-size: 10px;
39
+ color: red;
40
+ }
41
+ }
42
+
43
+ &__carrousel {
44
+ flex-direction: column;
45
+ width: 100%;
46
+
47
+ &-images {
48
+ position: relative;
49
+ display: flex;
50
+ align-items: center;
51
+ overflow: hidden;
52
+ height: calc( 768px * $scale-factor );
53
+
54
+ .ai-carrousel {
55
+ &__prev {
56
+ left: 0;
57
+ }
58
+
59
+ &__next {
60
+ right: 0;
61
+ }
62
+
63
+ &__prev,
64
+ &__next {
65
+ &:hover {
66
+ background-color: rgba( 0, 0, 0, 0.3 );
67
+ opacity: 1;
68
+ }
69
+
70
+ &-icon {
71
+ fill: white;
72
+ &.is-disabled {
73
+ fill: gray;
74
+ }
75
+ }
76
+
77
+ cursor: pointer;
78
+ z-index: 1;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ transition: opacity 0.3s;
83
+ border: none;
84
+ position: absolute;
85
+ top: 0;
86
+ bottom: 0;
87
+ height: 100%;
88
+ width: 60px;
89
+ opacity: 0;
90
+ background: transparent;
91
+ }
92
+ }
93
+ }
94
+
95
+ &-footer {
96
+ display: flex;
97
+ justify-content: space-between;
98
+ margin-top: 8px;
99
+ }
100
+
101
+ &-footer-left {
102
+ display: flex;
103
+ flex-grow: 1;
104
+ }
105
+
106
+ &-counter {
107
+ display: flex;
108
+ justify-content: flex-start;
109
+ align-items: center;
110
+ font-size: 13px;
111
+ padding: 6px 0;
112
+
113
+ .ai-carrousel {
114
+ &__prev,
115
+ &__next {
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ cursor: pointer;
120
+ border: none;
121
+ background: transparent;
122
+ padding: 0;
123
+
124
+ &-icon {
125
+ &.is-disabled {
126
+ fill: gray;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ &-image {
134
+ max-height: calc( 768px * $scale-factor );
135
+ max-width: calc( 1024px * $scale-factor );
136
+ width: auto;
137
+ height: auto;
138
+ margin: auto 0;
139
+ }
140
+
141
+ &-image-container {
142
+ display: flex;
143
+ width: 100%;
144
+ height: 100%;
145
+ position: absolute;
146
+ left: 8px;
147
+ transform: translateX( 100% );
148
+ transition: transform 0.5s;
149
+ flex-direction: column;
150
+ align-items: center;
151
+
152
+ &.is-prev {
153
+ transform: translateX( -100% );
154
+ left: -8px;
155
+ }
156
+
157
+ &.is-current {
158
+ transform: translateX( 0 );
159
+ position: static;
160
+ }
161
+ }
162
+ }
163
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { Spinner } from '@wordpress/components';
5
+ import { useEffect, useState } from '@wordpress/element';
6
+ import { __ } from '@wordpress/i18n';
7
+ import { Icon, chevronLeft, chevronRight } from '@wordpress/icons';
8
+ import clsx from 'clsx';
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import AiFeedbackThumbs from '../../ai-feedback/index.js';
13
+ import AiIcon from '../../ai-icon/index.js';
14
+ import './carrousel.scss';
15
+
16
+ export type CarrouselImageData = {
17
+ image?: string;
18
+ libraryId?: number | string;
19
+ prompt?: string;
20
+ revisedPrompt?: string;
21
+ libraryUrl?: string;
22
+ generating?: boolean;
23
+ error?: {
24
+ message: string;
25
+ };
26
+ };
27
+
28
+ export type CarrouselImages = CarrouselImageData[];
29
+
30
+ type BlankImageProps = {
31
+ children: React.ReactNode;
32
+ isDotted?: boolean;
33
+ contentClassName?: string;
34
+ };
35
+
36
+ /**
37
+ * BlankImage component
38
+ * @param {BlankImageProps} props - The component properties.
39
+ * @return {React.ReactElement} - rendered component.
40
+ */
41
+ function BlankImage( { children, isDotted = false, contentClassName = '' }: BlankImageProps ) {
42
+ const blankImage = (
43
+ <img
44
+ className="ai-assistant-image__carrousel-image"
45
+ src="data:image/svg+xml;utf8,<svg viewBox='0 0 1 1' width='1024' height='768' xmlns='http://www.w3.org/2000/svg'><path d='M0 0 L1 0 L1 1 L0 1 L0 0 Z' fill='none' /></svg>"
46
+ alt=""
47
+ />
48
+ );
49
+
50
+ return (
51
+ <div className="ai-assistant-image__blank">
52
+ { blankImage }
53
+ <div
54
+ className={ clsx( 'ai-assistant-image__blank-content', contentClassName, {
55
+ 'is-dotted': isDotted,
56
+ } ) }
57
+ >
58
+ { children }
59
+ </div>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ type CarrouselProps = {
65
+ images: CarrouselImages;
66
+ current: number;
67
+ handlePreviousImage: () => void;
68
+ handleNextImage: () => void;
69
+ actions?: React.JSX.Element;
70
+ };
71
+
72
+ /**
73
+ * Carrousel component
74
+ * @param {CarrouselProps} props - The component properties.
75
+ * @return {React.ReactElement} - rendered component.
76
+ */
77
+ export default function Carrousel( {
78
+ images,
79
+ current,
80
+ handlePreviousImage,
81
+ handleNextImage,
82
+ actions = null,
83
+ }: CarrouselProps ) {
84
+ const [ imageFeedbackDisabled, setImageFeedbackDisabled ] = useState( false );
85
+ const prevButton = (
86
+ <button className="ai-carrousel__prev" onClick={ handlePreviousImage }>
87
+ <Icon
88
+ icon={ chevronLeft }
89
+ className={ clsx( 'ai-carrousel__prev-icon', {
90
+ 'is-disabled': current === 0,
91
+ } ) }
92
+ />
93
+ </button>
94
+ );
95
+
96
+ const nextButton = (
97
+ <button className="ai-carrousel__next" onClick={ handleNextImage }>
98
+ <Icon
99
+ icon={ chevronRight }
100
+ className={ clsx( 'ai-carrousel__next-icon', {
101
+ 'is-disabled': current + 1 === images.length,
102
+ } ) }
103
+ />
104
+ </button>
105
+ );
106
+
107
+ const total = images?.filter?.(
108
+ item => item?.generating || Object.hasOwn( item, 'image' ) || Object.hasOwn( item, 'libraryId' )
109
+ )?.length;
110
+
111
+ const actual = current === 0 && total === 0 ? 0 : current + 1;
112
+
113
+ useEffect( () => {
114
+ const imageData = images[ current ];
115
+ if ( ! imageData ) {
116
+ setImageFeedbackDisabled( true );
117
+ }
118
+
119
+ const { image, generating, error } = imageData || {};
120
+
121
+ // disable if there's an empty modal
122
+ if ( ! image && ! generating && ! error ) {
123
+ return setImageFeedbackDisabled( true );
124
+ }
125
+ // also disable if we're generating or have an error
126
+ if ( generating || error ) {
127
+ return setImageFeedbackDisabled( true );
128
+ }
129
+
130
+ setImageFeedbackDisabled( false );
131
+ }, [ current, images ] );
132
+
133
+ return (
134
+ <div className="ai-assistant-image__carrousel">
135
+ <div className="ai-assistant-image__carrousel-images">
136
+ { images.length > 1 && prevButton }
137
+ { images.map( ( { image, generating, error, revisedPrompt, libraryUrl }, index ) => (
138
+ <div
139
+ key={ `image:` + index }
140
+ className={ clsx( 'ai-assistant-image__carrousel-image-container', {
141
+ 'is-current': current === index,
142
+ 'is-prev': current > index,
143
+ } ) }
144
+ >
145
+ { generating ? (
146
+ <BlankImage contentClassName="ai-assistant-image__loading">
147
+ { __( 'Creating image…', 'jetpack-ai-client' ) }
148
+ <Spinner
149
+ style={ {
150
+ width: '50px',
151
+ height: '50px',
152
+ } }
153
+ />
154
+ </BlankImage>
155
+ ) : (
156
+ <>
157
+ { error ? (
158
+ <BlankImage isDotted>
159
+ <div className="ai-assistant-image__error">
160
+ { __(
161
+ 'An error occurred while generating the image. Please, try again!',
162
+ 'jetpack-ai-client'
163
+ ) }
164
+ { error?.message && (
165
+ <span className="ai-assistant-image__error-message">
166
+ { error?.message }
167
+ </span>
168
+ ) }
169
+ </div>
170
+ </BlankImage>
171
+ ) : (
172
+ <>
173
+ { ! generating && ! image && ! libraryUrl ? (
174
+ <BlankImage>
175
+ <AiIcon />
176
+ </BlankImage>
177
+ ) : (
178
+ <img
179
+ className="ai-assistant-image__carrousel-image"
180
+ src={ image || libraryUrl }
181
+ alt={ revisedPrompt }
182
+ />
183
+ ) }
184
+ </>
185
+ ) }
186
+ </>
187
+ ) }
188
+ </div>
189
+ ) ) }
190
+ { images.length > 1 && nextButton }
191
+ </div>
192
+ <div className="ai-assistant-image__carrousel-footer">
193
+ <div className="ai-assistant-image__carrousel-footer-left">
194
+ <div className="ai-assistant-image__carrousel-counter">
195
+ { prevButton }
196
+ { actual } / { total }
197
+ { nextButton }
198
+ </div>
199
+
200
+ <AiFeedbackThumbs
201
+ disabled={ imageFeedbackDisabled }
202
+ ratedItem={ images[ current ]?.libraryUrl || '' }
203
+ iconSize={ 20 }
204
+ options={ {
205
+ mediaLibraryId: Number( images[ current ].libraryId ),
206
+ prompt: images[ current ].prompt,
207
+ revisedPrompt: images[ current ].revisedPrompt,
208
+ } }
209
+ feature="image-generator"
210
+ />
211
+ </div>
212
+
213
+ <div className="ai-assistant-image__carrousel-actions">{ actions }</div>
214
+ </div>
215
+ </div>
216
+ );
217
+ }