@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
@@ -35,34 +35,51 @@ export const fetchVoices = {
35
35
  status: 400
36
36
  });
37
37
  }
38
- // Call ElevenLabs API to fetch voices
39
- const response = await fetch('https://api.elevenlabs.io/v1/voices', {
40
- headers: {
41
- 'xi-api-key': apiKey
38
+ // Call ElevenLabs API to fetch voices with timeout
39
+ const controller = new AbortController();
40
+ const timeoutId = setTimeout(()=>controller.abort(), 15000) // 15s timeout
41
+ ;
42
+ try {
43
+ const response = await fetch('https://api.elevenlabs.io/v1/voices', {
44
+ headers: {
45
+ 'xi-api-key': apiKey
46
+ },
47
+ signal: controller.signal
48
+ });
49
+ clearTimeout(timeoutId);
50
+ if (!response.ok) {
51
+ const errorText = await response.text();
52
+ return Response.json({
53
+ message: `ElevenLabs API error: ${errorText}`
54
+ }, {
55
+ status: response.status
56
+ });
42
57
  }
43
- });
44
- if (!response.ok) {
45
- const errorText = await response.text();
58
+ const data = await response.json();
59
+ // Transform voices to match our schema
60
+ const voices = (data.voices || []).map((voice)=>({
61
+ id: voice.voice_id,
62
+ name: voice.name,
63
+ category: voice.category || 'premade',
64
+ enabled: true,
65
+ labels: voice.labels || {},
66
+ preview_url: voice.preview_url || ''
67
+ }));
46
68
  return Response.json({
47
- message: `ElevenLabs API error: ${errorText}`
48
- }, {
49
- status: response.status
69
+ success: true,
70
+ voices
50
71
  });
72
+ } catch (error) {
73
+ clearTimeout(timeoutId);
74
+ if (error instanceof Error && error.name === 'AbortError') {
75
+ return Response.json({
76
+ message: 'ElevenLabs API request timed out'
77
+ }, {
78
+ status: 504
79
+ });
80
+ }
81
+ throw error;
51
82
  }
52
- const data = await response.json();
53
- // Transform voices to match our schema
54
- const voices = (data.voices || []).map((voice)=>({
55
- id: voice.voice_id,
56
- name: voice.name,
57
- category: voice.category || 'premade',
58
- enabled: true,
59
- labels: voice.labels || {},
60
- preview_url: voice.preview_url || ''
61
- }));
62
- return Response.json({
63
- success: true,
64
- voices
65
- });
66
83
  } catch (error) {
67
84
  req.payload.logger.error(error, 'Error fetching ElevenLabs voices');
68
85
  return Response.json({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/fetchVoices.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../defaults.js'\n\ninterface ElevenLabsVoice {\n category?: string\n labels?: Record<string, string>\n name: string\n preview_url?: string\n voice_id: string\n}\n\nexport const fetchVoices: Endpoint = {\n handler: async (req: PayloadRequest) => {\n try {\n // Check authentication\n if (!req.user) {\n return Response.json({ message: 'Authentication required' }, { status: 401 })\n }\n\n // Fetch AI Settings global to get the encrypted API key\n const aiSettings = await req.payload.findGlobal({\n slug: 'ai-settings',\n context: { unsafe: true },\n })\n\n // Find the ElevenLabs provider block\n const elevenlabsProvider = aiSettings?.providers?.find(\n (provider: any) => provider.blockType === 'elevenlabs' && provider.enabled,\n )\n\n if (!elevenlabsProvider) {\n return Response.json(\n { message: 'ElevenLabs provider not found or not enabled in AI Settings' },\n { status: 400 },\n )\n }\n\n // Get the API key (already decrypted by afterRead hook due to unsafe context)\n const apiKey = elevenlabsProvider.apiKey\n\n if (!apiKey) {\n return Response.json(\n {\n message: 'API key not found. Please configure your ElevenLabs API key in AI Settings.',\n },\n { status: 400 },\n )\n }\n\n // Call ElevenLabs API to fetch voices\n const response = await fetch('https://api.elevenlabs.io/v1/voices', {\n headers: {\n 'xi-api-key': apiKey,\n },\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n return Response.json(\n { message: `ElevenLabs API error: ${errorText}` },\n { status: response.status },\n )\n }\n\n const data = await response.json()\n\n // Transform voices to match our schema\n const voices = (data.voices || []).map((voice: ElevenLabsVoice) => ({\n id: voice.voice_id,\n name: voice.name,\n category: voice.category || 'premade',\n enabled: true,\n labels: voice.labels || {},\n preview_url: voice.preview_url || '',\n }))\n\n return Response.json({\n success: true,\n voices,\n })\n } catch (error) {\n req.payload.logger.error(error, 'Error fetching ElevenLabs voices')\n return Response.json(\n { message: error instanceof Error ? error.message : 'Internal server error' },\n { status: 500 },\n )\n }\n },\n method: 'post',\n path: PLUGIN_API_ENDPOINT_FETCH_VOICES,\n}\n"],"names":["PLUGIN_API_ENDPOINT_FETCH_VOICES","fetchVoices","handler","req","user","Response","json","message","status","aiSettings","payload","findGlobal","slug","context","unsafe","elevenlabsProvider","providers","find","provider","blockType","enabled","apiKey","response","fetch","headers","ok","errorText","text","data","voices","map","voice","id","voice_id","name","category","labels","preview_url","success","error","logger","Error","method","path"],"mappings":"AAEA,SAASA,gCAAgC,QAAQ,iBAAgB;AAUjE,OAAO,MAAMC,cAAwB;IACnCC,SAAS,OAAOC;QACd,IAAI;YACF,uBAAuB;YACvB,IAAI,CAACA,IAAIC,IAAI,EAAE;gBACb,OAAOC,SAASC,IAAI,CAAC;oBAAEC,SAAS;gBAA0B,GAAG;oBAAEC,QAAQ;gBAAI;YAC7E;YAEA,wDAAwD;YACxD,MAAMC,aAAa,MAAMN,IAAIO,OAAO,CAACC,UAAU,CAAC;gBAC9CC,MAAM;gBACNC,SAAS;oBAAEC,QAAQ;gBAAK;YAC1B;YAEA,qCAAqC;YACrC,MAAMC,qBAAqBN,YAAYO,WAAWC,KAChD,CAACC,WAAkBA,SAASC,SAAS,KAAK,gBAAgBD,SAASE,OAAO;YAG5E,IAAI,CAACL,oBAAoB;gBACvB,OAAOV,SAASC,IAAI,CAClB;oBAAEC,SAAS;gBAA8D,GACzE;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,8EAA8E;YAC9E,MAAMa,SAASN,mBAAmBM,MAAM;YAExC,IAAI,CAACA,QAAQ;gBACX,OAAOhB,SAASC,IAAI,CAClB;oBACEC,SAAS;gBACX,GACA;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,sCAAsC;YACtC,MAAMc,WAAW,MAAMC,MAAM,uCAAuC;gBAClEC,SAAS;oBACP,cAAcH;gBAChB;YACF;YAEA,IAAI,CAACC,SAASG,EAAE,EAAE;gBAChB,MAAMC,YAAY,MAAMJ,SAASK,IAAI;gBACrC,OAAOtB,SAASC,IAAI,CAClB;oBAAEC,SAAS,CAAC,sBAAsB,EAAEmB,UAAU,CAAC;gBAAC,GAChD;oBAAElB,QAAQc,SAASd,MAAM;gBAAC;YAE9B;YAEA,MAAMoB,OAAO,MAAMN,SAAShB,IAAI;YAEhC,uCAAuC;YACvC,MAAMuB,SAAS,AAACD,CAAAA,KAAKC,MAAM,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC,QAA4B,CAAA;oBAClEC,IAAID,MAAME,QAAQ;oBAClBC,MAAMH,MAAMG,IAAI;oBAChBC,UAAUJ,MAAMI,QAAQ,IAAI;oBAC5Bf,SAAS;oBACTgB,QAAQL,MAAMK,MAAM,IAAI,CAAC;oBACzBC,aAAaN,MAAMM,WAAW,IAAI;gBACpC,CAAA;YAEA,OAAOhC,SAASC,IAAI,CAAC;gBACnBgC,SAAS;gBACTT;YACF;QACF,EAAE,OAAOU,OAAO;YACdpC,IAAIO,OAAO,CAAC8B,MAAM,CAACD,KAAK,CAACA,OAAO;YAChC,OAAOlC,SAASC,IAAI,CAClB;gBAAEC,SAASgC,iBAAiBE,QAAQF,MAAMhC,OAAO,GAAG;YAAwB,GAC5E;gBAAEC,QAAQ;YAAI;QAElB;IACF;IACAkC,QAAQ;IACRC,MAAM3C;AACR,EAAC"}
1
+ {"version":3,"sources":["../../src/endpoints/fetchVoices.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../defaults.js'\n\ninterface ElevenLabsVoice {\n category?: string\n labels?: Record<string, string>\n name: string\n preview_url?: string\n voice_id: string\n}\n\nexport const fetchVoices: Endpoint = {\n handler: async (req: PayloadRequest) => {\n try {\n // Check authentication\n if (!req.user) {\n return Response.json({ message: 'Authentication required' }, { status: 401 })\n }\n\n // Fetch AI Settings global to get the encrypted API key\n const aiSettings = await req.payload.findGlobal({\n slug: 'ai-settings',\n context: { unsafe: true },\n })\n\n // Find the ElevenLabs provider block\n const elevenlabsProvider = aiSettings?.providers?.find(\n (provider: any) => provider.blockType === 'elevenlabs' && provider.enabled,\n )\n\n if (!elevenlabsProvider) {\n return Response.json(\n { message: 'ElevenLabs provider not found or not enabled in AI Settings' },\n { status: 400 },\n )\n }\n\n // Get the API key (already decrypted by afterRead hook due to unsafe context)\n const apiKey = elevenlabsProvider.apiKey\n\n if (!apiKey) {\n return Response.json(\n {\n message: 'API key not found. Please configure your ElevenLabs API key in AI Settings.',\n },\n { status: 400 },\n )\n }\n\n // Call ElevenLabs API to fetch voices with timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 15000) // 15s timeout\n\n try {\n const response = await fetch('https://api.elevenlabs.io/v1/voices', {\n headers: {\n 'xi-api-key': apiKey,\n },\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n const errorText = await response.text()\n return Response.json(\n { message: `ElevenLabs API error: ${errorText}` },\n { status: response.status },\n )\n }\n\n const data = await response.json()\n\n // Transform voices to match our schema\n const voices = (data.voices || []).map((voice: ElevenLabsVoice) => ({\n id: voice.voice_id,\n name: voice.name,\n category: voice.category || 'premade',\n enabled: true,\n labels: voice.labels || {},\n preview_url: voice.preview_url || '',\n }))\n\n return Response.json({\n success: true,\n voices,\n })\n } catch (error: unknown) {\n clearTimeout(timeoutId)\n if (error instanceof Error && error.name === 'AbortError') {\n return Response.json({ message: 'ElevenLabs API request timed out' }, { status: 504 })\n }\n throw error\n }\n } catch (error) {\n req.payload.logger.error(error, 'Error fetching ElevenLabs voices')\n return Response.json(\n { message: error instanceof Error ? error.message : 'Internal server error' },\n { status: 500 },\n )\n }\n },\n method: 'post',\n path: PLUGIN_API_ENDPOINT_FETCH_VOICES,\n}\n"],"names":["PLUGIN_API_ENDPOINT_FETCH_VOICES","fetchVoices","handler","req","user","Response","json","message","status","aiSettings","payload","findGlobal","slug","context","unsafe","elevenlabsProvider","providers","find","provider","blockType","enabled","apiKey","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","headers","signal","clearTimeout","ok","errorText","text","data","voices","map","voice","id","voice_id","name","category","labels","preview_url","success","error","Error","logger","method","path"],"mappings":"AAEA,SAASA,gCAAgC,QAAQ,iBAAgB;AAUjE,OAAO,MAAMC,cAAwB;IACnCC,SAAS,OAAOC;QACd,IAAI;YACF,uBAAuB;YACvB,IAAI,CAACA,IAAIC,IAAI,EAAE;gBACb,OAAOC,SAASC,IAAI,CAAC;oBAAEC,SAAS;gBAA0B,GAAG;oBAAEC,QAAQ;gBAAI;YAC7E;YAEA,wDAAwD;YACxD,MAAMC,aAAa,MAAMN,IAAIO,OAAO,CAACC,UAAU,CAAC;gBAC9CC,MAAM;gBACNC,SAAS;oBAAEC,QAAQ;gBAAK;YAC1B;YAEA,qCAAqC;YACrC,MAAMC,qBAAqBN,YAAYO,WAAWC,KAChD,CAACC,WAAkBA,SAASC,SAAS,KAAK,gBAAgBD,SAASE,OAAO;YAG5E,IAAI,CAACL,oBAAoB;gBACvB,OAAOV,SAASC,IAAI,CAClB;oBAAEC,SAAS;gBAA8D,GACzE;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,8EAA8E;YAC9E,MAAMa,SAASN,mBAAmBM,MAAM;YAExC,IAAI,CAACA,QAAQ;gBACX,OAAOhB,SAASC,IAAI,CAClB;oBACEC,SAAS;gBACX,GACA;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,mDAAmD;YACnD,MAAMc,aAAa,IAAIC;YACvB,MAAMC,YAAYC,WAAW,IAAMH,WAAWI,KAAK,IAAI,OAAO,cAAc;;YAE5E,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAM,uCAAuC;oBAClEC,SAAS;wBACP,cAAcR;oBAChB;oBACAS,QAAQR,WAAWQ,MAAM;gBAC3B;gBAEAC,aAAaP;gBAEb,IAAI,CAACG,SAASK,EAAE,EAAE;oBAChB,MAAMC,YAAY,MAAMN,SAASO,IAAI;oBACrC,OAAO7B,SAASC,IAAI,CAClB;wBAAEC,SAAS,CAAC,sBAAsB,EAAE0B,UAAU,CAAC;oBAAC,GAChD;wBAAEzB,QAAQmB,SAASnB,MAAM;oBAAC;gBAE9B;gBAEA,MAAM2B,OAAO,MAAMR,SAASrB,IAAI;gBAEhC,uCAAuC;gBACvC,MAAM8B,SAAS,AAACD,CAAAA,KAAKC,MAAM,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC,QAA4B,CAAA;wBAClEC,IAAID,MAAME,QAAQ;wBAClBC,MAAMH,MAAMG,IAAI;wBAChBC,UAAUJ,MAAMI,QAAQ,IAAI;wBAC5BtB,SAAS;wBACTuB,QAAQL,MAAMK,MAAM,IAAI,CAAC;wBACzBC,aAAaN,MAAMM,WAAW,IAAI;oBACpC,CAAA;gBAEA,OAAOvC,SAASC,IAAI,CAAC;oBACnBuC,SAAS;oBACTT;gBACF;YACF,EAAE,OAAOU,OAAgB;gBACvBf,aAAaP;gBACb,IAAIsB,iBAAiBC,SAASD,MAAML,IAAI,KAAK,cAAc;oBACzD,OAAOpC,SAASC,IAAI,CAAC;wBAAEC,SAAS;oBAAmC,GAAG;wBAAEC,QAAQ;oBAAI;gBACtF;gBACA,MAAMsC;YACR;QACF,EAAE,OAAOA,OAAO;YACd3C,IAAIO,OAAO,CAACsC,MAAM,CAACF,KAAK,CAACA,OAAO;YAChC,OAAOzC,SAASC,IAAI,CAClB;gBAAEC,SAASuC,iBAAiBC,QAAQD,MAAMvC,OAAO,GAAG;YAAwB,GAC5E;gBAAEC,QAAQ;YAAI;QAElB;IACF;IACAyC,QAAQ;IACRC,MAAMlD;AACR,EAAC"}
@@ -4,6 +4,7 @@ import { filterEditorSchemaByNodes } from '../ai/utils/filterEditorSchemaByNodes
4
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';
5
5
  import { registerEditorHelper } from '../libraries/handlebars/helpers.js';
6
6
  import { replacePlaceholders } from '../libraries/handlebars/replacePlaceholders.js';
7
+ import { buildSmartPrompt, isGenericPrompt } from '../utilities/buildSmartPrompt.js';
7
8
  import { extractImageData } from '../utilities/extractImageData.js';
8
9
  import { fetchImages } from '../utilities/fetchImages.js';
9
10
  import { fieldToJsonSchema } from '../utilities/fieldToJsonSchema.js';
@@ -37,10 +38,31 @@ export const endpoints = (pluginConfig)=>({
37
38
  }
38
39
  const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection.admin;
39
40
  const { schema: editorSchema = {} } = editorConfig;
40
- 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
+ }
41
56
  let allowedEditorSchema = editorSchema;
42
57
  if (allowedEditorNodes.length) {
43
58
  allowedEditorSchema = filterEditorSchemaByNodes(editorSchema, allowedEditorNodes);
59
+ // Debug: Log what nodes were received and what definitions remain
60
+ if (pluginConfig.debugging) {
61
+ req.payload.logger.info({
62
+ receivedNodes: allowedEditorNodes,
63
+ remainingDefinitions: Object.keys(allowedEditorSchema.definitions || {})
64
+ }, '— AI Plugin: Schema filtering debug');
65
+ }
44
66
  }
45
67
  const schemaPath = String(instructions['schema-path']);
46
68
  const parts = (schemaPath || '').split('.') || [];
@@ -74,11 +96,13 @@ export const endpoints = (pluginConfig)=>({
74
96
  }
75
97
  // Build per-field JSON schema for structured generation when applicable
76
98
  let jsonSchema = allowedEditorSchema;
99
+ let targetField;
77
100
  try {
78
101
  const targetCollection = req.payload.config.collections.find((c)=>c.slug === collectionName);
79
102
  if (targetCollection && fieldName) {
80
- const targetField = getFieldBySchemaPath(targetCollection, schemaPath);
103
+ targetField = getFieldBySchemaPath(targetCollection, schemaPath);
81
104
  const supported = [
105
+ 'array',
82
106
  'text',
83
107
  'textarea',
84
108
  'select',
@@ -90,23 +114,37 @@ export const endpoints = (pluginConfig)=>({
90
114
  ];
91
115
  const t = String(targetField?.type || '');
92
116
  if (targetField && supported.includes(t)) {
93
- jsonSchema = fieldToJsonSchema(targetField, {
94
- nameOverride: fieldName
95
- });
117
+ // For array fields, use count from array-settings if available
118
+ if (t === 'array') {
119
+ const arraySettings = instructions['array-settings'] || {};
120
+ const count = arraySettings.count || 3;
121
+ // Override the field's maxRows with the requested count
122
+ const modifiedField = {
123
+ ...targetField,
124
+ maxRows: count,
125
+ minRows: count
126
+ };
127
+ jsonSchema = fieldToJsonSchema(modifiedField, {
128
+ nameOverride: fieldName
129
+ });
130
+ } else {
131
+ jsonSchema = fieldToJsonSchema(targetField, {
132
+ nameOverride: fieldName
133
+ });
134
+ }
96
135
  }
97
136
  }
98
137
  } catch (e) {
99
138
  req.payload.logger.error(e, '— AI Plugin: Error building field JSON schema');
100
139
  }
101
140
  // Get model settings from instruction
102
- const settingsName = instructions['model-id'] === 'richtext' ? 'richtext-settings' : instructions['model-id'] === 'text' ? 'text-settings' : undefined;
141
+ const settingsName = instructions['model-id'] === 'richtext' ? 'richtext-settings' : instructions['model-id'] === 'text' ? 'text-settings' : instructions['model-id'] === 'array' ? 'array-settings' : undefined;
103
142
  if (!settingsName) {
104
143
  throw new Error(`Unsupported model-id: ${instructions['model-id']}`);
105
144
  }
106
145
  const modelSettings = instructions[settingsName] || {};
107
146
  // Resolve @field:filename references from the prompt
108
- const { images: resolvedImages, processedPrompt } = await resolveImageReferences(prompts.prompt, contextData, req);
109
- console.log('resolvedImagesL ', resolvedImages);
147
+ const { images: resolvedImages, processedPrompt } = await resolveImageReferences(prompts.prompt, contextData, req, collectionName);
110
148
  // Extract hardcoded URLs from the processed prompt
111
149
  const hardcodedImages = extractImageData(processedPrompt);
112
150
  // Combine images
@@ -121,16 +159,60 @@ export const endpoints = (pluginConfig)=>({
121
159
  images = imageParts;
122
160
  }
123
161
  }
124
- // Use payload.ai.streamObject directly! 🎉
162
+ let promptToUse = processedPrompt;
163
+ let systemToUse = prompts.system;
164
+ // let messagesToUse: any = undefined
165
+ // Execute beforeGenerate hooks
166
+ if (targetField && targetField.custom?.ai?.beforeGenerate) {
167
+ const beforeHooks = targetField.custom.ai.beforeGenerate;
168
+ for (const hook of beforeHooks){
169
+ const result = await hook({
170
+ doc: contextData,
171
+ field: targetField,
172
+ headers: req.headers,
173
+ instructions,
174
+ payload: req.payload,
175
+ prompt: promptToUse,
176
+ req,
177
+ system: systemToUse
178
+ });
179
+ if (result) {
180
+ if (result.prompt) promptToUse = result.prompt;
181
+ if (result.system) systemToUse = result.system;
182
+ }
183
+ }
184
+ }
125
185
  const streamResult = await req.payload.ai.streamObject({
126
186
  // extractAttachments: modelSettings.extractAttachments as boolean | undefined,
127
187
  images,
128
188
  maxTokens: modelSettings.maxTokens,
189
+ // messages: messagesToUse,
129
190
  model: modelSettings.model,
191
+ onFinish: async ({ object })=>{
192
+ if (targetField && targetField.custom?.ai?.afterGenerate) {
193
+ const afterHooks = targetField.custom.ai.afterGenerate;
194
+ for (const hook of afterHooks){
195
+ await hook({
196
+ doc: contextData,
197
+ field: targetField,
198
+ headers: req.headers,
199
+ instructions,
200
+ payload: req.payload,
201
+ req,
202
+ result: object
203
+ });
204
+ }
205
+ }
206
+ },
130
207
  prompt: processedPrompt,
131
208
  provider: modelSettings.provider,
209
+ providerOptions: {
210
+ openai: {
211
+ strictJsonSchema: true
212
+ }
213
+ },
132
214
  schema: jsonSchema,
133
- system: prompts.system,
215
+ system: systemToUse,
134
216
  temperature: modelSettings.temperature
135
217
  });
136
218
  return streamResult;
@@ -189,18 +271,31 @@ export const endpoints = (pluginConfig)=>({
189
271
  req
190
272
  });
191
273
  }
192
- const { images: sampleImages = [], prompt: promptTemplate = '' } = instructions;
274
+ let { prompt: promptTemplate = '' } = instructions;
275
+ const { images: sampleImages = [] } = instructions;
193
276
  const schemaPath = String(instructions['schema-path']);
194
277
  registerEditorHelper(req.payload, schemaPath);
278
+ // Smart fallback: if prompt is generic, build a contextual prompt from field metadata
279
+ if (isGenericPrompt(promptTemplate)) {
280
+ promptTemplate = buildSmartPrompt({
281
+ documentData: contextData,
282
+ payload: req.payload,
283
+ schemaPath
284
+ });
285
+ if (pluginConfig.debugging) {
286
+ req.payload.logger.info({
287
+ smartPrompt: promptTemplate
288
+ }, `— AI Plugin: Using smart fallback prompt for ${schemaPath}`);
289
+ }
290
+ }
195
291
  const extendedContext = extendContextWithPromptFields(contextData, {
196
292
  type: String(instructions['field-type']),
197
293
  collection: collectionSlug
198
294
  }, pluginConfig);
199
295
  const text = await replacePlaceholders(promptTemplate, extendedContext);
200
- const modelId = instructions['model-id'];
201
296
  const uploadCollectionSlug = instructions['relation-to'];
202
297
  // Resolve @field:filename references from the prompt
203
- const { images: resolvedImages, processedPrompt } = await resolveImageReferences(text, contextData, req);
298
+ const { images: resolvedImages, processedPrompt } = await resolveImageReferences(text, contextData, req, collectionSlug);
204
299
  // Extract hardcoded URLs from the processed prompt and merge with resolved images and sample images
205
300
  const images = [
206
301
  ...extractImageData(processedPrompt),
@@ -209,30 +304,113 @@ export const endpoints = (pluginConfig)=>({
209
304
  ];
210
305
  // Process images - convert to ImagePart format using helper
211
306
  const editImages = await fetchImages(req, images);
307
+ let promptToUse = text;
308
+ let targetField;
309
+ try {
310
+ const targetCollection = req.payload.config.collections.find((c)=>c.slug === collectionSlug);
311
+ if (targetCollection && schemaPath) {
312
+ targetField = getFieldBySchemaPath(targetCollection, schemaPath);
313
+ }
314
+ } catch (e) {
315
+ req.payload.logger.error(e, '— AI Plugin: Error finding field for hooks');
316
+ }
317
+ if (targetField && targetField.custom?.ai?.beforeGenerate) {
318
+ const beforeHooks = targetField.custom.ai.beforeGenerate;
319
+ for (const hook of beforeHooks){
320
+ const result = await hook({
321
+ doc: contextData,
322
+ field: targetField,
323
+ headers: req.headers,
324
+ instructions,
325
+ payload: req.payload,
326
+ prompt: promptToUse,
327
+ req
328
+ });
329
+ if (result) {
330
+ if (result.prompt) {
331
+ promptToUse = result.prompt;
332
+ }
333
+ if (result.instructions) {
334
+ instructions = {
335
+ ...instructions,
336
+ ...result.instructions
337
+ };
338
+ }
339
+ }
340
+ }
341
+ }
212
342
  if (pluginConfig.debugging) {
213
343
  req.payload.logger.info({
214
- text
344
+ text: promptToUse
215
345
  }, `— AI Plugin: Executing media generation`);
216
346
  }
217
347
  // Prepare callback URL for async jobs
218
348
  const serverURL = req.payload.config?.serverURL || process.env.SERVER_URL || process.env.NEXT_PUBLIC_SERVER_URL;
219
349
  const callbackUrl = serverURL ? `${serverURL.replace(/\/$/, '')}/api${PLUGIN_API_ENDPOINT_VIDEOGEN_WEBHOOK}?instructionId=${instructionId}` : undefined;
220
350
  // Get model settings
351
+ // Re-evaluate settings name and settings in case instructions changed
352
+ const modelId = instructions['model-id'];
221
353
  const settingsName = modelId === 'image' ? 'image-settings' : modelId === 'video' ? 'video-settings' : modelId === 'tts' ? 'tts-settings' : undefined;
222
354
  if (!settingsName) {
223
355
  throw new Error(`Unsupported model-id: ${modelId}`);
224
356
  }
225
- const modelSettings = instructions[settingsName] || {};
357
+ // Get model settings from instruction
358
+ const instructionSettings = instructions[settingsName] || {};
359
+ // Fallback to AISettings global defaults if instruction-level settings are missing
360
+ let globalDefaults = {};
361
+ if (!instructionSettings.provider || !instructionSettings.model) {
362
+ try {
363
+ const aiSettings = await req.payload.findGlobal({
364
+ slug: 'ai-settings',
365
+ context: {
366
+ unsafe: true
367
+ }
368
+ });
369
+ // Map modelId to the corresponding default settings key
370
+ const defaultsKey = modelId === 'image' ? 'image' : modelId === 'video' ? 'video' : modelId === 'tts' ? 'tts' : undefined;
371
+ if (defaultsKey && aiSettings?.defaults?.[defaultsKey]) {
372
+ globalDefaults = aiSettings.defaults[defaultsKey];
373
+ if (pluginConfig.debugging) {
374
+ req.payload.logger.info({
375
+ globalDefaults
376
+ }, `— AI Plugin: Using AISettings defaults for ${modelId}`);
377
+ }
378
+ }
379
+ } catch (e) {
380
+ req.payload.logger.error(e, '— AI Plugin: Error fetching AISettings defaults');
381
+ }
382
+ }
383
+ // Merge: instruction settings take priority over global defaults
384
+ // Filter out null/undefined values so they don't overwrite valid defaults
385
+ const filteredInstructionSettings = Object.fromEntries(Object.entries(instructionSettings).filter(([_, v])=>v != null));
386
+ const modelSettings = {
387
+ ...globalDefaults,
388
+ ...filteredInstructionSettings
389
+ };
226
390
  // Use payload.ai.generateMedia directly! 🎉
227
391
  const result = await req.payload.ai.generateMedia({
228
392
  callbackUrl,
229
393
  images: editImages,
230
394
  instructionId,
231
395
  model: modelSettings.model,
232
- prompt: text,
396
+ prompt: promptToUse,
233
397
  provider: modelSettings.provider,
234
398
  ...modelSettings
235
399
  });
400
+ if (targetField && targetField.custom?.ai?.afterGenerate) {
401
+ const afterHooks = targetField.custom.ai.afterGenerate;
402
+ for (const hook of afterHooks){
403
+ await hook({
404
+ doc: contextData,
405
+ field: targetField,
406
+ headers: req.headers,
407
+ instructions,
408
+ payload: req.payload,
409
+ req,
410
+ result
411
+ });
412
+ }
413
+ }
236
414
  // If model returned a file immediately, proceed with upload
237
415
  if (result && 'file' in result) {
238
416
  let assetData;