@ai-stack/payloadcms 3.2.26 → 3.68.0-beta.2

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 (313) 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 +100 -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 +54 -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 +223 -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 +449 -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 +312 -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 +623 -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 +308 -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 +600 -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 +247 -0
  86. package/dist/ai/providers/blocks/xai.js.map +1 -0
  87. package/dist/ai/providers/icons.d.ts +7 -0
  88. package/dist/ai/providers/icons.js +9 -0
  89. package/dist/ai/providers/icons.js.map +1 -0
  90. package/dist/ai/providers/index.d.ts +2 -0
  91. package/dist/ai/providers/index.js +6 -0
  92. package/dist/ai/providers/index.js.map +1 -0
  93. package/dist/ai/providers/registry.d.ts +40 -0
  94. package/dist/ai/providers/registry.js +267 -0
  95. package/dist/ai/providers/registry.js.map +1 -0
  96. package/dist/ai/providers/types.d.ts +115 -0
  97. package/dist/ai/providers/types.js +4 -0
  98. package/dist/ai/providers/types.js.map +1 -0
  99. package/dist/ai/utils/systemGenerate.d.ts +1 -1
  100. package/dist/ai/utils/systemGenerate.js +19 -19
  101. package/dist/ai/utils/systemGenerate.js.map +1 -1
  102. package/dist/collections/AIJobs.d.ts +2 -0
  103. package/dist/collections/AIJobs.js +81 -0
  104. package/dist/collections/AIJobs.js.map +1 -0
  105. package/dist/collections/AISettings.d.ts +2 -0
  106. package/dist/collections/AISettings.js +279 -0
  107. package/dist/collections/AISettings.js.map +1 -0
  108. package/dist/collections/Instructions.js +224 -50
  109. package/dist/collections/Instructions.js.map +1 -1
  110. package/dist/defaults.d.ts +3 -0
  111. package/dist/defaults.js +3 -0
  112. package/dist/defaults.js.map +1 -1
  113. package/dist/endpoints/buildPromptUtils.d.ts +19 -0
  114. package/dist/endpoints/buildPromptUtils.js +114 -0
  115. package/dist/endpoints/buildPromptUtils.js.map +1 -0
  116. package/dist/endpoints/chat.d.ts +4 -0
  117. package/dist/endpoints/fetchFields.js +0 -7
  118. package/dist/endpoints/fetchFields.js.map +1 -1
  119. package/dist/endpoints/fetchVoices.d.ts +2 -0
  120. package/dist/endpoints/fetchVoices.js +79 -0
  121. package/dist/endpoints/fetchVoices.js.map +1 -0
  122. package/dist/endpoints/index.js +339 -232
  123. package/dist/endpoints/index.js.map +1 -1
  124. package/dist/exports/client.d.ts +9 -0
  125. package/dist/exports/client.js +9 -0
  126. package/dist/exports/client.js.map +1 -1
  127. package/dist/exports/fields.d.ts +1 -0
  128. package/dist/exports/fields.js +1 -0
  129. package/dist/exports/fields.js.map +1 -1
  130. package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
  131. package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
  132. package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
  133. package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
  134. package/dist/fields/ComposeField/ComposeField.js +2 -2
  135. package/dist/fields/ComposeField/ComposeField.js.map +1 -1
  136. package/dist/fields/ComposeField/ComposeField.jsx +2 -2
  137. package/dist/fields/PromptEditorField/PromptEditorField.js +162 -16
  138. package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
  139. package/dist/fields/PromptEditorField/PromptEditorField.jsx +123 -5
  140. package/dist/index.d.ts +3 -0
  141. package/dist/index.js +1 -0
  142. package/dist/index.js.map +1 -1
  143. package/dist/init.js +63 -65
  144. package/dist/init.js.map +1 -1
  145. package/dist/payload-ai.d.ts +149 -0
  146. package/dist/plugin.js +94 -46
  147. package/dist/plugin.js.map +1 -1
  148. package/dist/providers/InstructionsProvider/InstructionsProvider.js +38 -7
  149. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  150. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +30 -4
  151. package/dist/providers/InstructionsProvider/context.d.ts +1 -0
  152. package/dist/providers/InstructionsProvider/context.js +1 -0
  153. package/dist/providers/InstructionsProvider/context.js.map +1 -1
  154. package/dist/providers/InstructionsProvider/useInstructions.js +30 -10
  155. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  156. package/dist/styles.d.ts +11 -0
  157. package/dist/types/handlebars-async-helpers.d.ts +1 -0
  158. package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
  159. package/dist/types/react-mentions.d.ts +1 -0
  160. package/dist/types.d.ts +6 -16
  161. package/dist/types.js.map +1 -1
  162. package/dist/ui/AIConfigDashboard/index.d.ts +2 -0
  163. package/dist/ui/AIConfigDashboard/index.js +46 -0
  164. package/dist/ui/AIConfigDashboard/index.js.map +1 -0
  165. package/dist/ui/AIConfigDashboard/index.jsx +24 -0
  166. package/dist/ui/ApiKeyStatusIndicator/index.d.ts +6 -0
  167. package/dist/ui/ApiKeyStatusIndicator/index.js +39 -0
  168. package/dist/ui/ApiKeyStatusIndicator/index.js.map +1 -0
  169. package/dist/ui/ApiKeyStatusIndicator/index.jsx +29 -0
  170. package/dist/ui/Compose/Compose.d.ts +2 -2
  171. package/dist/ui/Compose/Compose.js +118 -92
  172. package/dist/ui/Compose/Compose.js.map +1 -1
  173. package/dist/ui/Compose/Compose.jsx +113 -103
  174. package/dist/ui/Compose/ComposePlaceholder.d.ts +7 -0
  175. package/dist/ui/Compose/ComposePlaceholder.js +78 -0
  176. package/dist/ui/Compose/ComposePlaceholder.js.map +1 -0
  177. package/dist/ui/Compose/ComposePlaceholder.jsx +66 -0
  178. package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
  179. package/dist/ui/Compose/UndoRedoActions.js +11 -6
  180. package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
  181. package/dist/ui/Compose/UndoRedoActions.jsx +8 -6
  182. package/dist/ui/Compose/compose.module.css +57 -17
  183. package/dist/ui/Compose/hooks/menu/itemsMap.js +13 -7
  184. package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
  185. package/dist/ui/Compose/hooks/menu/useMenu.d.ts +2 -1
  186. package/dist/ui/Compose/hooks/menu/useMenu.js +28 -17
  187. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  188. package/dist/ui/Compose/hooks/menu/useMenu.jsx +27 -14
  189. package/dist/ui/Compose/hooks/useActiveFieldTracking.js +69 -10
  190. package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -1
  191. package/dist/ui/Compose/hooks/useGenerate.d.ts +3 -0
  192. package/dist/ui/Compose/hooks/useGenerate.js +71 -11
  193. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  194. package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
  195. package/dist/ui/Compose/hooks/useHistory.js +113 -26
  196. package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
  197. package/dist/ui/DynamicModelSelect/index.d.ts +7 -0
  198. package/dist/ui/DynamicModelSelect/index.js +231 -0
  199. package/dist/ui/DynamicModelSelect/index.js.map +1 -0
  200. package/dist/ui/DynamicModelSelect/index.jsx +207 -0
  201. package/dist/ui/DynamicProviderSelect/index.d.ts +7 -0
  202. package/dist/ui/DynamicProviderSelect/index.js +101 -0
  203. package/dist/ui/DynamicProviderSelect/index.js.map +1 -0
  204. package/dist/ui/DynamicProviderSelect/index.jsx +90 -0
  205. package/dist/ui/DynamicVoiceSelect/index.d.ts +7 -0
  206. package/dist/ui/DynamicVoiceSelect/index.js +156 -0
  207. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -0
  208. package/dist/ui/DynamicVoiceSelect/index.jsx +102 -0
  209. package/dist/ui/EncryptedTextField/index.d.ts +8 -0
  210. package/dist/ui/EncryptedTextField/index.js +74 -0
  211. package/dist/ui/EncryptedTextField/index.js.map +1 -0
  212. package/dist/ui/EncryptedTextField/index.jsx +35 -0
  213. package/dist/ui/Icons/LottieAnimation.js +3 -1
  214. package/dist/ui/Icons/LottieAnimation.js.map +1 -1
  215. package/dist/ui/Icons/LottieAnimation.jsx +2 -1
  216. package/dist/ui/ModelRowLabel/index.d.ts +6 -0
  217. package/dist/ui/ModelRowLabel/index.js +41 -0
  218. package/dist/ui/ModelRowLabel/index.js.map +1 -0
  219. package/dist/ui/ModelRowLabel/index.jsx +26 -0
  220. package/dist/ui/ProviderOptionsEditor/index.d.ts +7 -0
  221. package/dist/ui/ProviderOptionsEditor/index.js +291 -0
  222. package/dist/ui/ProviderOptionsEditor/index.js.map +1 -0
  223. package/dist/ui/ProviderOptionsEditor/index.jsx +210 -0
  224. package/dist/ui/VoicesFetcher/index.d.ts +7 -0
  225. package/dist/ui/VoicesFetcher/index.js +118 -0
  226. package/dist/ui/VoicesFetcher/index.js.map +1 -0
  227. package/dist/ui/VoicesFetcher/index.jsx +79 -0
  228. package/dist/utilities/buildSmartPrompt.d.ts +22 -0
  229. package/dist/utilities/buildSmartPrompt.js +143 -0
  230. package/dist/utilities/buildSmartPrompt.js.map +1 -0
  231. package/dist/utilities/encryption.d.ts +2 -0
  232. package/dist/utilities/encryption.js +47 -0
  233. package/dist/utilities/encryption.js.map +1 -0
  234. package/dist/utilities/extractImageData.d.ts +9 -0
  235. package/dist/utilities/extractImageData.js +12 -2
  236. package/dist/utilities/extractImageData.js.map +1 -1
  237. package/dist/utilities/fetchImages.d.ts +14 -0
  238. package/dist/utilities/fetchImages.js +38 -0
  239. package/dist/utilities/fetchImages.js.map +1 -0
  240. package/dist/utilities/fieldToJsonSchema.d.ts +2 -1
  241. package/dist/utilities/fieldToJsonSchema.js +66 -3
  242. package/dist/utilities/fieldToJsonSchema.js.map +1 -1
  243. package/dist/utilities/getFieldBySchemaPath.d.ts +2 -2
  244. package/dist/utilities/getFieldBySchemaPath.js +15 -0
  245. package/dist/utilities/getFieldBySchemaPath.js.map +1 -1
  246. package/dist/utilities/getProviderOptionsFields.d.ts +16 -0
  247. package/dist/utilities/getProviderOptionsFields.js +80 -0
  248. package/dist/utilities/getProviderOptionsFields.js.map +1 -0
  249. package/dist/utilities/isPluginActivated.js +1 -2
  250. package/dist/utilities/isPluginActivated.js.map +1 -1
  251. package/dist/utilities/lexicalToHTML.js.map +1 -1
  252. package/dist/utilities/resolveImageReferences.d.ts +30 -0
  253. package/dist/utilities/resolveImageReferences.js +167 -0
  254. package/dist/utilities/resolveImageReferences.js.map +1 -0
  255. package/dist/utilities/schemaConverter.d.ts +3 -0
  256. package/dist/utilities/schemaConverter.js +93 -0
  257. package/dist/utilities/schemaConverter.js.map +1 -0
  258. package/dist/utilities/setSafeLexicalState.d.ts +1 -3
  259. package/dist/utilities/setSafeLexicalState.js +1 -1
  260. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  261. package/dist/utilities/updateFieldsConfig.js +27 -43
  262. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  263. package/package.json +23 -24
  264. package/dist/ai/models/anthropic/index.d.ts +0 -2
  265. package/dist/ai/models/anthropic/index.js +0 -129
  266. package/dist/ai/models/anthropic/index.js.map +0 -1
  267. package/dist/ai/models/elevenLabs/generateVoice.d.ts +0 -8
  268. package/dist/ai/models/elevenLabs/generateVoice.js +0 -20
  269. package/dist/ai/models/elevenLabs/generateVoice.js.map +0 -1
  270. package/dist/ai/models/elevenLabs/index.d.ts +0 -2
  271. package/dist/ai/models/elevenLabs/index.js +0 -133
  272. package/dist/ai/models/elevenLabs/index.js.map +0 -1
  273. package/dist/ai/models/elevenLabs/voices.d.ts +0 -8
  274. package/dist/ai/models/elevenLabs/voices.js +0 -24
  275. package/dist/ai/models/elevenLabs/voices.js.map +0 -1
  276. package/dist/ai/models/generateObject.d.ts +0 -11
  277. package/dist/ai/models/generateObject.js +0 -22
  278. package/dist/ai/models/generateObject.js.map +0 -1
  279. package/dist/ai/models/google/generateImage.d.ts +0 -9
  280. package/dist/ai/models/google/generateImage.js +0 -27
  281. package/dist/ai/models/google/generateImage.js.map +0 -1
  282. package/dist/ai/models/google/index.d.ts +0 -2
  283. package/dist/ai/models/google/index.js +0 -201
  284. package/dist/ai/models/google/index.js.map +0 -1
  285. package/dist/ai/models/index.d.ts +0 -2
  286. package/dist/ai/models/index.js +0 -13
  287. package/dist/ai/models/index.js.map +0 -1
  288. package/dist/ai/models/openai/generateImage.d.ts +0 -5
  289. package/dist/ai/models/openai/generateImage.js +0 -31
  290. package/dist/ai/models/openai/generateImage.js.map +0 -1
  291. package/dist/ai/models/openai/generateVoice.d.ts +0 -6
  292. package/dist/ai/models/openai/generateVoice.js +0 -19
  293. package/dist/ai/models/openai/generateVoice.js.map +0 -1
  294. package/dist/ai/models/openai/index.d.ts +0 -2
  295. package/dist/ai/models/openai/index.js +0 -428
  296. package/dist/ai/models/openai/index.js.map +0 -1
  297. package/dist/ai/models/openai/openai.d.ts +0 -1
  298. package/dist/ai/models/openai/openai.js +0 -8
  299. package/dist/ai/models/openai/openai.js.map +0 -1
  300. package/dist/ai/utils/editImagesWithOpenAI.d.ts +0 -10
  301. package/dist/ai/utils/editImagesWithOpenAI.js +0 -37
  302. package/dist/ai/utils/editImagesWithOpenAI.js.map +0 -1
  303. package/dist/styles.d.js +0 -2
  304. package/dist/styles.d.js.map +0 -1
  305. package/dist/types/handlebars-async-helpers.d.js +0 -2
  306. package/dist/types/handlebars-async-helpers.d.js.map +0 -1
  307. package/dist/types/handlebars-dist-handlebars.d.js +0 -2
  308. package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
  309. package/dist/types/react-mentions.d.js +0 -2
  310. package/dist/types/react-mentions.d.js.map +0 -1
  311. package/dist/utilities/getGenerationModels.d.ts +0 -2
  312. package/dist/utilities/getGenerationModels.js +0 -10
  313. package/dist/utilities/getGenerationModels.js.map +0 -1
