@ai-stack/payloadcms 3.2.24-beta → 3.68.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 (284) hide show
  1. package/{LICENSE.md → LICENSE} +1 -1
  2. package/README.md +218 -229
  3. package/dist/access/checkAccess.d.ts +4 -0
  4. package/dist/access/checkAccess.js +20 -0
  5. package/dist/access/checkAccess.js.map +1 -0
  6. package/dist/ai/core/generateObject.d.ts +7 -0
  7. package/dist/ai/core/generateObject.js +35 -0
  8. package/dist/ai/core/generateObject.js.map +1 -0
  9. package/dist/ai/core/generateText.d.ts +7 -0
  10. package/dist/ai/core/generateText.js +31 -0
  11. package/dist/ai/core/generateText.js.map +1 -0
  12. package/dist/ai/core/index.d.ts +11 -0
  13. package/dist/ai/core/index.js +10 -0
  14. package/dist/ai/core/index.js.map +1 -0
  15. package/dist/ai/core/media/generateMedia.d.ts +7 -0
  16. package/dist/ai/core/media/generateMedia.js +50 -0
  17. package/dist/ai/core/media/generateMedia.js.map +1 -0
  18. package/dist/ai/core/media/image/generateImage.d.ts +6 -0
  19. package/dist/ai/core/media/image/generateImage.js +41 -0
  20. package/dist/ai/core/media/image/generateImage.js.map +1 -0
  21. package/dist/ai/core/media/image/handlers/multimodal.d.ts +7 -0
  22. package/dist/ai/core/media/image/handlers/multimodal.js +95 -0
  23. package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -0
  24. package/dist/ai/core/media/image/handlers/standard.d.ts +7 -0
  25. package/dist/ai/core/media/image/handlers/standard.js +28 -0
  26. package/dist/ai/core/media/image/handlers/standard.js.map +1 -0
  27. package/dist/ai/core/media/image/index.d.ts +2 -0
  28. package/dist/ai/core/media/image/index.js +3 -0
  29. package/dist/ai/core/media/image/index.js.map +1 -0
  30. package/dist/ai/core/media/index.d.ts +2 -0
  31. package/dist/ai/core/media/index.js +3 -0
  32. package/dist/ai/core/media/index.js.map +1 -0
  33. package/dist/ai/core/media/speech/generateSpeech.d.ts +5 -0
  34. package/dist/ai/core/media/speech/generateSpeech.js +55 -0
  35. package/dist/ai/core/media/speech/generateSpeech.js.map +1 -0
  36. package/dist/ai/core/media/speech/index.d.ts +2 -0
  37. package/dist/ai/core/media/speech/index.js +3 -0
  38. package/dist/ai/core/media/speech/index.js.map +1 -0
  39. package/dist/ai/core/media/types.d.ts +74 -0
  40. package/dist/ai/core/media/types.js +5 -0
  41. package/dist/ai/core/media/types.js.map +1 -0
  42. package/dist/ai/core/media/utils.d.ts +11 -0
  43. package/dist/ai/core/media/utils.js +34 -0
  44. package/dist/ai/core/media/utils.js.map +1 -0
  45. package/dist/ai/core/media/video/generateVideo.d.ts +6 -0
  46. package/dist/ai/core/media/video/generateVideo.js +32 -0
  47. package/dist/ai/core/media/video/generateVideo.js.map +1 -0
  48. package/dist/ai/core/media/video/index.d.ts +2 -0
  49. package/dist/ai/core/media/video/index.js +3 -0
  50. package/dist/ai/core/media/video/index.js.map +1 -0
  51. package/dist/ai/core/streamObject.d.ts +7 -0
  52. package/dist/ai/core/streamObject.js +57 -0
  53. package/dist/ai/core/streamObject.js.map +1 -0
  54. package/dist/ai/core/streamText.d.ts +7 -0
  55. package/dist/ai/core/streamText.js +30 -0
  56. package/dist/ai/core/streamText.js.map +1 -0
  57. package/dist/ai/core/types.d.ts +85 -0
  58. package/dist/ai/core/types.js +5 -0
  59. package/dist/ai/core/types.js.map +1 -0
  60. package/dist/ai/index.d.ts +11 -0
  61. package/dist/ai/index.js +25 -0
  62. package/dist/ai/index.js.map +1 -0
  63. package/dist/ai/providers/blocks/anthropic.d.ts +2 -0
  64. package/dist/ai/providers/blocks/anthropic.js +222 -0
  65. package/dist/ai/providers/blocks/anthropic.js.map +1 -0
  66. package/dist/ai/providers/blocks/elevenlabs.d.ts +2 -0
  67. package/dist/ai/providers/blocks/elevenlabs.js +448 -0
  68. package/dist/ai/providers/blocks/elevenlabs.js.map +1 -0
  69. package/dist/ai/providers/blocks/fal.d.ts +2 -0
  70. package/dist/ai/providers/blocks/fal.js +311 -0
  71. package/dist/ai/providers/blocks/fal.js.map +1 -0
  72. package/dist/ai/providers/blocks/google.d.ts +2 -0
  73. package/dist/ai/providers/blocks/google.js +622 -0
  74. package/dist/ai/providers/blocks/google.js.map +1 -0
  75. package/dist/ai/providers/blocks/index.d.ts +2 -0
  76. package/dist/ai/providers/blocks/index.js +18 -0
  77. package/dist/ai/providers/blocks/index.js.map +1 -0
  78. package/dist/ai/providers/blocks/openai-compatible.d.ts +2 -0
  79. package/dist/ai/providers/blocks/openai-compatible.js +307 -0
  80. package/dist/ai/providers/blocks/openai-compatible.js.map +1 -0
  81. package/dist/ai/providers/blocks/openai.d.ts +2 -0
  82. package/dist/ai/providers/blocks/openai.js +599 -0
  83. package/dist/ai/providers/blocks/openai.js.map +1 -0
  84. package/dist/ai/providers/blocks/xai.d.ts +2 -0
  85. package/dist/ai/providers/blocks/xai.js +246 -0
  86. package/dist/ai/providers/blocks/xai.js.map +1 -0
  87. package/dist/ai/providers/index.d.ts +2 -0
  88. package/dist/ai/providers/index.js +6 -0
  89. package/dist/ai/providers/index.js.map +1 -0
  90. package/dist/ai/providers/registry.d.ts +40 -0
  91. package/dist/ai/providers/registry.js +256 -0
  92. package/dist/ai/providers/registry.js.map +1 -0
  93. package/dist/ai/providers/types.d.ts +115 -0
  94. package/dist/ai/providers/types.js +4 -0
  95. package/dist/ai/providers/types.js.map +1 -0
  96. package/dist/ai/utils/systemGenerate.d.ts +1 -1
  97. package/dist/ai/utils/systemGenerate.js +19 -19
  98. package/dist/ai/utils/systemGenerate.js.map +1 -1
  99. package/dist/collections/AIJobs.d.ts +2 -0
  100. package/dist/collections/AIJobs.js +81 -0
  101. package/dist/collections/AIJobs.js.map +1 -0
  102. package/dist/collections/AISettings.d.ts +2 -0
  103. package/dist/collections/AISettings.js +279 -0
  104. package/dist/collections/AISettings.js.map +1 -0
  105. package/dist/collections/Instructions.js +185 -37
  106. package/dist/collections/Instructions.js.map +1 -1
  107. package/dist/defaults.d.ts +3 -0
  108. package/dist/defaults.js +3 -0
  109. package/dist/defaults.js.map +1 -1
  110. package/dist/endpoints/buildPromptUtils.d.ts +19 -0
  111. package/dist/endpoints/buildPromptUtils.js +114 -0
  112. package/dist/endpoints/buildPromptUtils.js.map +1 -0
  113. package/dist/endpoints/chat.d.js +3 -0
  114. package/dist/endpoints/chat.d.js.map +1 -0
  115. package/dist/endpoints/fetchVoices.d.ts +2 -0
  116. package/dist/endpoints/fetchVoices.js +79 -0
  117. package/dist/endpoints/fetchVoices.js.map +1 -0
  118. package/dist/endpoints/index.js +253 -214
  119. package/dist/endpoints/index.js.map +1 -1
  120. package/dist/exports/client.d.ts +9 -0
  121. package/dist/exports/client.js +9 -0
  122. package/dist/exports/client.js.map +1 -1
  123. package/dist/fields/ComposeField/ComposeField.js +2 -2
  124. package/dist/fields/ComposeField/ComposeField.js.map +1 -1
  125. package/dist/fields/ComposeField/ComposeField.jsx +2 -2
  126. package/dist/fields/PromptEditorField/PromptEditorField.js +155 -14
  127. package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
  128. package/dist/fields/PromptEditorField/PromptEditorField.jsx +118 -3
  129. package/dist/index.d.ts +1 -0
  130. package/dist/index.js.map +1 -1
  131. package/dist/init.js +35 -13
  132. package/dist/init.js.map +1 -1
  133. package/dist/payload-ai.d.js +3 -0
  134. package/dist/payload-ai.d.js.map +1 -0
  135. package/dist/plugin.js +80 -9
  136. package/dist/plugin.js.map +1 -1
  137. package/dist/providers/InstructionsProvider/InstructionsProvider.js +35 -7
  138. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  139. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +27 -4
  140. package/dist/providers/InstructionsProvider/context.d.ts +1 -0
  141. package/dist/providers/InstructionsProvider/context.js +1 -0
  142. package/dist/providers/InstructionsProvider/context.js.map +1 -1
  143. package/dist/providers/InstructionsProvider/useInstructions.js +13 -6
  144. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  145. package/dist/types.d.ts +7 -7
  146. package/dist/types.js.map +1 -1
  147. package/dist/ui/AIConfigDashboard/index.d.ts +2 -0
  148. package/dist/ui/AIConfigDashboard/index.js +46 -0
  149. package/dist/ui/AIConfigDashboard/index.js.map +1 -0
  150. package/dist/ui/AIConfigDashboard/index.jsx +24 -0
  151. package/dist/ui/ApiKeyStatusIndicator/index.d.ts +6 -0
  152. package/dist/ui/ApiKeyStatusIndicator/index.js +39 -0
  153. package/dist/ui/ApiKeyStatusIndicator/index.js.map +1 -0
  154. package/dist/ui/ApiKeyStatusIndicator/index.jsx +29 -0
  155. package/dist/ui/Compose/Compose.d.ts +1 -2
  156. package/dist/ui/Compose/Compose.js +116 -90
  157. package/dist/ui/Compose/Compose.js.map +1 -1
  158. package/dist/ui/Compose/Compose.jsx +111 -101
  159. package/dist/ui/Compose/ComposePlaceholder.d.ts +7 -0
  160. package/dist/ui/Compose/ComposePlaceholder.js +78 -0
  161. package/dist/ui/Compose/ComposePlaceholder.js.map +1 -0
  162. package/dist/ui/Compose/ComposePlaceholder.jsx +66 -0
  163. package/dist/ui/Compose/UndoRedoActions.js +3 -1
  164. package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
  165. package/dist/ui/Compose/UndoRedoActions.jsx +2 -1
  166. package/dist/ui/Compose/compose.module.css +1 -1
  167. package/dist/ui/Compose/hooks/menu/itemsMap.js +1 -1
  168. package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
  169. package/dist/ui/Compose/hooks/menu/useMenu.d.ts +2 -1
  170. package/dist/ui/Compose/hooks/menu/useMenu.js +2 -2
  171. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  172. package/dist/ui/Compose/hooks/menu/useMenu.jsx +2 -2
  173. package/dist/ui/Compose/hooks/useActiveFieldTracking.js +69 -10
  174. package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -1
  175. package/dist/ui/Compose/hooks/useGenerate.d.ts +3 -0
  176. package/dist/ui/Compose/hooks/useGenerate.js +71 -11
  177. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  178. package/dist/ui/Compose/hooks/useHistory.js +52 -5
  179. package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
  180. package/dist/ui/DynamicModelSelect/index.d.ts +7 -0
  181. package/dist/ui/DynamicModelSelect/index.js +231 -0
  182. package/dist/ui/DynamicModelSelect/index.js.map +1 -0
  183. package/dist/ui/DynamicModelSelect/index.jsx +207 -0
  184. package/dist/ui/DynamicProviderSelect/index.d.ts +7 -0
  185. package/dist/ui/DynamicProviderSelect/index.js +101 -0
  186. package/dist/ui/DynamicProviderSelect/index.js.map +1 -0
  187. package/dist/ui/DynamicProviderSelect/index.jsx +90 -0
  188. package/dist/ui/DynamicVoiceSelect/index.d.ts +7 -0
  189. package/dist/ui/DynamicVoiceSelect/index.js +104 -0
  190. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -0
  191. package/dist/ui/DynamicVoiceSelect/index.jsx +69 -0
  192. package/dist/ui/EncryptedTextField/index.d.ts +8 -0
  193. package/dist/ui/EncryptedTextField/index.js +74 -0
  194. package/dist/ui/EncryptedTextField/index.js.map +1 -0
  195. package/dist/ui/EncryptedTextField/index.jsx +35 -0
  196. package/dist/ui/Icons/LottieAnimation.js +3 -1
  197. package/dist/ui/Icons/LottieAnimation.js.map +1 -1
  198. package/dist/ui/Icons/LottieAnimation.jsx +2 -1
  199. package/dist/ui/ModelRowLabel/index.d.ts +6 -0
  200. package/dist/ui/ModelRowLabel/index.js +41 -0
  201. package/dist/ui/ModelRowLabel/index.js.map +1 -0
  202. package/dist/ui/ModelRowLabel/index.jsx +26 -0
  203. package/dist/ui/ProviderOptionsEditor/index.d.ts +7 -0
  204. package/dist/ui/ProviderOptionsEditor/index.js +291 -0
  205. package/dist/ui/ProviderOptionsEditor/index.js.map +1 -0
  206. package/dist/ui/ProviderOptionsEditor/index.jsx +210 -0
  207. package/dist/ui/VoicesFetcher/index.d.ts +7 -0
  208. package/dist/ui/VoicesFetcher/index.js +72 -0
  209. package/dist/ui/VoicesFetcher/index.js.map +1 -0
  210. package/dist/ui/VoicesFetcher/index.jsx +56 -0
  211. package/dist/utilities/encryption.d.ts +2 -0
  212. package/dist/utilities/encryption.js +47 -0
  213. package/dist/utilities/encryption.js.map +1 -0
  214. package/dist/utilities/extractImageData.d.ts +9 -0
  215. package/dist/utilities/extractImageData.js +12 -2
  216. package/dist/utilities/extractImageData.js.map +1 -1
  217. package/dist/utilities/fetchImages.d.ts +14 -0
  218. package/dist/utilities/fetchImages.js +38 -0
  219. package/dist/utilities/fetchImages.js.map +1 -0
  220. package/dist/utilities/fieldToJsonSchema.d.ts +2 -1
  221. package/dist/utilities/fieldToJsonSchema.js +66 -3
  222. package/dist/utilities/fieldToJsonSchema.js.map +1 -1
  223. package/dist/utilities/getFieldBySchemaPath.js +15 -0
  224. package/dist/utilities/getFieldBySchemaPath.js.map +1 -1
  225. package/dist/utilities/getProviderOptionsFields.d.ts +16 -0
  226. package/dist/utilities/getProviderOptionsFields.js +80 -0
  227. package/dist/utilities/getProviderOptionsFields.js.map +1 -0
  228. package/dist/utilities/isPluginActivated.js +1 -2
  229. package/dist/utilities/isPluginActivated.js.map +1 -1
  230. package/dist/utilities/lexicalToHTML.js.map +1 -1
  231. package/dist/utilities/resolveImageReferences.d.ts +28 -0
  232. package/dist/utilities/resolveImageReferences.js +148 -0
  233. package/dist/utilities/resolveImageReferences.js.map +1 -0
  234. package/dist/utilities/schemaConverter.d.ts +3 -0
  235. package/dist/utilities/schemaConverter.js +93 -0
  236. package/dist/utilities/schemaConverter.js.map +1 -0
  237. package/dist/utilities/setSafeLexicalState.d.ts +1 -3
  238. package/dist/utilities/setSafeLexicalState.js +1 -1
  239. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  240. package/package.json +19 -21
  241. package/dist/ai/models/anthropic/index.d.ts +0 -2
  242. package/dist/ai/models/anthropic/index.js +0 -129
  243. package/dist/ai/models/anthropic/index.js.map +0 -1
  244. package/dist/ai/models/elevenLabs/generateVoice.d.ts +0 -8
  245. package/dist/ai/models/elevenLabs/generateVoice.js +0 -20
  246. package/dist/ai/models/elevenLabs/generateVoice.js.map +0 -1
  247. package/dist/ai/models/elevenLabs/index.d.ts +0 -2
  248. package/dist/ai/models/elevenLabs/index.js +0 -133
  249. package/dist/ai/models/elevenLabs/index.js.map +0 -1
  250. package/dist/ai/models/elevenLabs/voices.d.ts +0 -8
  251. package/dist/ai/models/elevenLabs/voices.js +0 -24
  252. package/dist/ai/models/elevenLabs/voices.js.map +0 -1
  253. package/dist/ai/models/generateObject.d.ts +0 -11
  254. package/dist/ai/models/generateObject.js +0 -22
  255. package/dist/ai/models/generateObject.js.map +0 -1
  256. package/dist/ai/models/google/generateImage.d.ts +0 -9
  257. package/dist/ai/models/google/generateImage.js +0 -27
  258. package/dist/ai/models/google/generateImage.js.map +0 -1
  259. package/dist/ai/models/google/index.d.ts +0 -2
  260. package/dist/ai/models/google/index.js +0 -201
  261. package/dist/ai/models/google/index.js.map +0 -1
  262. package/dist/ai/models/index.d.ts +0 -2
  263. package/dist/ai/models/index.js +0 -13
  264. package/dist/ai/models/index.js.map +0 -1
  265. package/dist/ai/models/openai/generateImage.d.ts +0 -5
  266. package/dist/ai/models/openai/generateImage.js +0 -31
  267. package/dist/ai/models/openai/generateImage.js.map +0 -1
  268. package/dist/ai/models/openai/generateVoice.d.ts +0 -6
  269. package/dist/ai/models/openai/generateVoice.js +0 -19
  270. package/dist/ai/models/openai/generateVoice.js.map +0 -1
  271. package/dist/ai/models/openai/index.d.ts +0 -2
  272. package/dist/ai/models/openai/index.js +0 -428
  273. package/dist/ai/models/openai/index.js.map +0 -1
  274. package/dist/ai/models/openai/openai.d.ts +0 -1
  275. package/dist/ai/models/openai/openai.js +0 -8
  276. package/dist/ai/models/openai/openai.js.map +0 -1
  277. package/dist/ai/utils/editImagesWithOpenAI.d.ts +0 -10
  278. package/dist/ai/utils/editImagesWithOpenAI.js +0 -37
  279. package/dist/ai/utils/editImagesWithOpenAI.js.map +0 -1
  280. package/dist/types.d.js +0 -3
  281. package/dist/types.d.js.map +0 -1
  282. package/dist/utilities/getGenerationModels.d.ts +0 -2
  283. package/dist/utilities/getGenerationModels.js +0 -10
  284. package/dist/utilities/getGenerationModels.js.map +0 -1
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { Compose } from './Compose.js';
4
+ /**
5
+ * Lightweight placeholder component that lazy-loads Compose when field becomes active.
6
+ * Uses MutationObserver to watch for .ai-plugin-active class added by useActiveFieldTracking.
7
+ */
8
+ export const ComposePlaceholder = (props) => {
9
+ const [shouldMount, setShouldMount] = useState(false);
10
+ const containerRef = useRef(null);
11
+ const unmountTimerRef = useRef(null);
12
+ useEffect(() => {
13
+ const container = containerRef.current;
14
+ if (!container) {
15
+ return;
16
+ }
17
+ // Find the parent .field-type container
18
+ const fieldContainer = container.closest('.field-type');
19
+ if (!fieldContainer) {
20
+ return;
21
+ }
22
+ // Watch for .ai-plugin-active class changes
23
+ const observer = new MutationObserver((mutations) => {
24
+ for (const mutation of mutations) {
25
+ if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
26
+ if (fieldContainer.classList.contains('ai-plugin-active')) {
27
+ // Field is active - mount Compose and cancel any pending unmount
28
+ if (unmountTimerRef.current) {
29
+ clearTimeout(unmountTimerRef.current);
30
+ unmountTimerRef.current = null;
31
+ }
32
+ setShouldMount(true);
33
+ }
34
+ else if (shouldMount) {
35
+ // Field is inactive - schedule unmount after delay
36
+ if (unmountTimerRef.current) {
37
+ clearTimeout(unmountTimerRef.current);
38
+ }
39
+ unmountTimerRef.current = setTimeout(() => {
40
+ setShouldMount(false);
41
+ unmountTimerRef.current = null;
42
+ }, 1200); // 500ms delay to prevent rapid remounting
43
+ }
44
+ break;
45
+ }
46
+ }
47
+ });
48
+ observer.observe(fieldContainer, {
49
+ attributeFilter: ['class'],
50
+ attributes: true,
51
+ });
52
+ // Check initial state in case field is already active
53
+ if (fieldContainer.classList.contains('ai-plugin-active')) {
54
+ setShouldMount(true);
55
+ }
56
+ return () => {
57
+ observer.disconnect();
58
+ if (unmountTimerRef.current) {
59
+ clearTimeout(unmountTimerRef.current);
60
+ }
61
+ };
62
+ }, [shouldMount]);
63
+ return (<div ref={containerRef} style={{ display: 'contents' }}>
64
+ {shouldMount ? <Compose {...props}/> : null}
65
+ </div>);
66
+ };
@@ -26,7 +26,9 @@ export const UndoRedoActions = ({ onChange })=>{
26
26
  useEffect(()=>{
27
27
  setIsMounted(true);
28
28
  }, []);
29
- if (!isMounted || !canUndo && !canRedo) return null;
29
+ if (!isMounted || !canUndo && !canRedo) {
30
+ return null;
31
+ }
30
32
  return /*#__PURE__*/ _jsxs(React.Fragment, {
31
33
  children: [
32
34
  /*#__PURE__*/ _jsx("button", {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/Compose/UndoRedoActions.tsx"],"sourcesContent":["import type { MouseEventHandler} from 'react';\n\nimport React, { useCallback, useEffect, useState } from 'react'\n\nimport { useHistory } from './hooks/useHistory.js'\n\nexport const UndoRedoActions = ({ onChange }: { onChange: (val: unknown) => void }) => {\n const { canRedo, canUndo, redo, undo } = useHistory()\n\n const redoHistoryValue = useCallback<MouseEventHandler>(\n (event) => {\n event.stopPropagation()\n\n const value = redo()\n if (value) {\n onChange(value)\n }\n },\n [redo],\n )\n\n const undoHistoryValue = useCallback<MouseEventHandler>(\n (event) => {\n event.stopPropagation()\n\n const value = undo()\n if (value) {\n onChange(value)\n }\n },\n [undo],\n )\n\n // Delay rendering until the client-side hydration is complete\n const [isMounted, setIsMounted] = useState(false)\n\n useEffect(() => {\n setIsMounted(true)\n }, [])\n\n if (!isMounted || (!canUndo && !canRedo)) return null\n\n return (\n <React.Fragment>\n <button\n className={`btn btn--size-small btn--style-secondary ${!canUndo && 'btn--disabled'}`}\n disabled={!canUndo}\n onClick={undoHistoryValue}\n style={{ marginBlock: 0 }}\n type=\"button\"\n >\n Undo\n </button>\n <button\n className={`btn btn--size-small btn--style-secondary ${!canRedo && 'btn--disabled'}`}\n disabled={!canRedo}\n onClick={redoHistoryValue}\n style={{ marginBlock: 0 }}\n type=\"button\"\n >\n Redo\n </button>\n </React.Fragment>\n )\n}\n"],"names":["React","useCallback","useEffect","useState","useHistory","UndoRedoActions","onChange","canRedo","canUndo","redo","undo","redoHistoryValue","event","stopPropagation","value","undoHistoryValue","isMounted","setIsMounted","Fragment","button","className","disabled","onClick","style","marginBlock","type"],"mappings":";AAEA,OAAOA,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE/D,SAASC,UAAU,QAAQ,wBAAuB;AAElD,OAAO,MAAMC,kBAAkB,CAAC,EAAEC,QAAQ,EAAwC;IAChF,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGN;IAEzC,MAAMO,mBAAmBV,YACvB,CAACW;QACCA,MAAMC,eAAe;QAErB,MAAMC,QAAQL;QACd,IAAIK,OAAO;YACTR,SAASQ;QACX;IACF,GACA;QAACL;KAAK;IAGR,MAAMM,mBAAmBd,YACvB,CAACW;QACCA,MAAMC,eAAe;QAErB,MAAMC,QAAQJ;QACd,IAAII,OAAO;YACTR,SAASQ;QACX;IACF,GACA;QAACJ;KAAK;IAGR,8DAA8D;IAC9D,MAAM,CAACM,WAAWC,aAAa,GAAGd,SAAS;IAE3CD,UAAU;QACRe,aAAa;IACf,GAAG,EAAE;IAEL,IAAI,CAACD,aAAc,CAACR,WAAW,CAACD,SAAU,OAAO;IAEjD,qBACE,MAACP,MAAMkB,QAAQ;;0BACb,KAACC;gBACCC,WAAW,CAAC,yCAAyC,EAAE,CAACZ,WAAW,gBAAgB,CAAC;gBACpFa,UAAU,CAACb;gBACXc,SAASP;gBACTQ,OAAO;oBAAEC,aAAa;gBAAE;gBACxBC,MAAK;0BACN;;0BAGD,KAACN;gBACCC,WAAW,CAAC,yCAAyC,EAAE,CAACb,WAAW,gBAAgB,CAAC;gBACpFc,UAAU,CAACd;gBACXe,SAASX;gBACTY,OAAO;oBAAEC,aAAa;gBAAE;gBACxBC,MAAK;0BACN;;;;AAKP,EAAC"}
1
+ {"version":3,"sources":["../../../src/ui/Compose/UndoRedoActions.tsx"],"sourcesContent":["import type { MouseEventHandler} from 'react';\n\nimport React, { useCallback, useEffect, useState } from 'react'\n\nimport { useHistory } from './hooks/useHistory.js'\n\nexport const UndoRedoActions = ({ onChange }: { onChange: (val: unknown) => void }) => {\n const { canRedo, canUndo, redo, undo } = useHistory()\n\n const redoHistoryValue = useCallback<MouseEventHandler>(\n (event) => {\n event.stopPropagation()\n\n const value = redo()\n if (value) {\n onChange(value)\n }\n },\n [redo],\n )\n\n const undoHistoryValue = useCallback<MouseEventHandler>(\n (event) => {\n event.stopPropagation()\n\n const value = undo()\n if (value) {\n onChange(value)\n }\n },\n [undo],\n )\n\n // Delay rendering until the client-side hydration is complete\n const [isMounted, setIsMounted] = useState(false)\n\n useEffect(() => {\n setIsMounted(true)\n }, [])\n\n if (!isMounted || (!canUndo && !canRedo)) {return null}\n\n return (\n <React.Fragment>\n <button\n className={`btn btn--size-small btn--style-secondary ${!canUndo && 'btn--disabled'}`}\n disabled={!canUndo}\n onClick={undoHistoryValue}\n style={{ marginBlock: 0 }}\n type=\"button\"\n >\n Undo\n </button>\n <button\n className={`btn btn--size-small btn--style-secondary ${!canRedo && 'btn--disabled'}`}\n disabled={!canRedo}\n onClick={redoHistoryValue}\n style={{ marginBlock: 0 }}\n type=\"button\"\n >\n Redo\n </button>\n </React.Fragment>\n )\n}\n"],"names":["React","useCallback","useEffect","useState","useHistory","UndoRedoActions","onChange","canRedo","canUndo","redo","undo","redoHistoryValue","event","stopPropagation","value","undoHistoryValue","isMounted","setIsMounted","Fragment","button","className","disabled","onClick","style","marginBlock","type"],"mappings":";AAEA,OAAOA,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE/D,SAASC,UAAU,QAAQ,wBAAuB;AAElD,OAAO,MAAMC,kBAAkB,CAAC,EAAEC,QAAQ,EAAwC;IAChF,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGN;IAEzC,MAAMO,mBAAmBV,YACvB,CAACW;QACCA,MAAMC,eAAe;QAErB,MAAMC,QAAQL;QACd,IAAIK,OAAO;YACTR,SAASQ;QACX;IACF,GACA;QAACL;KAAK;IAGR,MAAMM,mBAAmBd,YACvB,CAACW;QACCA,MAAMC,eAAe;QAErB,MAAMC,QAAQJ;QACd,IAAII,OAAO;YACTR,SAASQ;QACX;IACF,GACA;QAACJ;KAAK;IAGR,8DAA8D;IAC9D,MAAM,CAACM,WAAWC,aAAa,GAAGd,SAAS;IAE3CD,UAAU;QACRe,aAAa;IACf,GAAG,EAAE;IAEL,IAAI,CAACD,aAAc,CAACR,WAAW,CAACD,SAAU;QAAC,OAAO;IAAI;IAEtD,qBACE,MAACP,MAAMkB,QAAQ;;0BACb,KAACC;gBACCC,WAAW,CAAC,yCAAyC,EAAE,CAACZ,WAAW,gBAAgB,CAAC;gBACpFa,UAAU,CAACb;gBACXc,SAASP;gBACTQ,OAAO;oBAAEC,aAAa;gBAAE;gBACxBC,MAAK;0BACN;;0BAGD,KAACN;gBACCC,WAAW,CAAC,yCAAyC,EAAE,CAACb,WAAW,gBAAgB,CAAC;gBACpFc,UAAU,CAACd;gBACXe,SAASX;gBACTY,OAAO;oBAAEC,aAAa;gBAAE;gBACxBC,MAAK;0BACN;;;;AAKP,EAAC"}
@@ -21,8 +21,9 @@ export const UndoRedoActions = ({ onChange }) => {
21
21
  useEffect(() => {
22
22
  setIsMounted(true);
23
23
  }, []);
24
- if (!isMounted || (!canUndo && !canRedo))
24
+ if (!isMounted || (!canUndo && !canRedo)) {
25
25
  return null;
26
+ }
26
27
  return (<React.Fragment>
27
28
  <button className={`btn btn--size-small btn--style-secondary ${!canUndo && 'btn--disabled'}`} disabled={!canUndo} onClick={undoHistoryValue} style={{ marginBlock: 0 }} type="button">
28
29
  Undo
@@ -8,7 +8,7 @@
8
8
  pointer-events: none;
9
9
  padding: 0 5px;
10
10
  margin: 0;
11
- z-index: 1;
11
+ z-index: 99;
12
12
  transform: scaleY(0) skewX(90deg) translateY(-5px);
13
13
  transform-origin: top;
14
14
  max-height: 0;
@@ -1,5 +1,5 @@
1
- import { MemoizedTranslateMenu } from './TranslateMenu.js';
2
1
  import { Compose, Expand, Proofread, Rephrase, Settings, Simplify, Summarize } from './items.js';
2
+ import { MemoizedTranslateMenu } from './TranslateMenu.js';
3
3
  export const menuItemsMap = [
4
4
  {
5
5
  name: 'Proofread',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/itemsMap.ts"],"sourcesContent":["import type React from 'react'\n\nimport type { ActionMenuItems, BaseItemProps } from '../../../../types.js'\n\nimport { MemoizedTranslateMenu, TranslateMenu } from './TranslateMenu.js'\nimport { Compose, Expand, Proofread, Rephrase, Settings, Simplify, Summarize } from './items.js'\n\ntype MenuItemsMapType = {\n component: React.FC<BaseItemProps>\n excludedFor?: string[]\n loadingText?: string\n name: ActionMenuItems\n}\n\nexport const menuItemsMap: MenuItemsMapType[] = [\n { name: 'Proofread', component: Proofread, excludedFor: ['upload'], loadingText: 'Proofreading' },\n { name: 'Rephrase', component: Rephrase, excludedFor: ['upload'], loadingText: 'Rephrasing' },\n {\n name: 'Translate',\n component: MemoizedTranslateMenu,\n excludedFor: ['upload'],\n loadingText: 'Translating',\n },\n { name: 'Expand', component: Expand, excludedFor: ['upload', 'text'], loadingText: 'Expanding' },\n {\n // Turned off - WIP\n name: 'Summarize',\n component: Summarize,\n excludedFor: ['upload', 'text', 'richText'],\n loadingText: 'Summarizing',\n },\n { name: 'Simplify', component: Simplify, excludedFor: ['upload'], loadingText: 'Simplifying' },\n { name: 'Compose', component: Compose, loadingText: 'Composing' },\n { name: 'Settings', component: Settings },\n]\n"],"names":["MemoizedTranslateMenu","Compose","Expand","Proofread","Rephrase","Settings","Simplify","Summarize","menuItemsMap","name","component","excludedFor","loadingText"],"mappings":"AAIA,SAASA,qBAAqB,QAAuB,qBAAoB;AACzE,SAASC,OAAO,EAAEC,MAAM,EAAEC,SAAS,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,aAAY;AAShG,OAAO,MAAMC,eAAmC;IAC9C;QAAEC,MAAM;QAAaC,WAAWP;QAAWQ,aAAa;YAAC;SAAS;QAAEC,aAAa;IAAe;IAChG;QAAEH,MAAM;QAAYC,WAAWN;QAAUO,aAAa;YAAC;SAAS;QAAEC,aAAa;IAAa;IAC5F;QACEH,MAAM;QACNC,WAAWV;QACXW,aAAa;YAAC;SAAS;QACvBC,aAAa;IACf;IACA;QAAEH,MAAM;QAAUC,WAAWR;QAAQS,aAAa;YAAC;YAAU;SAAO;QAAEC,aAAa;IAAY;IAC/F;QACE,mBAAmB;QACnBH,MAAM;QACNC,WAAWH;QACXI,aAAa;YAAC;YAAU;YAAQ;SAAW;QAC3CC,aAAa;IACf;IACA;QAAEH,MAAM;QAAYC,WAAWJ;QAAUK,aAAa;YAAC;SAAS;QAAEC,aAAa;IAAc;IAC7F;QAAEH,MAAM;QAAWC,WAAWT;QAASW,aAAa;IAAY;IAChE;QAAEH,MAAM;QAAYC,WAAWL;IAAS;CACzC,CAAA"}
1
+ {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/itemsMap.ts"],"sourcesContent":["import type React from 'react'\n\nimport type { ActionMenuItems, BaseItemProps } from '../../../../types.js'\n\nimport { Compose, Expand, Proofread, Rephrase, Settings, Simplify, Summarize } from './items.js'\nimport { MemoizedTranslateMenu, TranslateMenu } from './TranslateMenu.js'\n\ntype MenuItemsMapType = {\n component: React.FC<BaseItemProps>\n excludedFor?: string[]\n loadingText?: string\n name: ActionMenuItems\n}\n\nexport const menuItemsMap: MenuItemsMapType[] = [\n { name: 'Proofread', component: Proofread, excludedFor: ['upload'], loadingText: 'Proofreading' },\n { name: 'Rephrase', component: Rephrase, excludedFor: ['upload'], loadingText: 'Rephrasing' },\n {\n name: 'Translate',\n component: MemoizedTranslateMenu,\n excludedFor: ['upload'],\n loadingText: 'Translating',\n },\n { name: 'Expand', component: Expand, excludedFor: ['upload', 'text'], loadingText: 'Expanding' },\n {\n // Turned off - WIP\n name: 'Summarize',\n component: Summarize,\n excludedFor: ['upload', 'text', 'richText'],\n loadingText: 'Summarizing',\n },\n { name: 'Simplify', component: Simplify, excludedFor: ['upload'], loadingText: 'Simplifying' },\n { name: 'Compose', component: Compose, loadingText: 'Composing' },\n { name: 'Settings', component: Settings },\n]\n"],"names":["Compose","Expand","Proofread","Rephrase","Settings","Simplify","Summarize","MemoizedTranslateMenu","menuItemsMap","name","component","excludedFor","loadingText"],"mappings":"AAIA,SAASA,OAAO,EAAEC,MAAM,EAAEC,SAAS,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,aAAY;AAChG,SAASC,qBAAqB,QAAuB,qBAAoB;AASzE,OAAO,MAAMC,eAAmC;IAC9C;QAAEC,MAAM;QAAaC,WAAWR;QAAWS,aAAa;YAAC;SAAS;QAAEC,aAAa;IAAe;IAChG;QAAEH,MAAM;QAAYC,WAAWP;QAAUQ,aAAa;YAAC;SAAS;QAAEC,aAAa;IAAa;IAC5F;QACEH,MAAM;QACNC,WAAWH;QACXI,aAAa;YAAC;SAAS;QACvBC,aAAa;IACf;IACA;QAAEH,MAAM;QAAUC,WAAWT;QAAQU,aAAa;YAAC;YAAU;SAAO;QAAEC,aAAa;IAAY;IAC/F;QACE,mBAAmB;QACnBH,MAAM;QACNC,WAAWJ;QACXK,aAAa;YAAC;YAAU;YAAQ;SAAW;QAC3CC,aAAa;IACf;IACA;QAAEH,MAAM;QAAYC,WAAWL;QAAUM,aAAa;YAAC;SAAS;QAAEC,aAAa;IAAc;IAC7F;QAAEH,MAAM;QAAWC,WAAWV;QAASY,aAAa;IAAY;IAChE;QAAEH,MAAM;QAAYC,WAAWN;IAAS;CACzC,CAAA"}
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { UseMenuEvents, UseMenuOptions } from '../../../../types.js';
3
3
  export declare const useMenu: (menuEvents: UseMenuEvents, options: UseMenuOptions) => {
4
- ActiveComponent: ({ isLoading, stop }: {
4
+ ActiveComponent: ({ isLoading, loadingLabel, stop }: {
5
5
  isLoading: boolean;
6
+ loadingLabel?: string;
6
7
  stop: () => void;
7
8
  }) => React.JSX.Element;
8
9
  Menu: ({ isLoading, onClose }: {
@@ -45,7 +45,7 @@ export const useMenu = (menuEvents, options)=>{
45
45
  fieldType
46
46
  ]);
47
47
  const MemoizedActiveComponent = useMemo(()=>{
48
- return ({ isLoading, stop })=>{
48
+ return ({ isLoading, loadingLabel, stop })=>{
49
49
  const ActiveComponent = getActiveComponent(activeComponent);
50
50
  const activeItem = menuItemsMap.find((i)=>i.name === activeComponent);
51
51
  return /*#__PURE__*/ _jsx(ActiveComponent, {
@@ -63,7 +63,7 @@ export const useMenu = (menuEvents, options)=>{
63
63
  }
64
64
  },
65
65
  title: isLoading ? 'Click to stop' : activeItem.name,
66
- children: isLoading && activeItem.loadingText
66
+ children: isLoading && (loadingLabel ?? activeItem.loadingText)
67
67
  });
68
68
  };
69
69
  }, [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport type { ActionMenuItems, UseMenuEvents, UseMenuOptions } from '../../../../types.js'\n\nimport { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js'\nimport { Compose, Proofread, Rephrase } from './items.js'\nimport { menuItemsMap } from './itemsMap.js'\nimport styles from './menu.module.scss'\n\nconst getActiveComponent = (ac: ActionMenuItems) => {\n switch (ac) {\n case 'Compose':\n return Compose\n case 'Proofread':\n return Proofread\n case 'Rephrase':\n return Rephrase\n default:\n return Rephrase\n }\n}\n\nexport const useMenu = (menuEvents: UseMenuEvents, options: UseMenuOptions) => {\n const { field:{ type: fieldType } = {}, path: pathFromContext } = useFieldProps()\n const field = useField({ path: pathFromContext ?? '' })\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n const { initialValue, value } = field\n\n useEffect(() => {\n if (!value) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n if (typeof value === 'string' && value !== initialValue) {\n setActiveComponent('Proofread')\n } else {\n setActiveComponent('Rephrase')\n }\n }, [initialValue, value, fieldType])\n\n const MemoizedActiveComponent = useMemo(() => {\n return ({ isLoading, stop }: { isLoading: boolean; stop: () => void }) => {\n const ActiveComponent = getActiveComponent(activeComponent)\n const activeItem = menuItemsMap.find((i) => i.name === activeComponent)!\n return (\n <ActiveComponent\n hideIcon\n onClick={(data: unknown) => {\n if (!isLoading) {\n const trigger = menuEvents[`on${activeComponent}`]\n if (typeof trigger === 'function') {\n trigger(data)\n } else {\n console.error('No trigger found for', activeComponent)\n }\n } else {\n stop()\n }\n }}\n title={isLoading ? 'Click to stop' : activeItem.name}\n >\n {isLoading && activeItem.loadingText}\n </ActiveComponent>\n )\n }\n }, [activeComponent, menuEvents])\n\n const filteredMenuItems = useMemo(\n () =>\n menuItemsMap.filter((i) => {\n if (i.name === 'Settings' && !options.isConfigAllowed) {\n return false\n } // Disable settings if a user role is not permitted\n return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '')\n }),\n [activeComponent, fieldType, options.isConfigAllowed],\n )\n\n const MemoizedMenu = useMemo(() => {\n return ({ isLoading, onClose }: { isLoading: boolean; onClose: () => void }) => (\n <div className={styles.menu}>\n {filteredMenuItems.map((i) => {\n const Action = i.component\n return (\n <Action\n disabled={isLoading}\n key={i.name}\n onClick={(data: unknown) => {\n if (i.name !== 'Settings') {\n setActiveComponent(i.name)\n }\n\n menuEvents[`on${i.name}`]?.(data)\n onClose()\n }}\n >\n {isLoading && i.loadingText}\n </Action>\n )\n })}\n </div>\n )\n }, [filteredMenuItems, menuEvents])\n\n return {\n ActiveComponent: MemoizedActiveComponent,\n Menu: MemoizedMenu,\n }\n}\n"],"names":["useField","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","field","type","fieldType","path","pathFromContext","activeComponent","setActiveComponent","initialValue","value","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","data","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport type { ActionMenuItems, UseMenuEvents, UseMenuOptions } from '../../../../types.js'\n\nimport { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js'\nimport { Compose, Proofread, Rephrase } from './items.js'\nimport { menuItemsMap } from './itemsMap.js'\nimport styles from './menu.module.scss'\n\nconst getActiveComponent = (ac: ActionMenuItems) => {\n switch (ac) {\n case 'Compose':\n return Compose\n case 'Proofread':\n return Proofread\n case 'Rephrase':\n return Rephrase\n default:\n return Rephrase\n }\n}\n\nexport const useMenu = (menuEvents: UseMenuEvents, options: UseMenuOptions) => {\n const { field:{ type: fieldType } = {}, path: pathFromContext } = useFieldProps()\n const field = useField({ path: pathFromContext ?? '' })\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n const { initialValue, value } = field\n\n useEffect(() => {\n if (!value) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n if (typeof value === 'string' && value !== initialValue) {\n setActiveComponent('Proofread')\n } else {\n setActiveComponent('Rephrase')\n }\n }, [initialValue, value, fieldType])\n\n const MemoizedActiveComponent = useMemo(() => {\n return ({ isLoading, loadingLabel, stop }: { isLoading: boolean; loadingLabel?: string; stop: () => void }) => {\n const ActiveComponent = getActiveComponent(activeComponent)\n const activeItem = menuItemsMap.find((i) => i.name === activeComponent)!\n return (\n <ActiveComponent\n hideIcon\n onClick={(data: unknown) => {\n if (!isLoading) {\n const trigger = menuEvents[`on${activeComponent}`]\n if (typeof trigger === 'function') {\n trigger(data)\n } else {\n console.error('No trigger found for', activeComponent)\n }\n } else {\n stop()\n }\n }}\n title={isLoading ? 'Click to stop' : activeItem.name}\n >\n {isLoading && (loadingLabel ?? activeItem.loadingText)}\n </ActiveComponent>\n )\n }\n }, [activeComponent, menuEvents])\n\n const filteredMenuItems = useMemo(\n () =>\n menuItemsMap.filter((i) => {\n if (i.name === 'Settings' && !options.isConfigAllowed) {\n return false\n } // Disable settings if a user role is not permitted\n return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '')\n }),\n [activeComponent, fieldType, options.isConfigAllowed],\n )\n\n const MemoizedMenu = useMemo(() => {\n return ({ isLoading, onClose }: { isLoading: boolean; onClose: () => void }) => (\n <div className={styles.menu}>\n {filteredMenuItems.map((i) => {\n const Action = i.component\n return (\n <Action\n disabled={isLoading}\n key={i.name}\n onClick={(data: unknown) => {\n if (i.name !== 'Settings') {\n setActiveComponent(i.name)\n }\n\n menuEvents[`on${i.name}`]?.(data)\n onClose()\n }}\n >\n {isLoading && i.loadingText}\n </Action>\n )\n })}\n </div>\n )\n }, [filteredMenuItems, menuEvents])\n\n return {\n ActiveComponent: MemoizedActiveComponent,\n Menu: MemoizedMenu,\n }\n}\n"],"names":["useField","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","field","type","fieldType","path","pathFromContext","activeComponent","setActiveComponent","initialValue","value","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","loadingLabel","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","data","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"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"}
@@ -39,7 +39,7 @@ export const useMenu = (menuEvents, options) => {
39
39
  }
40
40
  }, [initialValue, value, fieldType]);
41
41
  const MemoizedActiveComponent = useMemo(() => {
42
- return ({ isLoading, stop }) => {
42
+ return ({ isLoading, loadingLabel, stop }) => {
43
43
  const ActiveComponent = getActiveComponent(activeComponent);
44
44
  const activeItem = menuItemsMap.find((i) => i.name === activeComponent);
45
45
  return (<ActiveComponent hideIcon onClick={(data) => {
@@ -56,7 +56,7 @@ export const useMenu = (menuEvents, options) => {
56
56
  stop();
57
57
  }
58
58
  }} title={isLoading ? 'Click to stop' : activeItem.name}>
59
- {isLoading && activeItem.loadingText}
59
+ {isLoading && (loadingLabel ?? activeItem.loadingText)}
60
60
  </ActiveComponent>);
61
61
  };
62
62
  }, [activeComponent, menuEvents]);
@@ -8,6 +8,12 @@ import { useEffect } from 'react';
8
8
  'textarea',
9
9
  'rich-text-lexical'
10
10
  ];
11
+ // Performance optimization: Cache container and field type lookups
12
+ const containerCache = new WeakMap();
13
+ const fieldTypeCache = new WeakMap();
14
+ // Performance optimization: Throttle/debounce timers
15
+ let pointerDownThrottleTimer = null;
16
+ let focusDebounceTimer = null;
11
17
  let currentContainer = null;
12
18
  let rafId = null // Track RAF to cancel if needed
13
19
  ;
@@ -21,7 +27,16 @@ let rafId = null // Track RAF to cancel if needed
21
27
  };
22
28
  /**
23
29
  * Find container from React Select dropdown elements
30
+ * Performance: Early exit if not in a listbox/option element
24
31
  */ const findContainerFromReactSelect = (target)=>{
32
+ // Early exit if element doesn't have role indicator for React Select
33
+ const role = target.getAttribute('role');
34
+ if (!role || ![
35
+ 'listbox',
36
+ 'option'
37
+ ].includes(role)) {
38
+ return null;
39
+ }
25
40
  const listbox = target.closest('[role="listbox"]');
26
41
  if (!listbox?.id) {
27
42
  return null;
@@ -33,27 +48,45 @@ let rafId = null // Track RAF to cancel if needed
33
48
  };
34
49
  /**
35
50
  * Check if a container has one of the allowed field type classes
51
+ * Performance: Uses WeakMap cache to avoid repeated class list checks
36
52
  */ const isAllowedFieldType = (container)=>{
37
- return ALLOWED_FIELD_TYPES.some((type)=>container.classList.contains(type) || container.classList.contains(`field-type-${type}`));
53
+ // Check cache first
54
+ if (fieldTypeCache.has(container)) {
55
+ return fieldTypeCache.get(container);
56
+ }
57
+ // Compute and cache result
58
+ const result = ALLOWED_FIELD_TYPES.some((type)=>container.classList.contains(type) || container.classList.contains(`field-type-${type}`));
59
+ fieldTypeCache.set(container, result);
60
+ return result;
38
61
  };
39
62
  /**
40
63
  * Resolve the .field-type container for a given event target
41
64
  * Only returns containers that match allowed field types
65
+ * Performance: Uses WeakMap cache to avoid repeated DOM traversals
42
66
  */ const resolveContainerFromTarget = (target)=>{
43
67
  if (!(target instanceof HTMLElement)) {
44
68
  return null;
45
69
  }
46
- // Check for direct parent first
70
+ // Check cache first
71
+ if (containerCache.has(target)) {
72
+ const cached = containerCache.get(target);
73
+ // Validate cache entry is still in DOM
74
+ if (!cached || cached.isConnected) {
75
+ return cached;
76
+ }
77
+ // Invalidate stale cache entry
78
+ containerCache.delete(target);
79
+ }
80
+ // Perform lookup
47
81
  let container = target.closest('.field-type');
48
- // If not found, fall back to React Select logic
82
+ // Fall back to React Select logic if needed
49
83
  if (!container) {
50
84
  container = findContainerFromReactSelect(target);
51
85
  }
52
- // Only return if it's an allowed field type
53
- if (container && isAllowedFieldType(container)) {
54
- return container;
55
- }
56
- return null;
86
+ // Validate field type and cache result
87
+ const result = container && isAllowedFieldType(container) ? container : null;
88
+ containerCache.set(target, result);
89
+ return result;
57
90
  };
58
91
  /**
59
92
  * Update the active container and toggle CSS class
@@ -116,6 +149,7 @@ const isInteractiveElement = (element)=>{
116
149
  };
117
150
  /**
118
151
  * Handle focus events - only activate if focus is on an interactive element within .field-type
152
+ * Performance: Debounced by 10ms to handle rapid focus changes
119
153
  */ const onFocusIn = (e)=>{
120
154
  const target = e.target;
121
155
  if (!(target instanceof HTMLElement)) {
@@ -129,11 +163,20 @@ const isInteractiveElement = (element)=>{
129
163
  if (!isInteractiveElement(target)) {
130
164
  return;
131
165
  }
132
- const container = resolveContainerFromTarget(target);
133
- setActiveContainer(container);
166
+ // Clear any pending debounce
167
+ if (focusDebounceTimer !== null) {
168
+ clearTimeout(focusDebounceTimer);
169
+ }
170
+ // Debounce to reduce work during rapid focus changes (e.g., fast tabbing)
171
+ focusDebounceTimer = window.setTimeout(()=>{
172
+ focusDebounceTimer = null;
173
+ const container = resolveContainerFromTarget(target);
174
+ setActiveContainer(container);
175
+ }, 10);
134
176
  };
135
177
  /**
136
178
  * Handle pointer/mouse events - only switch when clicking a different .field-type
179
+ * Performance: Early exit for non-field clicks + 50ms throttling
137
180
  */ const onPointerDown = (e)=>{
138
181
  const target = e.target;
139
182
  if (!(target instanceof HTMLElement)) {
@@ -143,8 +186,24 @@ const isInteractiveElement = (element)=>{
143
186
  if (currentContainer?.isConnected && currentContainer.contains(target)) {
144
187
  return;
145
188
  }
189
+ // Performance: Quick check before expensive traversal
190
+ // If click is nowhere near a field, just clear active state
191
+ if (!target.closest('.field-type')) {
192
+ if (currentContainer) {
193
+ setActiveContainer(null);
194
+ }
195
+ return;
196
+ }
197
+ // Throttle to prevent excessive work on rapid clicking
198
+ if (pointerDownThrottleTimer !== null) {
199
+ return;
200
+ }
146
201
  const container = resolveContainerFromTarget(target);
147
202
  setActiveContainer(container);
203
+ // Set throttle timer for 50ms
204
+ pointerDownThrottleTimer = window.setTimeout(()=>{
205
+ pointerDownThrottleTimer = null;
206
+ }, 50);
148
207
  };
149
208
  /**
150
209
  * Handle keyboard navigation (Tab key)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/Compose/hooks/useActiveFieldTracking.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\n\n/**\n * Allowed field type classes that should show the active state\n */\nconst ALLOWED_FIELD_TYPES = ['upload', 'text', 'textarea', 'rich-text-lexical']\n\nlet currentContainer: HTMLElement | null = null\nlet rafId: null | number = null // Track RAF to cancel if needed\n\n/**\n * Safely escape CSS selector values\n */\nconst cssEscape = (value: string): string => {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {\n return CSS.escape(value)\n }\n return value.replace(/([ #;?%&,.+*~':\"!^$[\\]()=>|/@])/g, '\\\\$1')\n}\n\n/**\n * Find container from React Select dropdown elements\n */\nconst findContainerFromReactSelect = (target: HTMLElement): HTMLElement | null => {\n const listbox = target.closest<HTMLElement>('[role=\"listbox\"]')\n if (!listbox?.id) {\n return null\n }\n\n const id = cssEscape(listbox.id)\n const selector = `[aria-controls=\"${id}\"], [aria-owns=\"${id}\"]`\n const control = document.querySelector<HTMLElement>(selector)\n\n return control?.closest<HTMLElement>('.field-type') ?? null\n}\n\n/**\n * Check if a container has one of the allowed field type classes\n */\nconst isAllowedFieldType = (container: HTMLElement): boolean => {\n return ALLOWED_FIELD_TYPES.some(\n (type) =>\n container.classList.contains(type) || container.classList.contains(`field-type-${type}`),\n )\n}\n\n/**\n * Resolve the .field-type container for a given event target\n * Only returns containers that match allowed field types\n */\nconst resolveContainerFromTarget = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof HTMLElement)) {\n return null\n }\n\n // Check for direct parent first\n let container = target.closest<HTMLElement>('.field-type')\n\n // If not found, fall back to React Select logic\n if (!container) {\n container = findContainerFromReactSelect(target)\n }\n\n // Only return if it's an allowed field type\n if (container && isAllowedFieldType(container)) {\n return container\n }\n\n return null\n}\n\n/**\n * Update the active container and toggle CSS class\n * - Avoids acting on disconnected nodes\n * - Avoids redundant class work\n */\nconst setActiveContainer = (next: HTMLElement | null): void => {\n // Normalize both references against disconnected nodes\n if (currentContainer && !currentContainer.isConnected) {\n currentContainer = null\n }\n if (next && !next.isConnected) {\n next = null\n }\n\n if (currentContainer === next) {\n return\n }\n\n currentContainer?.classList.remove('ai-plugin-active')\n if (next) {\n next.classList.add('ai-plugin-active')\n }\n currentContainer = next\n}\n\nconst clearActiveContainer = (): void => {\n if (currentContainer) {\n currentContainer.classList.remove('ai-plugin-active')\n currentContainer = null\n }\n\n // Cancel any pending RAF\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n rafId = null\n }\n}\n\nconst isInteractiveElement = (element: HTMLElement): boolean => {\n const tagName = element.tagName.toLowerCase()\n const interactiveTags = ['input', 'textarea', 'select', 'button']\n\n if (interactiveTags.includes(tagName)) {\n return true\n }\n\n // Check for contenteditable\n if (element.isContentEditable) {\n return true\n }\n\n // Check for elements with role=\"textbox\" or role=\"combobox\" (React Select)\n const role = element.getAttribute('role')\n if (role && ['combobox', 'listbox', 'searchbox', 'textbox'].includes(role)) {\n return true\n }\n\n return false\n}\n\n/**\n * Handle focus events - only activate if focus is on an interactive element within .field-type\n */\nconst onFocusIn = (e: FocusEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Early exit if we're already inside the current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n // Only activate if the focused element is actually interactive\n if (!isInteractiveElement(target)) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n}\n\n/**\n * Handle pointer/mouse events - only switch when clicking a different .field-type\n */\nconst onPointerDown = (e: PointerEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Early exit if clicking within current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n}\n\n/**\n * Handle keyboard navigation (Tab key)\n */\nconst onKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Tab') {\n return\n }\n\n // Cancel any pending RAF to prevent queuing\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n }\n\n // Defer until after focus has shifted\n rafId = requestAnimationFrame(() => {\n rafId = null\n const container = resolveContainerFromTarget(document.activeElement)\n setActiveContainer(container)\n })\n}\n\n/**\n * Handle visibility changes to properly cleanup when page is hidden\n */\nconst onVisibilityChange = (): void => {\n if (typeof document !== 'undefined' && document.hidden) {\n // Clear active state and cancel pending operations\n clearActiveContainer()\n }\n}\n\n/**\n * Initialize document-level listeners to track the active field container.\n * When a container is active, it receives the 'ai-plugin-active' class.\n */\nexport const useActiveFieldTracking = (): void => {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return\n }\n\n const pluginWindow = window as {\n __aiComposeTracking?: boolean\n __aiComposeTrackingController?: AbortController\n __aiComposeTrackingCount?: number\n } & Window\n\n // Track number of mounted users of the hook\n pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 0) + 1\n\n // Initialize listeners only once\n if (!pluginWindow.__aiComposeTracking) {\n const controller = new AbortController()\n pluginWindow.__aiComposeTrackingController = controller\n\n // Use capture for early handling\n document.addEventListener('focusin', onFocusIn, {\n capture: true,\n signal: controller.signal,\n })\n document.addEventListener('pointerdown', onPointerDown, {\n capture: true,\n passive: true,\n signal: controller.signal,\n })\n document.addEventListener('keydown', onKeyDown, {\n capture: true,\n signal: controller.signal,\n })\n document.addEventListener('visibilitychange', onVisibilityChange, {\n signal: controller.signal,\n })\n\n pluginWindow.__aiComposeTracking = true\n }\n\n return () => {\n // Decrement and cleanup when the last user unmounts\n pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 1) - 1\n\n if ((pluginWindow.__aiComposeTrackingCount ?? 0) <= 0) {\n // Atomically remove all listeners\n pluginWindow.__aiComposeTrackingController?.abort()\n pluginWindow.__aiComposeTrackingController = undefined\n\n // Clear active state and cancel pending operations\n clearActiveContainer()\n\n // Reset all state\n pluginWindow.__aiComposeTracking = false\n pluginWindow.__aiComposeTrackingCount = 0\n }\n }\n }, [])\n}\n"],"names":["useEffect","ALLOWED_FIELD_TYPES","currentContainer","rafId","cssEscape","value","CSS","escape","replace","findContainerFromReactSelect","target","listbox","closest","id","selector","control","document","querySelector","isAllowedFieldType","container","some","type","classList","contains","resolveContainerFromTarget","HTMLElement","setActiveContainer","next","isConnected","remove","add","clearActiveContainer","cancelAnimationFrame","isInteractiveElement","element","tagName","toLowerCase","interactiveTags","includes","isContentEditable","role","getAttribute","onFocusIn","e","onPointerDown","onKeyDown","key","requestAnimationFrame","activeElement","onVisibilityChange","hidden","useActiveFieldTracking","window","pluginWindow","__aiComposeTrackingCount","__aiComposeTracking","controller","AbortController","__aiComposeTrackingController","addEventListener","capture","signal","passive","abort","undefined"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/ui/Compose/hooks/useActiveFieldTracking.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\n\n/**\n * Allowed field type classes that should show the active state\n */\nconst ALLOWED_FIELD_TYPES = ['upload', 'text', 'textarea', 'rich-text-lexical']\n\n// Performance optimization: Cache container and field type lookups\nconst containerCache = new WeakMap<HTMLElement, HTMLElement | null>()\nconst fieldTypeCache = new WeakMap<HTMLElement, boolean>()\n\n// Performance optimization: Throttle/debounce timers\nlet pointerDownThrottleTimer: null | number = null\nlet focusDebounceTimer: null | number = null\n\nlet currentContainer: HTMLElement | null = null\nlet rafId: null | number = null // Track RAF to cancel if needed\n\n/**\n * Safely escape CSS selector values\n */\nconst cssEscape = (value: string): string => {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {\n return CSS.escape(value)\n }\n return value.replace(/([ #;?%&,.+*~':\"!^$[\\]()=>|/@])/g, '\\\\$1')\n}\n\n/**\n * Find container from React Select dropdown elements\n * Performance: Early exit if not in a listbox/option element\n */\nconst findContainerFromReactSelect = (target: HTMLElement): HTMLElement | null => {\n // Early exit if element doesn't have role indicator for React Select\n const role = target.getAttribute('role')\n if (!role || !['listbox', 'option'].includes(role)) {\n return null\n }\n\n const listbox = target.closest<HTMLElement>('[role=\"listbox\"]')\n if (!listbox?.id) {\n return null\n }\n\n const id = cssEscape(listbox.id)\n const selector = `[aria-controls=\"${id}\"], [aria-owns=\"${id}\"]`\n const control = document.querySelector<HTMLElement>(selector)\n\n return control?.closest<HTMLElement>('.field-type') ?? null\n}\n\n/**\n * Check if a container has one of the allowed field type classes\n * Performance: Uses WeakMap cache to avoid repeated class list checks\n */\nconst isAllowedFieldType = (container: HTMLElement): boolean => {\n // Check cache first\n if (fieldTypeCache.has(container)) {\n return fieldTypeCache.get(container)!\n }\n\n // Compute and cache result\n const result = ALLOWED_FIELD_TYPES.some(\n (type) =>\n container.classList.contains(type) || container.classList.contains(`field-type-${type}`),\n )\n\n fieldTypeCache.set(container, result)\n return result\n}\n\n/**\n * Resolve the .field-type container for a given event target\n * Only returns containers that match allowed field types\n * Performance: Uses WeakMap cache to avoid repeated DOM traversals\n */\nconst resolveContainerFromTarget = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof HTMLElement)) {\n return null\n }\n\n // Check cache first\n if (containerCache.has(target)) {\n const cached = containerCache.get(target)!\n // Validate cache entry is still in DOM\n if (!cached || cached.isConnected) {\n return cached\n }\n // Invalidate stale cache entry\n containerCache.delete(target)\n }\n\n // Perform lookup\n let container = target.closest<HTMLElement>('.field-type')\n\n // Fall back to React Select logic if needed\n if (!container) {\n container = findContainerFromReactSelect(target)\n }\n\n // Validate field type and cache result\n const result = container && isAllowedFieldType(container) ? container : null\n containerCache.set(target, result)\n\n return result\n}\n\n/**\n * Update the active container and toggle CSS class\n * - Avoids acting on disconnected nodes\n * - Avoids redundant class work\n */\nconst setActiveContainer = (next: HTMLElement | null): void => {\n // Normalize both references against disconnected nodes\n if (currentContainer && !currentContainer.isConnected) {\n currentContainer = null\n }\n if (next && !next.isConnected) {\n next = null\n }\n\n if (currentContainer === next) {\n return\n }\n\n currentContainer?.classList.remove('ai-plugin-active')\n if (next) {\n next.classList.add('ai-plugin-active')\n }\n currentContainer = next\n}\n\nconst clearActiveContainer = (): void => {\n if (currentContainer) {\n currentContainer.classList.remove('ai-plugin-active')\n currentContainer = null\n }\n\n // Cancel any pending RAF\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n rafId = null\n }\n}\n\nconst isInteractiveElement = (element: HTMLElement): boolean => {\n const tagName = element.tagName.toLowerCase()\n const interactiveTags = ['input', 'textarea', 'select', 'button']\n\n if (interactiveTags.includes(tagName)) {\n return true\n }\n\n // Check for contenteditable\n if (element.isContentEditable) {\n return true\n }\n\n // Check for elements with role=\"textbox\" or role=\"combobox\" (React Select)\n const role = element.getAttribute('role')\n if (role && ['combobox', 'listbox', 'searchbox', 'textbox'].includes(role)) {\n return true\n }\n\n return false\n}\n\n/**\n * Handle focus events - only activate if focus is on an interactive element within .field-type\n * Performance: Debounced by 10ms to handle rapid focus changes\n */\nconst onFocusIn = (e: FocusEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Early exit if we're already inside the current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n // Only activate if the focused element is actually interactive\n if (!isInteractiveElement(target)) {\n return\n }\n\n // Clear any pending debounce\n if (focusDebounceTimer !== null) {\n clearTimeout(focusDebounceTimer)\n }\n\n // Debounce to reduce work during rapid focus changes (e.g., fast tabbing)\n focusDebounceTimer = window.setTimeout(() => {\n focusDebounceTimer = null\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n }, 10)\n}\n\n/**\n * Handle pointer/mouse events - only switch when clicking a different .field-type\n * Performance: Early exit for non-field clicks + 50ms throttling\n */\nconst onPointerDown = (e: PointerEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Early exit if clicking within current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n // Performance: Quick check before expensive traversal\n // If click is nowhere near a field, just clear active state\n if (!target.closest('.field-type')) {\n if (currentContainer) {\n setActiveContainer(null)\n }\n return\n }\n\n // Throttle to prevent excessive work on rapid clicking\n if (pointerDownThrottleTimer !== null) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n\n // Set throttle timer for 50ms\n pointerDownThrottleTimer = window.setTimeout(() => {\n pointerDownThrottleTimer = null\n }, 50)\n}\n\n/**\n * Handle keyboard navigation (Tab key)\n */\nconst onKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Tab') {\n return\n }\n\n // Cancel any pending RAF to prevent queuing\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n }\n\n // Defer until after focus has shifted\n rafId = requestAnimationFrame(() => {\n rafId = null\n const container = resolveContainerFromTarget(document.activeElement)\n setActiveContainer(container)\n })\n}\n\n/**\n * Handle visibility changes to properly cleanup when page is hidden\n */\nconst onVisibilityChange = (): void => {\n if (typeof document !== 'undefined' && document.hidden) {\n // Clear active state and cancel pending operations\n clearActiveContainer()\n }\n}\n\n/**\n * Initialize document-level listeners to track the active field container.\n * When a container is active, it receives the 'ai-plugin-active' class.\n */\nexport const useActiveFieldTracking = (): void => {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return\n }\n\n const pluginWindow = window as {\n __aiComposeTracking?: boolean\n __aiComposeTrackingController?: AbortController\n __aiComposeTrackingCount?: number\n } & Window\n\n // Track number of mounted users of the hook\n pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 0) + 1\n\n // Initialize listeners only once\n if (!pluginWindow.__aiComposeTracking) {\n const controller = new AbortController()\n pluginWindow.__aiComposeTrackingController = controller\n\n // Use capture for early handling\n document.addEventListener('focusin', onFocusIn, {\n capture: true,\n signal: controller.signal,\n })\n document.addEventListener('pointerdown', onPointerDown, {\n capture: true,\n passive: true,\n signal: controller.signal,\n })\n document.addEventListener('keydown', onKeyDown, {\n capture: true,\n signal: controller.signal,\n })\n document.addEventListener('visibilitychange', onVisibilityChange, {\n signal: controller.signal,\n })\n\n pluginWindow.__aiComposeTracking = true\n }\n\n return () => {\n // Decrement and cleanup when the last user unmounts\n pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 1) - 1\n\n if ((pluginWindow.__aiComposeTrackingCount ?? 0) <= 0) {\n // Atomically remove all listeners\n pluginWindow.__aiComposeTrackingController?.abort()\n pluginWindow.__aiComposeTrackingController = undefined\n\n // Clear active state and cancel pending operations\n clearActiveContainer()\n\n // Reset all state\n pluginWindow.__aiComposeTracking = false\n pluginWindow.__aiComposeTrackingCount = 0\n }\n }\n }, [])\n}\n"],"names":["useEffect","ALLOWED_FIELD_TYPES","containerCache","WeakMap","fieldTypeCache","pointerDownThrottleTimer","focusDebounceTimer","currentContainer","rafId","cssEscape","value","CSS","escape","replace","findContainerFromReactSelect","target","role","getAttribute","includes","listbox","closest","id","selector","control","document","querySelector","isAllowedFieldType","container","has","get","result","some","type","classList","contains","set","resolveContainerFromTarget","HTMLElement","cached","isConnected","delete","setActiveContainer","next","remove","add","clearActiveContainer","cancelAnimationFrame","isInteractiveElement","element","tagName","toLowerCase","interactiveTags","isContentEditable","onFocusIn","e","clearTimeout","window","setTimeout","onPointerDown","onKeyDown","key","requestAnimationFrame","activeElement","onVisibilityChange","hidden","useActiveFieldTracking","pluginWindow","__aiComposeTrackingCount","__aiComposeTracking","controller","AbortController","__aiComposeTrackingController","addEventListener","capture","signal","passive","abort","undefined"],"mappings":"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"}
@@ -7,7 +7,10 @@ export declare const useGenerate: ({ instructionId }: {
7
7
  instructionId: string;
8
8
  }) => {
9
9
  generate: (options?: ActionCallbackParams) => Promise<void | Response>;
10
+ isJobActive: boolean;
10
11
  isLoading: boolean;
12
+ jobProgress: number;
13
+ jobStatus: string | undefined;
11
14
  stop: () => void;
12
15
  };
13
16
  export {};
@@ -2,8 +2,8 @@ import { experimental_useObject as useObject } from '@ai-sdk/react';
2
2
  import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client';
3
3
  import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
4
4
  import { jsonSchema } from 'ai';
5
- import { useCallback, useEffect, useMemo, useRef } from 'react';
6
- import { PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../../../defaults.js';
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../../../defaults.js';
7
7
  import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
8
8
  import { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js';
9
9
  import { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js';
@@ -27,10 +27,15 @@ export const useGenerate = ({ instructionId })=>{
27
27
  path: pathFromContext ?? ''
28
28
  });
29
29
  const { set: setHistory } = useHistory();
30
+ // Async job UI state
31
+ const [jobStatus, setJobStatus] = useState(undefined);
32
+ const [jobProgress, setJobProgress] = useState(0);
33
+ const [isJobActive, setIsJobActive] = useState(false);
30
34
  const { getData } = useForm();
31
35
  const { id: documentId, collectionSlug } = useDocumentInfo();
32
36
  const localFromContext = useLocale();
33
- const { config: { collections } } = useConfig();
37
+ // Reuse config from above instead of calling useConfig again
38
+ const { collections } = config;
34
39
  const collection = collections.find((collection)=>collection.slug === PLUGIN_INSTRUCTIONS_TABLE);
35
40
  const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {};
36
41
  const { schema: editorSchema = {} } = editorConfig;
@@ -110,7 +115,8 @@ export const useGenerate = ({ instructionId })=>{
110
115
  }, [
111
116
  object,
112
117
  editor,
113
- field
118
+ field,
119
+ setValue
114
120
  ]);
115
121
  const streamObject = useCallback(({ action = 'Compose', params })=>{
116
122
  const doc = getData();
@@ -154,19 +160,70 @@ export const useGenerate = ({ instructionId })=>{
154
160
  method: 'POST'
155
161
  }).then(async (uploadResponse)=>{
156
162
  if (uploadResponse.ok) {
157
- const { result } = await uploadResponse.json();
158
- if (!result) {
159
- throw new Error('generateUpload: Something went wrong');
163
+ const json = await uploadResponse.json();
164
+ const { job, result } = json || {};
165
+ if (result) {
166
+ // Set the upload ID
167
+ setValue(result?.id);
168
+ setHistory(result?.id);
169
+ // Show toast to prompt user to save
170
+ toast.success('Image generated successfully! Click Save to see the preview.');
171
+ return uploadResponse;
172
+ }
173
+ // Async job: poll AI Jobs collection for status/progress/result_id
174
+ if (job && job.id) {
175
+ setIsJobActive(true);
176
+ const cancelled = false;
177
+ let attempts = 0;
178
+ const maxAttempts = 600 // up to ~10 minutes @ 1s
179
+ ;
180
+ // Basic in-hook state via closure variables; UI will re-render off fetches below
181
+ const poll = async ()=>{
182
+ if (cancelled) {
183
+ return;
184
+ }
185
+ try {
186
+ const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {
187
+ credentials: 'include'
188
+ });
189
+ if (res.ok) {
190
+ const jobDoc = await res.json();
191
+ const { progress, result_id, status } = jobDoc || {};
192
+ setJobStatus(status);
193
+ setJobProgress(progress ?? 0);
194
+ // When result present, set field and finish
195
+ if (status === 'completed' && result_id) {
196
+ // Force upload field to refetch by clearing then setting the ID
197
+ setValue(null);
198
+ setTimeout(()=>{
199
+ setValue(result_id);
200
+ }, 0);
201
+ setHistory(result_id);
202
+ setIsJobActive(false);
203
+ return;
204
+ }
205
+ if (status === 'failed') {
206
+ setIsJobActive(false);
207
+ throw new Error('Video generation failed');
208
+ }
209
+ }
210
+ } catch (e) {
211
+ // silent retry
212
+ }
213
+ attempts += 1;
214
+ if (!cancelled && attempts < maxAttempts) {
215
+ setTimeout(poll, 1000);
216
+ }
217
+ };
218
+ setTimeout(poll, 1000);
219
+ return uploadResponse;
160
220
  }
161
- setValue(result?.id);
162
- setHistory(result?.id);
163
- console.log('Image updated...', result);
221
+ throw new Error('generateUpload: Unexpected response');
164
222
  } else {
165
223
  const { errors = [] } = await uploadResponse.json();
166
224
  const errStr = errors.map((error)=>error.message).join(', ');
167
225
  throw new Error(errStr);
168
226
  }
169
- return uploadResponse;
170
227
  }).catch((error)=>{
171
228
  toast.error(`Failed to generate: ${error.message}`);
172
229
  console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error);
@@ -200,7 +257,10 @@ export const useGenerate = ({ instructionId })=>{
200
257
  ]);
201
258
  return {
202
259
  generate,
260
+ isJobActive,
203
261
  isLoading: loadingObject,
262
+ jobProgress,
263
+ jobStatus,
204
264
  stop
205
265
  };
206
266
  };