@ai-stack/payloadcms 3.68.0 → 3.76.0-beta.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 (154) hide show
  1. package/dist/ai/core/media/image/generateImage.js +2 -6
  2. package/dist/ai/core/media/image/generateImage.js.map +1 -1
  3. package/dist/ai/core/media/image/handlers/multimodal.js +5 -0
  4. package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
  5. package/dist/ai/core/streamObject.js +3 -3
  6. package/dist/ai/core/streamObject.js.map +1 -1
  7. package/dist/ai/core/types.d.ts +3 -0
  8. package/dist/ai/core/types.js.map +1 -1
  9. package/dist/ai/prompts.d.ts +1 -2
  10. package/dist/ai/prompts.js +0 -110
  11. package/dist/ai/prompts.js.map +1 -1
  12. package/dist/ai/providers/blocks/anthropic.js +2 -1
  13. package/dist/ai/providers/blocks/anthropic.js.map +1 -1
  14. package/dist/ai/providers/blocks/elevenlabs.js +3 -2
  15. package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
  16. package/dist/ai/providers/blocks/fal.js +2 -1
  17. package/dist/ai/providers/blocks/fal.js.map +1 -1
  18. package/dist/ai/providers/blocks/google.js +11 -6
  19. package/dist/ai/providers/blocks/google.js.map +1 -1
  20. package/dist/ai/providers/blocks/openai-compatible.js +2 -1
  21. package/dist/ai/providers/blocks/openai-compatible.js.map +1 -1
  22. package/dist/ai/providers/blocks/openai.js +3 -2
  23. package/dist/ai/providers/blocks/openai.js.map +1 -1
  24. package/dist/ai/providers/blocks/xai.js +2 -1
  25. package/dist/ai/providers/blocks/xai.js.map +1 -1
  26. package/dist/ai/providers/icons.d.ts +7 -0
  27. package/dist/ai/providers/icons.js +9 -0
  28. package/dist/ai/providers/icons.js.map +1 -0
  29. package/dist/ai/providers/registry.js +34 -24
  30. package/dist/ai/providers/registry.js.map +1 -1
  31. package/dist/ai/utils/filterEditorSchemaByNodes.d.ts +9 -0
  32. package/dist/ai/utils/filterEditorSchemaByNodes.js +30 -3
  33. package/dist/ai/utils/filterEditorSchemaByNodes.js.map +1 -1
  34. package/dist/ai/utils/nodeToSchemaMap.d.ts +22 -0
  35. package/dist/ai/utils/nodeToSchemaMap.js +72 -0
  36. package/dist/ai/utils/nodeToSchemaMap.js.map +1 -0
  37. package/dist/collections/AIJobs.js +1 -1
  38. package/dist/collections/AIJobs.js.map +1 -1
  39. package/dist/collections/AISettings.js +47 -20
  40. package/dist/collections/AISettings.js.map +1 -1
  41. package/dist/collections/Instructions.js +37 -0
  42. package/dist/collections/Instructions.js.map +1 -1
  43. package/dist/defaults.d.ts +1 -0
  44. package/dist/defaults.js +8 -0
  45. package/dist/defaults.js.map +1 -1
  46. package/dist/endpoints/chat.d.ts +4 -0
  47. package/dist/endpoints/fetchFields.js +10 -0
  48. package/dist/endpoints/fetchFields.js.map +1 -1
  49. package/dist/endpoints/fetchVoices.js +41 -24
  50. package/dist/endpoints/fetchVoices.js.map +1 -1
  51. package/dist/endpoints/index.js +194 -16
  52. package/dist/endpoints/index.js.map +1 -1
  53. package/dist/exports/fields.d.ts +1 -0
  54. package/dist/exports/fields.js +1 -0
  55. package/dist/exports/fields.js.map +1 -1
  56. package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
  57. package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
  58. package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
  59. package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
  60. package/dist/fields/PromptEditorField/PromptEditorField.js +7 -2
  61. package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
  62. package/dist/fields/PromptEditorField/PromptEditorField.jsx +5 -2
  63. package/dist/index.d.ts +3 -1
  64. package/dist/index.js +2 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/payload-ai.d.ts +152 -0
  67. package/dist/plugin.js +16 -32
  68. package/dist/plugin.js.map +1 -1
  69. package/dist/providers/InstructionsProvider/InstructionsProvider.js +47 -15
  70. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  71. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +39 -16
  72. package/dist/providers/InstructionsProvider/context.d.ts +3 -0
  73. package/dist/providers/InstructionsProvider/context.js +2 -0
  74. package/dist/providers/InstructionsProvider/context.js.map +1 -1
  75. package/dist/providers/InstructionsProvider/useInstructions.js +21 -2
  76. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  77. package/dist/styles.d.ts +11 -0
  78. package/dist/types/handlebars-async-helpers.d.ts +1 -0
  79. package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
  80. package/dist/types/react-mentions.d.ts +1 -0
  81. package/dist/types.d.ts +34 -5
  82. package/dist/types.js +1 -0
  83. package/dist/types.js.map +1 -1
  84. package/dist/ui/AIConfigDashboard/index.js +198 -22
  85. package/dist/ui/AIConfigDashboard/index.js.map +1 -1
  86. package/dist/ui/AIConfigDashboard/index.jsx +159 -13
  87. package/dist/ui/Compose/Compose.d.ts +1 -0
  88. package/dist/ui/Compose/Compose.js +23 -4
  89. package/dist/ui/Compose/Compose.js.map +1 -1
  90. package/dist/ui/Compose/Compose.jsx +23 -4
  91. package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
  92. package/dist/ui/Compose/UndoRedoActions.js +8 -5
  93. package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
  94. package/dist/ui/Compose/UndoRedoActions.jsx +6 -5
  95. package/dist/ui/Compose/compose.module.css +56 -16
  96. package/dist/ui/Compose/hooks/menu/itemsMap.js +12 -6
  97. package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
  98. package/dist/ui/Compose/hooks/menu/useMenu.js +26 -15
  99. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  100. package/dist/ui/Compose/hooks/menu/useMenu.jsx +25 -12
  101. package/dist/ui/Compose/hooks/useGenerate.js +26 -174
  102. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  103. package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
  104. package/dist/ui/Compose/hooks/useGenerateUpload.js +150 -0
  105. package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
  106. package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
  107. package/dist/ui/Compose/hooks/useHistory.js +65 -25
  108. package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
  109. package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
  110. package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
  111. package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
  112. package/dist/ui/DynamicVoiceSelect/index.js +63 -11
  113. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
  114. package/dist/ui/DynamicVoiceSelect/index.jsx +47 -14
  115. package/dist/ui/EncryptedTextField/index.js +4 -4
  116. package/dist/ui/EncryptedTextField/index.js.map +1 -1
  117. package/dist/ui/EncryptedTextField/index.jsx +4 -4
  118. package/dist/ui/VoicesFetcher/index.js +34 -16
  119. package/dist/ui/VoicesFetcher/index.js.map +1 -1
  120. package/dist/ui/VoicesFetcher/index.jsx +32 -15
  121. package/dist/utilities/buildSmartPrompt.d.ts +22 -0
  122. package/dist/utilities/buildSmartPrompt.js +141 -0
  123. package/dist/utilities/buildSmartPrompt.js.map +1 -0
  124. package/dist/utilities/encryption.js +2 -1
  125. package/dist/utilities/encryption.js.map +1 -1
  126. package/dist/utilities/fieldToJsonSchema.js +32 -3
  127. package/dist/utilities/fieldToJsonSchema.js.map +1 -1
  128. package/dist/utilities/resolveImageReferences.d.ts +3 -1
  129. package/dist/utilities/resolveImageReferences.js +21 -2
  130. package/dist/utilities/resolveImageReferences.js.map +1 -1
  131. package/dist/utilities/seedProperties.d.ts +7 -0
  132. package/dist/utilities/seedProperties.js +100 -0
  133. package/dist/utilities/seedProperties.js.map +1 -0
  134. package/dist/utilities/setSafeLexicalState.js +79 -6
  135. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  136. package/dist/utilities/updateFieldsConfig.d.ts +1 -1
  137. package/dist/utilities/updateFieldsConfig.js +8 -1
  138. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  139. package/package.json +35 -33
  140. package/dist/endpoints/chat.d.js +0 -3
  141. package/dist/endpoints/chat.d.js.map +0 -1
  142. package/dist/init.d.ts +0 -7
  143. package/dist/init.js +0 -135
  144. package/dist/init.js.map +0 -1
  145. package/dist/payload-ai.d.js +0 -3
  146. package/dist/payload-ai.d.js.map +0 -1
  147. package/dist/styles.d.js +0 -2
  148. package/dist/styles.d.js.map +0 -1
  149. package/dist/types/handlebars-async-helpers.d.js +0 -2
  150. package/dist/types/handlebars-async-helpers.d.js.map +0 -1
  151. package/dist/types/handlebars-dist-handlebars.d.js +0 -2
  152. package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
  153. package/dist/types/react-mentions.d.js +0 -2
  154. package/dist/types/react-mentions.d.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useField } from '@payloadcms/ui';