@@ -1,144 +1,19 @@
1
1
  import * as process from 'node:process';
2
- import { defaultPrompts } from '../ai/prompts.js';
2
+ import { checkAccess } from '../access/checkAccess.js';
3
3
  import { filterEditorSchemaByNodes } from '../ai/utils/filterEditorSchemaByNodes.js';
4
- import { PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../defaults.js';
5
- import { asyncHandlebars } from '../libraries/handlebars/asyncHandlebars.js';
4
+ import { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD, PLUGIN_API_ENDPOINT_VIDEOGEN_WEBHOOK, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../defaults.js';
6
5
  import { registerEditorHelper } from '../libraries/handlebars/helpers.js';
7
- import { handlebarsHelpersMap } from '../libraries/handlebars/helpersMap.js';
8
6
  import { replacePlaceholders } from '../libraries/handlebars/replacePlaceholders.js';
7
+ import { buildSmartPrompt, isGenericPrompt } from '../utilities/buildSmartPrompt.js';
9
8
  import { extractImageData } from '../utilities/extractImageData.js';
9
+ import { fetchImages } from '../utilities/fetchImages.js';
10
10
  import { fieldToJsonSchema } from '../utilities/fieldToJsonSchema.js';
11
11
  import { getFieldBySchemaPath } from '../utilities/getFieldBySchemaPath.js';
12
- import { getGenerationModels } from '../utilities/getGenerationModels.js';
13
- const requireAuthentication = (req)=>{
14
- if (!req.user) {
15
- throw new Error('Authentication required. Please log in to use AI features.');
16
- }
17
- return true;
18
- };
19
- const checkAccess = async (req, pluginConfig)=>{
20
- requireAuthentication(req);
21
- if (pluginConfig.access?.generate) {
22
- const hasAccess = await pluginConfig.access.generate({
23
- req
24
- });
25
- if (!hasAccess) {
26
- throw new Error('Insufficient permissions to use AI generation features.');
27
- }
28
- }
29
- return true;
30
- };
31
- const extendContextWithPromptFields = (data, ctx, pluginConfig)=>{
32
- const { promptFields = [] } = pluginConfig;
33
- const fieldsMap = new Map(promptFields.filter((f)=>!f.collections || f.collections.includes(ctx.collection)).map((f)=>[
34
- f.name,
35
- f
36
- ]));
37
- return new Proxy(data, {
38
- get: (target, prop)=>{
39
- const field = fieldsMap.get(prop);
40
- if (field?.getter) {
41
- const value = field.getter(data, ctx);
42
- return Promise.resolve(value).then((v)=>new asyncHandlebars.SafeString(v));
43
- }
44
- // {{prop}} escapes content by default. Here we make sure it won't be escaped.
45
- const value = typeof target === 'object' ? target[prop] : undefined;
46
- return typeof value === 'string' ? new asyncHandlebars.SafeString(value) : value;
47
- },
48
- // It's used by the handlebars library to determine if the property is enumerable
49
- getOwnPropertyDescriptor: (target, prop)=>{
50
- const field = fieldsMap.get(prop);
51
- if (field) {
52
- return {
53
- configurable: true,
54
- enumerable: true
55
- };
56
- }
57
- return Object.getOwnPropertyDescriptor(target, prop);
58
- },
59
- has: (target, prop)=>{
60
- return fieldsMap.has(prop) || target && prop in target;
61
- },
62
- ownKeys: (target)=>{
63
- return [
64
- ...fieldsMap.keys(),
65
- ...Object.keys(target || {})
66
- ];
67
- }
68
- });
69
- };
70
- const buildRichTextSystem = (baseSystem, layout)=>{
71
- return `${baseSystem}
72
-
73
- RULES:
74
- - Generate original and unique content based on the given topic.
75
- - Strictly adhere to the specified layout and formatting instructions.
76
- - Utilize the provided rich text editor tools for appropriate formatting.
77
- - Ensure the output follows the structure of the sample output object.
78
- - Produce valid JSON with no undefined or null values.
79
- ---
80
- LAYOUT INSTRUCTIONS:
81
- ${layout}
82
-
83
- ---
84
- ADDITIONAL GUIDELINES:
85
- - Ensure coherence and logical flow between all sections.
86
- - Maintain a consistent tone and style throughout the content.
87
- - Use clear and concise language appropriate for the target audience.
88
- `;
89
- };
90
- const assignPrompt = async (action, { type, actionParams, collection, context, field, layout, locale, pluginConfig, systemPrompt = '', template })=>{
91
- const extendedContext = extendContextWithPromptFields(context, {
92
- type,
93
- collection
94
- }, pluginConfig);
95
- const prompt = await replacePlaceholders(template, extendedContext);
96
- const toLexicalHTML = type === 'richText' ? handlebarsHelpersMap.toHTML.name : '';
97
- const assignedPrompts = {
98
- layout: type === 'richText' ? layout : undefined,
99
- prompt,
100
- //TODO: Define only once on a collection level
101
- system: type === 'richText' ? buildRichTextSystem(systemPrompt, layout) : undefined
102
- };
103
- if (action === 'Compose') {
104
- if (locale && locale !== 'en') {
105
- /**
106
- * NOTE: Avoid using the "system prompt" for setting the output language,
107
- * as it causes quotation marks to appear in the output (Currently only tested with openai models).
108
- * Appending the language instruction directly to the prompt resolves this issue.
109
- **/ assignedPrompts.prompt += `
110
- ---
111
- OUTPUT LANGUAGE: ${locale}
112
- `;
113
- }
114
- return assignedPrompts;
115
- }
116
- const prompts = [
117
- ...pluginConfig.prompts || [],
118
- ...defaultPrompts
119
- ];
120
- const foundPrompt = prompts.find((p)=>p.name === action);
121
- const getLayout = foundPrompt?.layout;
122
- const getSystemPrompt = foundPrompt?.system;
123
- let updatedLayout = layout;
124
- if (getLayout) {
125
- updatedLayout = getLayout();
126
- }
127
- const system = getSystemPrompt ? getSystemPrompt({
128
- ...actionParams || {},
129
- prompt,
130
- systemPrompt
131
- }) : '';
132
- return {
133
- layout: updatedLayout,
134
- // TODO: revisit this toLexicalHTML
135
- prompt: await replacePlaceholders(`{{${toLexicalHTML} ${field}}}`, extendedContext),
136
- system
137
- };
138
- };
12
+ import { resolveImageReferences } from '../utilities/resolveImageReferences.js';
13
+ import { assignPrompt, extendContextWithPromptFields } from './buildPromptUtils.js';
139
14
  export const endpoints = (pluginConfig)=>({
140
15
  textarea: {
141
- //TODO: This is the main endpoint for generating content - its just needs to be renamed to 'generate' or something.
16
+ // Text/rich-text generation endpoint using payload.ai.streamObject
142
17
  handler: async (req)=>{
143
18
  try {
144
19
  // Check authentication and authorization first
@@ -150,15 +25,10 @@ export const endpoints = (pluginConfig)=>({
150
25
  if (!instructionId) {
151
26
  throw new Error(`Instruction ID is required for "${PLUGIN_NAME}" to work, please check your configuration, or try again`);
152
27
  }
153
- const { defaultLocale, locales = [] } = req.payload.config.localization || {};
154
- const localeData = locales.find((l)=>{
155
- return l.code === locale;
156
- });
157
28
  // Verify user has access to the specific instruction
158
29
  const instructions = await req.payload.findByID({
159
30
  id: instructionId,
160
31
  collection: PLUGIN_INSTRUCTIONS_TABLE,
161
- locale: locales.length > 0 && locale ? locale : undefined,
162
32
  req
163
33
  });
164
34
  const { collections } = req.payload.config;
@@ -168,30 +38,38 @@ export const endpoints = (pluginConfig)=>({
168
38
  }
169
39
  const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection.admin;
170
40
  const { schema: editorSchema = {} } = editorConfig;
171
- const { prompt: promptTemplate = '' } = instructions;
41
+ let { prompt: promptTemplate = '' } = instructions;
42
+ // Smart fallback: if prompt is generic, build a contextual prompt from field metadata
43
+ if (isGenericPrompt(promptTemplate)) {
44
+ const schemaPath = String(instructions['schema-path']);
45
+ promptTemplate = buildSmartPrompt({
46
+ documentData: contextData,
47
+ payload: req.payload,
48
+ schemaPath
49
+ });
50
+ if (pluginConfig.debugging) {
51
+ req.payload.logger.info({
52
+ smartPrompt: promptTemplate
53
+ }, `— AI Plugin: Using smart fallback prompt for ${schemaPath}`);
54
+ }
55
+ }
172
56
  let allowedEditorSchema = editorSchema;
173
57
  if (allowedEditorNodes.length) {
174
58
  allowedEditorSchema = filterEditorSchemaByNodes(editorSchema, allowedEditorNodes);
175
59
  }
176
- const schemaPath = instructions['schema-path'];
177
- const parts = schemaPath?.split('.') || [];
60
+ const schemaPath = String(instructions['schema-path']);
61
+ const parts = (schemaPath || '').split('.') || [];
178
62
  const collectionName = parts[0];
179
63
  const fieldName = parts.length > 1 ? parts[parts.length - 1] : '';
180
64
  registerEditorHelper(req.payload, schemaPath);
65
+ const { defaultLocale, locales = [] } = req.payload.config.localization || {};
66
+ const localeData = locales.find((l)=>{
67
+ return l.code === locale;
68
+ });
181
69
  let localeInfo = locale;
182
70
  if (localeData && defaultLocale && localeData.label && typeof localeData.label === 'object' && defaultLocale in localeData.label) {
183
71
  localeInfo = localeData.label[defaultLocale];
184
72
  }
185
- const models = getGenerationModels(pluginConfig);
186
- const model = models && Array.isArray(models) ? models.find((model)=>model.id === instructions['model-id']) : undefined;
187
- if (!model) {
188
- throw new Error('Model not found');
189
- }
190
- const settingsName = model.settings && 'name' in model.settings ? model.settings.name : undefined;
191
- if (!settingsName) {
192
- req.payload.logger.error('— AI Plugin: Error fetching settings name!');
193
- }
194
- const modelOptions = settingsName ? instructions[settingsName] || {} : {};
195
73
  const prompts = await assignPrompt(action, {
196
74
  type: String(instructions['field-type']),
197
75
  actionParams,
@@ -207,17 +85,16 @@ export const endpoints = (pluginConfig)=>({
207
85
  if (pluginConfig.debugging) {
208
86
  req.payload.logger.info({
209
87
  prompts
210
- }, `— AI Plugin: Executing text prompt on ${schemaPath} using ${model.id}`);
88
+ }, `— AI Plugin: Executing text prompt on ${schemaPath}`);
211
89
  }
212
90
  // Build per-field JSON schema for structured generation when applicable
213
91
  let jsonSchema = allowedEditorSchema;
214
92
  try {
215
93
  const targetCollection = req.payload.config.collections.find((c)=>c.slug === collectionName);
216
- const targetGlobal = req.payload.config.globals?.find((g)=>g.slug === collectionName);
217
- const targetConfig = targetCollection || targetGlobal;
218
- if (targetConfig && fieldName) {
219
- const targetField = getFieldBySchemaPath(targetConfig, schemaPath);
94
+ if (targetCollection && fieldName) {
95
+ const targetField = getFieldBySchemaPath(targetCollection, schemaPath);
220
96
  const supported = [
97
+ 'array',
221
98
  'text',
222
99
  'textarea',
223
100
  'select',
@@ -229,21 +106,64 @@ export const endpoints = (pluginConfig)=>({
229
106
  ];
230
107
  const t = String(targetField?.type || '');
231
108
  if (targetField && supported.includes(t)) {
232
- jsonSchema = fieldToJsonSchema(targetField, {
233
- nameOverride: fieldName
234
- });
109
+ // For array fields, use count from array-settings if available
110
+ if (t === 'array') {
111
+ const arraySettings = instructions['array-settings'] || {};
112
+ const count = arraySettings.count || 3;
113
+ // Override the field's maxRows with the requested count
114
+ const modifiedField = {
115
+ ...targetField,
116
+ maxRows: count,
117
+ minRows: count
118
+ };
119
+ jsonSchema = fieldToJsonSchema(modifiedField, {
120
+ nameOverride: fieldName
121
+ });
122
+ } else {
123
+ jsonSchema = fieldToJsonSchema(targetField, {
124
+ nameOverride: fieldName
125
+ });
126
+ }
235
127
  }
236
128
  }
237
129
  } catch (e) {
238
130
  req.payload.logger.error(e, '— AI Plugin: Error building field JSON schema');
239
131
  }
240
- return model.handler?.(prompts.prompt, {
241
- ...modelOptions,
242
- layout: prompts.layout,
243
- locale: localeInfo,
132
+ // Get model settings from instruction
133
+ const settingsName = instructions['model-id'] === 'richtext' ? 'richtext-settings' : instructions['model-id'] === 'text' ? 'text-settings' : instructions['model-id'] === 'array' ? 'array-settings' : undefined;
134
+ if (!settingsName) {
135
+ throw new Error(`Unsupported model-id: ${instructions['model-id']}`);
136
+ }
137
+ const modelSettings = instructions[settingsName] || {};
138
+ // Resolve @field:filename references from the prompt
139
+ const { images: resolvedImages, processedPrompt } = await resolveImageReferences(prompts.prompt, contextData, req, collectionName);
140
+ // Extract hardcoded URLs from the processed prompt
141
+ const hardcodedImages = extractImageData(processedPrompt);
142
+ // Combine images
143
+ const allImages = [
144
+ ...hardcodedImages,
145
+ ...resolvedImages
146
+ ];
147
+ let images;
148
+ if (allImages.length > 0) {
149
+ const imageParts = await fetchImages(req, allImages);
150
+ if (imageParts.length > 0) {
151
+ images = imageParts;
152
+ }
153
+ }
154
+ // Use payload.ai.streamObject directly! 🎉
155
+ const streamResult = await req.payload.ai.streamObject({
156
+ // extractAttachments: modelSettings.extractAttachments as boolean | undefined,
157
+ images,
158
+ maxTokens: modelSettings.maxTokens,
159
+ model: modelSettings.model,
160
+ prompt: processedPrompt,
161
+ provider: modelSettings.provider,
244
162
  schema: jsonSchema,
245
- system: prompts.system
163
+ system: prompts.system,
164
+ temperature: modelSettings.temperature
246
165
  });
166
+ return streamResult;
247
167
  } catch (error) {
248
168
  req.payload.logger.error(error, 'Error generating content: ');
249
169
  const message = error && typeof error === 'object' && 'message' in error ? error.message : String(error);
@@ -261,6 +181,7 @@ export const endpoints = (pluginConfig)=>({
261
181
  path: PLUGIN_API_ENDPOINT_GENERATE
262
182
  },
263
183
  upload: {
184
+ // Image/video generation endpoint using payload.ai.generateMedia
264
185
  handler: async (req)=>{
265
186
  try {
266
187
  // Check authentication and authorization first
@@ -291,107 +212,293 @@ export const endpoints = (pluginConfig)=>({
291
212
  prompt: ''
292
213
  };
293
214
  if (instructionId) {
294
- // Get locale from request if available
295
- const { locale: requestLocale } = data;
296
- const { locales = [] } = req.payload.config.localization || {};
297
215
  // Verify user has access to the specific instruction
298
- // Pass locale if localization is enabled for the Instructions collection
299
216
  instructions = await req.payload.findByID({
300
217
  id: instructionId,
301
218
  collection: PLUGIN_INSTRUCTIONS_TABLE,
302
- locale: locales.length > 0 && requestLocale ? requestLocale : undefined,
303
219
  req
304
220
  });
305
221
  }
306
- const { images: sampleImages = [], prompt: promptTemplate = '' } = instructions;
307
- const schemaPath = instructions['schema-path'];
222
+ let { prompt: promptTemplate = '' } = instructions;
223
+ const { images: sampleImages = [] } = instructions;
224
+ const schemaPath = String(instructions['schema-path']);
308
225
  registerEditorHelper(req.payload, schemaPath);
226
+ // Smart fallback: if prompt is generic, build a contextual prompt from field metadata
227
+ if (isGenericPrompt(promptTemplate)) {
228
+ promptTemplate = buildSmartPrompt({
229
+ documentData: contextData,
230
+ payload: req.payload,
231
+ schemaPath
232
+ });
233
+ if (pluginConfig.debugging) {
234
+ req.payload.logger.info({
235
+ smartPrompt: promptTemplate
236
+ }, `— AI Plugin: Using smart fallback prompt for ${schemaPath}`);
237
+ }
238
+ }
309
239
  const extendedContext = extendContextWithPromptFields(contextData, {
310
- type: instructions['field-type'],
240
+ type: String(instructions['field-type']),
311
241
  collection: collectionSlug
312
242
  }, pluginConfig);
313
243
  const text = await replacePlaceholders(promptTemplate, extendedContext);
314
244
  const modelId = instructions['model-id'];
315
245
  const uploadCollectionSlug = instructions['relation-to'];
246
+ // Resolve @field:filename references from the prompt
247
+ const { images: resolvedImages, processedPrompt } = await resolveImageReferences(text, contextData, req, collectionSlug);
248
+ // Extract hardcoded URLs from the processed prompt and merge with resolved images and sample images
316
249
  const images = [
317
- ...extractImageData(text),
250
+ ...extractImageData(processedPrompt),
251
+ ...resolvedImages,
318
252
  ...sampleImages
319
253
  ];
320
- const editImages = [];
321
- for (const img of images){
322
- const serverURL = req.payload.config?.serverURL || process.env.SERVER_URL || process.env.NEXT_PUBLIC_SERVER_URL;
323
- let url = img.image.thumbnailURL || img.image.url;
324
- if (!url.startsWith('http')) {
325
- url = `${serverURL}${url}`;
326
- }
254
+ // Process images - convert to ImagePart format using helper
255
+ const editImages = await fetchImages(req, images);
256
+ if (pluginConfig.debugging) {
257
+ req.payload.logger.info({
258
+ text
259
+ }, `— AI Plugin: Executing media generation`);
260
+ }
261
+ // Prepare callback URL for async jobs
262
+ const serverURL = req.payload.config?.serverURL || process.env.SERVER_URL || process.env.NEXT_PUBLIC_SERVER_URL;
263
+ const callbackUrl = serverURL ? `${serverURL.replace(/\/$/, '')}/api${PLUGIN_API_ENDPOINT_VIDEOGEN_WEBHOOK}?instructionId=${instructionId}` : undefined;
264
+ // Get model settings
265
+ const settingsName = modelId === 'image' ? 'image-settings' : modelId === 'video' ? 'video-settings' : modelId === 'tts' ? 'tts-settings' : undefined;
266
+ if (!settingsName) {
267
+ throw new Error(`Unsupported model-id: ${modelId}`);
268
+ }
269
+ // Get model settings from instruction
270
+ const instructionSettings = instructions[settingsName] || {};
271
+ // Fallback to AISettings global defaults if instruction-level settings are missing
272
+ let globalDefaults = {};
273
+ if (!instructionSettings.provider || !instructionSettings.model) {
327
274
  try {
328
- const response = await fetch(url, {
329
- headers: {
330
- //TODO: Further testing needed or so find a proper way.
331
- Authorization: `Bearer ${req.headers.get('Authorization')?.split('Bearer ')[1] || ''}`
332
- },
333
- method: 'GET'
334
- });
335
- const blob = await response.blob();
336
- editImages.push({
337
- name: img.image.name,
338
- type: img.image.type,
339
- data: blob,
340
- size: blob.size,
341
- url
275
+ const aiSettings = await req.payload.findGlobal({
276
+ slug: 'ai-settings',
277
+ context: {
278
+ unsafe: true
279
+ }
342
280
  });
281
+ // Map modelId to the corresponding default settings key
282
+ const defaultsKey = modelId === 'image' ? 'image' : modelId === 'video' ? 'video' : modelId === 'tts' ? 'tts' : undefined;
283
+ if (defaultsKey && aiSettings?.defaults?.[defaultsKey]) {
284
+ globalDefaults = aiSettings.defaults[defaultsKey];
285
+ if (pluginConfig.debugging) {
286
+ req.payload.logger.info({
287
+ globalDefaults
288
+ }, `— AI Plugin: Using AISettings defaults for ${modelId}`);
289
+ }
290
+ }
343
291
  } catch (e) {
344
- req.payload.logger.error(e, `Error fetching reference image ${url}`);
345
- throw Error("We couldn't fetch the images. Please ensure the images are accessible and hosted publicly.");
292
+ req.payload.logger.error(e, '— AI Plugin: Error fetching AISettings defaults');
346
293
  }
347
294
  }
348
- const modelsUpload = getGenerationModels(pluginConfig);
349
- const model = modelsUpload && Array.isArray(modelsUpload) ? modelsUpload.find((model)=>model.id === modelId) : undefined;
350
- if (!model) {
351
- throw new Error('Model not found');
295
+ // Merge: instruction settings take priority over global defaults
296
+ // Filter out null/undefined values so they don't overwrite valid defaults
297
+ const filteredInstructionSettings = Object.fromEntries(Object.entries(instructionSettings).filter(([_, v])=>v != null));
298
+ const modelSettings = {
299
+ ...globalDefaults,
300
+ ...filteredInstructionSettings
301
+ };
302
+ // Use payload.ai.generateMedia directly! 🎉
303
+ const result = await req.payload.ai.generateMedia({
304
+ callbackUrl,
305
+ images: editImages,
306
+ instructionId,
307
+ model: modelSettings.model,
308
+ prompt: text,
309
+ provider: modelSettings.provider,
310
+ ...modelSettings
311
+ });
312
+ // If model returned a file immediately, proceed with upload
313
+ if (result && 'file' in result) {
314
+ let assetData;
315
+ if (typeof pluginConfig.mediaUpload === 'function') {
316
+ assetData = await pluginConfig.mediaUpload(result, {
317
+ collection: uploadCollectionSlug,
318
+ request: req
319
+ });
320
+ } else {
321
+ assetData = await req.payload.create({
322
+ collection: uploadCollectionSlug,
323
+ data: {
324
+ alt: text
325
+ },
326
+ file: result.file,
327
+ req
328
+ });
329
+ }
330
+ if (!assetData.id) {
331
+ req.payload.logger.error('Error uploading generated media, is your media upload function correct?');
332
+ throw new Error('Error uploading generated media!');
333
+ }
334
+ return new Response(JSON.stringify({
335
+ result: {
336
+ id: assetData.id,
337
+ alt: assetData.alt
338
+ }
339
+ }));
352
340
  }
353
- // @ts-ignore
354
- const settingsName = model && model.settings ? model.settings.name : undefined;
355
- if (!settingsName) {
356
- req.payload.logger.error('— AI Plugin: Error fetching settings name!');
341
+ // Otherwise, assume async job launch
342
+ if (result && ('jobId' in result || 'taskId' in result)) {
343
+ const externalTaskId = result.jobId || result.taskId;
344
+ const status = result.status || 'queued';
345
+ const progress = result.progress ?? 0;
346
+ // Create AI Job doc and return only its id
347
+ const createdJob = await req.payload.create({
348
+ collection: PLUGIN_AI_JOBS_TABLE,
349
+ data: {
350
+ instructionId,
351
+ progress,
352
+ status,
353
+ task_id: externalTaskId
354
+ },
355
+ overrideAccess: true,
356
+ req
357
+ });
358
+ return new Response(JSON.stringify({
359
+ job: {
360
+ id: createdJob.id
361
+ }
362
+ }), {
363
+ headers: {
364
+ 'Content-Type': 'application/json'
365
+ }
366
+ });
357
367
  }
358
- let modelOptions = settingsName ? instructions[settingsName] || {} : {};
359
- modelOptions = {
360
- ...modelOptions,
361
- images: editImages
362
- };
363
- if (pluginConfig.debugging) {
364
- req.payload.logger.info({
365
- text
366
- }, `— AI Plugin: Executing image prompt using ${model.id}`);
368
+ throw new Error('Unexpected model response.');
369
+ } catch (error) {
370
+ req.payload.logger.error(// @ts-expect-error
371
+ error?.type || error.message, 'Error generating upload: ');
372
+ const message = error && typeof error === 'object' && 'message' in error ? error.message : String(error);
373
+ return new Response(JSON.stringify({
374
+ error: message
375
+ }), {
376
+ headers: {
377
+ 'Content-Type': 'application/json'
378
+ },
379
+ status: message.includes('Authentication required') || message.includes('Insufficient permissions') ? 401 : 500
380
+ });
381
+ }
382
+ },
383
+ method: 'post',
384
+ path: PLUGIN_API_ENDPOINT_GENERATE_UPLOAD
385
+ },
386
+ videogenWebhook: {
387
+ handler: async (req)=>{
388
+ console.log('videogenWebhook --> ', req);
389
+ try {
390
+ const urlAll = new URL(req.url || '');
391
+ const qpSecret = urlAll.searchParams.get('secret') || '';
392
+ const headerSecret = req.headers.get('x-webhook-secret') || '';
393
+ const falSecret = process.env.FAL_WEBHOOK_SECRET;
394
+ const legacySecret = process.env.VIDEOGEN_WEBHOOK_SECRET;
395
+ const provided = qpSecret || headerSecret;
396
+ // TODO: fal is failing because of auth but webhook seem to work
397
+ if (!provided || (falSecret ? provided !== falSecret : provided !== legacySecret)) {
398
+ return new Response('Unauthorized', {
399
+ status: 401
400
+ });
367
401
  }
368
- const result = await model.handler?.(text, modelOptions);
369
- let assetData;
370
- if (typeof pluginConfig.mediaUpload === 'function') {
371
- assetData = await pluginConfig.mediaUpload(result, {
372
- collection: uploadCollectionSlug,
373
- request: req
402
+ const instructionId = urlAll.searchParams.get('instructionId');
403
+ if (!instructionId) {
404
+ throw new Error('instructionId missing');
405
+ }
406
+ const body = await req.json?.();
407
+ // Normalize fal webhook payload
408
+ const status = body && (body.status || body.data?.status || body.response?.status) || undefined;
409
+ const progress = (body && (body.progress ?? body.data?.progress ?? body.response?.progress)) ?? undefined;
410
+ const requestId = body && (body.taskId || body.request_id || body.gateway_request_id || body.request?.request_id) || undefined;
411
+ const error = body?.error || body?.data?.error || body?.response?.error;
412
+ // Update AI Job row by task_id (and instructionId)
413
+ const jobSearch = await req.payload.find({
414
+ collection: PLUGIN_AI_JOBS_TABLE,
415
+ depth: 0,
416
+ limit: 1,
417
+ where: {
418
+ and: [
419
+ {
420
+ task_id: {
421
+ equals: requestId
422
+ }
423
+ },
424
+ {
425
+ instructionId: {
426
+ equals: instructionId
427
+ }
428
+ }
429
+ ]
430
+ }
431
+ });
432
+ const jobDoc = jobSearch.docs?.[0];
433
+ if (jobDoc) {
434
+ await req.payload.update({
435
+ id: jobDoc.id,
436
+ collection: PLUGIN_AI_JOBS_TABLE,
437
+ data: {
438
+ progress,
439
+ status,
440
+ task_id: requestId
441
+ },
442
+ overrideAccess: true,
443
+ req
374
444
  });
375
- } else {
376
- assetData = await req.payload.create({
445
+ }
446
+ console.log('fal webhook body: ', body);
447
+ const videoUrl = body?.outputs?.[0]?.url || body?.data?.outputs?.[0]?.url || body?.video?.url || body?.data?.video?.url || body?.response?.video?.url || body?.videos?.[0]?.url || body?.data?.videos?.[0]?.url;
448
+ if (status === 'completed' && videoUrl) {
449
+ // Fetch the related instruction to get upload collection
450
+ const instructions = await req.payload.findByID({
451
+ id: instructionId,
452
+ collection: PLUGIN_INSTRUCTIONS_TABLE,
453
+ req
454
+ });
455
+ const uploadCollectionSlug = instructions['relation-to'];
456
+ const videoResp = await fetch(videoUrl);
457
+ if (!videoResp.ok) {
458
+ throw new Error(`Failed to fetch output: ${videoResp.status}`);
459
+ }
460
+ const buffer = Buffer.from(await videoResp.arrayBuffer());
461
+ const created = await req.payload.create({
377
462
  collection: uploadCollectionSlug,
378
- data: result.data,
379
- file: result.file,
463
+ data: {
464
+ alt: 'video generation'
465
+ },
466
+ file: {
467
+ name: 'video_generation.mp4',
468
+ data: buffer,
469
+ mimetype: 'video/mp4',
470
+ size: buffer.byteLength
471
+ },
472
+ overrideAccess: true,
380
473
  req
381
474
  });
475
+ // Persist the result on the AI Job record
476
+ if (jobDoc) {
477
+ await req.payload.update({
478
+ id: jobDoc.id,
479
+ collection: PLUGIN_AI_JOBS_TABLE,
480
+ data: {
481
+ progress: 100,
482
+ result_id: created?.id,
483
+ status: 'completed'
484
+ },
485
+ overrideAccess: true,
486
+ req
487
+ });
488
+ }
382
489
  }
383
- if (!assetData.id) {
384
- req.payload.logger.error('Error uploading generated media, is your media upload function correct?');
385
- throw new Error('Error uploading generated media!');
490
+ if (status === 'failed' && error) {
491
+ req.payload.logger.error(error, 'Video generation failed: ');
386
492
  }
387
493
  return new Response(JSON.stringify({
388
- result: {
389
- id: assetData.id,
390
- alt: assetData.alt
494
+ ok: true
495
+ }), {
496
+ headers: {
497
+ 'Content-Type': 'application/json'
391
498
  }
392
- }));
499
+ });
393
500
  } catch (error) {
394
- req.payload.logger.error(error, 'Error generating upload: ');
501
+ req.payload.logger.error(error, 'Error in videogen webhook: ');
395
502
  const message = error && typeof error === 'object' && 'message' in error ? error.message : String(error);
396
503
  return new Response(JSON.stringify({
397
504
  error: message
@@ -399,12 +506,12 @@ export const endpoints = (pluginConfig)=>({
399
506
  headers: {
400
507
  'Content-Type': 'application/json'
401
508
  },
402
- status: message.includes('Authentication required') || message.includes('Insufficient permissions') ? 401 : 500
509
+ status: 500
403
510
  });
404
511
  }
405
512
  },
406
513
  method: 'post',
407
- path: PLUGIN_API_ENDPOINT_GENERATE_UPLOAD
514
+ path: PLUGIN_API_ENDPOINT_VIDEOGEN_WEBHOOK
408
515
  }
409
516
  });
410
517