3
+ import { useForm } from '@payloadcms/ui';
4
+ import { getSiblingData } from 'payload/shared';
4
5
  import React, { useEffect, useMemo, useState } from 'react';
5
6
  import { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js';
6
7
  import { Compose, Proofread, Rephrase } from './items.js';
@@ -19,14 +20,27 @@ const getActiveComponent = (ac)=>{
19
20
  }
20
21
  };
21
22
  export const useMenu = (menuEvents, options)=>{
22
- const { field: { type: fieldType } = {}, path: pathFromContext } = useFieldProps();
23
- const field = useField({
24
- path: pathFromContext ?? ''
25
- });
23
+ const { field: { type: fieldType } = {}, path } = useFieldProps();
24
+ const { getData } = useForm();
26
25
  const [activeComponent, setActiveComponent] = useState('Rephrase');
27
- const { initialValue, value } = field;
26
+ // Check value once on mount or when path/type changes
28
27
  useEffect(()=>{
29
- if (!value) {
28
+ let hasValue = false;
29
+ try {
30
+ const data = getData();
31
+ if (path) {
32
+ const val = getSiblingData(data, path);
33
+ hasValue = val !== undefined && val !== null;
34
+ // For richTextFields, we might need a more robust check (e.g. check for root.children.length > 0)
35
+ // But for now, simple truthiness covers most cases or at least defaults safely
36
+ if (fieldType === 'richText' && val && typeof val === 'object' && 'root' in val) {
37
+ // Basic lexical check could go here if needed
38
+ }
39
+ }
40
+ } catch (e) {
41
+ // ignore
42
+ }
43
+ if (!hasValue) {
30
44
  setActiveComponent('Compose');
31
45
  return;
32
46
  }
@@ -34,15 +48,12 @@ export const useMenu = (menuEvents, options)=>{
34
48
  setActiveComponent('Compose');
35
49
  return;
36
50
  }
37
- if (typeof value === 'string' && value !== initialValue) {
38
- setActiveComponent('Proofread');
39
- } else {
40
- setActiveComponent('Rephrase');
41
- }
51
+ // Default to Rephrase if value exists
52
+ setActiveComponent('Rephrase');
42
53
  }, [
43
- initialValue,
44
- value,
45
- fieldType
54
+ fieldType,
55
+ getData,
56
+ path
46
57
  ]);
47
58
  const MemoizedActiveComponent = useMemo(()=>{
48
59
  return ({ isLoading, loadingLabel, stop })=>{
@@ -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, 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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"}
1
+ {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useForm } from '@payloadcms/ui'\nimport { getSiblingData } from 'payload/shared'\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 } = useFieldProps()\n const { getData } = useForm()\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n // Check value once on mount or when path/type changes\n useEffect(() => {\n let hasValue = false\n\n try {\n const data = getData()\n if (path) {\n const val = getSiblingData(data, path)\n hasValue = val !== undefined && val !== null\n // For richTextFields, we might need a more robust check (e.g. check for root.children.length > 0)\n // But for now, simple truthiness covers most cases or at least defaults safely\n if (fieldType === 'richText' && val && typeof val === 'object' && 'root' in val) {\n // Basic lexical check could go here if needed\n }\n }\n } catch (e) {\n // ignore\n }\n\n if (!hasValue) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n // Default to Rephrase if value exists\n setActiveComponent('Rephrase')\n }, [fieldType, getData, path])\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\n"],"names":["useForm","getSiblingData","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","field","type","fieldType","path","getData","activeComponent","setActiveComponent","hasValue","data","val","undefined","e","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","loadingLabel","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  'use client';
2
- import { useField } from '@payloadcms/ui';
2
+ import { useForm } from '@payloadcms/ui';
3
+ import { getSiblingData } from 'payload/shared';
3
4
  import React, { useEffect, useMemo, useState } from 'react';
4
5
  import { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js';
5
6
  import { Compose, Proofread, Rephrase } from './items.js';
@@ -18,12 +19,28 @@ const getActiveComponent = (ac) => {
18
19
  }
19
20
  };
20
21
  export const useMenu = (menuEvents, options) => {
21
- const { field: { type: fieldType } = {}, path: pathFromContext } = useFieldProps();
22
- const field = useField({ path: pathFromContext ?? '' });
22
+ const { field: { type: fieldType } = {}, path } = useFieldProps();
23
+ const { getData } = useForm();
23
24
  const [activeComponent, setActiveComponent] = useState('Rephrase');
24
- const { initialValue, value } = field;
25
+ // Check value once on mount or when path/type changes
25
26
  useEffect(() => {
26
- if (!value) {
27
+ let hasValue = false;
28
+ try {
29
+ const data = getData();
30
+ if (path) {
31
+ const val = getSiblingData(data, path);
32
+ hasValue = val !== undefined && val !== null;
33
+ // For richTextFields, we might need a more robust check (e.g. check for root.children.length > 0)
34
+ // But for now, simple truthiness covers most cases or at least defaults safely
35
+ if (fieldType === 'richText' && val && typeof val === 'object' && 'root' in val) {
36
+ // Basic lexical check could go here if needed
37
+ }
38
+ }
39
+ }
40
+ catch (e) {
41
+ // ignore
42
+ }
43
+ if (!hasValue) {
27
44
  setActiveComponent('Compose');
28
45
  return;
29
46
  }
@@ -31,13 +48,9 @@ export const useMenu = (menuEvents, options) => {
31
48
  setActiveComponent('Compose');
32
49
  return;
33
50
  }
34
- if (typeof value === 'string' && value !== initialValue) {
35
- setActiveComponent('Proofread');
36
- }
37
- else {
38
- setActiveComponent('Rephrase');
39
- }
40
- }, [initialValue, value, fieldType]);
51
+ // Default to Rephrase if value exists
52
+ setActiveComponent('Rephrase');
53
+ }, [fieldType, getData, path]);
41
54
  const MemoizedActiveComponent = useMemo(() => {
42
55
  return ({ isLoading, loadingLabel, stop }) => {
43
56
  const ActiveComponent = getActiveComponent(activeComponent);
@@ -1,14 +1,14 @@
1
1
  import { experimental_useObject as useObject } from '@ai-sdk/react';
2
2
  import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client';
3
- import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
3
+ import { toast, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
4
4
  import { jsonSchema } from 'ai';
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';
5
+ import { useCallback, useEffect, useRef } from 'react';
6
+ import { PLUGIN_API_ENDPOINT_GENERATE } from '../../../defaults.js';
7
7
  import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
8
- import { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js';
9
- import { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js';
10
8
  import { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js';
9
+ import { useGenerateUpload } from './useGenerateUpload.js';
11
10
  import { useHistory } from './useHistory.js';
11
+ import { useStreamingUpdate } from './useStreamingUpdate.js';
12
12
  export const useGenerate = ({ instructionId })=>{
13
13
  // Create a ref to hold the current instructionId
14
14
  const instructionIdRef = useRef(instructionId);
@@ -20,66 +20,13 @@ export const useGenerate = ({ instructionId })=>{
20
20
  ]);
21
21
  const { field, path: pathFromContext } = useFieldProps();
22
22
  const editorConfigContext = useEditorConfigContext();
23
- const { editor } = editorConfigContext;
24
- const { config } = useConfig();
25
- const { routes: { api }, serverURL } = config;
26
23
  const { setValue } = useField({
27
24
  path: pathFromContext ?? ''
28
25
  });
29
26
  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);
34
27
  const { getData } = useForm();
35
- const { id: documentId, collectionSlug } = useDocumentInfo();
28
+ const { id: documentId } = useDocumentInfo();
36
29
  const localFromContext = useLocale();
37
- // Reuse config from above instead of calling useConfig again
38
- const { collections } = config;
39
- const collection = collections.find((collection)=>collection.slug === PLUGIN_INSTRUCTIONS_TABLE);
40
- const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {};
41
- const { schema: editorSchema = {} } = editorConfig;
42
- const memoizedValidator = useMemo(()=>{
43
- return editorSchemaValidator(editorSchema);
44
- }, [
45
- editorSchema
46
- ]);
47
- const memoizedSchema = useMemo(()=>jsonSchema(editorSchema, {
48
- validate: (value)=>{
49
- const isValid = memoizedValidator(value);
50
- if (isValid) {
51
- return {
52
- success: true,
53
- value
54
- };
55
- } else {
56
- return {
57
- error: new Error('Invalid schema'),
58
- success: false
59
- };
60
- }
61
- }
62
- }), [
63
- memoizedValidator
64
- ]);
65
- // Active JSON schema for useObject based on field type
66
- const activeSchema = useMemo(()=>{
67
- const f = field;
68
- const fieldType = f?.type;
69
- if (fieldType === 'richText') {
70
- return memoizedSchema;
71
- }
72
- if (f && f.name && fieldType) {
73
- const schemaJson = fieldToJsonSchema(f);
74
- if (schemaJson && Object.keys(schemaJson).length > 0) {
75
- return jsonSchema(schemaJson);
76
- }
77
- }
78
- return undefined;
79
- }, [
80
- field,
81
- memoizedSchema
82
- ]);
83
30
  const { isLoading: loadingObject, object, stop: objectStop, submit } = useObject({
84
31
  api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,
85
32
  onError: (error)=>{
@@ -90,8 +37,8 @@ export const useGenerate = ({ instructionId })=>{
90
37
  if (result.object && field) {
91
38
  if (field.type === 'richText') {
92
39
  setHistory(result.object);
93
- setValue(result.object);
94
- } else if ('name' in field) {
40
+ setSafeLexicalState(result.object, editor);
41
+ } else if ('name' in field && result.object[field.name]) {
95
42
  setHistory(result.object[field.name]);
96
43
  setValue(result.object[field.name]);
97
44
  }
@@ -99,25 +46,23 @@ export const useGenerate = ({ instructionId })=>{
99
46
  console.log('onFinish: result, field ', result, field);
100
47
  }
101
48
  },
102
- schema: activeSchema
49
+ schema: jsonSchema({
50
+ type: 'object',
51
+ additionalProperties: true,
52
+ properties: {}
53
+ })
103
54
  });
104
- useEffect(()=>{
105
- if (!object) {
106
- return;
107
- }
108
- requestAnimationFrame(()=>{
109
- if (field?.type === 'richText') {
110
- setSafeLexicalState(object, editor);
111
- } else if (field && 'name' in field && object[field.name]) {
112
- setValue(object[field.name]);
113
- }
114
- });
115
- }, [
116
- object,
55
+ const { editor } = editorConfigContext;
56
+ // Hook: Handle high-frequency streaming updates
57
+ useStreamingUpdate({
117
58
  editor,
118
- field,
119
- setValue
120
- ]);
59
+ isLoading: loadingObject,
60
+ object
61
+ });
62
+ // Hook 2: Handle Upload generation and polling
63
+ const { generateUpload, isJobActive, jobProgress, jobStatus } = useGenerateUpload({
64
+ instructionIdRef
65
+ });
121
66
  const streamObject = useCallback(({ action = 'Compose', params })=>{
122
67
  const doc = getData();
123
68
  const currentInstructionId = instructionIdRef.current;
@@ -138,103 +83,10 @@ export const useGenerate = ({ instructionId })=>{
138
83
  }, [
139
84
  localFromContext?.code,
140
85
  instructionIdRef,
141
- documentId
142
- ]);
143
- const generateUpload = useCallback(async ()=>{
144
- const doc = getData();
145
- const currentInstructionId = instructionIdRef.current;
146
- return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {
147
- body: JSON.stringify({
148
- collectionSlug: collectionSlug ?? '',
149
- doc,
150
- documentId,
151
- locale: localFromContext?.code,
152
- options: {
153
- instructionId: currentInstructionId
154
- }
155
- }),
156
- credentials: 'include',
157
- headers: {
158
- 'Content-Type': 'application/json'
159
- },
160
- method: 'POST'
161
- }).then(async (uploadResponse)=>{
162
- if (uploadResponse.ok) {
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;
220
- }
221
- throw new Error('generateUpload: Unexpected response');
222
- } else {
223
- const { errors = [] } = await uploadResponse.json();
224
- const errStr = errors.map((error)=>error.message).join(', ');
225
- throw new Error(errStr);
226
- }
227
- }).catch((error)=>{
228
- toast.error(`Failed to generate: ${error.message}`);
229
- console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error);
230
- });
231
- }, [
232
- getData,
233
- localFromContext?.code,
234
- instructionIdRef,
235
- setValue,
236
86
  documentId,
237
- collectionSlug
87
+ getData,
88
+ submit,
89
+ editor
238
90
  ]);
239
91
  const generate = useCallback(async (options)=>{
240
92
  if (field?.type === 'upload') {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerate.ts"],"sourcesContent":["import { experimental_useObject as useObject } from '@ai-sdk/react'\nimport { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'\nimport { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { jsonSchema } from 'ai'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { ActionMenuItems, GenerateTextarea } from '../../../types.js'\n\nimport {\n PLUGIN_AI_JOBS_TABLE,\n PLUGIN_API_ENDPOINT_GENERATE,\n PLUGIN_API_ENDPOINT_GENERATE_UPLOAD,\n PLUGIN_INSTRUCTIONS_TABLE,\n PLUGIN_NAME,\n} from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js'\nimport { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\nimport { useHistory } from './useHistory.js'\n\ntype ActionCallbackParams = { action: ActionMenuItems; params?: unknown }\n\nexport const useGenerate = ({ instructionId }: { instructionId: string }) => {\n // Create a ref to hold the current instructionId\n const instructionIdRef = useRef(instructionId)\n\n // Update the ref whenever instructionId changes\n useEffect(() => {\n instructionIdRef.current = instructionId\n }, [instructionId])\n\n const { field, path: pathFromContext } = useFieldProps()\n const editorConfigContext = useEditorConfigContext()\n\n const { editor } = editorConfigContext\n\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n\n const { set: setHistory } = useHistory()\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const { getData } = useForm()\n const { id: documentId, collectionSlug } = useDocumentInfo()\n\n const localFromContext = useLocale()\n \n // Reuse config from above instead of calling useConfig again\n const { collections } = config\n\n const collection = collections.find((collection) => collection.slug === PLUGIN_INSTRUCTIONS_TABLE)\n const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {}\n const { schema: editorSchema = {} } = editorConfig\n\n const memoizedValidator = useMemo(() => {\n return editorSchemaValidator(editorSchema)\n }, [editorSchema])\n\n const memoizedSchema = useMemo(\n () =>\n jsonSchema(editorSchema, {\n validate: (value) => {\n const isValid = memoizedValidator(value)\n\n if (isValid) {\n return {\n success: true,\n value,\n }\n } else {\n return {\n error: new Error('Invalid schema'),\n success: false,\n }\n }\n },\n }),\n [memoizedValidator],\n )\n\n // Active JSON schema for useObject based on field type\n const activeSchema = useMemo(() => {\n const f = field as any\n const fieldType = f?.type as string | undefined\n if (fieldType === 'richText') {\n return memoizedSchema\n }\n if (f && f.name && fieldType) {\n const schemaJson = fieldToJsonSchema(f)\n if (schemaJson && Object.keys(schemaJson).length > 0) {\n return jsonSchema(schemaJson)\n }\n }\n return undefined\n }, [field, memoizedSchema])\n\n const {\n isLoading: loadingObject,\n object,\n stop: objectStop,\n submit,\n } = useObject({\n api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,\n onError: (error: any) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error('Error generating object:', error)\n },\n onFinish: (result) => {\n if (result.object && field) {\n if (field.type === 'richText') {\n setHistory(result.object)\n setValue(result.object)\n } else if ('name' in field) {\n setHistory(result.object[field.name])\n setValue(result.object[field.name])\n }\n } else {\n console.log('onFinish: result, field ', result, field)\n }\n },\n schema: activeSchema as any,\n })\n\n useEffect(() => {\n if (!object) {\n return\n }\n\n requestAnimationFrame(() => {\n if (field?.type === 'richText') {\n setSafeLexicalState(object, editor)\n } else if (field && 'name' in field && object[field.name]) {\n setValue(object[field.name])\n }\n })\n }, [object, editor, field, setValue])\n\n const streamObject = useCallback(\n ({ action = 'Compose', params }: ActionCallbackParams) => {\n const doc = getData()\n\n const currentInstructionId = instructionIdRef.current\n\n const options = {\n action,\n actionParams: params,\n instructionId: currentInstructionId,\n }\n\n submit({\n allowedEditorNodes: Array.from(editor?._nodes?.keys() || []),\n doc: {\n ...doc,\n id: documentId,\n },\n locale: localFromContext?.code,\n options,\n })\n },\n [localFromContext?.code, instructionIdRef, documentId],\n )\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n // Set the upload ID\n setValue(result?.id)\n setHistory(result?.id)\n \n // Show toast to prompt user to save\n toast.success('Image generated successfully! Click Save to see the preview.')\n \n return uploadResponse\n }\n\n // Async job: poll AI Jobs collection for status/progress/result_id\n if (job && job.id) {\n setIsJobActive(true)\n const cancelled = false\n let attempts = 0\n const maxAttempts = 600 // up to ~10 minutes @ 1s\n\n // Basic in-hook state via closure variables; UI will re-render off fetches below\n const poll = async (): Promise<void> => {\n if (cancelled) {\n return\n }\n try {\n const res = await fetch(\n `${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`,\n { credentials: 'include' },\n )\n if (res.ok) {\n const jobDoc = await res.json()\n const { progress, result_id, status } = jobDoc || {}\n setJobStatus(status)\n setJobProgress(progress ?? 0)\n // When result present, set field and finish\n if (status === 'completed' && result_id) {\n // Force upload field to refetch by clearing then setting the ID\n setValue(null)\n setTimeout(() => {\n setValue(result_id)\n }, 0)\n setHistory(result_id)\n setIsJobActive(false)\n return\n }\n if (status === 'failed') {\n setIsJobActive(false)\n throw new Error('Video generation failed')\n }\n }\n } catch (e) {\n // silent retry\n }\n\n attempts += 1\n if (!cancelled && attempts < maxAttempts) {\n setTimeout(poll, 1000)\n }\n }\n setTimeout(poll, 1000)\n return uploadResponse\n }\n\n throw new Error('generateUpload: Unexpected response')\n } else {\n const { errors = [] } = await uploadResponse.json()\n const errStr = errors.map((error: any) => error.message).join(', ')\n throw new Error(errStr)\n }\n })\n .catch((error) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error(\n 'Error generating or setting your upload, please set it manually if its saved in your media files.',\n error,\n )\n })\n }, [getData, localFromContext?.code, instructionIdRef, setValue, documentId, collectionSlug])\n\n const generate = useCallback(\n async (options?: ActionCallbackParams) => {\n if ((field as any)?.type === 'upload') {\n return generateUpload()\n }\n // All supported types use structured object generation when schema is provided server-side\n return streamObject(options ?? { action: 'Compose' })\n },\n [generateUpload, streamObject, field],\n )\n\n const stop = useCallback(() => {\n console.log('Stopping...')\n objectStop()\n }, [objectStop])\n\n return {\n generate,\n isJobActive,\n isLoading: loadingObject,\n jobProgress,\n jobStatus,\n stop,\n }\n}\n"],"names":["experimental_useObject","useObject","useEditorConfigContext","toast","useConfig","useDocumentInfo","useField","useForm","useLocale","jsonSchema","useCallback","useEffect","useMemo","useRef","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","PLUGIN_INSTRUCTIONS_TABLE","PLUGIN_NAME","useFieldProps","editorSchemaValidator","fieldToJsonSchema","setSafeLexicalState","useHistory","useGenerate","instructionId","instructionIdRef","current","field","path","pathFromContext","editorConfigContext","editor","config","routes","api","serverURL","setValue","set","setHistory","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","getData","id","documentId","collectionSlug","localFromContext","collections","collection","find","slug","custom","editorConfig","admin","schema","editorSchema","memoizedValidator","memoizedSchema","validate","value","isValid","success","error","Error","activeSchema","f","fieldType","type","name","schemaJson","Object","keys","length","isLoading","loadingObject","object","stop","objectStop","submit","onError","message","console","onFinish","result","log","requestAnimationFrame","streamObject","action","params","doc","currentInstructionId","options","actionParams","allowedEditorNodes","Array","from","_nodes","locale","code","generateUpload","fetch","body","JSON","stringify","credentials","headers","method","then","uploadResponse","ok","json","job","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","setTimeout","e","errors","errStr","map","join","catch","generate"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerate.ts"],"sourcesContent":["import { experimental_useObject as useObject } from '@ai-sdk/react'\nimport { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'\nimport { toast, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { jsonSchema } from 'ai'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport type { ActionMenuItems } from '../../../types.js'\n\nimport { PLUGIN_API_ENDPOINT_GENERATE } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\nimport { useGenerateUpload } from './useGenerateUpload.js'\nimport { useHistory } from './useHistory.js'\nimport { useStreamingUpdate } from './useStreamingUpdate.js'\n\ntype ActionCallbackParams = { action: ActionMenuItems; params?: unknown }\n\nexport const useGenerate = ({ instructionId }: { instructionId: string }) => {\n // Create a ref to hold the current instructionId\n const instructionIdRef = useRef(instructionId)\n\n // Update the ref whenever instructionId changes\n useEffect(() => {\n instructionIdRef.current = instructionId\n }, [instructionId])\n\n const { field, path: pathFromContext } = useFieldProps()\n const editorConfigContext = useEditorConfigContext()\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n const { set: setHistory } = useHistory()\n const { getData } = useForm()\n const { id: documentId } = useDocumentInfo()\n const localFromContext = useLocale()\n\n const {\n isLoading: loadingObject,\n object,\n stop: objectStop,\n submit,\n } = useObject({\n api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,\n onError: (error: any) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error('Error generating object:', error)\n },\n onFinish: (result) => {\n if (result.object && field) {\n if (field.type === 'richText') {\n setHistory(result.object)\n setSafeLexicalState(result.object, editor)\n } else if ('name' in field && result.object[field.name]) {\n setHistory(result.object[field.name])\n setValue(result.object[field.name])\n }\n } else {\n console.log('onFinish: result, field ', result, field)\n }\n },\n schema: jsonSchema({\n type: 'object',\n additionalProperties: true,\n properties: {},\n }) as any,\n })\n\n const { editor } = editorConfigContext\n\n // Hook: Handle high-frequency streaming updates\n useStreamingUpdate({\n editor,\n isLoading: loadingObject,\n object,\n })\n\n // Hook 2: Handle Upload generation and polling\n const { generateUpload, isJobActive, jobProgress, jobStatus } = useGenerateUpload({\n instructionIdRef,\n })\n\n const streamObject = useCallback(\n ({ action = 'Compose', params }: ActionCallbackParams) => {\n const doc = getData()\n\n const currentInstructionId = instructionIdRef.current\n\n const options = {\n action,\n actionParams: params,\n instructionId: currentInstructionId,\n }\n\n submit({\n allowedEditorNodes: Array.from(editor?._nodes?.keys() || []),\n doc: {\n ...doc,\n id: documentId,\n },\n locale: localFromContext?.code,\n options,\n })\n },\n [localFromContext?.code, instructionIdRef, documentId, getData, submit, editor],\n )\n\n const generate = useCallback(\n async (options?: ActionCallbackParams) => {\n if ((field as any)?.type === 'upload') {\n return generateUpload()\n }\n // All supported types use structured object generation when schema is provided server-side\n return streamObject(options ?? { action: 'Compose' })\n },\n [generateUpload, streamObject, field],\n )\n\n const stop = useCallback(() => {\n console.log('Stopping...')\n objectStop()\n }, [objectStop])\n\n return {\n generate,\n isJobActive,\n isLoading: loadingObject,\n jobProgress,\n jobStatus,\n stop,\n }\n}\n"],"names":["experimental_useObject","useObject","useEditorConfigContext","toast","useDocumentInfo","useField","useForm","useLocale","jsonSchema","useCallback","useEffect","useRef","PLUGIN_API_ENDPOINT_GENERATE","useFieldProps","setSafeLexicalState","useGenerateUpload","useHistory","useStreamingUpdate","useGenerate","instructionId","instructionIdRef","current","field","path","pathFromContext","editorConfigContext","setValue","set","setHistory","getData","id","documentId","localFromContext","isLoading","loadingObject","object","stop","objectStop","submit","api","onError","error","message","console","onFinish","result","type","editor","name","log","schema","additionalProperties","properties","generateUpload","isJobActive","jobProgress","jobStatus","streamObject","action","params","doc","currentInstructionId","options","actionParams","allowedEditorNodes","Array","from","_nodes","keys","locale","code","generate"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type RefObject } from 'react';
2
+ type UseGenerateUploadParams = {
3
+ instructionIdRef: RefObject<string>;
4
+ };
5
+ export declare const useGenerateUpload: ({ instructionIdRef }: UseGenerateUploadParams) => {
6
+ generateUpload: () => Promise<void | Response>;
7
+ isJobActive: boolean;
8
+ jobProgress: number;
9
+ jobStatus: string | undefined;
10
+ };
11
+ export {};
@@ -0,0 +1,150 @@
1
+ import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
2
+ import { useCallback, useState } from 'react';
3
+ import { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js';
4
+ import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
5
+ import { useHistory } from './useHistory.js';
6
+ export const useGenerateUpload = ({ instructionIdRef })=>{
7
+ const { config } = useConfig();
8
+ const { routes: { api }, serverURL } = config;
9
+ const { id: documentId, collectionSlug } = useDocumentInfo();
10
+ const localFromContext = useLocale();
11
+ const { getData } = useForm();
12
+ const { set: setHistory } = useHistory();
13
+ const { field, path: pathFromContext } = useFieldProps();
14
+ const { setValue } = useField({
15
+ path: pathFromContext ?? ''
16
+ });
17
+ // Async job UI state
18
+ const [jobStatus, setJobStatus] = useState(undefined);
19
+ const [jobProgress, setJobProgress] = useState(0);
20
+ const [isJobActive, setIsJobActive] = useState(false);
21
+ const generateUpload = useCallback(async ()=>{
22
+ const doc = getData();
23
+ const currentInstructionId = instructionIdRef.current;
24
+ return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {
25
+ body: JSON.stringify({
26
+ collectionSlug: collectionSlug ?? '',
27
+ doc,
28
+ documentId,
29
+ locale: localFromContext?.code,
30
+ options: {
31
+ instructionId: currentInstructionId
32
+ }
33
+ }),
34
+ credentials: 'include',
35
+ headers: {
36
+ 'Content-Type': 'application/json'
37
+ },
38
+ method: 'POST'
39
+ }).then(async (uploadResponse)=>{
40
+ if (uploadResponse.ok) {
41
+ const json = await uploadResponse.json();
42
+ const { job, result } = json || {};
43
+ if (result) {
44
+ setValue(result?.id);
45
+ setHistory(result?.id);
46
+ // Show toast to prompt user to save
47
+ toast.success('Image generated successfully! Click Save to see the preview.');
48
+ return uploadResponse;
49
+ }
50
+ // Async job: poll AI Jobs collection for status/progress/result_id
51
+ if (job && job.id) {
52
+ setIsJobActive(true);
53
+ const cancelled = false;
54
+ let attempts = 0;
55
+ const maxAttempts = 600 // up to ~10 minutes @ 1s
56
+ ;
57
+ // Basic in-hook state via closure variables; UI will re-render off fetches below
58
+ const poll = async ()=>{
59
+ if (cancelled) {
60
+ return;
61
+ }
62
+ try {
63
+ const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {
64
+ credentials: 'include'
65
+ });
66
+ if (res.ok) {
67
+ const jobDoc = await res.json();
68
+ const { progress, result_id, status } = jobDoc || {};
69
+ setJobStatus(status);
70
+ setJobProgress(progress ?? 0);
71
+ // When result present, set field and finish
72
+ if (status === 'completed' && result_id) {
73
+ let valueToSet = result_id;
74
+ // Attempt to fetch full document for immediate preview
75
+ if (field && 'relationTo' in field && typeof field.relationTo === 'string') {
76
+ let attempts = 0;
77
+ const maxAttempts = 3;
78
+ while(attempts < maxAttempts){
79
+ try {
80
+ const docRes = await fetch(`${serverURL}${api}/${field.relationTo}/${result_id}`, {
81
+ credentials: 'include'
82
+ });
83
+ if (docRes.ok) {
84
+ const doc = await docRes.json();
85
+ // Verify we have a URL for preview
86
+ if (doc && doc.url) {
87
+ valueToSet = doc;
88
+ break;
89
+ }
90
+ }
91
+ } catch (e) {
92
+ console.error('Failed to fetch generated document for preview:', e);
93
+ }
94
+ attempts++;
95
+ if (attempts < maxAttempts) {
96
+ await new Promise((resolve)=>setTimeout(resolve, 500));
97
+ }
98
+ }
99
+ }
100
+ setValue(valueToSet);
101
+ setHistory(result_id);
102
+ setIsJobActive(false);
103
+ return;
104
+ }
105
+ if (status === 'failed') {
106
+ setIsJobActive(false);
107
+ throw new Error('Video generation failed');
108
+ }
109
+ }
110
+ } catch (_) {
111
+ // silent retry
112
+ }
113
+ attempts += 1;
114
+ if (!cancelled && attempts < maxAttempts) {
115
+ setTimeout(poll, 1000);
116
+ }
117
+ };
118
+ setTimeout(poll, 1000);
119
+ return uploadResponse;
120
+ }
121
+ throw new Error('generateUpload: Unexpected response');
122
+ } else {
123
+ const { errors = [] } = await uploadResponse.json();
124
+ const errStr = errors.map((error)=>error.message).join(', ');
125
+ throw new Error(errStr);
126
+ }
127
+ }).catch((error)=>{
128
+ toast.error(`Failed to generate: ${error.message}`);
129
+ console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error);
130
+ });
131
+ }, [
132
+ getData,
133
+ localFromContext?.code,
134
+ instructionIdRef,
135
+ // setValue,
136
+ documentId,
137
+ collectionSlug,
138
+ serverURL,
139
+ api,
140
+ setHistory
141
+ ]);
142
+ return {
143
+ generateUpload,
144
+ isJobActive,
145
+ jobProgress,
146
+ jobStatus
147
+ };
148
+ };
149
+
150
+ //# sourceMappingURL=useGenerateUpload.js.map